#!/usr/bin/env bash # Phase 9 fail-closed gate. Stages refuse skipped PostgreSQL tests, zero-test # runs, and an OpenAPI document that does not list the admin surface. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" APP="$(cd "$ROOT/../fonoteka.go" && pwd)" REVIEW="$ROOT/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-SECURITY-REVIEW.md" VALIDATION="$ROOT/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-VALIDATION.md" usage() { cat >&2 <<'EOF' usage: check-phase9.sh --self-test check-phase9.sh --security check-phase9.sh --postgres check-phase9.sh --openapi check-phase9.sh --evidence check-phase9.sh --all EOF exit 2 } # phase9_detect reads go test -json. Exit 1 fail, 2 skip, 3 zero tests. phase9_detect() { python3 - "$1" <<'PY' import json, sys path = sys.argv[1] saw = False with open(path, encoding="utf-8", errors="replace") as fh: for raw in fh: line = raw.strip() if not line.startswith("{"): continue try: ev = json.loads(line) except json.JSONDecodeError: print("refuse: non-json test output", file=sys.stderr) sys.exit(4) action = ev.get("Action") test = ev.get("Test") or "" if action == "skip" and test: print(f"refuse: skipped {test}", file=sys.stderr) sys.exit(2) if action == "fail": name = test or ev.get("Package") or "unknown" print(f"refuse: failed {name}", file=sys.stderr) sys.exit(1) if action == "pass" and test: saw = True if not saw: print("refuse: zero tests", file=sys.stderr) sys.exit(3) PY } phase9_go() { local dir="$1" shift local log err log="$(mktemp)" err="$(mktemp)" set +e (cd "$dir" && go test -json -count=1 "$@") >"$log" 2>"$err" local rc=$? set -e if [[ -s "$err" ]]; then cat "$err" >&2 fi local dc=0 phase9_detect "$log" || dc=$? if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then tail -n 30 "$log" >&2 || true rm -f "$log" "$err" echo "refuse: go test $* in $dir (test=$rc detect=$dc)" >&2 exit 1 fi rm -f "$log" "$err" } expect_detect_fails() { local name="$1" local payload="$2" local log log="$(mktemp)" printf '%s\n' "$payload" >"$log" local dc=0 phase9_detect "$log" || dc=$? rm -f "$log" if [[ "$dc" -eq 0 ]]; then echo "refuse: self-test $name accepted a bad run" >&2 exit 1 fi } run_self_test() { bash -n "${BASH_SOURCE[0]}" local log log="$(mktemp)" printf '%s\n' '{"Action":"pass","Test":"TestPhase09GuardIsolation"}' >"$log" phase9_detect "$log" rm -f "$log" expect_detect_fails skip '{"Action":"skip","Test":"TestPhase09MigrationsFreshRollback"}' expect_detect_fails zero '{"Action":"pass","Package":"git.golem15.com/golem15/summercms/lagoon"}' expect_detect_fails fail '{"Action":"fail","Test":"TestPhase09ContractInventory"}' for flag in --self-test --security --postgres --openapi --evidence --all; do grep -q -- "$flag" "${BASH_SOURCE[0]}" || { echo "refuse: missing mode $flag" >&2 exit 1 } done echo "phase9 self-test passed" } run_security() { phase9_go "$ROOT" ./bouncer ./cabana -run '^TestPhase09' phase9_go "$APP" ./plugins/golem15/fonoteka -run '^TestPhase09Security' echo "phase9 security passed" } run_postgres() { phase9_go "$ROOT" ./lagoon -run '^TestPhase09MigrationsFreshRollback$' phase9_go "$APP" ./plugins/golem15/fonoteka -run '^TestPhase09AssembledAcceptance$' echo "phase9 postgres passed" } run_openapi() { (cd "$APP" && bash scripts/check-openapi.sh) phase9_go "$ROOT" ./cabana -run '^TestPhase09ContractInventory$' echo "phase9 openapi passed" } run_evidence() { [[ -f "$REVIEW" && -f "$VALIDATION" ]] || { echo "refuse: security review or validation file is missing" >&2 exit 1 } python3 - "$REVIEW" "$VALIDATION" <<'PY' import pathlib, sys review = pathlib.Path(sys.argv[1]).read_text() validation = pathlib.Path(sys.argv[2]).read_text() required = [f"T-09-{i:02d}" for i in range(1, 22)] + ["T-09-SC"] missing = [item for item in required if item not in review] if missing: print("refuse: review missing " + ", ".join(missing), file=sys.stderr) sys.exit(1) if "nyquist_compliant: true" not in validation: print("refuse: validation is not nyquist_compliant", file=sys.stderr) sys.exit(1) for line in validation.splitlines(): if line.startswith("|") and "pending" in line: print("refuse: validation row still pending: " + line, file=sys.stderr) sys.exit(1) for req in ("AUTH-08", "ADMIN-01", "ADMIN-02", "ADMIN-03", "ADMIN-04", "ADMIN-05"): if req not in validation: print(f"refuse: validation missing {req}", file=sys.stderr) sys.exit(1) print("phase9 evidence files passed") PY run_security run_postgres run_openapi echo "phase9 evidence passed" } case "${1:-}" in --self-test) run_self_test ;; --security) run_security ;; --postgres) run_postgres ;; --openapi) run_openapi ;; --evidence) run_evidence ;; --all) run_self_test run_security run_postgres run_openapi run_evidence ;; *) usage ;; esac