From 43e2b571b95b0f5ca7fb0f710bb34a1fdae69a01 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:58:52 +0000 Subject: [PATCH] The gate manifest regenerated Co-Authored-By: Claude Fable 5.1 --- tools/ci/registry-evidence-check.sh | 121 ++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100755 tools/ci/registry-evidence-check.sh diff --git a/tools/ci/registry-evidence-check.sh b/tools/ci/registry-evidence-check.sh new file mode 100755 index 000000000..a7118073d --- /dev/null +++ b/tools/ci/registry-evidence-check.sh @@ -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 : +# 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 , 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 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() { # ; 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 | --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