Files
summercms/scripts/check-phase13.sh
Jakub Zych ad79b3cca9 test(13-06): run TestOverlapConstraintFallsThrough in the gate's named stage
- The evidence stage refused T-13-23's review row because the named stage
  did not run the test its removal check RC-01 relies on
2026-10-03 11:32:35 +02:00

1013 lines
49 KiB
Bash
Executable File

#!/usr/bin/env bash
# Phase 13 fail-closed gate (wishlist, notifications, CSV import and export,
# credentials, onboarding and public routes: API-03 to API-07).
#
# Every stage exits non-zero on a failing command, a go test run that fails,
# skips, matches zero tests or prints "no tests to run", a named test that
# did not pass, a data race, a parity count other than the expected one, a
# coverage floor missed, a corpus secret or an evidence gap. --self-test
# proves each detector fails closed on planted inputs.
#
# --removal is the anchor-exact mutation harness behind the RC rows of
# 13-SECURITY-REVIEW.md: it removes one protection at a time, requires its
# named test to fail on an assertion, and restores the file byte for byte
# (checked with cmp). It refuses a file with uncommitted changes and edits
# tracked source while it runs, so it is not part of --all.
#
# Framework commands run in summercms.go; application commands run in the
# sibling repository named by PHASE13_APP (default ../fonoteka.go).
set -euo pipefail
# A colour-forcing shell variable changes the output some framework tests
# compare byte for byte; the gate runs without it.
unset FORCE_COLOR
ROOT="${PHASE13_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
APP="${PHASE13_APP:-$(cd "$ROOT/../fonoteka.go" && pwd)}"
PHASE_DIR="${PHASE13_PHASE_DIR:-$ROOT/.planning/phases/13-p-ytarium-api-wishlist-notifications-csv-credentials-public}"
REVIEW="$PHASE_DIR/13-SECURITY-REVIEW.md"
VALIDATION="$PHASE_DIR/13-VALIDATION.md"
APP_PLUGINS=(./plugins/golem15/fonoteka/... ./plugins/golem15/user/...)
EXPECTED_PORTED=157
EXPECTED_PENDING=14
COVERAGE_FLOOR=80
# The user plugin's controllers package before Phase 13 (measured at
# sm-user-plugin c258e9f); Phase 13 may not lower it.
USER_CONTROLLERS_PRE=68.8
XTEXT_VERSION="v0.42.0"
FUZZ_CORPUS="plugins/golem15/fonoteka/testdata/fuzz"
usage() {
cat >&2 <<'EOF'
usage:
check-phase13.sh --self-test
check-phase13.sh --go
check-phase13.sh --parity
check-phase13.sh --named
check-phase13.sh --removal
check-phase13.sh --coverage
check-phase13.sh --evidence
check-phase13.sh --all
EOF
exit 2
}
# phase13_detect reads go test -json. Exit 1 fail, 2 skip, 3 zero tests or
# "no tests to run", 4 non-JSON, 5 a required test did not pass, 6 a data
# race was reported. PHASE13_REQUIRE lists tests that must pass.
phase13_detect() {
python3 - "$1" <<'PY'
import json, os, sys
path = sys.argv[1]
require = set(os.environ.get("PHASE13_REQUIRE", "").split())
passed = set()
failed_tests, failed_pkgs = {}, []
build_failed = 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 ""
pkg = ev.get("Package") or ""
if action == "build-fail":
build_failed = True
if action == "output":
text = ev.get("Output") or ""
if "no tests to run" in text:
print(f"refuse: no tests to run in {pkg}", file=sys.stderr)
sys.exit(3)
if "WARNING: DATA RACE" in text:
print(f"refuse: data race in {pkg} {test}", file=sys.stderr)
sys.exit(6)
if action == "skip" and test:
print(f"refuse: skipped {pkg} {test}", file=sys.stderr)
sys.exit(2)
if action == "fail":
if ev.get("FailedBuild"):
build_failed = True
if test:
failed_tests.setdefault(pkg, []).append(test)
else:
failed_pkgs.append(pkg)
if action == "pass" and test:
passed.add(test)
if build_failed:
print("refuse: build failed", file=sys.stderr)
sys.exit(1)
for pkg, tests in failed_tests.items():
for test in tests:
print(f"refuse: failed {pkg} {test}", file=sys.stderr)
sys.exit(1)
for pkg in failed_pkgs:
print(f"refuse: failed {pkg or 'unknown package'}", file=sys.stderr)
sys.exit(1)
missing = sorted(name for name in require if name not in passed)
if missing:
print("refuse: required tests did not pass: " + ", ".join(missing), file=sys.stderr)
sys.exit(5)
if not passed:
print("refuse: zero tests", file=sys.stderr)
sys.exit(3)
PY
}
# phase13_go DIR ARGS... runs go test -json -count=1 ARGS through the
# detector. With PHASE13_KEEP set, the JSON log is copied there.
phase13_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
local dc=0
phase13_detect "$log" || dc=$?
if [[ "$dc" -ne 0 || "$rc" -ne 0 ]]; then
cat "$err" >&2 || true
tail -n 40 "$log" >&2 || true
rm -f "$log" "$err"
echo "refuse: go test $* in $dir (test=$rc detect=$dc)" >&2
exit 1
fi
if [[ -n "${PHASE13_KEEP:-}" ]]; then
cp "$log" "$PHASE13_KEEP"
fi
rm -f "$log" "$err"
}
# phase13_tests DIR PKG [-race] TEST... requires every named test to run and
# pass, each matched by its exact name.
phase13_tests() {
local dir="$1" pkg="$2"
shift 2
local extra=()
if [[ "${1:-}" == "-race" ]]; then
extra=(-race)
shift
fi
local names="$*"
local regex="^($(tr ' ' '|' <<<"$names"))\$"
PHASE13_REQUIRE="$names" phase13_go "$dir" "$pkg" "${extra[@]}" -run "$regex"
}
expect_detect() {
local name="$1" want="$2" payload="$3"
local log dc=0
log="$(mktemp)"
printf '%s\n' "$payload" >"$log"
phase13_detect "$log" 2>/dev/null || dc=$?
rm -f "$log"
if [[ "$dc" -ne "$want" ]]; then
echo "refuse: self-test $name: detector exit $dc, want $want" >&2
exit 1
fi
}
# The named tests: every test 13-VALIDATION.md names, by package. The
# evidence stage refuses a validation or review row naming a test missing
# here.
NAMED_ROOT_SURF="TestOverlappingConstrainedRoutes TestOverlapConstraintFallsThrough TestOverlapFamilyOfThree TestOverlapHeadAndAllow TestOverlapFamilyAcrossPlugins TestOverlapUnsupportedShapes"
NAMED_ROOT_CONGA="TestUnregisteredKindWithWorker TestUnregisteredKindWithoutWorker TestUnregisteredKindRefusalAndDelay"
NAMED_ROOT_LAGOON="TestValidateRequestProhibited TestValidateRequestProhibitedNested"
NAMED_ROOT_TIDE="TestNormalizeContentDispositionDate TestNormalizeNotificationPublication TestNormalizePhase13Edges"
NAMED_APP_USER="TestRegisterEventPayload TestRegisterUserExports"
NAMED_APP_FONOTEKA="TestWishlistOverlapPatternsDispatch TestJobContractDispatchWhileWorkerRuns TestNotificationsRoutes TestCredentialsCRUD TestCredentialSecretsNeverSerialized TestResolveAIConfigPrecedence TestDiscogsSharedMirror TestBootstrapConcurrent TestRegisterInvitationListener TestInspectInvitation TestWishlistOwnListAndShow TestWishlistItemAddedOncePerPath TestDigestCoalescing TestWishlistShareSettingsHousehold TestReserveConcurrent TestRevealIdempotent TestReservationMask TestWishlistSubscriptions TestPurchaseSideEffects TestPurchaseMailAfterCommit TestWishlistOverlapRoutesAssembled TestCsvExport TestCsvStoreAndShow TestCsvImportScope TestCsvCommitCAS TestCsvJobRows TestCsvCancel TestCsvRowPickSeam TestPublicResolve TestPubfailCounter TestPubfailCounterPerApp TestPublicBucketsPerRoute TestPublicAlbumFieldSet TestPublicAlbumsIndex TestPublicAlbumsEngine TestRouteTablePhase13 TestRouteTablePhase12 TestFullRouteTableAuthGroupMutualExclusivity FuzzWriteEndpoints TestPhase13Threats TestPhase13EmptyBodies TestPhase13Boundaries TestPhase13HandlersFailClosed TestPhase09SecurityRoutes"
NAMED_APP_CLASSES="TestJobContract TestPhase13ReservationMask TestPhase13PubfailWindow TestPhase13SmallHelpers TestPhase13NilHandles"
NAMED_APP_CSV="TestPHPFputcsv TestCsvParserTruthTable TestCsvDetectorTruthTable TestCsvPHPCasts TestCsvOrderedMap"
NAMED_APP_API="TestCsvMappingInput"
NAMED_APP_MIDDLEWARE="TestPublicShareHeadersRewrites429 TestPublicShareHeadersLeaves200Body TestPublicShareHeadersExactBytes"
NAMED_APP_PARITY="TestCheckCorpusPortedCaseStatus TestParityCorpus TestBroadcastGoldens TestFonotekaNuxtFlows TestUserAPINuxtFlows"
all_named() {
echo "$NAMED_ROOT_SURF $NAMED_ROOT_CONGA $NAMED_ROOT_LAGOON $NAMED_ROOT_TIDE $NAMED_APP_USER $NAMED_APP_FONOTEKA $NAMED_APP_CLASSES $NAMED_APP_CSV $NAMED_APP_API $NAMED_APP_MIDDLEWARE $NAMED_APP_PARITY"
}
# module_pin MODLIST: golang.org/x/text stays at the audited version
# (T-13-SC: the CSV decoder's charmap).
REASON_PIN="golang.org/x/text is not pinned at $XTEXT_VERSION"
module_pin() {
local modlist="$1" hits
hits="$(grep -E '^golang\.org/x/text ' "$modlist" | sort -u || true)"
if [[ -z "$hits" ]] || grep -vqE "^golang\.org/x/text $XTEXT_VERSION\$" <<<"$hits"; then
echo "refuse: hygiene: $REASON_PIN: ${hits:-<absent>}" >&2
return 1
fi
return 0
}
run_go() {
(cd "$ROOT" && go vet ./...)
phase13_go "$ROOT" ./...
(cd "$APP" && go vet ./... "${APP_PLUGINS[@]}")
phase13_go "$APP" ./... "${APP_PLUGINS[@]}"
local modlist
modlist="$(mktemp)"
(cd "$ROOT" && go list -m all) >"$modlist"
(cd "$APP" && go list -m all) >>"$modlist"
if ! module_pin "$modlist"; then
rm -f "$modlist"
exit 1
fi
rm -f "$modlist"
echo "phase13 go passed"
}
# corpus_scan DIR: the fuzz seed corpus holds synthetic values only
# (T-13-36): no 64-hex token, inv_ personal token, JWT or bearer header.
corpus_scan() {
python3 - "$1" <<'PY'
import os, re, sys
root = sys.argv[1]
if not os.path.isdir(root):
print(f"refuse: fuzz corpus {root} is missing", file=sys.stderr)
sys.exit(1)
patterns = [
("64-hex value", re.compile(r"(?<![0-9A-Fa-f])[0-9A-Fa-f]{64}(?![0-9A-Fa-f])")),
("personal token", re.compile(r"inv_[A-Za-z0-9]{16,}")),
("JWT", re.compile(r"eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.")),
("bearer header", re.compile(r"(?i)bearer\s+[A-Za-z0-9._-]{12,}")),
]
files = 0
for dirpath, _, names in os.walk(root):
for name in names:
files += 1
text = open(os.path.join(dirpath, name), encoding="utf-8", errors="replace").read()
for label, rx in patterns:
if rx.search(text):
print(f"refuse: fuzz corpus {os.path.join(dirpath, name)} holds a {label}", file=sys.stderr)
sys.exit(1)
if files == 0:
print(f"refuse: fuzz corpus {root} is empty", file=sys.stderr)
sys.exit(1)
PY
}
# manifest_count MANIFEST STATUS: the number of routes with that status.
manifest_count() {
grep -cE "^[[:space:]]*status:[[:space:]]*$2[[:space:]]*\$" "$1" || true
}
# corpus_coverage LOG: the replay's own coverage line in a go test -json
# log ("recorded R/T passing P failing F unrecorded U pending Q") must say
# EXPECTED_PORTED passing, 0 failing, 0 unrecorded and EXPECTED_PENDING
# pending. Pending routes are never counted as passing.
corpus_coverage() {
python3 - "$1" "$EXPECTED_PORTED" "$EXPECTED_PENDING" <<'PY'
import json, re, sys
path, ported, pending = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
rx = re.compile(r"recorded (\d+)/(\d+) passing (\d+) failing (\d+) unrecorded (\d+) pending (\d+)")
found = None
for raw in open(path, encoding="utf-8", errors="replace"):
raw = raw.strip()
if not raw.startswith("{"):
continue
try:
ev = json.loads(raw)
except json.JSONDecodeError:
continue
m = rx.search(ev.get("Output") or "")
if m:
found = [int(x) for x in m.groups()]
if found is None:
print("refuse: the corpus replay printed no coverage line", file=sys.stderr)
sys.exit(1)
recorded, total, passing, failing, unrecorded, pend = found
if passing != ported or failing != 0 or unrecorded != 0 or pend != pending or recorded != total or passing + pend != total:
print(f"refuse: corpus coverage recorded {recorded}/{total} passing {passing} failing {failing} unrecorded {unrecorded} pending {pend}, want {ported} passing, 0 failing, {pending} pending", file=sys.stderr)
sys.exit(1)
print(f"phase13 corpus: {passing} ported and passing, 0 failing, {pend} pending")
PY
}
PARITY_REQUIRE="TestParityCorpus TestParityCorpus/coverage TestBroadcastGoldens TestBroadcastGoldens/created TestBroadcastGoldens/updated TestBroadcastGoldens/deleted TestBroadcastGoldens/bulk TestBroadcastGoldens/wishlist-item-added TestBroadcastGoldens/reservation-revealed TestBroadcastGoldens/wishlist-purchased TestFonotekaNuxtFlows TestFonotekaNuxtFlows/nuxt-collections TestFonotekaNuxtFlows/nuxt-albums TestFonotekaNuxtFlows/onboarding TestFonotekaNuxtFlows/nuxt-wishlist TestFonotekaNuxtFlows/mcp-wishlist TestFonotekaNuxtFlows/nuxt-csv TestFonotekaNuxtFlows/public-anonymous TestFonotekaNuxtFlows/public-pubfail TestUserAPINuxtFlows TestCheckCorpusPortedCaseStatus"
run_parity() {
local n p
n="$(manifest_count "$APP/parity/manifest.yaml" ported)"
p="$(manifest_count "$APP/parity/manifest.yaml" pending)"
if [[ "$n" -ne "$EXPECTED_PORTED" || "$p" -ne "$EXPECTED_PENDING" ]]; then
echo "refuse: parity manifest has $n ported and $p pending routes, want $EXPECTED_PORTED and $EXPECTED_PENDING" >&2
exit 1
fi
local log
log="$(mktemp)"
PHASE13_KEEP="$log" PHASE13_REQUIRE="$PARITY_REQUIRE" \
phase13_go "$APP" ./parity -run '^(TestParityCorpus|TestBroadcastGoldens|TestFonotekaNuxtFlows|TestUserAPINuxtFlows|TestCheckCorpusPortedCaseStatus)$'
if ! corpus_coverage "$log"; then
rm -f "$log"
exit 1
fi
rm -f "$log"
(cd "$APP" && go run ./parity/check_corpus.go --manifest parity/manifest.yaml --require-recorded --check-secrets)
corpus_scan "$APP/$FUZZ_CORPUS"
PHASE13_REQUIRE="TestDocsTree" phase13_go "$ROOT" ./cmd/summer -run '^TestDocsTree$'
(cd "$ROOT" && go run ./cmd/summer docs:build --check)
echo "phase13 parity passed ($n ported, 0 failing, $p pending)"
}
run_named() {
phase13_tests "$ROOT" ./modules/surf $NAMED_ROOT_SURF
phase13_tests "$ROOT" ./modules/conga $NAMED_ROOT_CONGA
phase13_tests "$ROOT" ./modules/lagoon $NAMED_ROOT_LAGOON
phase13_tests "$ROOT" ./modules/tide $NAMED_ROOT_TIDE
phase13_tests "$APP" ./plugins/golem15/user $NAMED_APP_USER
phase13_tests "$APP" ./plugins/golem15/fonoteka -race $NAMED_APP_FONOTEKA
phase13_tests "$APP" ./plugins/golem15/fonoteka/classes $NAMED_APP_CLASSES
phase13_tests "$APP" ./plugins/golem15/fonoteka/classes/csv $NAMED_APP_CSV
phase13_tests "$APP" ./plugins/golem15/fonoteka/controllers/api $NAMED_APP_API
phase13_tests "$APP" ./plugins/golem15/fonoteka/middleware $NAMED_APP_MIDDLEWARE
phase13_tests "$APP" ./parity $NAMED_APP_PARITY
echo "phase13 named passed"
}
# coverage_report FLOOR PROFILE... prints one line per package of the merged
# profiles (a block counts as covered when any profile covered it) and
# refuses any package below FLOOR percent. COVERAGE_ONLY limits the report
# to packages whose import path ends with one of its words.
coverage_report() {
python3 - "$@" <<'PY'
import collections, os, sys
floor = float(sys.argv[1])
only = os.environ.get("COVERAGE_ONLY", "").split()
blocks = {}
for path in sys.argv[2:]:
for line in open(path):
if line.startswith("mode:") or not line.strip():
continue
loc, n, c = line.rsplit(" ", 2)
n, c = int(n), int(c)
prev = blocks.get(loc, (n, 0))
blocks[loc] = (n, max(prev[1], c))
total, covered = collections.Counter(), collections.Counter()
for loc, (n, c) in blocks.items():
pkg = loc.split(":")[0].rsplit("/", 1)[0]
if only and not any(pkg.endswith(o) for o in only):
continue
total[pkg] += n
if c:
covered[pkg] += n
if not total:
print("refuse: coverage profile is empty", file=sys.stderr)
sys.exit(1)
low = []
for pkg in sorted(total):
pct = 100.0 * covered[pkg] / total[pkg]
print(f"coverage {pkg} {pct:.1f}%")
if pct < floor:
low.append(f"{pkg} {pct:.1f}%")
if low:
print(f"refuse: below the {floor:.1f}% coverage floor: " + ", ".join(low), file=sys.stderr)
sys.exit(1)
PY
}
# func_floor FLOOR FILE: reads go tool cover -func output on stdin and
# refuses any function of FILE below FLOOR percent, or none at all.
func_floor() {
python3 -c '
import sys
floor, suffix = float(sys.argv[1]), sys.argv[2]
seen = 0
low = []
for line in sys.stdin:
parts = line.split()
if len(parts) < 3 or not parts[0].split(":")[0].endswith(suffix):
continue
seen += 1
pct = float(parts[-1].rstrip("%"))
print(f"coverage {parts[0]} {parts[1]} {pct:.1f}%")
if pct < floor:
low.append(f"{parts[1]} {pct:.1f}%")
if not seen:
print(f"refuse: no function of {suffix} in the profile", file=sys.stderr)
sys.exit(1)
if low:
print(f"refuse: below the {floor:.0f}% function floor in {suffix}: " + ", ".join(low), file=sys.stderr)
sys.exit(1)
' "$@"
}
# cover_profile DIR OUT ARGS... writes a coverage profile of go test ARGS.
cover_profile() {
local dir="$1" out="$2"
shift 2
local log
log="$(mktemp)"
if ! (cd "$dir" && go test -count=1 -coverprofile="$out" "$@") >"$log" 2>&1; then
tail -n 40 "$log" >&2
rm -f "$log"
echo "refuse: go test -coverprofile $* in $dir" >&2
exit 1
fi
rm -f "$log"
}
run_coverage() {
local dir
dir="$(mktemp -d)"
trap 'rm -rf "$dir"' RETURN
local pkg i=0
# Framework packages changed in Phase 13: each package's own tests.
for pkg in ./modules/surf ./modules/conga ./modules/lagoon ./modules/tide; do
i=$((i + 1))
cover_profile "$ROOT" "$dir/root$i.out" "$pkg"
done
coverage_report "$COVERAGE_FLOOR" "$dir"/root*.out
# Application packages: every test of the plugin that exercises them.
cover_profile "$APP" "$dir/app.out" ./plugins/golem15/fonoteka/... \
-coverpkg=./plugins/golem15/fonoteka/classes,./plugins/golem15/fonoteka/classes/csv,./plugins/golem15/fonoteka/controllers/api,./plugins/golem15/fonoteka/middleware
coverage_report "$COVERAGE_FLOOR" "$dir/app.out"
# The shared user plugin: classes at the floor, every registration
# export at the floor, the controllers package not below its pre-phase
# value.
cover_profile "$APP" "$dir/user.out" ./plugins/golem15/user/... \
-coverpkg=./plugins/golem15/user/classes,./plugins/golem15/user/controllers
COVERAGE_ONLY="/classes" coverage_report "$COVERAGE_FLOOR" "$dir/user.out"
COVERAGE_ONLY="/controllers" coverage_report "$USER_CONTROLLERS_PRE" "$dir/user.out"
(cd "$APP" && go tool cover -func="$dir/user.out") | func_floor "$COVERAGE_FLOOR" controllers/registration.go
echo "phase13 coverage passed"
}
# removal_table: the RC rows of 13-SECURITY-REVIEW.md. Fields: id, threat,
# repo (root|app|script, or rootapp for a framework file whose test runs in
# the application), file, anchor, replacement, package, test regex.
# Anchors must occur exactly once.
removal_table() {
cat <<'EOF'
[
["RC-01", "T-13-23", "root", "modules/surf/overlap.go",
"\t\tif !m.constraintsMatch(vals) {\n\t\t\tcontinue\n\t\t}\n", "", "./modules/surf", "^TestOverlapConstraintFallsThrough$"],
["RC-02", "T-13-23", "root", "modules/surf/overlap.go",
"\t\tif s.param == \"\" && s.lit != vals[i] {\n\t\t\treturn false\n\t\t}", "\t\tif false && s.param == \"\" && s.lit != vals[i] {\n\t\t\treturn false\n\t\t}", "./modules/surf", "^TestOverlappingConstrainedRoutes$"],
["RC-03", "T-13-23", "rootapp", "modules/surf/overlap.go",
"\t\tif s.param == \"\" && s.lit != vals[i] {\n\t\t\treturn false\n\t\t}", "\t\tif false && s.param == \"\" && s.lit != vals[i] {\n\t\t\treturn false\n\t\t}", "./plugins/golem15/fonoteka", "^TestRouteTablePhase13$"],
["RC-04", "T-13-22", "root", "modules/conga/conga.go",
"\t}\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\treturn m.insertOnlyLocked()\n}\n\n// insertClient", "\t}\n\treturn m.insertClient()\n}\n\n// insertClient", "./modules/conga", "^TestUnregisteredKindWithWorker$"],
["RC-05", "T-13-05", "app", "plugins/golem15/fonoteka/classes/reservations.go",
"if rc.IsOwner && !revealed {", "if false && rc.IsOwner && !revealed {", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-05$"],
["RC-06", "T-13-03", "app", "plugins/golem15/fonoteka/classes/share_service.go",
"Where(\"LOWER(public_token) = ? AND public_enabled = ? AND kind = ?\", strings.ToLower(token), true, kind)",
"Where(\"LOWER(public_token) = ? AND ? AND kind = ?\", strings.ToLower(token), true, kind)", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-03$"],
["RC-07", "T-13-29", "app", "plugins/golem15/fonoteka/classes/share_service.go",
"subtle.ConstantTimeCompare([]byte(*stored), []byte(token)) == 1",
"subtle.ConstantTimeCompare([]byte(strings.ToLower(*stored)), []byte(strings.ToLower(token))) == 1", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-29$"],
["RC-08", "T-13-01", "app", "plugins/golem15/fonoteka/classes/public_share.go",
"\tif w.hits+w.inflight >= c.limit {\n\t\tc.release(key, w)\n\t\treturn nil, false\n\t}",
"\tif false && w.hits+w.inflight >= c.limit {\n\t\tc.release(key, w)\n\t\treturn nil, false\n\t}", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-01$"],
["RC-09", "T-13-01", "app", "plugins/golem15/fonoteka/classes/public_share.go",
"too := w.hits+w.inflight >= c.limit", "too := false && w.hits+w.inflight >= c.limit", "./plugins/golem15/fonoteka", "^TestPubfailCounter$"],
["RC-10", "T-13-20", "app", "plugins/golem15/user/controllers/registration.go",
"\tdelete(payload, \"password_confirmation\")\n", "", "./plugins/golem15/user", "^TestRegisterEventPayload$"],
["RC-11", "T-13-20", "app", "plugins/golem15/user/controllers/registration.go",
"\tdelete(payload, \"password_confirmation\")\n", "", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-20$"],
["RC-12", "T-13-18", "app", "plugins/golem15/fonoteka/classes/onboarding.go",
"\t\tn, err := countLiveUsers(tx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif n != 0 {\n\t\t\treturn ErrOnboardingCompleted\n\t\t}\n", "", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-18$"],
["RC-13", "T-13-12", "app", "plugins/golem15/fonoteka/classes/csv_import_service.go",
"\tif n == 0 {\n\t\treturn nil, nil\n\t}\n\treturn &imps[0], nil", "\t_ = n\n\treturn &imps[0], nil", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-12$"],
["RC-14", "T-13-17", "app", "plugins/golem15/fonoteka/classes/csv_import_service.go",
"WHERE id = ? AND status = ?`", "WHERE id = ? AND ? <> ''`", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-17$"],
["RC-15", "T-13-10", "app", "plugins/golem15/fonoteka/classes/credential_write_service.go",
"var CredentialFillFields = []string{\"provider\", \"model\", \"base_url\"}",
"var CredentialFillFields = []string{\"provider\", \"model\", \"base_url\", \"user_id\"}", "./plugins/golem15/fonoteka", "^FuzzWriteEndpoints$"],
["RC-16", "T-13-10", "app", "plugins/golem15/fonoteka/classes/credential_write_service.go",
"var CredentialFillFields = []string{\"provider\", \"model\", \"base_url\"}",
"var CredentialFillFields = []string{\"provider\", \"model\", \"base_url\", \"user_id\"}", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-10$"],
["RC-17", "T-13-21", "app", "plugins/golem15/fonoteka/classes/notifications.go",
"WHERE user_id = ? AND id = ?`, userID, id)", "WHERE ? > 0 AND id = ?`, userID, id)", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-21$"],
["RC-18", "T-13-16", "app", "plugins/golem15/fonoteka/classes/csv_import_service.go",
"func (unavailableReleaseFetcher) FetchRelease(context.Context, *usermodels.User, string) (map[string]any, error) {\n\treturn nil, ErrDiscogsUnavailable",
"func (unavailableReleaseFetcher) FetchRelease(context.Context, *usermodels.User, string) (map[string]any, error) {\n\treturn map[string]any{\"name\": \"Forged\"}, nil", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-16$"],
["RC-19", "T-13-28", "app", "plugins/golem15/fonoteka/classes/wishlist_notifications.go",
"if len(inserted) != 1 || !inserted[0] {", "if len(inserted) != 1 {", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-28$"],
["RC-20", "T-13-02", "app", "plugins/golem15/fonoteka/classes/serialize_public_album.go",
"\tCreatedAt *wire.Time `json:\"created_at\"`\n}",
"\tCreatedAt *wire.Time `json:\"created_at\"`\n\tShelf *string `json:\"shelf\"`\n}", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-02$"],
["RC-21", "T-13-08", "app", "plugins/golem15/fonoteka/controllers/api/credentials_controller.go",
"err := gdb.WithContext(r.Context()).Select(\"id\", \"provider\", \"model\", \"base_url\").Where(\"user_id = ?\", user.ID).Take(&cred).Error\n\t\twriteAICredentialStatus(w, cred.Provider, cred.Model, cred.BaseURL, err)",
"err := gdb.WithContext(r.Context()).Where(\"user_id = ?\", user.ID).Take(&cred).Error\n\t\t_ = err\n\t\twriteJSON(w, http.StatusOK, map[string]any{\"configured\": true, \"provider\": cred.Provider, \"api_key\": cred.APIKey.Reveal()})", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-08$"],
["RC-22", "T-13-07", "app", "plugins/golem15/fonoteka/routes.go",
"g.Get(\"/wishlist/albums\", wishlistIndex, \"inv.scope:read\")", "g.Get(\"/wishlist/albums\", wishlistIndex, \"inv.scope:read\", \"inv.scope:read\")", "./plugins/golem15/fonoteka", "^TestRouteTablePhase13$"],
["RC-23", "T-13-04", "app", "plugins/golem15/fonoteka/classes/reservations.go",
"WHERE album_id = ? AND user_id = ?`, albumID, userID)", "WHERE album_id = ? AND ? > 0`, albumID, userID)", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-04$"],
["RC-24", "T-13-06", "app", "plugins/golem15/fonoteka/controllers/api/wishlist_subscriptions_controller.go",
"Model(&models.Collection{}).Scopes(classes.WishlistsVisibleTo(user.ID)).\n", "Model(&models.Collection{}).Where(\"kind = 'wishlist' AND ? > 0\", user.ID).\n", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-06$"],
["RC-25", "T-13-09", "app", "plugins/golem15/fonoteka/controllers/api/credentials_controller.go",
"\t\tif !mayManageOrg(w, r, gdb, user) {\n\t\t\treturn\n\t\t}\n\t\tfields, apiKey, ok := aiCredentialInput(w, r, app, gdb)",
"\t\tfields, apiKey, ok := aiCredentialInput(w, r, app, gdb)", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-09$"],
["RC-26", "T-13-13", "app", "plugins/golem15/fonoteka/controllers/api/csv_import_controller.go",
"\t\tbucket, err := csvBucket(app)\n\t\tif err != nil {\n\t\t\twriteOpaque500(w)\n\t\t\treturn\n\t\t}\n\t\timp, err := classes.StoreCsvImport(",
"\t\tbucket, err := uploadBucket(app), error(nil)\n\t\tif err != nil {\n\t\t\twriteOpaque500(w)\n\t\t\treturn\n\t\t}\n\t\timp, err := classes.StoreCsvImport(", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-13$"],
["RC-27", "T-13-19", "app", "plugins/golem15/fonoteka/classes/onboarding.go",
"expires_at > NOW() AND LOWER(email) = ?", "expires_at > NOW() AND (LOWER(email) = ? OR TRUE)", "./plugins/golem15/fonoteka", "^TestPhase13Threats$/^T-13-19$"],
["RC-28", "T-13-SC", "script", "scripts/check-phase13.sh",
"hits=\"$(grep -E '^golang\\.org/x/text ' \"$modlist\" | sort -u || true)\"", "hits=\"golang.org/x/text $XTEXT_VERSION\"", "", "--self-test"],
["RC-29", "T-13-36", "script", "scripts/check-phase13.sh",
"holds a {label}\", file=sys.stderr)\n sys.exit(1)", "holds a {label}\", file=sys.stderr)\n pass", "", "--self-test"],
["RC-30", "T-13-35", "script", "scripts/check-phase13.sh",
" if not any(re.match(r\"^\\| RC-\\d+ \\| \" + re.escape(tid) + r\" \\|\", l) for l in removal):", " if False:", "", "--self-test"],
["RC-31", "T-13-34", "script", "scripts/check-phase13.sh",
" if dirty:\n", " if False:\n", "", "--self-test"]
]
EOF
}
# removal_harness TABLE_FILE: for each row, refuse a file with uncommitted
# changes, save it, apply the anchor-exact mutation, run the named test (or,
# for the gate script, its --self-test on a mutated copy) and require it to
# fail on an assertion, then restore the file and require cmp to match.
removal_harness() {
python3 - "$1" "$ROOT" "$APP" <<'PY'
import json, os, shutil, signal, subprocess, sys, tempfile
table = json.load(open(sys.argv[1]))
root, app = sys.argv[2], sys.argv[3]
only = set(os.environ.get("PHASE13_RC", "").split())
current = {}
def restore(*_):
# A signal mid-run still puts the file back.
if current:
with open(current["path"], "wb") as fh:
fh.write(current["original"])
sys.exit(1)
signal.signal(signal.SIGINT, restore)
signal.signal(signal.SIGTERM, restore)
failures = 0
for rc, threat, repo, rel, anchor, repl, pkg, run in table:
if only and rc not in only:
continue
base = {"root": root, "app": app, "script": root, "rootapp": root}[repo]
run_dir = {"root": root, "app": app, "script": root, "rootapp": app}[repo]
path = os.path.join(base, rel)
tracked = subprocess.run(["git", "-C", os.path.dirname(path), "rev-parse", "--is-inside-work-tree"], capture_output=True, text=True).returncode == 0
if tracked and repo != "script":
dirty = subprocess.run(["git", "-C", os.path.dirname(path), "status", "--porcelain", "--", os.path.basename(path)], capture_output=True, text=True).stdout.strip()
if dirty:
print(f"refuse: {rc}: {rel} is dirty; commit or restore it first", file=sys.stderr)
sys.exit(1)
original = open(path, "rb").read()
text = original.decode()
n = text.count(anchor)
if n != 1:
print(f"refuse: {rc} {threat}: anchor occurs {n} times in {rel}", file=sys.stderr)
sys.exit(1)
mutated = text.replace(anchor, repl, 1)
scratch = tempfile.mkdtemp(prefix="phase13-rc-")
saved = os.path.join(scratch, "saved")
shutil.copyfile(path, saved)
try:
if repo == "script":
copy = os.path.join(scratch, os.path.basename(rel))
open(copy, "w").write(mutated)
env = dict(os.environ, PHASE13_ROOT=root, PHASE13_APP=app)
proc = subprocess.run(["bash", copy, run], cwd=root, env=env, capture_output=True, text=True, timeout=900)
out = proc.stdout + proc.stderr
ok = proc.returncode != 0 and "refuse:" in out
evidence = next((l for l in out.splitlines() if l.startswith("refuse:")), "")
else:
current.update(path=path, original=original)
with open(path, "w") as fh:
fh.write(mutated)
proc = subprocess.run(["go", "test", pkg, "-run", run, "-count=1"], cwd=run_dir, capture_output=True, text=True, timeout=1800)
out = proc.stdout + proc.stderr
build = "[build failed]" in out or "[setup failed]" in out
ok = proc.returncode != 0 and "--- FAIL" in out and not build
fails = [l.strip() for l in out.splitlines() if l.strip().startswith("--- FAIL")]
names = [l.split()[2] for l in fails if len(l.split()) > 2]
evidence = ", ".join(names[:5]) + (f" (+{len(names) - 5} more)" if len(names) > 5 else "") if names else ("build failed" if build else "no failure")
finally:
with open(path, "wb") as fh:
fh.write(original)
current.clear()
same = subprocess.run(["cmp", "-s", saved, path]).returncode == 0
shutil.rmtree(scratch, ignore_errors=True)
if not same:
print(f"refuse: {rc}: {rel} was not restored byte for byte", file=sys.stderr)
sys.exit(1)
status = "fails as required" if ok else "SURVIVED"
print(f"{rc} {threat} {rel}: {status}: {evidence}", flush=True)
if not ok:
failures += 1
if failures:
print(f"refuse: {failures} removal check(s) survived", file=sys.stderr)
sys.exit(1)
PY
}
run_removal() {
local table
table="$(mktemp)"
removal_table >"$table"
if ! removal_harness "$table"; then
rm -f "$table"
exit 1
fi
rm -f "$table"
echo "phase13 removal passed"
}
# removal_harness_in ROOT TABLE runs the harness against another root.
removal_harness_in() {
local root="$1" table="$2"
(
ROOT="$root"
APP="$root"
export GOWORK=off GOFLAGS=-mod=mod
removal_harness "$table"
)
}
# evidence_check PHASE_DIR REVIEW VALIDATION NAMED: every T-13 threat the
# plans declare has exactly one review row copying its strictest severity
# and disposition (a threat several plans declare takes the strictest);
# a mitigated threat names a test the --named stage runs or a gate stage;
# a high mitigated threat has a removal row; the validation file is
# validated, Nyquist-compliant, Wave 0 complete, without a pending or TBD
# row, names API-03 to API-07, and every test it names is run by --named.
evidence_check() {
python3 - "$@" <<'PY'
import glob, os, re, sys
phase_dir, review_path, validation_path, named = sys.argv[1], sys.argv[2], sys.argv[3], set(sys.argv[4].split())
for p in (review_path, validation_path):
if not os.path.isfile(p):
print(f"refuse: {p} is missing", file=sys.stderr)
sys.exit(1)
review = open(review_path).read()
validation = open(validation_path).read()
sev_rank = {"low": 0, "medium": 1, "high": 2}
declared = {}
for plan in sorted(glob.glob(os.path.join(phase_dir, "13-0*-PLAN.md"))):
for line in open(plan):
m = re.match(r"^\| (T-13-(?:\d\d|SC)) \|", line)
if not m:
continue
cells = [c.strip().lower() for c in line.strip().strip("|").split("|")]
prev = declared.get(m.group(1))
if prev is None:
declared[m.group(1)] = cells
continue
sev = max(prev[3], cells[3], key=lambda s: sev_rank.get(s, -1))
disp = "mitigate" if "mitigate" in (prev[4], cells[4]) else prev[4]
declared[m.group(1)] = prev[:3] + [sev, disp] + prev[5:]
if not declared:
print("refuse: no plan declares a T-13 threat", file=sys.stderr)
sys.exit(1)
lines = review.splitlines()
removal = [l for l in lines if re.match(r"^\| RC-\d+ \| T-13-", l)]
for tid, cells in sorted(declared.items()):
rows = [l for l in lines if l.startswith("| " + tid + " |")]
if len(rows) != 1:
print(f"refuse: review has {len(rows)} threat rows for {tid}, want 1", file=sys.stderr)
sys.exit(1)
row = [c.strip().lower() for c in rows[0].strip().strip("|").split("|")]
severity, disposition = cells[3], cells[4]
if severity not in row or disposition not in row:
print(f"refuse: review row {tid} does not copy severity {severity!r} and disposition {disposition!r}", file=sys.stderr)
sys.exit(1)
if disposition == "mitigate":
tests = set(re.findall(r"\b(?:Test|Fuzz)[A-Z][A-Za-z0-9_]*", rows[0]))
if not tests and "check-phase13.sh" not in rows[0]:
print(f"refuse: mitigated threat {tid} names no test or gate stage", file=sys.stderr)
sys.exit(1)
unrun = sorted(t for t in tests if t not in named)
if unrun:
print(f"refuse: threat {tid} names {', '.join(unrun)}, which the --named stage does not run", file=sys.stderr)
sys.exit(1)
if severity == "high" and disposition == "mitigate":
if not any(re.match(r"^\| RC-\d+ \| " + re.escape(tid) + r" \|", l) for l in removal):
print(f"refuse: high threat {tid} has no removal check row", file=sys.stderr)
sys.exit(1)
for flag in ("nyquist_compliant: true", "wave_0_complete: true", "status: validated"):
if not re.search(r"^" + re.escape(flag) + r"$", validation, re.M):
print(f"refuse: validation lacks {flag!r}", file=sys.stderr)
sys.exit(1)
status_word = re.compile(r"(?<![A-Za-z])pending(?![A-Za-z])|\u2b1c|\| TBD \|", re.I)
for line in validation.splitlines():
if line.startswith("|") and status_word.search(line):
print("refuse: validation row still pending: " + line, file=sys.stderr)
sys.exit(1)
for req in ["API-03", "API-04", "API-05", "API-06", "API-07"]:
if req not in validation:
print(f"refuse: validation does not name {req}", file=sys.stderr)
sys.exit(1)
task_rows = "\n".join(l for l in validation.splitlines() if re.match(r"^\| 13-\d\d-T\d", l))
if not task_rows:
print("refuse: validation has no per-task verification rows", file=sys.stderr)
sys.exit(1)
for name in sorted(set(re.findall(r"\b(?:Test|Fuzz)[A-Z][A-Za-z0-9_]*", task_rows))):
if name not in named:
print(f"refuse: validation names {name}, which the --named stage does not run", file=sys.stderr)
sys.exit(1)
print("phase13 evidence passed")
PY
}
run_evidence() {
evidence_check "$PHASE_DIR" "$REVIEW" "$VALIDATION" "$(all_named)"
}
run_self_test() {
bash -n "${BASH_SOURCE[0]}"
expect_detect pass 0 '{"Action":"pass","Package":"p","Test":"TestPhase13Threats"}'
expect_detect fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p","Test":"TestPhase13Threats/T-13-01"}'
expect_detect skip 2 '{"Action":"skip","Package":"p","Test":"TestPhase13Threats"}'
expect_detect zero 3 '{"Action":"pass","Package":"p"}'
expect_detect no-tests-to-run 3 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"output","Package":"q","Output":"testing: warning: no tests to run\n"}'
expect_detect nonjson 4 '{"Action":"pass",'
expect_detect build 1 '{"Action":"build-fail","ImportPath":"p"}
{"Action":"pass","Package":"q","Test":"TestA"}'
expect_detect build-flag 1 '{"Action":"pass","Package":"q","Test":"TestA"}
{"Action":"fail","Package":"p","FailedBuild":"p"}'
expect_detect package 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p"}'
expect_detect race 6 '{"Action":"output","Package":"p","Test":"TestA","Output":"WARNING: DATA RACE\n"}
{"Action":"pass","Package":"p","Test":"TestA"}'
PHASE13_REQUIRE="TestRouteTablePhase13 TestPhase13Threats" expect_detect required 5 \
'{"Action":"pass","Package":"p","Test":"TestRouteTablePhase13"}'
local flag
for flag in --self-test --go --parity --named --removal --coverage --evidence --all; do
grep -q -- "^$flag)" "${BASH_SOURCE[0]}" || {
echo "refuse: missing mode $flag" >&2
exit 1
}
done
if [[ -n "${FORCE_COLOR+x}" ]]; then
echo "refuse: self-test FORCE_COLOR is still set" >&2
exit 1
fi
local scratch
scratch="$(mktemp -d)"
trap 'rm -rf "$scratch"' RETURN
# The module pin refuses a changed or missing x/text and accepts the
# audited line.
printf 'golang.org/x/text %s\n' "$XTEXT_VERSION" >"$scratch/mods"
module_pin "$scratch/mods" 2>/dev/null || {
echo "refuse: self-test module_pin rejected the audited version" >&2
exit 1
}
for plant in 'golang.org/x/text v0.41.0' ''; do
printf '%s\n' "$plant" >"$scratch/mods"
if module_pin "$scratch/mods" 2>/dev/null; then
echo "refuse: self-test module_pin accepted ${plant:-a missing x/text}" >&2
exit 1
fi
done
# The corpus scan refuses each planted secret shape and accepts
# synthetic values.
mkdir -p "$scratch/corpus"
printf 'go test fuzz v1\nstring("POST /x")\nstring("{\\"status\\":\\"imported\\",\\"pad\\":\\"QQQQ\\"}")\n' >"$scratch/corpus/seed"
corpus_scan "$scratch/corpus" 2>/dev/null || {
echo "refuse: self-test corpus_scan rejected a synthetic seed" >&2
exit 1
}
local secret
for secret in "$(printf 'a%.0s' $(seq 64))" "inv_ABCDEFGHIJKLMNOPQRST" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig" "Bearer abcdefghijklmnop"; do
printf 'string("%s")\n' "$secret" >"$scratch/corpus/planted"
if corpus_scan "$scratch/corpus" 2>/dev/null; then
echo "refuse: self-test corpus_scan accepted a planted secret ${secret:0:12}" >&2
exit 1
fi
done
rm -f "$scratch/corpus/planted" "$scratch/corpus/seed"
if corpus_scan "$scratch/corpus" 2>/dev/null; then
echo "refuse: self-test corpus_scan accepted an empty corpus" >&2
exit 1
fi
# The manifest counter reads only status lines.
printf 'routes:\n - id: a\n status: ported\n - id: b\n status: pending\n - id: c\n status: ported\n# status: ported\n' >"$scratch/manifest.yaml"
if [[ "$(manifest_count "$scratch/manifest.yaml" ported)" -ne 2 || "$(manifest_count "$scratch/manifest.yaml" pending)" -ne 1 ]]; then
echo "refuse: self-test manifest_count miscounted" >&2
exit 1
fi
# The corpus coverage line must show the expected counts.
local line="recorded $((EXPECTED_PORTED + EXPECTED_PENDING))/$((EXPECTED_PORTED + EXPECTED_PENDING)) passing $EXPECTED_PORTED failing 0 unrecorded 0 pending $EXPECTED_PENDING"
printf '{"Action":"output","Package":"p","Test":"TestParityCorpus/coverage","Output":"%s\\n"}\n' "$line" >"$scratch/cov.json"
corpus_coverage "$scratch/cov.json" >/dev/null 2>&1 || {
echo "refuse: self-test corpus_coverage rejected the expected counts" >&2
exit 1
}
local total=$((EXPECTED_PORTED + EXPECTED_PENDING)) planted
for planted in \
"recorded $total/$total passing $((EXPECTED_PORTED - 1)) failing 1 unrecorded 0 pending $EXPECTED_PENDING" \
"recorded $((total + 1))/$((total + 1)) passing $EXPECTED_PORTED failing 0 unrecorded 0 pending $((EXPECTED_PENDING + 1))" \
"recorded $((total - 1))/$total passing $EXPECTED_PORTED failing 0 unrecorded 1 pending $((EXPECTED_PENDING - 1))" \
"recorded $total/$total passing $((EXPECTED_PORTED + 1)) failing 0 unrecorded 0 pending $((EXPECTED_PENDING - 1))"; do
printf '{"Action":"output","Package":"p","Output":"%s\\n"}\n' "$planted" >"$scratch/cov.json"
if corpus_coverage "$scratch/cov.json" >/dev/null 2>&1; then
echo "refuse: self-test corpus_coverage accepted: $planted" >&2
exit 1
fi
done
printf '{"Action":"pass","Package":"p","Test":"TestParityCorpus"}\n' >"$scratch/cov.json"
if corpus_coverage "$scratch/cov.json" >/dev/null 2>&1; then
echo "refuse: self-test corpus_coverage accepted a log without a coverage line" >&2
exit 1
fi
# The coverage report refuses a package under the floor and accepts one
# over it; a block covered by any profile counts once; COVERAGE_ONLY
# narrows the report.
printf 'mode: set\nexample.test/a/x.go:1.1,2.2 8 1\nexample.test/a/x.go:3.1,4.2 2 0\n' >"$scratch/p1"
printf 'mode: set\nexample.test/a/x.go:3.1,4.2 2 1\nexample.test/b/y.go:1.1,2.2 5 0\nexample.test/b/y.go:3.1,4.2 5 1\n' >"$scratch/p2"
local out
out="$(coverage_report 80 "$scratch/p1" 2>&1)" || {
echo "refuse: self-test coverage_report refused 80% at an 80% floor: $out" >&2
exit 1
}
if out="$(coverage_report 80 "$scratch/p1" "$scratch/p2" 2>&1)"; then
echo "refuse: self-test coverage_report accepted a 50% package" >&2
exit 1
fi
grep -q "coverage example.test/a 100.0%" <<<"$out" || {
echo "refuse: self-test coverage_report did not merge profiles: $out" >&2
exit 1
}
COVERAGE_ONLY="/a" coverage_report 80 "$scratch/p1" "$scratch/p2" >/dev/null 2>&1 || {
echo "refuse: self-test COVERAGE_ONLY did not narrow the report" >&2
exit 1
}
printf 'mode: set\n' >"$scratch/empty"
if coverage_report 80 "$scratch/empty" 2>/dev/null; then
echo "refuse: self-test coverage_report accepted an empty profile" >&2
exit 1
fi
# The function floor refuses a function below it and a file with no
# functions in the profile.
printf 'example.test/c/registration.go:10:\tRegisterUser\t84.4%%\nexample.test/c/registration.go:40:\tIssueToken\t100.0%%\ntotal:\t(statements)\t90.0%%\n' >"$scratch/func"
func_floor 80 c/registration.go <"$scratch/func" >/dev/null 2>&1 || {
echo "refuse: self-test func_floor rejected functions over the floor" >&2
exit 1
}
printf 'example.test/c/registration.go:10:\tRegisterUser\t79.9%%\n' >"$scratch/func"
if func_floor 80 c/registration.go <"$scratch/func" >/dev/null 2>&1; then
echo "refuse: self-test func_floor accepted a 79.9% function" >&2
exit 1
fi
if func_floor 80 c/other.go <"$scratch/func" >/dev/null 2>&1; then
echo "refuse: self-test func_floor accepted a file without functions" >&2
exit 1
fi
# The evidence check refuses a missing threat row, a wrong disposition,
# a high threat without a removal row, a test the named stage does not
# run, a pending validation row, a missing Wave 0 flag and a validation
# test the named stage does not run; a threat two plans declare takes
# the stricter severity and disposition.
mkdir -p "$scratch/phase"
printf '| T-13-90 | Spoofing | x | high | mitigate | y |\n| T-13-91 | Tampering | x | low | accept | y |\n' >"$scratch/phase/13-01-PLAN.md"
printf '| T-13-91 | Tampering | x | medium | mitigate | y |\n' >"$scratch/phase/13-02-PLAN.md"
cat >"$scratch/review.md" <<'EOR'
| T-13-90 | Spoofing | x | high | mitigate | y | TestAlpha | pass | none |
| T-13-91 | Tampering | x | medium | mitigate | y | TestAlpha | pass | none |
| RC-90 | T-13-90 | f | a | b | c | fails |
EOR
cat >"$scratch/validation.md" <<'EOV'
status: validated
nyquist_compliant: true
wave_0_complete: true
| 13-01-T1 | API-03, API-04, API-05, API-06, API-07 | `go test -run '^TestAlpha$'` | ✅ green |
EOV
evidence_check "$scratch/phase" "$scratch/review.md" "$scratch/validation.md" "TestAlpha" >/dev/null 2>&1 || {
echo "refuse: self-test evidence_check rejected a complete record" >&2
exit 1
}
local case
for case in missing-row disposition removal unrun pending wave0 unnamed; do
cp "$scratch/review.md" "$scratch/review.case"
cp "$scratch/validation.md" "$scratch/validation.case"
local named="TestAlpha"
case "$case" in
missing-row) sed -i '/^| T-13-91 /d' "$scratch/review.case" ;;
disposition) sed -i 's/| medium | mitigate |/| low | accept |/' "$scratch/review.case" ;;
removal) sed -i '/^| RC-90 /d' "$scratch/review.case" ;;
unrun) sed -i 's/| TestAlpha | pass | none |$/| TestGamma | pass | none |/' "$scratch/review.case" ;;
pending) printf '| 13-02-T1 | API-03 | x | ⬜ pending |\n' >>"$scratch/validation.case" ;;
wave0) sed -i '/^wave_0_complete: true$/d' "$scratch/validation.case" ;;
unnamed) printf '| 13-02-T1 | API-03 | `go test -run TestBeta` | ✅ green |\n' >>"$scratch/validation.case" ;;
esac
if evidence_check "$scratch/phase" "$scratch/review.case" "$scratch/validation.case" "$named" >/dev/null 2>&1; then
echo "refuse: self-test evidence_check accepted the $case plant" >&2
exit 1
fi
done
# The removal harness refuses an anchor that is not unique and a dirty
# tracked file, restores the file byte for byte, and reports a mutation
# whose test passes.
local fake="$scratch/fake"
mkdir -p "$fake/modules/acme"
printf 'module example.test/acme\n\ngo 1.27\n' >"$fake/go.mod"
printf 'package acme\n\nfunc Guard(n int) bool {\n\tif n > 3 {\n\t\treturn false\n\t}\n\treturn true\n}\n' >"$fake/modules/acme/acme.go"
printf 'package acme\n\nimport "testing"\n\nfunc TestGuard(t *testing.T) {\n\tif Guard(4) {\n\t\tt.Fatal("guard removed")\n\t}\n}\n\nfunc TestOther(t *testing.T) {}\n' >"$fake/modules/acme/acme_test.go"
cp "$fake/modules/acme/acme.go" "$scratch/acme.go.saved"
local table="$scratch/table.json"
printf '[["RC-T1","T-X","root","modules/acme/acme.go","if n > 3 {","if false {","./modules/acme","^TestGuard$"]]' >"$table"
out="$(removal_harness_in "$fake" "$table" 2>&1)" || {
echo "refuse: self-test removal harness did not catch a guarded mutation: $out" >&2
exit 1
}
grep -q "RC-T1 T-X modules/acme/acme.go: fails as required" <<<"$out" || {
echo "refuse: self-test removal harness output: $out" >&2
exit 1
}
cmp -s "$fake/modules/acme/acme.go" "$scratch/acme.go.saved" || {
echo "refuse: self-test removal harness did not restore the file" >&2
exit 1
}
printf '[["RC-T2","T-X","root","modules/acme/acme.go","if n > 3 {","if false {","./modules/acme","^TestOther$"]]' >"$table"
if out="$(removal_harness_in "$fake" "$table" 2>&1)"; then
echo "refuse: self-test removal harness accepted a mutation whose test passes: $out" >&2
exit 1
fi
grep -q "RC-T2 T-X modules/acme/acme.go: SURVIVED" <<<"$out" || {
echo "refuse: self-test removal harness refused a surviving mutation for the wrong reason: $out" >&2
exit 1
}
printf '[["RC-T3","T-X","root","modules/acme/acme.go","return","x","./modules/acme","^TestGuard$"]]' >"$table"
if out="$(removal_harness_in "$fake" "$table" 2>&1)"; then
echo "refuse: self-test removal harness accepted a non-unique anchor" >&2
exit 1
fi
grep -q "anchor occurs 2 times" <<<"$out" || {
echo "refuse: self-test removal harness refused a non-unique anchor for the wrong reason: $out" >&2
exit 1
}
printf '[["RC-T5","T-X","root","modules/acme/acme.go","if n > 3 {","if n > 3 {\\n\\tundefinedCall()","./modules/acme","^TestGuard$"]]' >"$table"
if out="$(removal_harness_in "$fake" "$table" 2>&1)"; then
echo "refuse: self-test removal harness counted a build failure as a failing test" >&2
exit 1
fi
cmp -s "$fake/modules/acme/acme.go" "$scratch/acme.go.saved" || {
echo "refuse: self-test removal harness did not restore after a build failure" >&2
exit 1
}
(cd "$fake" && git init -q && git add -A && git -c user.email=gate@example.test -c user.name=gate commit -qm init) >/dev/null
printf '// local edit\n' >>"$fake/modules/acme/acme.go"
printf '[["RC-T4","T-X","root","modules/acme/acme.go","if n > 3 {","if false {","./modules/acme","^TestGuard$"]]' >"$table"
if out="$(removal_harness_in "$fake" "$table" 2>&1)"; then
echo "refuse: self-test removal harness mutated a dirty file" >&2
exit 1
fi
grep -q "is dirty" <<<"$out" || {
echo "refuse: self-test removal harness refused a dirty file for the wrong reason: $out" >&2
exit 1
}
# Every row of the real removal table names a unique anchor in the
# current tree (the --removal stage would refuse it otherwise).
removal_table >"$table"
python3 - "$table" "$ROOT" "$APP" <<'PY' || exit 1
import json, os, sys
table, root, app = json.load(open(sys.argv[1])), sys.argv[2], sys.argv[3]
for rc, threat, repo, rel, anchor, repl, pkg, run in table:
base = {"root": root, "app": app, "script": root, "rootapp": root}[repo]
path = os.path.join(base, rel)
if not os.path.isfile(path):
print(f"refuse: self-test {rc}: {rel} is missing", file=sys.stderr)
sys.exit(1)
n = open(path).read().count(anchor)
if n != 1:
print(f"refuse: self-test {rc}: anchor occurs {n} times in {rel}", file=sys.stderr)
sys.exit(1)
PY
echo "phase13 self-test passed"
}
case "${1:-}" in
--self-test) run_self_test ;;
--go) run_go ;;
--parity) run_parity ;;
--named) run_named ;;
--removal) run_removal ;;
--coverage) run_coverage ;;
--evidence) run_evidence ;;
--all)
run_self_test
run_go
run_parity
run_named
run_coverage
run_evidence
echo "phase13 all passed"
;;
*) usage ;;
esac