123set -euo pipefail45source_repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"6production_harness="$source_repo/scripts/public-alpha-uat.sh"7temporary_root="$(mktemp -d)"8trap 'rm -rf "$temporary_root"' EXIT910passes=011pass() {12 passes=$((passes + 1))13 printf 'ok %d - %s\n' "$passes" "$1"14}1516expect_failure() {17 local description="$1"18 shift19 if "$@" > /dev/null 2>&1; then20 printf 'not ok - %s unexpectedly succeeded\n' "$description" >&221 exit 122 fi23 pass "$description"24}2526fixture_repo="$temporary_root/repo"27mkdir -p "$fixture_repo/scripts" "$fixture_repo/release"28fixture_bin="$temporary_root/bin"29mkdir -m 700 "$fixture_bin"30# Exercise the Darwin-only production branch with fixture verifiers on any CI31# platform. The production harness itself has no test switch.32# shellcheck disable=SC201633printf '%s\n' '#!/usr/bin/env bash' 'if [[ "${1:-}" == -s ]]; then printf "Darwin\n"; else exec /usr/bin/uname "$@"; fi' \34 > "$fixture_bin/uname"35chmod 755 "$fixture_bin/uname"36# The generated GitHub CLI fixture exposes only the exact API and attestation37# operations used by the production harness.38# shellcheck disable=SC201639printf '%s\n' \40 '#!/usr/bin/env bash' \41 'set -euo pipefail' \42 'if [[ "${1:-}" == api && $# == 2 ]]; then' \43 ' case "$2" in' \44 ' /repos/FelineStateMachine/goq/releases/tags/*) cat "$UAT_GITHUB_RELEASE_JSON" ;;' \45 ' /repos/FelineStateMachine/goq/git/ref/tags/*) cat "$UAT_GITHUB_REF_JSON" ;;' \46 ' *) exit 1 ;;' \47 ' esac' \48 'elif [[ "${1:-}" == attestation && "${2:-}" == verify ]]; then' \49 ' if [[ -e "$UAT_ATTESTATION_LOG.fail" ]]; then exit 1; fi' \50 ' [[ -f "$3" ]]' \51 ' [[ " $* " == *" --repo FelineStateMachine/goq "* ]]' \52 ' [[ " $* " == *" --signer-workflow FelineStateMachine/goq/.github/workflows/portal-release.yml "* ]]' \53 ' [[ " $* " == *" --source-ref refs/tags/v1.2.3 "* ]]' \54 ' [[ " $* " == *" --source-digest $UAT_GITHUB_COMMIT "* ]]' \55 ' [[ " $* " == *" --deny-self-hosted-runners "* ]]' \56 ' printf "%s\n" "$3" >> "$UAT_ATTESTATION_LOG"' \57 'else' \58 ' exit 1' \59 'fi' \60 > "$fixture_bin/gh"61chmod 755 "$fixture_bin/gh"62export PATH="$fixture_bin:$PATH"63cp "$production_harness" "$fixture_repo/scripts/public-alpha-uat.sh"64chmod 755 "$fixture_repo/scripts/public-alpha-uat.sh"65printf '%s\n' 'untrusted comment: fixture key' 'RWRmaXh0dXJlUHVibGljS2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' \66 > "$fixture_repo/release/sigil-minisign.pub"67printf '%s\n' 'ABC123DEF4' > "$fixture_repo/release/portal-apple-team-id.txt"68chmod 644 "$fixture_repo/release/sigil-minisign.pub"69chmod 644 "$fixture_repo/release/portal-apple-team-id.txt"7071# The single-quoted fixture lines intentionally defer expansion to the generated verifier.72# shellcheck disable=SC201673printf '%s\n' \74 '#!/usr/bin/env bash' \75 'set -euo pipefail' \76 'tag=""; archive=""; commit=""; key=""' \77 'while (($#)); do' \78 ' case "$1" in' \79 ' --tag) tag="$2"; shift 2 ;;' \80 ' --archive) archive="$2"; shift 2 ;;' \81 ' --source-commit) commit="$2"; shift 2 ;;' \82 ' --public-key-file) key="$2"; shift 2 ;;' \83 ' *) exit 1 ;;' \84 ' esac' \85 'done' \86 '[[ "$tag" == v1.2.3 ]]' \87 '[[ "$(basename -- "$archive")" == sigil-v1.2.3-linux-glibc2.17-x86_64.tar.gz ]]' \88 '[[ "$commit" =~ ^[0-9a-f]{40}$ ]]' \89 '[[ -s "$archive.sha256" && "$(<"$archive.minisig")" == fixture-signature ]]' \90 'grep -q fixture "$key"' \91 > "$fixture_repo/scripts/verify-sigil-release.sh"92chmod 755 "$fixture_repo/scripts/verify-sigil-release.sh"9394printf '%s\n' \95 '#!/usr/bin/env python3' \96 'import pathlib' \97 'import sys' \98 'args = sys.argv[1:]' \99 'def value(name):' \100 ' return args[args.index(name) + 1]' \101 'if not args or args[0] != "assets": raise SystemExit(1)' \102 'if value("--release-tag") != "v1.2.3": raise SystemExit(1)' \103 'asset_dir = pathlib.Path(value("--asset-dir"))' \104 'dmg = asset_dir / "Portal-1.2.3-arm64.dmg"' \105 'expected = {"Portal-1.2.3-arm64.dmg", "Portal-1.2.3-arm64.dmg.sha256", "Portal-1.2.3-arm64.json"}' \106 'if {entry.name for entry in asset_dir.iterdir()} != expected: raise SystemExit(1)' \107 'if dmg.read_text() != "verified portal dmg fixture\n": raise SystemExit(1)' \108 > "$fixture_repo/scripts/verify-portal-release.py"109chmod 755 "$fixture_repo/scripts/verify-portal-release.py"110111# The single-quoted fixture lines intentionally defer expansion to the generated verifier.112# shellcheck disable=SC2016113printf '%s\n' \114 '#!/usr/bin/env bash' \115 'set -euo pipefail' \116 'if [[ -n "${UAT_SIGNATURE_MARKER:-}" && -e "$UAT_SIGNATURE_MARKER.fail" ]]; then exit 1; fi' \117 '[[ "$1" == --dmg && "$3" == --expected-version && "$4" == 1.2.3 ]]' \118 '[[ "$(<"$2")" == "verified portal dmg fixture" ]]' \119 'if [[ -n "${UAT_SIGNATURE_MARKER:-}" ]]; then printf invoked > "$UAT_SIGNATURE_MARKER"; fi' \120 '[[ "$5" == --expected-team-id && "$6" == ABC123DEF4 ]]' \121 'printf "portal_signature_verification=ok\\n"' \122 > "$fixture_repo/scripts/verify-macos-portal-signature.sh"123chmod 755 "$fixture_repo/scripts/verify-macos-portal-signature.sh"124125release_tag=v1.2.3126sigil_bin="$temporary_root/sigil"127wrong_sigil_bin="$temporary_root/wrong-sigil"128printf '%s\n' 'exact Sigil release candidate' > "$sigil_bin"129printf '%s\n' 'arbitrary executable' > "$wrong_sigil_bin"130chmod 600 "$sigil_bin" "$wrong_sigil_bin"131132archive_dir="$temporary_root/sigil-assets"133payload_root="$temporary_root/payload-root"134mkdir -p "$archive_dir" "$payload_root/payload/release"135cp "$sigil_bin" "$payload_root/payload/release/sigil"136sigil_archive="$archive_dir/sigil-v1.2.3-linux-glibc2.17-x86_64.tar.gz"137tar -czf "$sigil_archive" -C "$payload_root" payload138printf '%s\n' 'fixture checksum' > "$sigil_archive.sha256"139printf '%s\n' 'fixture-signature' > "$sigil_archive.minisig"140chmod 600 "$sigil_archive" "$sigil_archive.sha256" "$sigil_archive.minisig"141142portal_assets="$temporary_root/portal-assets"143mkdir -m 700 "$portal_assets"144printf '%s\n' 'verified portal dmg fixture' > "$portal_assets/Portal-1.2.3-arm64.dmg"145printf '%s\n' 'fixture checksum' > "$portal_assets/Portal-1.2.3-arm64.dmg.sha256"146printf '%s\n' '{}' > "$portal_assets/Portal-1.2.3-arm64.json"147chmod 600 "$portal_assets"/*148149git -C "$fixture_repo" init -q150git -C "$fixture_repo" config user.name 'UAT Fixture'151git -C "$fixture_repo" config user.email 'uat-fixture@example.invalid'152git -C "$fixture_repo" add scripts release153git -C "$fixture_repo" commit -qm 'fixture release verifiers'154git -C "$fixture_repo" tag "$release_tag"155156export UAT_GITHUB_COMMIT157UAT_GITHUB_COMMIT="$(git -C "$fixture_repo" rev-parse "$release_tag^{commit}")"158export UAT_GITHUB_RELEASE_JSON="$temporary_root/github-release.json"159export UAT_GITHUB_REF_JSON="$temporary_root/github-ref.json"160export UAT_ATTESTATION_LOG="$temporary_root/github-attestations.log"161162write_remote_ref_fixture() {163 local commit="${1:-$UAT_GITHUB_COMMIT}"164 printf '{"object":{"type":"commit","sha":"%s"}}\n' "$commit" > "$UAT_GITHUB_REF_JSON"165}166167write_remote_release_fixture() {168 local draft="${1:-false}"169 local prerelease="${2:-true}"170 local tag="${3:-$release_tag}"171 PYTHONDONTWRITEBYTECODE=1 python3 - \172 "$UAT_GITHUB_RELEASE_JSON" "$draft" "$prerelease" "$tag" \173 "$sigil_archive" "$sigil_archive.sha256" "$sigil_archive.minisig" \174 "$portal_assets/Portal-1.2.3-arm64.dmg" \175 "$portal_assets/Portal-1.2.3-arm64.dmg.sha256" \176 "$portal_assets/Portal-1.2.3-arm64.json" <<'PY'177import hashlib178import json179import pathlib180import sys181182output = pathlib.Path(sys.argv[1])183draft = sys.argv[2] == "true"184prerelease = sys.argv[3] == "true"185tag = sys.argv[4]186paths = [pathlib.Path(value) for value in sys.argv[5:]]187assets = []188for index, path in enumerate(paths, start=101):189 content = path.read_bytes()190 assets.append({191 "id": index,192 "name": path.name,193 "state": "uploaded",194 "size": len(content),195 "digest": "sha256:" + hashlib.sha256(content).hexdigest(),196 "url": f"https://api.github.com/repos/FelineStateMachine/goq/releases/assets/{index}",197 })198output.write_text(json.dumps({199 "id": 42,200 "tag_name": tag,201 "draft": draft,202 "prerelease": prerelease,203 "published_at": "2026-07-21T12:00:00Z",204 "html_url": f"https://github.com/FelineStateMachine/goq/releases/tag/{tag}",205 "assets": assets,206}))207PY208}209210write_remote_ref_fixture211write_remote_release_fixture212213harness="$fixture_repo/scripts/public-alpha-uat.sh"214signature_marker="$temporary_root/signature-verifier-invoked"215export UAT_SIGNATURE_MARKER="$signature_marker"216217manifest_value() {218 local bundle="$1"219 local key="$2"220 awk -F '\t' -v wanted="$key" '$1 == wanted { print $2 }' "$bundle/manifest.tsv"221}222223new_bundle() {224 local name="$1"225 local max_age="${2:-604800}"226 local bundle="$temporary_root/$name"227 "$harness" init \228 --evidence-dir "$bundle" \229 --release-tag "$release_tag" \230 --sigil-archive "$sigil_archive" \231 --sigil-bin "$sigil_bin" \232 --portal-assets "$portal_assets" \233 --max-age-seconds "$max_age" > /dev/null234 printf '%s\n' "$bundle"235}236237write_common() {238 local bundle="$1"239 local kind="$2"240 local observed="$3"241 local output="$4"242 {243 printf 'uat_schema=goq-public-alpha-evidence-v2\n'244 printf 'evidence_kind=%s\n' "$kind"245 printf 'observed_at_unix=%s\n' "$observed"246 printf 'git_commit=%s\n' "$(manifest_value "$bundle" git_commit)"247 printf 'release_tag=%s\n' "$(manifest_value "$bundle" release_tag)"248 printf 'sigil_sha256=%s\n' "$(manifest_value "$bundle" sigil_sha256)"249 printf 'portal_sha256=%s\n' "$(manifest_value "$bundle" portal_sha256)"250 } > "$output"251}252253write_fixture() {254 local bundle="$1"255 local kind="$2"256 local output="$3"257 local observed="${4:-$(date +%s)}"258 local hitch_p99="${5:-40}"259 write_common "$bundle" "$kind" "$observed" "$output"260 case "$kind" in261 cold-boot)262 printf '%s\n' 'cold_boot_result=pass' 'cold_boot_failure_count=0' \263 'cold_boot_insufficient_count=0' 'headless_connector_state=ok' \264 'gaming_autologin_session=ok' 'sigil_host_enabled=enabled' \265 'sigil_host_active=active' 'gamescope_pipewire_node=ok' \266 'gamescope_before_first_ssh=ok' 'sigil_unit_before_first_ssh=ok' \267 'sigil_ready_before_first_ssh=ok' >> "$output"268 ;;269 controller)270 printf '%s\n' 'physical_controller_attached_to_portal=pass' \271 'actual_game_controlled=pass' \272 'controller_coverage=abxy,dpad,sticks,triggers,shoulders,start-back' \273 'neutral_release_on_disconnect=pass' 'neutral_buttons=pass' \274 'neutral_axes=pass' 'session_seconds=600' >> "$output"275 ;;276 mouse)277 printf '%s\n' 'target_application=actual-game' 'left_click_consumed=pass' \278 'right_click_consumed=pass' 'consumption_observed_in_target=pass' \279 'click_attempts=10' >> "$output"280 ;;281 soak)282 printf '%s\n' 'duration_seconds=7200' 'samples=120' 'capture_fps_p50=59.8' \283 'presentation_fps_p50=59.2' 'frame_interval_p95_ms=20' \284 "hitch_p99_ms=$hitch_p99" 'video_queue_p95_frames=1' \285 'decode_queue_p95_frames=1' 'audio_queue_p95_ms=50' 'av_skew_p95_ms=30' \286 'max_queue_age_p95_ms=45' 'cpu_p95_percent=50' 'gpu_p95_percent=70' \287 'rss_p95_mib=512' 'transport_drops=2' 'frontend_drops=3' 'audio_drops=0' \288 'latency_first_window_p95_ms=30' 'latency_last_window_p95_ms=33' \289 'disconnects=0' >> "$output"290 ;;291 network-direct)292 printf '%s\n' 'path_mode=direct' 'nat_scenario=ordinary' 'session_seconds=900' \293 'rtt_p50_ms=10' 'rtt_p95_ms=20' 'input_ack_p95_ms=30' \294 'presentation_latency_p95_ms=60' 'packet_loss_percent=0.1' >> "$output"295 ;;296 network-relay)297 printf '%s\n' 'path_mode=relay' 'nat_scenario=difficult' 'session_seconds=900' \298 'rtt_p50_ms=40' 'rtt_p95_ms=80' 'input_ack_p95_ms=90' \299 'presentation_latency_p95_ms=120' 'packet_loss_percent=1.0' >> "$output"300 ;;301 reconnect)302 printf '%s\n' 'reconnect_cycles=10' 'reconnect_successes=10' \303 'reconnect_failures=0' 'state_preserved=pass' \304 'keyframe_recovery_p95_ms=900' >> "$output"305 ;;306 second-client)307 printf '%s\n' 'second_client_attempts=3' 'second_client_rejections=3' \308 'authorized_primary_uninterrupted=pass' 'rejection_reason=active-client' >> "$output"309 ;;310 loopback-preflight)311 printf '%s\n' 'loopback_proof=ok' 'profile=release' \312 "host_sha256=$(manifest_value "$bundle" sigil_sha256)" \313 'active_client_rejection=ok' 'reconnect_cycles=3' 'cleanup=ok' >> "$output"314 ;;315 *) printf 'unknown fixture kind: %s\n' "$kind" >&2; exit 1 ;;316 esac317 chmod 600 "$output"318}319320record_all_required() {321 local bundle="$1"322 local kind323 local evidence324 for kind in cold-boot controller mouse soak network-direct network-relay reconnect second-client; do325 evidence="$temporary_root/$kind-$RANDOM.evidence"326 write_fixture "$bundle" "$kind" "$evidence"327 "$harness" record --evidence-dir "$bundle" --kind "$kind" --file "$evidence" > /dev/null328 done329}330331verify_bundle() {332 "$harness" verify --evidence-dir "$1" --sigil-archive "$sigil_archive" \333 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"334}335336complete_bundle="$(new_bundle complete)"337[[ -f "$signature_marker" ]] || { printf 'macOS signature verifier was not invoked\n' >&2; exit 1; }338[[ "$(wc -l < "$UAT_ATTESTATION_LOG" | tr -d ' ')" == 3 ]] \339 || { printf 'all three Portal attestations were not verified\n' >&2; exit 1; }340record_all_required "$complete_bundle"341loopback_source="$temporary_root/loopback.evidence"342write_fixture "$complete_bundle" loopback-preflight "$loopback_source"343"$harness" record --evidence-dir "$complete_bundle" --kind loopback-preflight \344 --file "$loopback_source" > /dev/null345verify_bundle "$complete_bundle" | grep '^public_alpha_uat=pass$' > /dev/null346pass 'complete bundle verifies only after all release verifiers run'347348write_remote_ref_fixture 0000000000000000000000000000000000000000349expect_failure 'a published tag resolving to a different commit is rejected' \350 "$harness" init --evidence-dir "$temporary_root/wrong-remote-commit" \351 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \352 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"353write_remote_ref_fixture354355write_remote_release_fixture false true v1.2.4356expect_failure 'a GitHub release carrying a different tag is rejected' \357 "$harness" init --evidence-dir "$temporary_root/wrong-remote-tag" \358 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \359 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"360write_remote_release_fixture361362touch "$UAT_ATTESTATION_LOG.fail"363expect_failure 'missing or invalid Portal build provenance is rejected' \364 "$harness" init --evidence-dir "$temporary_root/attestation-failure" \365 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \366 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"367rm "$UAT_ATTESTATION_LOG.fail"368369write_remote_release_fixture true true370expect_failure 'a draft GitHub release is rejected' \371 "$harness" init --evidence-dir "$temporary_root/remote-draft" \372 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \373 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"374write_remote_release_fixture false false375expect_failure 'a published non-prerelease GitHub release is rejected' \376 "$harness" init --evidence-dir "$temporary_root/remote-not-prerelease" \377 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \378 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"379write_remote_release_fixture380381expect_failure 'verify rejects an executable different from the signed Sigil payload' \382 "$harness" verify --evidence-dir "$complete_bundle" --sigil-archive "$sigil_archive" \383 --sigil-bin "$wrong_sigil_bin" --portal-assets "$portal_assets"384385printf failed > "$signature_marker.fail"386expect_failure 'UAT fails closed when macOS platform verification cannot run' \387 "$harness" init \388 --evidence-dir "$temporary_root/signature-failure" --release-tag "$release_tag" \389 --sigil-archive "$sigil_archive" --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"390rm "$signature_marker.fail"391392expect_failure 'HEAD is rejected in place of an immutable release tag' \393 "$harness" init --evidence-dir "$temporary_root/head" --release-tag HEAD \394 --sigil-archive "$sigil_archive" --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"395396printf '%s\n' dirty > "$fixture_repo/untracked-release-residue"397expect_failure 'a dirty exact-tag worktree is rejected' \398 "$harness" init --evidence-dir "$temporary_root/dirty-tag" --release-tag "$release_tag" \399 --sigil-archive "$sigil_archive" --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"400rm "$fixture_repo/untracked-release-residue"401402arbitrary_dir="$temporary_root/arbitrary-sigil"403mkdir "$arbitrary_dir"404arbitrary_archive="$arbitrary_dir/sigil-v1.2.3-linux-glibc2.17-x86_64.tar.gz"405printf '%s\n' 'arbitrary text archive' > "$arbitrary_archive"406printf '%s\n' 'fixture checksum' > "$arbitrary_archive.sha256"407printf '%s\n' 'fixture-signature' > "$arbitrary_archive.minisig"408chmod 600 "$arbitrary_archive" "$arbitrary_archive.sha256" "$arbitrary_archive.minisig"409expect_failure 'an arbitrary text file cannot stand in for the signed Sigil archive' \410 "$harness" init --evidence-dir "$temporary_root/arbitrary-archive-bundle" \411 --release-tag "$release_tag" --sigil-archive "$arbitrary_archive" \412 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"413414expect_failure 'verify reruns Sigil archive verification instead of trusting stored hashes' \415 "$harness" verify --evidence-dir "$complete_bundle" --sigil-archive "$arbitrary_archive" \416 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"417418expect_failure 'a different Sigil executable is rejected against the archive payload' \419 "$harness" init --evidence-dir "$temporary_root/wrong-sigil-bundle" \420 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \421 --sigil-bin "$wrong_sigil_bin" --portal-assets "$portal_assets"422423substituted_sigil_dir="$temporary_root/substituted-sigil"424mkdir -m 700 "$substituted_sigil_dir"425substituted_sigil_archive="$substituted_sigil_dir/$(basename -- "$sigil_archive")"426tar -czf "$substituted_sigil_archive" -C "$payload_root" payload427printf '%s\n' 'fixture checksum' > "$substituted_sigil_archive.sha256"428printf '%s\n' 'fixture-signature' > "$substituted_sigil_archive.minisig"429chmod 600 "$substituted_sigil_archive" "$substituted_sigil_archive.sha256" \430 "$substituted_sigil_archive.minisig"431if [[ "$(shasum -a 256 "$substituted_sigil_archive" | awk '{print $1}')" == \432 "$(shasum -a 256 "$sigil_archive" | awk '{print $1}')" ]]; then433 printf '\n' >> "$substituted_sigil_archive.sha256"434fi435expect_failure 'a locally valid substituted Sigil asset set is rejected by published digests' \436 "$harness" init --evidence-dir "$temporary_root/substituted-sigil-bundle" \437 --release-tag "$release_tag" --sigil-archive "$substituted_sigil_archive" \438 --sigil-bin "$sigil_bin" --portal-assets "$portal_assets"439440arbitrary_portal="$temporary_root/arbitrary-portal"441mkdir -m 700 "$arbitrary_portal"442printf '%s\n' 'arbitrary DMG' > "$arbitrary_portal/Portal-1.2.3-arm64.dmg"443printf '%s\n' 'arbitrary checksum' > "$arbitrary_portal/Portal-1.2.3-arm64.dmg.sha256"444printf '%s\n' '{}' > "$arbitrary_portal/Portal-1.2.3-arm64.json"445chmod 600 "$arbitrary_portal"/*446expect_failure 'arbitrary Portal files are rejected by release policy verification' \447 "$harness" init --evidence-dir "$temporary_root/arbitrary-portal-bundle" \448 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \449 --sigil-bin "$sigil_bin" --portal-assets "$arbitrary_portal"450451expect_failure 'verify reruns Portal release and platform verification' \452 "$harness" verify --evidence-dir "$complete_bundle" --sigil-archive "$sigil_archive" \453 --sigil-bin "$sigil_bin" --portal-assets "$arbitrary_portal"454455substituted_portal="$temporary_root/substituted-portal"456mkdir -m 700 "$substituted_portal"457cp "$portal_assets"/* "$substituted_portal/"458printf '%s\n' '{"substituted":true}' > "$substituted_portal/Portal-1.2.3-arm64.json"459chmod 600 "$substituted_portal"/*460expect_failure 'a locally valid substituted Portal asset set is rejected by published digests' \461 "$harness" init --evidence-dir "$temporary_root/substituted-portal-bundle" \462 --release-tag "$release_tag" --sigil-archive "$sigil_archive" \463 --sigil-bin "$sigil_bin" --portal-assets "$substituted_portal"464465missing_bundle="$(new_bundle missing)"466expect_failure 'missing hardware gates fail closed' verify_bundle "$missing_bundle"467468stale_bundle="$(new_bundle stale 3600)"469stale_source="$temporary_root/stale.evidence"470write_fixture "$stale_bundle" controller "$stale_source" "$(( $(date +%s) - 3601 ))"471expect_failure 'stale evidence is rejected at ingestion' \472 "$harness" record --evidence-dir "$stale_bundle" --kind controller --file "$stale_source"473474sensitive_bundle="$(new_bundle sensitive)"475sensitive_source="$temporary_root/sensitive.evidence"476write_fixture "$sensitive_bundle" mouse "$sensitive_source"477printf 'node_id=0123456789abcdef\n' >> "$sensitive_source"478expect_failure 'node IDs are rejected before normalization' \479 "$harness" record --evidence-dir "$sensitive_bundle" --kind mouse --file "$sensitive_source"480481permissions_bundle="$(new_bundle permissions)"482permissions_source="$temporary_root/permissions.evidence"483write_fixture "$permissions_bundle" controller "$permissions_source"484chmod 666 "$permissions_source"485expect_failure 'group- or other-writable evidence is rejected' \486 "$harness" record --evidence-dir "$permissions_bundle" --kind controller --file "$permissions_source"487488threshold_bundle="$(new_bundle threshold)"489threshold_source="$temporary_root/threshold.evidence"490write_fixture "$threshold_bundle" soak "$threshold_source" "$(date +%s)" 51491expect_failure 'a soak percentile over threshold is rejected' \492 "$harness" record --evidence-dir "$threshold_bundle" --kind soak --file "$threshold_source"493494printf 'tamper=1\n' >> "$complete_bundle/records/controller.evidence"495expect_failure 'post-ingestion evidence tampering is rejected' verify_bundle "$complete_bundle"496497unexpected_bundle="$(new_bundle unexpected)"498record_all_required "$unexpected_bundle"499printf '%s\n' 'not allowed' > "$unexpected_bundle/untracked.txt"500chmod 600 "$unexpected_bundle/untracked.txt"501expect_failure 'unexpected bundle content is rejected' verify_bundle "$unexpected_bundle"502503printf '1..%d\n' "$passes"