#!/usr/bin/env bash # Fail-closed Phase 8 RED verifier (08-CONTEXT.md D-04/D-18; 08-01-PLAN.md # Task 1). Every later Phase 8 plan proves its RED test through this script # before implementing the matching GREEN, so its acceptance is intentionally # strict: it must accept exactly one deliberate, exact-sentinel behavior # failure and reject every other failure class (unrelated test/package, # compile/setup failure, panic, malformed output, or zero selection). # # D-01: standard library only. The `go` mode delegates JSON-event evaluation # to a small stdlib-only Go program (D-01 applies to the verifier too, not # just wristband) so this script never depends on jq or another JSON tool. # # Usage: # check-phase8-red.sh go -- # check-phase8-red.sh shell -- set -euo pipefail usage() { cat >&2 <<'EOF' usage: check-phase8-red.sh go -- check-phase8-red.sh shell -- EOF exit 2 } refuse() { echo "REFUSE: $*" >&2 exit 1 } [[ $# -ge 1 ]] || usage MODE="$1" shift case "$MODE" in go) [[ $# -ge 4 ]] || usage SENTINEL="$1" PKG="$2" TEST="$3" shift 3 [[ "${1:-}" == "--" ]] || usage shift [[ $# -ge 1 ]] || usage OUT_FILE="$(mktemp)" CHECKER_FILE="$(mktemp --suffix=.go)" cleanup_go() { rm -f "$OUT_FILE" "$CHECKER_FILE"; } trap cleanup_go EXIT set +e "$@" >"$OUT_FILE" set -e cat >"$CHECKER_FILE" <<'GOEOF' // Command check-phase8-red-checker evaluates one go test -json event stream // against the Phase 8 fail-closed RED contract (08-01-PLAN.md Task 1). It is // intentionally stdlib-only (D-01) and is invoked by check-phase8-red.sh via // `go run`, never built into the module. package main import ( "bufio" "encoding/json" "fmt" "os" "strings" ) type event struct { Action string `json:"Action"` Package string `json:"Package"` Test string `json:"Test"` Output string `json:"Output"` FailedBuild string `json:"FailedBuild"` } func refuse(format string, args ...any) { fmt.Fprintf(os.Stderr, "REFUSE: "+format+"\n", args...) os.Exit(1) } func main() { if len(os.Args) != 5 { refuse("usage: checker ") } sentinel, pkg, test, outPath := os.Args[1], os.Args[2], os.Args[3], os.Args[4] f, err := os.Open(outPath) if err != nil { refuse("cannot open captured output: %v", err) } defer f.Close() scanner := bufio.NewScanner(f) scanner.Buffer(make([]byte, 1<<20), 1<<24) var ( lineCount int selectedRun bool selectedFail bool packageFail bool sentinelCount int ) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" { continue } lineCount++ var e event if err := json.Unmarshal([]byte(line), &e); err != nil { refuse("malformed JSON event (not go test -json output): %s", line) } if strings.Contains(e.Output, "panic:") { refuse("panic detected in test output: %s", strings.TrimSpace(e.Output)) } if strings.Contains(e.Output, "[build failed]") || strings.Contains(e.Output, "[setup failed]") { refuse("compile/setup failure detected: %s", strings.TrimSpace(e.Output)) } if e.FailedBuild != "" { refuse("build failure detected (FailedBuild=%s)", e.FailedBuild) } if e.Action == "build-fail" { refuse("build failure (build-fail action) for %s", e.Package) } sentinelCount += strings.Count(e.Output, sentinel) switch e.Action { case "run": if e.Test == test && e.Package == pkg { selectedRun = true } case "fail": switch { case e.Test == test && e.Package == pkg: selectedFail = true case e.Test == "" && e.Package == pkg: packageFail = true default: refuse("unrelated failure: package=%q test=%q", e.Package, e.Test) } } } if err := scanner.Err(); err != nil { refuse("reading captured output: %v", err) } if lineCount == 0 { refuse("no JSON events observed (empty or non -json output)") } if !selectedRun { refuse("selected test %q in package %q never ran (zero selection or build/setup failure)", test, pkg) } if !selectedFail { refuse("selected test %q in package %q did not fail", test, pkg) } if !packageFail { refuse("package %q did not report a package-level failure", pkg) } if sentinelCount == 0 { refuse("sentinel %q was not observed in test output", sentinel) } if sentinelCount > 1 { refuse("sentinel %q observed %d times, expected exactly 1", sentinel, sentinelCount) } fmt.Printf("PHASE8_RED_OK:%s\n", sentinel) } GOEOF go run "$CHECKER_FILE" "$SENTINEL" "$PKG" "$TEST" "$OUT_FILE" ;; shell) [[ $# -ge 2 ]] || usage SENTINEL="$1" STAGE="$2" shift 2 [[ "${1:-}" == "--" ]] || usage shift [[ $# -ge 1 ]] || usage set +e OUT="$("$@" 2>&1)" STATUS=$? set -e if [[ "$STATUS" -ne 86 ]]; then refuse "expected exit 86, got $STATUS" fi EXPECTED_LINE="PHASE8_STAGE:${STAGE}:FAIL:${SENTINEL}" STAGE_LINE_COUNT=0 MATCH_COUNT=0 while IFS= read -r line; do [[ "$line" == PHASE8_STAGE:* ]] || continue STAGE_LINE_COUNT=$((STAGE_LINE_COUNT + 1)) if [[ "$line" == "$EXPECTED_LINE" ]]; then MATCH_COUNT=$((MATCH_COUNT + 1)) fi done <<<"$OUT" if [[ "$MATCH_COUNT" -ne 1 ]]; then refuse "expected exactly one line '$EXPECTED_LINE', found $MATCH_COUNT" fi if [[ "$STAGE_LINE_COUNT" -ne 1 ]]; then refuse "unexpected additional PHASE8_STAGE lines (found $STAGE_LINE_COUNT total)" fi echo "PHASE8_RED_OK:${SENTINEL}" ;; *) usage ;; esac