#!/usr/bin/env bash # The public seam is the installed `panama` command. This fixture repository # proves the runner's manifest policy and diagnostics without touching the host. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" fixture="$(mktemp -d)" output="" status=0 background_cli_pid="" background_contract_pgid="" cleanup() { trap - EXIT INT TERM if [[ "$background_cli_pid" =~ ^[1-9][0-9]*$ ]]; then kill -TERM "$background_cli_pid" 2>/dev/null || true wait "$background_cli_pid" 2>/dev/null || true fi if [[ "$background_contract_pgid" =~ ^[1-9][0-9]*$ ]]; then kill -KILL -- "-$background_contract_pgid" 2>/dev/null || true fi rm -rf -- "$fixture" } trap cleanup EXIT trap 'cleanup; exit 130' INT trap 'cleanup; exit 143' TERM fail() { printf 'test runner: %s\n' "$*" >&2; exit 1; } assert_contains() { local needle="$1" haystack="$2" [[ "$haystack" == *"$needle"* ]] || fail "expected output to contain: $needle\n$haystack" } assert_not_contains() { local needle="$1" haystack="$2" [[ "$haystack" != *"$needle"* ]] || fail "expected output not to contain: $needle\n$haystack" } assert_before() { local first="$1" second="$2" haystack="$3" [[ "$haystack" == *"$first"*"$second"* ]] \ || fail "expected '$first' before '$second':\n$haystack" } assert_execution() { local expected="$1" actual actual="$(sort "$fixture/executions" 2>/dev/null || true)" [[ "$actual" == "$expected" ]] || fail "expected executions '$expected', got '$actual'" } reset_executions() { : > "$fixture/executions"; } wait_for_file() { local path="$1" attempt for (( attempt = 0; attempt < 100; attempt++ )); do [[ -s "$path" ]] && return 0 sleep 0.05 done return 1 } wait_for_process_exit() { local pid="$1" attempt for (( attempt = 0; attempt < 100; attempt++ )); do kill -0 "$pid" 2>/dev/null || return 0 sleep 0.05 done return 1 } wait_for_path_removal() { local path="$1" attempt for (( attempt = 0; attempt < 100; attempt++ )); do [[ ! -e "$path" ]] && return 0 sleep 0.05 done return 1 } run_panama() { output="$(cd "$fixture" && TMPDIR="$fixture" PANAMA_TEST_FIXTURE="$fixture" "$fixture/bin/panama" "$@" &1)" status=$? } run_panama_with_timeout() { output="$(cd "$fixture" && TMPDIR="$fixture" PANAMA_TEST_TIMEOUT_SECONDS=1 PANAMA_TEST_FIXTURE="$fixture" "$fixture/bin/panama" "$@" &1)" status=$? } run_panama_tty_default_no() { local command tty_stdout="$fixture/tty.stdout" local pty_state="$fixture/pty-state" mkdir -p "$pty_state"/{config,state,cache,data,runtime} chmod 700 "$pty_state/runtime" printf -v command 'cd %q && TMPDIR=%q PANAMA_TEST_FIXTURE=%q %q test composite > %q' \ "$fixture" "$fixture" "$fixture" "$fixture/bin/panama" "$tty_stdout" output="$( HOME="$fixture/pty-home" \ BASH_ENV="$fixture/pty-bash-env" \ PANAMA_PTY_STARTUP_SENTINEL="$fixture/pty-startup-sourced" \ python3 - "$command" "$fixture/pty-home" \ "$pty_state/config" "$pty_state/state" "$pty_state/cache" \ "$pty_state/data" "$pty_state/runtime" "$fixture" <<'PY' import errno import os import pty import sys command = sys.argv[1] environment = os.environ.copy() environment.pop('BASH_ENV', None) environment.pop('ENV', None) environment.update({ 'HOME': sys.argv[2], 'XDG_CONFIG_HOME': sys.argv[3], 'XDG_STATE_HOME': sys.argv[4], 'XDG_CACHE_HOME': sys.argv[5], 'XDG_DATA_HOME': sys.argv[6], 'XDG_RUNTIME_DIR': sys.argv[7], 'TMPDIR': sys.argv[8], }) pid, terminal = pty.fork() if pid == 0: os.execve('/bin/bash', ['bash', '--noprofile', '--norc', '-c', command], environment) chunks = [] replied = False while True: try: chunk = os.read(terminal, 1024) except OSError as error: if error.errno == errno.EIO: break raise if not chunk: break chunks.append(chunk) if not replied and b'[y/N]' in b''.join(chunks): os.write(terminal, b'\n') replied = True _, child_status = os.waitpid(pid, 0) sys.stdout.buffer.write(b''.join(chunks)) sys.exit(os.waitstatus_to_exitcode(child_status)) PY )" status=$? } assert_occurrences() { local needle="$1" haystack="$2" expected="$3" actual actual="$(grep -oF -- "$needle" <<<"$haystack" | wc -l)" [[ "$actual" == "$expected" ]] || fail "expected $expected occurrence(s) of '$needle', got $actual\n$haystack" } replace_manifest_line() { local original="$1" replacement="$2" line while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$line" == "$original" ]]; then [[ "$replacement" == __REMOVE__ ]] || printf '%s\n' "$replacement" else printf '%s\n' "$line" fi done <<<"$valid_manifest" } swap_manifest_contract_paths() { local line while IFS= read -r line || [[ -n "$line" ]]; do case "$line" in 'live-compositor,live-desktop tests/composite-contract') printf '%s\n' 'live-compositor,live-desktop tests/desktop-contract' ;; 'live-desktop tests/desktop-contract') printf '%s\n' 'live-desktop tests/composite-contract' ;; *) printf '%s\n' "$line" ;; esac done <<<"$valid_manifest" } expect_manifest_rejection() { local label="$1" expected="$2" contents="$3" printf '%s\n' "$contents" >"$fixture/tests/contracts.manifest" reset_executions run_panama test pass [[ $status -ne 0 ]] || fail "$label manifest unexpectedly allowed test execution" assert_contains "$expected" "$output" assert_execution '' run_panama contracts config/subject [[ $status -ne 0 ]] || fail "$label manifest unexpectedly allowed contracts lookup" assert_contains "$expected" "$output" assert_execution '' } mkdir -p "$fixture/bin" "$fixture/tests" "$fixture/config" cp "$repo_dir/bin/panama" "$fixture/bin/panama" chmod +x "$fixture/bin/panama" touch "$fixture/config/subject" git -C "$fixture" init --quiet cat > "$fixture/tests/contracts.manifest" <<'EOF' # Maps the live desktop and reads compositor state. live-compositor,live-desktop tests/composite-contract # Maps the live desktop. live-desktop tests/desktop-contract hermetic tests/fail-contract hermetic tests/hang-contract # Reads a host fixture. live-host tests/host-contract # Contacts a fixture endpoint. network tests/network-contract hermetic tests/pass-contract # Elevates a fixture boundary. privileged tests/privileged-contract hermetic tests/stderr-contract EOF valid_manifest="$(<"$fixture/tests/contracts.manifest")" cat > "$fixture/tests/pass-contract" <<'EOF' #!/usr/bin/env bash printf 'pass\n' >> "$PANAMA_TEST_FIXTURE/executions" printf 'pass stdout\n' # config/subject EOF cat > "$fixture/tests/fail-contract" <<'EOF' #!/usr/bin/env bash printf 'fail\n' >> "$PANAMA_TEST_FIXTURE/executions" printf 'failure stdout\n' printf 'failure stderr\n' >&2 exit 7 EOF cat > "$fixture/tests/stderr-contract" <<'EOF' #!/usr/bin/env bash printf 'stderr\n' >> "$PANAMA_TEST_FIXTURE/executions" printf 'warning on success\n' >&2 EOF cat > "$fixture/tests/hang-contract" <<'EOF' #!/usr/bin/env bash printf 'hang\n' >> "$PANAMA_TEST_FIXTURE/executions" printf '%s\n' "$BASHPID" > "$PANAMA_TEST_FIXTURE/hang.pid" finish() { printf 'terminated\n' >"$PANAMA_TEST_FIXTURE/terminated" exit "$1" } trap 'finish 130' INT trap 'finish 143' TERM while :; do sleep 1; done EOF cat > "$fixture/tests/host-contract" <<'EOF' #!/usr/bin/env bash printf 'host\n' >> "$PANAMA_TEST_FIXTURE/executions" EOF cat > "$fixture/tests/desktop-contract" <<'EOF' #!/usr/bin/env bash printf 'desktop\n' >> "$PANAMA_TEST_FIXTURE/executions" printf 'desktop fixture complete\n' >&2 # config/subject EOF cat > "$fixture/tests/composite-contract" <<'EOF' #!/usr/bin/env bash printf 'composite\n' >> "$PANAMA_TEST_FIXTURE/executions" # config/subject EOF cat > "$fixture/tests/network-contract" <<'EOF' #!/usr/bin/env bash printf 'network\n' >> "$PANAMA_TEST_FIXTURE/executions" EOF cat > "$fixture/tests/privileged-contract" <<'EOF' #!/usr/bin/env bash printf 'privileged\n' >> "$PANAMA_TEST_FIXTURE/executions" EOF chmod +x "$fixture/tests"/{composite,desktop,fail,hang,host,network,pass,privileged,stderr}-contract : > "$fixture/executions" # A PTY-backed default-no confirmation remains visible when stdout is redirected # but stdin and stderr are terminals. The fixture proves that one prompt gates # the selected composite capability set without running its contract. mkdir -p "$fixture/pty-home" for profile in .bash_profile .bashrc .profile; do cat >"$fixture/pty-home/$profile" <<'EOF' printf 'profile\n' >>"${PANAMA_PTY_STARTUP_SENTINEL:?}" EOF done cat >"$fixture/pty-bash-env" <<'EOF' printf 'BASH_ENV\n' >>"${PANAMA_PTY_STARTUP_SENTINEL:?}" EOF run_panama_tty_default_no [[ $status -ne 0 ]] || fail 'TTY default-no prompt unexpectedly ran the fixture' assert_execution '' assert_contains 'Run 1 contract(s) requiring: live-compositor live-desktop?' "$output" assert_occurrences 'Run 1 contract(s) requiring:' "$output" 1 assert_contains 'No contracts were run.' "$(<"$fixture/tty.stdout")" assert_not_contains 'Run 1 contract(s) requiring:' "$(<"$fixture/tty.stdout")" [[ ! -e "$fixture/pty-startup-sourced" ]] \ || fail 'PTY fixture sourced a shell profile or BASH_ENV' # --safe must select hermetic entries from the manifest, not merely omit a # legacy desktop list. The failing and timed-out fixtures make the command # nonzero, but every selected hermetic contract still runs and each external # capability reports its skipped count. rm -f -- "$fixture/terminated" run_panama_with_timeout test --safe [[ $status -ne 0 ]] || fail '--safe unexpectedly passed a failing fixture' assert_execution $'fail\nhang\npass\nstderr' [[ -f "$fixture/terminated" ]] || fail '--safe did not run and terminate the hermetic hang fixture' assert_contains 'Skipped 1 live-host contract(s).' "$output" assert_contains 'Skipped 1 live-compositor contract(s).' "$output" assert_contains 'Skipped 2 live-desktop contract(s).' "$output" assert_contains 'Skipped 1 network contract(s).' "$output" assert_contains 'Skipped 1 privileged contract(s).' "$output" reset_executions run_panama test desktop [[ $status -ne 0 ]] || fail 'non-TTY desktop run unexpectedly passed without a grant' assert_execution '' assert_contains 'pass --allow live-desktop' "$output" run_panama test --allow live-desktop desktop [[ $status -eq 0 ]] || fail "explicit desktop grant failed: $output" assert_execution 'desktop' assert_contains 'Running desktop-contract [live-desktop]' "$output" assert_before 'Running desktop-contract [live-desktop]' 'desktop fixture complete' "$output" reset_executions run_panama test --allow live-compositor --allow live-desktop composite [[ $status -eq 0 ]] || fail "repeatable grants failed: $output" assert_execution 'composite' reset_executions run_panama test --allow live-desktop network [[ $status -ne 0 ]] || fail 'desktop grant incorrectly allowed network' assert_execution '' assert_contains 'network' "$output" for args in '--unknown' 'pass-contract second-pattern' '--allow unknown' '--safe --allow live-desktop'; do # shellcheck disable=SC2086 run_panama test $args [[ $status -eq 2 ]] || fail "usage error did not exit 2 for: $args\n$output" done reset_executions run_panama_with_timeout test hang [[ $status -ne 0 ]] || fail 'timed-out contract unexpectedly passed' assert_execution 'hang' [[ -f "$fixture/terminated" ]] || fail 'timed-out contract was not terminated with TERM' assert_contains 'timed out' "$output" # INT/TERM ownership belongs to the exact public CLI PID, not a runner # subshell. The CLI must wait for the timeout process group and remove its # capture directory before returning the signal-derived status. reset_executions rm -f -- "$fixture/hang.pid" "$fixture/terminated" ( cd "$fixture" || exit 1 exec env TMPDIR="$fixture" PANAMA_TEST_FIXTURE="$fixture" \ "$fixture/bin/panama" test hang ) >"$fixture/exact-term.out" 2>&1 & background_cli_pid=$! wait_for_file "$fixture/hang.pid" \ || fail 'exact-PID TERM fixture never started the hang contract' hang_pid="$(<"$fixture/hang.pid")" background_contract_pgid="$(ps -o pgid= -p "$hang_pid" | tr -d '[:space:]')" [[ "$background_contract_pgid" =~ ^[1-9][0-9]*$ ]] \ || fail "could not resolve hang process group for PID $hang_pid" mapfile -t active_capture_dirs < <( find "$fixture" -mindepth 1 -maxdepth 1 -type d -name 'tmp.*' -print ) (( ${#active_capture_dirs[@]} == 1 )) \ || fail "expected one active capture directory, got ${#active_capture_dirs[@]}" active_capture_dir="${active_capture_dirs[0]}" kill -TERM "$background_cli_pid" \ || fail 'could not send TERM to the exact public CLI PID' term_status=0 wait "$background_cli_pid" || term_status=$? background_cli_pid="" [[ "$term_status" -eq 143 ]] \ || fail "exact-PID TERM returned $term_status instead of 143: $(<"$fixture/exact-term.out")" wait_for_process_exit "$hang_pid" \ || fail "hang contract PID $hang_pid survived exact-PID TERM" background_contract_pgid="" wait_for_path_removal "$active_capture_dir" \ || fail "capture directory survived exact-PID TERM: $active_capture_dir" [[ -f "$fixture/terminated" ]] \ || fail 'exact-PID TERM did not reach the hang contract cleanup trap' reset_executions run_panama test fail [[ $status -ne 0 ]] || fail 'failed contract unexpectedly passed' assert_contains 'failure stdout' "$output" assert_contains 'failure stderr' "$output" reset_executions run_panama test stderr [[ $status -eq 0 ]] || fail "stderr success contract failed: $output" assert_contains 'warning on success' "$output" reset_executions run_panama test pass [[ $status -eq 0 ]] || fail "pass contract failed: $output" assert_not_contains 'pass stdout' "$output" assert_not_contains 'pass-contract [hermetic]' "$output" reset_executions printf 'not a directory\n' >"$fixture/invalid-tmpdir" output="$( cd "$fixture" && \ TMPDIR="$fixture/invalid-tmpdir" PANAMA_TEST_FIXTURE="$fixture" \ "$fixture/bin/panama" test pass &1 )" status=$? [[ $status -ne 0 ]] || fail 'invalid TMPDIR unexpectedly allowed contract execution' assert_contains 'Could not create contract capture directory.' "$output" assert_execution '' reset_executions run_panama test --safe desktop [[ $status -ne 0 ]] || fail 'only-skipped pattern unexpectedly passed' assert_contains 'Every contract matching' "$output" assert_not_contains 'No contracts match' "$output" output="$(cd "$fixture" && "$fixture/bin/panama" contracts config/subject 2>&1)" status=$? [[ $status -eq 0 ]] || fail "contracts lookup failed: $output" assert_contains 'tests/desktop-contract [live-desktop]' "$output" assert_contains 'tests/composite-contract [live-compositor,live-desktop]' "$output" assert_contains 'tests/pass-contract [hermetic]' "$output" # Both public manifest consumers fail closed on the complete format and # discovery set. Validation happens before selection, lookup, or contract # execution, so even a malformed entry unrelated to the requested pattern is # fatal and leaves the execution log empty. expect_manifest_rejection unknown-capability \ 'unknown capability hermetik on tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetik tests/pass-contract')" expect_manifest_rejection mixed-hermetic \ 'hermetic must appear alone on tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic,network tests/pass-contract')" expect_manifest_rejection duplicate-path \ 'duplicate path tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' $'hermetic tests/pass-contract\nhermetic tests/pass-contract')" expect_manifest_rejection stale-path \ 'stale manifest path tests/stale-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic tests/stale-contract')" expect_manifest_rejection missing-contract \ 'missing contract tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' __REMOVE__)" expect_manifest_rejection extra-field \ 'manifest line is not exactly two fields' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic tests/pass-contract unexpected')" expect_manifest_rejection empty-capability \ 'empty capability on tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic, tests/pass-contract')" expect_manifest_rejection duplicate-capability \ 'duplicate capability hermetic on tests/pass-contract' \ "$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic,hermetic tests/pass-contract')" expect_manifest_rejection unsorted-paths \ 'paths are not lexicographically sorted' \ "$(swap_manifest_contract_paths)" expect_manifest_rejection uncommented-non-hermetic \ 'tests/desktop-contract is non-hermetic but lacks a directly preceding comment' \ "$(replace_manifest_line '# Maps the live desktop.' __REMOVE__)" expect_manifest_rejection blank-comment \ 'tests/desktop-contract is non-hermetic but lacks a non-empty directly preceding comment' \ "$(replace_manifest_line '# Maps the live desktop.' '#')" printf '%s\n' "$valid_manifest" >"$fixture/tests/contracts.manifest" mv "$fixture/tests/contracts.manifest" "$fixture/tests/contracts.manifest.missing" reset_executions run_panama test pass [[ $status -ne 0 ]] || fail 'missing manifest unexpectedly allowed test execution' assert_contains 'contracts.manifest' "$output" assert_execution '' mv "$fixture/tests/contracts.manifest.missing" "$fixture/tests/contracts.manifest" capture_dirs="$(find "$fixture" -mindepth 1 -maxdepth 1 -type d -name 'tmp.*' -print)" [[ -z "$capture_dirs" ]] || fail "runner leaked capture directory: $capture_dirs" printf 'test runner: PASS\n'