Files
summercms/scripts/check-phase8-red.sh
Jakub Zych 24d35d85e8 test(08-01): add failing RFC 8414 metadata RED test and fail-closed verifier
- wristband.Server.Metadata is a compiling 501 stub; TestPhase8RedMetadata
  asserts the exact unwrapped PHP metadata document, headers and status and
  fails with the PHASE8_RED:metadata sentinel (D-06)
- scripts/check-phase8-red.sh implements the shared go/shell RED contract
  for the rest of Phase 8: exact selected test/package failure plus sentinel,
  rejecting unrelated fail actions, compile/setup failures, panics,
  malformed JSON, missing/duplicate sentinels and zero selection (D-04/D-18)
2026-09-23 19:09:14 +02:00

223 lines
5.4 KiB
Bash
Executable File

#!/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 <sentinel> <package> <test> -- <go test -json command...>
# check-phase8-red.sh shell <sentinel> <stage> -- <command...>
set -euo pipefail
usage() {
cat >&2 <<'EOF'
usage:
check-phase8-red.sh go <sentinel> <package> <test> -- <go test -json command...>
check-phase8-red.sh shell <sentinel> <stage> -- <command...>
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> <package> <test> <output-file>")
}
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