The gate manifest regenerated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 17:58:52 +00:00
parent a6cf4ada87
commit 43e2b571b9

View file

@ -0,0 +1,121 @@
#!/usr/bin/env bash
# The registry's evidence rules (the founder's standard: GOV-02 thresholds before results, GOV-04 raw evidence preserved, GOV-08
# stale evidence invalidated; main through the coordinator, 8 October 2026, 18:4x UK). Run by merge-to-master.sh before the
# merge on <base> <head>:
# 1. a landing that sets a case's run_status to PASS must carry the evidence_path it names: a path in the tree at <head>, or
# an artefact on a build box (build-N:/srv/artefacts/... or /srv/artefacts/..., checked over ssh when the box answers;
# a box that does not answer is a line, not a refusal); an empty or missing path is refused
# 2. a landing that touches a file under docs/analysis/ or an evidence directory that a registry row names, without
# updating that row (its `updated` field) in the same diff, is named (a refusal: the row and its evidence move together)
# 3. a case whose evidence_record.manifest_sha is set and differs from the registry's pinned manifest (top-level
# `pinned_manifest_sha`, when present) reads NOT RUN, never PASS: stale evidence (GOV-08)
# 4. a run_status may be written only while the registry's approval stands (top-level `approval` present): thresholds are
# frozen before results (GOV-02)
# tools/ci/registry-evidence-check.sh <base> <head> exit 0 clean, 1 refused (the rows named), 2 bad args
# tools/ci/registry-evidence-check.sh --self-test
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd -P)"; ME="$HERE/$(basename "$0")"
REG="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"
check() { # <base> <head> ; prints "refused ..." lines; returns 1 when any
local base="$1" head="$2" rc=0
git cat-file -e "$head:$REG" 2>/dev/null || return 0
local tmp_h tmp_b; tmp_h=$(mktemp); tmp_b=$(mktemp); git show "$head:$REG" > "$tmp_h"; git show "$base:$REG" > "$tmp_b" 2>/dev/null || : > "$tmp_b"
local touched; touched=$(git diff --name-only "$base" "$head" --)
python3 - "$tmp_h" "$tmp_b" "$head" "$touched" <<'PY' || rc=1
import json, sys, subprocess, os
h = json.load(open(sys.argv[1])); b = json.load(open(sys.argv[2])) if os.path.getsize(sys.argv[2]) else {}
head = sys.argv[3]; touched = [t for t in sys.argv[4].split('\n') if t]
cases = lambda r: [t for s in r.get('suites', []) for t in s.get('tests', [])]
hb = {c['id']: c for c in cases(b)}; refused = []
approved = bool(h.get('approval')); pinned = h.get('pinned_manifest_sha')
def in_tree(p):
return subprocess.run(['git', 'cat-file', '-e', f'{head}:{p}'], capture_output=True).returncode == 0
def on_box(p):
box, _, path = p.partition(':') if ':' in p else ('build-1', '', p)
ip = {'build-1': '188.40.146.49', 'build-2': '142.132.249.238', 'build-3': '62.238.91.43', 'build-4': '178.63.182.105'}.get(box)
if not ip: return 'unknown-box'
r = subprocess.run(['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=6', f'build@{ip}', f'test -e {path!r} && echo yes || echo no'], capture_output=True, text=True)
if r.returncode != 0: return 'no-answer'
return 'yes' if 'yes' in r.stdout else 'no'
for c in cases(h):
cid = c['id']; old = hb.get(cid, {}); st = c.get('run_status'); changed = (st != old.get('run_status')) or (c.get('run_id') != old.get('run_id')) or (c.get('evidence_path') != old.get('evidence_path'))
if changed and st and st != 'NOT RUN' and not approved:
refused.append(f'refused {cid}: run_status {st} written while the registry carries no approval (GOV-02: thresholds before results)')
rec = c.get('evidence_record') or {}
if pinned and rec.get('manifest_sha') and not str(pinned).startswith(str(rec['manifest_sha'])[:8]) and not str(rec['manifest_sha']).startswith(str(pinned)[:8]) and st == 'PASS':
refused.append(f'refused {cid}: PASS on evidence pinned to manifest {rec["manifest_sha"]} while the registry pins {pinned} (GOV-08: stale evidence reads NOT RUN)')
if changed and st == 'PASS':
p = (c.get('evidence_path') or '').strip()
if not p: refused.append(f'refused {cid}: PASS with no evidence_path'); continue
for one in [x.strip() for x in p.split(';') if x.strip()]:
if one.startswith('/srv/') or ':/srv/' in one:
if os.environ.get('REGISTRY_CHECK_NO_SSH') == '1': continue
v = on_box(one)
if v == 'no': refused.append(f'refused {cid}: PASS names {one}, which is not on the box')
elif v in ('no-answer', 'unknown-box'): print(f'registry-evidence: {cid}: {one} could not be checked ({v}); not a refusal')
elif not in_tree(one): refused.append(f'refused {cid}: PASS names {one}, which is not in the tree at {head[:8]}')
# rule 2: a touched evidence file under docs/analysis or named by a row, without that row's `updated` moving
named = {}
for c in cases(h):
for one in [x.strip() for x in (c.get('evidence_path') or '').split(';') if x.strip()]:
if not one.startswith('/') and ':/' not in one: named.setdefault(one.rstrip('/'), []).append(c['id'])
REG_PATH = os.environ.get('REGISTRY_PATH', 'docs/plans/igneum-2.0-test-registry.json')
for t in touched:
if t == REG_PATH: continue # the registry names itself as GOV-02's evidence (the approval recorded in it); it always moves with itself
owners = [ids for p, ids in named.items() if t == p or t.startswith(p + '/')]
if not owners and not t.startswith('docs/analysis/'): continue
ids = sorted({i for ids in owners for i in ids})
if not ids: continue # docs/analysis file no row names: free
moved = [i for i in ids if (h and {c['id']: c for c in cases(h)}[i].get('updated')) != hb.get(i, {}).get('updated')]
if not moved: refused.append(f'refused: {t} changed but its registry row(s) {", ".join(ids)} did not (the row and its evidence move together)')
for r in refused: print(r)
sys.exit(1 if refused else 0)
PY
rm -f "$tmp_h" "$tmp_b"; return $rc
}
if [ "${1:-}" = --self-test ]; then
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; fails=0; export REGISTRY_CHECK_NO_SSH=1
mk() { python3 - "$@" <<'PY'
import json, sys
path, approval, pinned, rows = sys.argv[1], sys.argv[2] == '1', sys.argv[3], json.loads(sys.argv[4])
reg = {'title': 't', 'suites': [{'code': 'X', 'tests': [dict({'id': i, 'method': 'Automated', 'accept': 'a'}, **r) for i, r in rows.items()]}]}
if approval: reg['approval'] = 'yes'
if pinned != '-': reg['pinned_manifest_sha'] = pinned
json.dump(reg, open(path, 'w'), indent=1)
PY
}
( cd "$d" && git init -q -b master . && mkdir -p docs/plans docs/analysis evidence && echo e > evidence/a.log && echo r > docs/analysis/row.md
mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md", "updated": "t0"}}'
git add -A && git -c user.name=t -c user.email=t@t commit -q -m base && git tag base
# pass-ok: PASS with an evidence path in the tree
mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {"run_status": "PASS", "evidence_path": "evidence/a.log", "updated": "t1"}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md", "updated": "t0"}}'
git -c user.name=t -c user.email=t@t commit -qam pass-ok && git tag pass-ok
# pass-missing: PASS naming a path not in the tree
git checkout -q -b m base; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {"run_status": "PASS", "evidence_path": "evidence/none.log", "updated": "t1"}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md", "updated": "t0"}}'
git -c user.name=t -c user.email=t@t commit -qam pass-missing && git tag pass-missing
# evidence-touched: docs/analysis/row.md changed, X-2's updated did not
git checkout -q -b e base; echo r2 > docs/analysis/row.md; git -c user.name=t -c user.email=t@t commit -qam touched && git tag touched
# evidence-with-row: the same change with X-2's updated moved
git checkout -q -b e2 base; echo r2 > docs/analysis/row.md; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md", "updated": "t1"}}'; git -c user.name=t -c user.email=t@t commit -qam with-row && git tag with-row
# stale: PASS on evidence pinned to another manifest
git checkout -q -b s base; mk docs/plans/igneum-2.0-test-registry.json 1 aaaaaaaa '{"X-1": {"run_status": "PASS", "evidence_path": "evidence/a.log", "updated": "t1", "evidence_record": {"manifest_sha": "bbbbbbbb"}}, "X-2": {}}'; git -c user.name=t -c user.email=t@t commit -qam stale && git tag stale
# self-ref: a row names the registry itself as its evidence; a registry change must not trip rule 2 on it
git checkout -q -b r base; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {"run_status": "RUNNING", "evidence_path": "evidence/a.log", "updated": "t1"}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/plans/igneum-2.0-test-registry.json", "updated": "t0"}}'; git -c user.name=t -c user.email=t@t commit -qam self-ref && git tag self-ref
# unapproved: a run_status written with no approval
git checkout -q -b u base; mk docs/plans/igneum-2.0-test-registry.json 0 - '{"X-1": {"run_status": "RUNNING", "evidence_path": "evidence/a.log", "updated": "t1"}, "X-2": {}}'; git -c user.name=t -c user.email=t@t commit -qam unapproved && git tag unapproved ) >/dev/null 2>&1
cd "$d"
bash "$ME" base pass-ok >/dev/null 2>&1 || { echo "self-test failed: a PASS with its evidence in the tree was refused: $(bash "$ME" base pass-ok 2>&1)"; fails=1; }
out=$(bash "$ME" base pass-missing 2>&1) && { echo "self-test failed: a PASS naming a missing path passed"; fails=1; }; case "$out" in *"not in the tree"*) ;; *) echo "self-test failed: the missing path was not named: $out"; fails=1 ;; esac
out=$(bash "$ME" base touched 2>&1) && { echo "self-test failed: a touched evidence file without its row passed"; fails=1; }; case "$out" in *"X-2"*"move together"*) ;; *) echo "self-test failed: the unmoved row was not named: $out"; fails=1 ;; esac
bash "$ME" base with-row >/dev/null 2>&1 || { echo "self-test failed: a touched evidence file with its row updated was refused: $(bash "$ME" base with-row 2>&1)"; fails=1; }
out=$(bash "$ME" base stale 2>&1) && { echo "self-test failed: a PASS on stale evidence passed"; fails=1; }; case "$out" in *"stale evidence"*) ;; *) echo "self-test failed: the stale evidence was not named: $out"; fails=1 ;; esac
bash "$ME" base self-ref >/dev/null 2>&1 || { echo "self-test failed: a row naming the registry itself as evidence tripped the move-together rule on a registry change: $(bash "$ME" base self-ref 2>&1)"; fails=1; }
out=$(bash "$ME" base unapproved 2>&1) && { echo "self-test failed: a run_status without approval passed"; fails=1; }; case "$out" in *"thresholds before results"*) ;; *) echo "self-test failed: the missing approval was not named: $out"; fails=1 ;; esac
[ "$fails" = 0 ] && echo "self-test passed: a PASS must name evidence that exists (tree or box); a touched evidence file moves with its registry row; evidence pinned to another manifest cannot be PASS; a run_status needs the registry's approval"
exit $fails
fi
[ $# -eq 2 ] || { echo "usage: registry-evidence-check.sh <base> <head> | --self-test" >&2; exit 2; }
rc=0; out=$(check "$1" "$2") || rc=$?
printf '%s\n' "$out" | sed -n 's/^refused/registry-evidence: REFUSED:/p; /^registry-evidence:/p' | grep . || true
[ "$rc" = 0 ] && echo "registry-evidence: every PASS names existing evidence, every touched evidence file moves with its row, no stale evidence reads PASS"
exit $rc