Merge ci-rev-suite 1dd0576c into master (gate: green on 1dd0576c, recorded by tools/ci/pre-push.sh; landed on the box mirror under the exception declared by main: main's ruling, 7 Oct 2026 19:5x UK: the GitHub account is suspended, lanes land on the box mirror's master, the box gate stamp is the verdict; GitHub gets the fast-forward when it answers)

This commit is contained in:
igneum-labs 2026-10-08 19:32:51 +00:00
commit 259f799ec3
17 changed files with 2005 additions and 174 deletions

View file

@ -6,8 +6,8 @@ The fixture every case of the Test and Acceptance Standard (docs/plans/igneum-2.
| Field | Value | Read from | Owner |
|---|---|---|---|
| Miner cut tip (release-2.0.1) | 2826f37e (2ea7b43f + the DMG README line + the node pin ef0f2ed8 + elf/prior from key-succession-pin; the app crate byte-identical to 2ea7b43f's, so the pow and app cells read on 2ea7b43f cover it by content; a re-run on 2826f37e records the literal) | the shipper's line 19:3x | shipper (ae892a8b0f78fe31c) |
| Node sha for the roll | ef0f2ed8 = 291ee6ae (key succession over 417c4a57) + the workspace version 2.0.1 | the node lane's line 19:0x | node lane (a283f5f0d364ceef0) |
| Miner cut tip (release-2.0.1) | aa0e0f45 (9c844503 + the pin file; 9c844503 = 2826f37e + the six-target packaged peer list; the app crate byte-identical to 2ea7b43f's throughout, so the pow and app cells read on 9c844503 cover it by content) | the shipper's line 19:5x | shipper (ae892a8b0f78fe31c) |
| Node sha for the roll | 7cfa422a = ef0f2ed8 (291ee6ae + the 2.0.1 version) + the miner base-unit fix, amended (777214af did not compile: a self-recursive connect, caught by the steward's read at 19:50) | the node lane's and shipper's lines 19:5x | node lane (a283f5f0d364ceef0) |
| Node line read green tonight | 4cdcc488, d5981514, bee41b5e, 9fc9f42a, 5713d547, 417c4a57, 291ee6ae (ef0f2ed8 = 291ee6ae + the version bump, its own read on build-4 recorded as the literal) | the steward's matrices on build-2 and build-4 | CI steward |
| Network | igneum-devnet-4, a fresh genesis; EVM chain id 4465 (0x1171), set in devnet4_params, read by the canaries as eth_chainId on every candidate tonight, pinned by the digest be5f4068; every 2.0 devnet node, pool and reference app signs with it; mainnet's and the testnet's ids unchanged from the 0.3 line | the node lane's line 19:3x | node lane |
| Object digest | be5f406802cec1227a5ef50d42181ab42444f79af170775b22c85cb9a917273b (4cdcc488 and every sha after it; no live digest move since) | the node lane's lines | node lane |
@ -37,6 +37,13 @@ The fixture every case of the Test and Acceptance Standard (docs/plans/igneum-2.
| Trust anchors (light client, oracle) | BLOCKED | reference-apps lane | VER-01 and VER-04 read BLOCKED |
| The class v6 freeze line in packaging/pow-freeze.txt | open: three D1 candidates on the node mirrors (class-v6-node 3af510ec on c245d50b9 fingerprint a65e4c5a; class-v6-node-b 46e7ac18 with the acceptance fix, fingerprint 6cdd922a; class-v6-node-review 2f6eb9e9 on 04442d9ca, fingerprint 7a1dec1c, six suites green on build-1 at 19:3x) | hash lane, node lane | class v5 stays the mining class |
## Resolved by the founder (8 October 2026, 19:57 UK, through the coordinator)
| Field | Ruling |
|---|---|
| F04 (Review B), the recovery lock | kept, and always labelled "recovery", never "final", on every surface (the checkpoint field, the explorer, receipts, the light client, the oracle, the site) |
| F14 (Review B), fleet control and the public client | the public miner ships from 2.0.2 without remote jobs; our own fleet runs the lab build with its own signing root |
## Signatures (by 23:30 UK, 8 October 2026)
| Role | Name or lane | Commit signed | Time |

View file

@ -1,6 +1,6 @@
# Igneum 2.0 test harness map
Generated from tools/ci/test-map.json by tools/ci/test-map-doc.mjs; edit the JSON, never this page. Registry: docs/plans/igneum-2.0-test-registry.json (128 cases).
Generated from tools/ci/test-map.json by tools/ci/test-map-doc.mjs; edit the JSON, never this page. Registry: docs/plans/igneum-2.0-test-registry.json (172 cases).
Rule: a case maps to a cell only where the cell's tests visibly answer it; coverage names what the cell proves and what remains; a mapped cell's green writes RUNNING, PASS only when coverage is full and the evidence file exists; an automated case with no cell reads NOT RUN with its reason, never PASS by inference.
@ -328,7 +328,51 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
- LEAD-05 Measure control and dependency concentration: observation window
- LEAD-06 Complete the reliability observation window: observation window
- LEAD-08 Keep leadership claims valid after release: observation window
- REV-F01-1 Valid proof A, warm cache, carried statement B: refuse exactly as a cold node does.: F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map
- REV-F01-2 Run valid/invalid contexts in both orders, concurrently and across cache eviction/restart.: F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map
- REV-F01-3 Change payout, network, kind, program ID and activation context; no accepted misbinding.: F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map
- REV-F01-4 A native two-node test must agree on block validity and payouts despite different cache histories.: F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map
- REV-F02-1 Release build cannot activate test bypass.: F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
- REV-F02-2 Missing oracle or pinned keys prevents service readiness after enforcement activation.: F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
- REV-F02-3 Missing proof bytes retry without incorrectly marking a valid block permanently invalid.: F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
- REV-F03-1 Clean build from one manifest, including the pool and workers, with no unpublished vendor tree.: F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
- REV-F03-2 Same job context produces identical accepted work in node, CPU reference, CUDA, Metal, OpenCL and pool.: F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
- REV-F03-3 Cross every scheduled transition with old/new client behavior documented and identical rule identities.: F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
- REV-F04-1 Native multi-node 40/40/20 partition, equivocation and dust-valid mining on both sides past the window.: F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
- REV-F04-2 Proof that the chosen recovery guarantee matches the public finality claim; two valid contradictory certificates are a hard failure for strong finality.: F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
- REV-F04-3 Pause-only resume with historical backfill, missing historical data and all old keys returning.: F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
- REV-F04-4 Wallet, receipt and oracle consumers distinguish any weaker recovery state.: F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
- REV-F05-1 Native reference-vs-incremental expression equivalence across all source registers and real instruction updates.: F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map
- REV-F05-2 Full-kernel output equivalence, registers, spills, wall power and accepted throughput across target GPUs.: F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map
- REV-F05-3 Adversary physical design includes prefix caching/recomputation cost; no assumed full 63-read cost on every load.: F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map
- REV-F06-1 Acceptance/reference/emitter evaluate the same activated schedule.: F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map
- REV-F06-2 Full live-dataset census on unseen seeds and the complete v6 pack.: F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map
- REV-F06-3 A failed experimental rule does not silently exhaust generation or bypass the intended resource requirement.: F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map
- REV-F07-1 Same final v6 configuration: mine -> evict -> prove -> aggregate -> submit -> rebuild -> resume; no leaked reservations.: F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map
- REV-F07-2 OOM, process crash and stale work recover without losing wallet state or silently consuming power.: F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map
- REV-F07-3 16 GB+ simultaneous mode only after measured peak plus next-epoch headroom; smaller-card modes labelled separately.: F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map
- REV-F08-1 Report every eligible job outcome, including expired work; a bounded list cannot hide losses.: F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map
- REV-F08-2 Consumer tiers complete and receive payment for declared jobs before claims about income.: F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map
- REV-F08-3 Sustained real workload across class transitions, with restart/retry and no publisher intervention.: F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map
- REV-F09-1 Generate all pass/fail cells directly from the declared inequalities and inputs.: F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map
- REV-F09-2 Independent feasibility and cost evaluation of the strongest modeled SRAM/hybrid opponent.: F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map
- REV-F09-3 GPU owners and entrants remain viable under the approved scenario envelope without assuming chip absence or death.: F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map
- REV-F10-1 Compare exact found nonce/hash sets with full-read mode, including all-hit overflow and zero-hit cases.: F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map
- REV-F10-2 Test stale jobs, high-32 rollover, tail batches and asynchronous buffer reuse.: F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map
- REV-F10-3 Compare production throughput and wall energy, not only the isolated kernel timer.: F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map
- REV-F11-1 Goal switch from an efficiency prior can explore higher core/power when policy allows.: F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map
- REV-F11-2 Driver, workload, dataset, compiler and device changes invalidate certification while retaining optional hints.: F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map
- REV-F11-3 Thermal/error/late-share events revert safely, including process or machine crash.: F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map
- REV-F12-1 Crash before/after broadcast, response timeout, restart before snapshot: no duplicate payment or silent debt loss.: F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F12-2 Same signed transaction retried, fee replacement reconciled by intent, not a new payment.: F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F12-3 Receipt reorg and finality pause leave correct pending obligations.: F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F13-1 Repeated authorization rejected or atomically replaces and cleans prior state.: F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F13-2 Oversized unterminated frame rejected within fixed memory/time budget.: F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F13-3 Slow clients and invalid share floods cannot grow unbounded member, nonce or outgoing state.: F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map
- REV-F14-1 Public build has no default arbitrary remote execution and a documented least-privilege boundary.: F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
- REV-F14-2 Automatic updates off remains off for urgent manifests until explicit action.: F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
- REV-F14-3 A compromised fleet/update signing key cannot silently acquire wallet access or activate a consensus change.: F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
## Count
109 automated cases: 56 mapped to a cell, 58 NOT RUN with a reason.
153 automated cases: 56 mapped to a cell, 102 NOT RUN with a reason.

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,10 @@
| the red watcher fires on cancelled and timed-out runs too (`ci-red.yml`, `red-watch.mjs`) | The watcher's `if` missing any of failure, cancelled, timed_out, or the conclusion not handed to the record step (the self-test reads the workflow file); the line names the kind: CI red, CI cancelled, CI timed out. | 7 October 2026 |
| gh's active account is the stored Igneum entry (`gh-account-check.sh`, in Igneum's own gh directory `~/.config/gh-igneum` through `gh-env.sh`, never the founder's) | A push or a landing from this Mac while Igneum's gh directory names any other account as active, or none (the refusal names the one step: the founder or main stores the Igneum token there with `GH_CONFIG_DIR=~/.config/gh-igneum gh auth login --with-token`; no lane does); skipped with a line while `github-suspended` stands. RULE: no lane switches gh accounts on this Mac, ever; the second owner's login belongs to other projects and must never touch Igneum; the stored entry's name is in ~/.config/igneum/gh-user, never in the repository. | 7 October 2026, 21:41 UK: a lane switched gh to the other login during the suspension; nobody could say which |
| F02 (Review B): the proof-rule test bypass cannot reach a release build (`proof-rule-bypass-check.sh`; a cell of the node matrix) | An env read of IGNEUM_TEST_SKIP_PROOF_RULE with no cfg(test) or cfg(feature) guard in the 12 lines above, or under a feature in the crate's default features; a release igneumd carrying the bypass string. Red on every node sha until the proving lane's change (the read under a non-default feature or cfg(test)) lands | 8 Oct 2026 |
| the test map merges structurally at a landing (`test-map-merge.py`) and the harness page regenerates from the merged map (`merge-to-master.sh`) | Nothing by itself: two lanes adding cells collided as text and the regenerated page lost rule 26's race; the merge now keeps master's cells plus the branch's, minus what the branch removed and master left, and regenerates the page | 8 Oct 2026 |
| a push that lost the ref race retries without re-running the hook (`merge-to-master.sh` `push_race`, 12 tries) | Nothing by itself: under one landing a minute a 70-second hook per try never won master's compare-and-swap (Review B's landing lost three in a row); once the hook has passed on the first try and the rejection is a ref race, later tries push --no-verify (both parents gated) | 8 Oct 2026 |
| the REV suite is generated from an external review's findings.json and dispatch.md (`review-suite.mjs`; one case per required regression, NOT RUN, the owner from the dispatch table) | A registry whose REV suite differs from the generator's output (--check) | 8 Oct 2026 |
| a registry landing carries its batches (`tools/ci/batches/<run id>.json`; `merge-to-master.sh` replays them onto master's copy at the merge) | Nothing by itself: the registry is a hot file, and a branch whose own copy of it was recorded during a seven-minute gate lost the race to another lane's rows three times in a row (8 Oct 2026, 19:1x UK). A branch that adds batch files is merged with master's registry, every added batch replayed through `test-record.mjs --record` (idempotent), and the evidence rules run on the merged result; rule 26 does not bind the registry path for such a branch | 8 Oct 2026 |
| the registry's evidence rules (`registry-evidence-check.sh`, called by `merge-to-master.sh` after rule 26) | A landing that sets a case's run_status to PASS without an evidence_path that exists (in the tree at the landing, or on a build box over ssh; a box that does not answer is a line, not a refusal); a landing that changes a file under docs/analysis/ or a path a registry row names without moving that row's `updated` (the row and its evidence move together, GOV-04); a PASS whose evidence record pins another manifest than the registry's pinned_manifest_sha (stale evidence reads NOT RUN, GOV-08); a run_status written while the registry carries no approval block (thresholds before results, GOV-02) | 8 Oct 2026 |
| the acceptance layer (`test-record.mjs`, `test-map.json`, `test-map-doc.mjs`; the founder's Test and Acceptance Standard, docs/plans/igneum-2.0-test-registry.json) | An automated case of the registry with no cell in the map and no NOT RUN reason; a map naming an unknown case; a stale harness-map page (generated from the JSON); the recorder's self-test: a run batch writes run_status, run_id, evidence_path, updated and the evidence record to the mapped cases only, never an accept text, and a case with no harness reads NOT RUN with its reason, never PASS by inference | 8 Oct 2026 |

View file

@ -0,0 +1,52 @@
{
"run_id": "201-7cfa422a-aa0e0f45",
"manifest_sha": "7cfa422a",
"cut_tip": "aa0e0f45 (miner; cells on 9c844503, the crate identical); node 7cfa422a on the key-succession pairing",
"evidence_dir": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45",
"boxes": [
"build-2",
"build-4"
],
"cells": [
{
"cell": "suite:pow",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/miner-9c844503/box2-pow.log"
},
{
"cell": "suite:app",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/miner-9c844503/box2-app.log"
},
{
"cell": "suite:core",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-core.log"
},
{
"cell": "suite:consensus",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-consensus.log"
},
{
"cell": "suite:exec",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-exec.log"
},
{
"cell": "suite:miner",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-miner.log"
},
{
"cell": "suite:p2p-flows",
"status": "RUNNING",
"evidence": "build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-p2p-flows.log"
},
{
"cell": "check:freeze",
"status": "RUNNING",
"evidence": "docs/plans/igneum-2.0-f0-manifest.md; packaging/pow-freeze.txt; build-1:/srv/artefacts/tas/201-7cfa422a-aa0e0f45/node-7cfa422a/box4-kaspad-check.log"
}
]
}

View file

@ -76,6 +76,9 @@ the harness map page is generated from tools/ci/test-map.json and current
P01 part A, the million-vector driver: a clean run is PASS, one wrong hash or one unanswered nonce is FAIL naming it (self-test, a fake worker)
the proving outcome ledger (review B F08): every claimed job ends in one outcome; the report's self-test reads a log and a state file to known numbers
the registry's evidence rules: a PASS names evidence that exists, a touched evidence file moves with its row, stale evidence never reads PASS, a run_status needs the approval (self-test)
F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)
the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)
the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)
the public ledger (docs/ledger-public.md) is what docs/fud-ledger.md generates: one row per item, no commit ids, times or team names (self-test first)
the ledger page reads both entry heading forms (M1 and AP-F8-1) so no in-house pass row is dropped from /ledger (known-failed first)
every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)

View file

@ -26,7 +26,7 @@ MERGE_PID_FILE="$(git rev-parse --git-dir)/igneum-merge.pid"
printf '%s %s %s\n' "$$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${IGNEUM_MERGE_TITLE:-untitled}" > "$MERGE_PID_FILE" 2>/dev/null || true
trap 'rm -f "$MERGE_PID_FILE"' EXIT
. tools/ci/gh-env.sh # every gh call here reads Igneum's own gh directory, never the founder's (8 October 2026)
BRANCH="$(git rev-parse --abbrev-ref HEAD)"; TRIES=6; CI_WAIT_MIN="${CI_WAIT_MIN:-25}"; FIXES_MASTER=0; SELF_TEST=0; REMOTE="${MERGE_REMOTE:-origin}"
BRANCH="$(git rev-parse --abbrev-ref HEAD)"; TRIES=12; CI_WAIT_MIN="${CI_WAIT_MIN:-25}"; FIXES_MASTER=0; SELF_TEST=0; REMOTE="${MERGE_REMOTE:-origin}"; NOVERIFY=""
while [ $# -gt 0 ]; do case "$1" in --tries) TRIES="$2"; shift 2 ;; --ci-wait) CI_WAIT_MIN="$2"; shift 2 ;; --fixes-master) FIXES_MASTER=1; shift ;; --remote) REMOTE="$2"; shift 2 ;; --self-test) SELF_TEST=1; shift ;; -*) echo "unknown option $1" >&2; exit 2 ;; *) BRANCH="$1"; shift ;; esac; done
# --remote <name>: land on another remote's master (a box mirror, build@<box>:/srv/igneum.git, while GitHub is unreachable; main's
# ruling of 7 October 2026, 19:5x UK). CI runs on GitHub only, so the CI rule binds the origin remote; on a mirror the box gate
@ -107,21 +107,59 @@ mirror_master() { # <sha> [landed-remote-url]: the landed master to every othe
# merge_with_batches <tip> <sha> <message>: in the current worktree (at <tip>), the merge of <sha>; when the branch added batch files,
# the registry takes master's copy and every batch is replayed onto it, then the evidence rules run on the result; returns 1 on a
# conflict outside the registry or a red evidence rule
# push_race <push output>: 0 when a rejected push lost only the ref's compare-and-swap (another landing moved master between the
# fetch and the push), 1 when the hook refused or something else failed. On a race the merge is rebuilt on the new tip; the hook
# already passed on the first try and both parents are gated (the branch fully, master's tip on its own landing), so the retry
# pushes with --no-verify: under tonight's landing rate (one every minute, 8 October 2026, 20:0x UK) a 70-second hook per try
# never wins the swap
push_race() { case "$1" in *"REFUSED"*|*" RED "*) return 1 ;; *"cannot lock ref"*|*"failed to update ref"*|*"fetch first"*|*"non-fast-forward"*) return 0 ;; *) return 1 ;; esac; }
merge_with_batches() {
local tip="$1" sha="$2" msg="$3" conflicts f
local MAP_CHANGED="${MAP_CHANGED:-0}" MAP_PATH="${MAP_PATH:-tools/ci/test-map.json}" PAGE_PATH="${PAGE_PATH:-docs/plans/igneum-2.0-test-harness-map.md}" BATCHES="${BATCHES:-}" NOTES="${NOTES:-}" REGISTRY_PATH="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"
if git "${AUTHOR[@]}" merge -q --no-ff --no-commit "$sha" >/dev/null 2>&1; then :; else
conflicts=$(git diff --name-only --diff-filter=U)
if [ -z "$BATCHES" ] || [ "$conflicts" != "$REGISTRY_PATH" ]; then git merge --abort 2>/dev/null; return 1; fi
local c ok=1
for c in $conflicts; do
case "$c" in
"$REGISTRY_PATH") [ -n "$BATCHES" ] || ok=0 ;; # rebuilt from master's copy below
"$PAGE_PATH") [ "$MAP_CHANGED" = 1 ] || ok=0 ;; # regenerated from the merged map below
"$MAP_PATH") [ "$MAP_CHANGED" = 1 ] || ok=0 ;; # merged structurally below (tools/ci/test-map-merge.py)
*) ok=0 ;;
esac
done
if [ "$ok" != 1 ]; then git merge --abort 2>/dev/null; return 1; fi
[ -n "$BATCHES" ] || git checkout -q "$tip" -- "$REGISTRY_PATH" 2>/dev/null || true
fi
if [ "$MAP_CHANGED" = 1 ] && git diff --name-only --diff-filter=U 2>/dev/null | grep -qx "$MAP_PATH"; then
local base3; base3=$(git merge-base "$tip" "$sha")
git show "$base3:$MAP_PATH" > /tmp/map-base.$$ ; git show "$tip:$MAP_PATH" > /tmp/map-master.$$ ; git show "$sha:$MAP_PATH" > /tmp/map-branch.$$
python3 tools/ci/test-map-merge.py /tmp/map-base.$$ /tmp/map-master.$$ /tmp/map-branch.$$ "$MAP_PATH" || { echo "merge-to-master: the map does not merge structurally" >&2; git merge --abort 2>/dev/null; return 1; }
rm -f /tmp/map-base.$$ /tmp/map-master.$$ /tmp/map-branch.$$; git add "$MAP_PATH"; echo "merge-to-master: merged $MAP_PATH structurally (master's cells plus the branch's)"
fi
if [ -n "$BATCHES" ]; then
git checkout -q "$tip" -- "$REGISTRY_PATH" # master's copy, never the branch's
if [ -f tools/ci/review-suite.mjs ] && [ -f docs/analysis/review-2026-10-08-b/findings.json ]; then
node tools/ci/review-suite.mjs --findings docs/analysis/review-2026-10-08-b/findings.json --dispatch docs/analysis/review-2026-10-08-b/dispatch.md --prefix REV --write >/dev/null || { echo "merge-to-master: the REV suite does not regenerate on the merged tree" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: regenerated the REV suite on master's registry"
fi
for f in $BATCHES; do
[ "$f" = . ] && continue
git checkout -q "$sha" -- "$f"
node tools/ci/test-record.mjs --record "$f" >/dev/null || { echo "merge-to-master: the batch $f does not replay onto master's registry" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: replayed $f onto master's registry"
done
for f in ${NOTES:-}; do
git checkout -q "$sha" -- "$f"
node tools/ci/test-record.mjs --note-file "$f" >/dev/null || { echo "merge-to-master: the note $f does not apply" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: replayed the note $f"
done
git add -A
fi
if [ "$MAP_CHANGED" = 1 ] && [ -f tools/ci/test-map-doc.mjs ]; then
git checkout -q "$tip" -- "$PAGE_PATH" 2>/dev/null || true # start from master's page; the generator overwrites it from the merged map
node tools/ci/test-map-doc.mjs >/dev/null || { echo "merge-to-master: the harness map page does not regenerate from the merged map" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: regenerated $PAGE_PATH from the merged map"; git add -A
fi
git "${AUTHOR[@]}" commit -q -m "$msg" || return 1
if [ -n "$BATCHES" ]; then
bash tools/ci/registry-evidence-check.sh "$tip" HEAD || { echo "merge-to-master: REFUSED by the registry's evidence rules on the merged registry (above)" >&2; return 1; }
@ -208,7 +246,7 @@ success 4 u push run
# the merge takes master's copy and replays the batch, so both rows land (the hot-file race, 8 October 2026, 19:1x UK)
rb="$d/rb"; mkdir -p "$rb" && ( cd "$rb" && git init -q -b master . && mkdir -p docs/plans tools/ci/batches && cp "$ROOT/tools/ci/test-record.mjs" tools/ci/ && cp "$ROOT/tools/ci/registry-evidence-check.sh" tools/ci/ && cp "$ROOT/tools/ci/docs-only-check.sh" tools/ci/ 2>/dev/null
printf '{"approval":"yes","suites":[{"code":"X","tests":[{"id":"X-1","method":"Automated","accept":"a"},{"id":"X-2","method":"Automated","accept":"b"}]}]}\n' > docs/plans/igneum-2.0-test-registry.json
printf '{"cells":{"c1":{"command":"x","box_class":"b","fixtures":[],"cases":["X-1"]},"c2":{"command":"y","box_class":"b","fixtures":[],"cases":["X-2"]}},"not_run":{}}\n' > tools/ci/test-map.json
printf '{"title":"t","registry":"docs/plans/igneum-2.0-test-registry.json","rule":"r","cells":{"c1":{"command":"x","box_class":"b","fixtures":[],"cases":["X-1"]},"c2":{"command":"y","box_class":"b","fixtures":[],"cases":["X-2"]}},"not_run":{}}\n' > tools/ci/test-map.json
git add -A && git -c user.name=t -c user.email=t@t commit -q -m base && git tag base
git checkout -q -b branch; printf '{"run_id":"r-branch","manifest_sha":"m","cells":[{"cell":"c1","status":"RUNNING","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-branch.json
node tools/ci/test-record.mjs --record tools/ci/batches/r-branch.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "batch r-branch"
@ -217,7 +255,24 @@ success 4 u push run
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse branch) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge with replay" 2>&1 && python3 -c "
import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); t={c['id']:c.get('run_id') for s in d['suites'] for c in s['tests']}; print('rows', t)" )
case "$out" in *"'X-1': 'r-branch'"*"'X-2': 'r-master'"*|*"'X-2': 'r-master'"*"'X-1': 'r-branch'"*) ;; *) echo "self-test failed: the batch replay did not land both the branch's row and master's row: $out"; fails=1 ;; esac
[ "$fails" = 0 ] && echo "self-test passed: a GitHub remote is refused with exit 2 while the suspension marker stands and a mirror remote is not; the CI rule binds a GitHub remote only; a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix; a registry landing's batches replay onto master's copy at the merge"
# the page race: the branch adds cell c3 to the map (page regenerated), master adds c4 (page regenerated); the merge regenerates the page with both
( cd "$rb" && cp "$ROOT/tools/ci/test-map-doc.mjs" "$ROOT/tools/ci/test-map-merge.py" tools/ci/ && mkdir -p docs/plans && git checkout -q master && node tools/ci/test-map-doc.mjs >/dev/null 2>&1; git add -A; git -c user.name=t -c user.email=t@t commit -q -m page0; git tag pbase
git checkout -q -b pb; python3 -c "import json; m=json.load(open('tools/ci/test-map.json')); m['cells']['c3']={'command':'z','box_class':'b','fixtures':[],'cases':['X-1']}; json.dump(m,open('tools/ci/test-map.json','w'))"; node tools/ci/test-map-doc.mjs >/dev/null 2>&1; git add -A; git -c user.name=t -c user.email=t@t commit -q -m c3
git checkout -q master; python3 -c "import json; m=json.load(open('tools/ci/test-map.json')); m['cells']['c4']={'command':'w','box_class':'b','fixtures':[],'cases':['X-2']}; json.dump(m,open('tools/ci/test-map.json','w'))"; node tools/ci/test-map-doc.mjs >/dev/null 2>&1; git add -A; git -c user.name=t -c user.email=t@t commit -q -m c4 ) >/dev/null 2>&1 || { echo "self-test failed: the page-race fixture did not build"; fails=1; }
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse pb) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES="" && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge with page" 2>&1 && grep -c -E '^### c[34]$' docs/plans/igneum-2.0-test-harness-map.md )
case "$out" in *regenerated*2) ;; *) echo "self-test failed: the merge did not regenerate the page with both sides' cells: $out"; fails=1 ;; esac
# both at once: the branch records a batch (its registry copy conflicts with master's) and adds a map cell; the page must regenerate
# after the registry is rebuilt, never from a copy with conflict markers (8 October 2026, 20:24 UK: the REV landing lost to this)
( cd "$rb" && git checkout -q -b both pbase && printf '{"run_id":"r-both","manifest_sha":"m","cells":[{"cell":"c1","status":"RUNNING","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-both.json && node tools/ci/test-record.mjs --record tools/ci/batches/r-both.json >/dev/null && python3 -c "import json; m=json.load(open('tools/ci/test-map.json')); m['cells']['c5']={'command':'v','box_class':'b','fixtures':[],'cases':['X-1']}; json.dump(m,open('tools/ci/test-map.json','w'))" && node tools/ci/test-map-doc.mjs >/dev/null 2>&1; git add -A; git -c user.name=t -c user.email=t@t commit -q -m both ) >/dev/null 2>&1
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse both) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge both" 2>&1 && grep -c -E '^### c[45]$' docs/plans/igneum-2.0-test-harness-map.md && python3 -c "import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); print('ok' if any(c.get('run_id')=='r-both' for s in d['suites'] for c in s['tests']) else 'no-batch')" )
case "$out" in *replayed*2*ok*) ;; *) echo "self-test failed: a landing with both a batch and a map change did not land both (the page before the registry rebuild?): $out"; fails=1 ;; esac
push_race "To x
! [remote rejected] HEAD -> master (failed to update ref)
remote: error: cannot lock ref 'refs/heads/master': is at a but expected b" || { echo "self-test failed: a lost compare-and-swap was not read as a race"; fails=1; }
push_race "pre-push gate: REFUSED. master takes only a commit whose own ci run is green" && { echo "self-test failed: a hook refusal was read as a race"; fails=1; }
push_race " RED 3s no conflict markers in tracked files
error: failed to push some refs" && { echo "self-test failed: a red check was read as a race"; fails=1; }
[ "$fails" = 0 ] && echo "self-test passed: a GitHub remote is refused with exit 2 while the suspension marker stands and a mirror remote is not; the CI rule binds a GitHub remote only; a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix; a registry landing's batches replay onto master's copy at the merge; a map change regenerates the harness page at the merge; a push that lost the ref race retries without re-running the hook"
exit $fails
fi
if [ "$SELF_TEST" != 1 ] && github_suspended_refusal "$REMOTE"; then exit 2; fi
@ -250,7 +305,16 @@ bash tools/ci/rule24-crate-gate.sh "$BASE" "$SHA" || { echo "merge-to-master: RE
# copy is never what lands and rule 26 does not bind the registry path for such a branch (the evidence rules run on the merged result)
REGISTRY_PATH="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"
BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true)
NOTES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true) # {suite, text}, replayed through test-record.mjs --note-file
REVGEN=0; git diff --quiet "$BASE" "$SHA" -- tools/ci/review-suite.mjs docs/analysis/review-2026-10-08-b/findings.json docs/analysis/review-2026-10-08-b/dispatch.md 2>/dev/null || REVGEN=1 # the REV suite regenerates on the merged tree
[ -n "$NOTES" ] || [ "$REVGEN" = 1 ] && BATCHES="${BATCHES:-.}" # the registry is rebuilt from master's copy whenever any transform rides
RULE26_SKIP_PATHS=""; [ -n "$BATCHES" ] && RULE26_SKIP_PATHS="$REGISTRY_PATH"
# the harness map page is generated from tools/ci/test-map.json (test-map-doc.mjs); a branch that changed the map regenerated the
# whole page, and master's page moves under every lane (8 October 2026, 19:5x UK: the enforced-proving lane lost rule 26's race
# twice on the page alone). The merge regenerates the page from the MERGED map, as it replays batches onto master's registry.
MAP_PATH="${MAP_PATH:-tools/ci/test-map.json}"; PAGE_PATH="${PAGE_PATH:-docs/plans/igneum-2.0-test-harness-map.md}"
MAP_CHANGED=0; git diff --quiet "$BASE" "$SHA" -- "$MAP_PATH" 2>/dev/null || MAP_CHANGED=1
[ "$MAP_CHANGED" = 1 ] && RULE26_SKIP_PATHS="$RULE26_SKIP_PATHS $PAGE_PATH"
# rule 26 (8 October 2026, 17:5x UK): a site/ or docs/ path another lane landed since the branch point is merged, never replaced
RULE26_SKIP_PATHS="$RULE26_SKIP_PATHS" bash tools/ci/rule26-no-revert.sh "$BASE" "$SHA" "$REMOTE/master" || { echo "merge-to-master: REFUSED by rule 26 (above)" >&2; exit 1; }
# the registry's evidence rules (8 October 2026, 18:4x UK): a PASS carries existing evidence, a touched evidence file moves with its
@ -262,14 +326,16 @@ for i in $(seq 1 "$TRIES"); do
W=$(mktemp -d "${TMPDIR:-/tmp}/merge-to-master.XXXXXX"); rmdir "$W"
git worktree add -q --detach "$W" "$TIP"
if ( cd "$W" && merge_with_batches "$TIP" "$SHA" "Merge $BRANCH ${SHA:0:8} into master ($VERDICT)" ); then
if ( cd "$W" && git push -q "$REMOTE" HEAD:master ); then # on a GitHub remote the hook asks ci-state about ${SHA:0:8} once more
pushout=$( cd "$W" && git push "$REMOTE" HEAD:master ${NOVERIFY:+--no-verify} 2>&1 ) && pushed=1 || pushed=0
[ "$pushed" = 1 ] || { printf '%s\n' "$pushout" | grep -E 'REFUSED| RED |rejected|error' | head -4 | cut -c1-160; }
if [ "$pushed" = 1 ]; then # on a GitHub remote the hook asks ci-state about ${SHA:0:8} once more
git worktree remove --force "$W"; git fetch -q "$REMOTE" master
echo "merge-to-master: pushed on try $i: $REMOTE/master $(git log -1 --format='%h %ci' "$REMOTE/master") $(TZ=Europe/London date '+%H:%M %Z')"
# the landed master to every other mirror, whichever remote took the landing (8 October 2026, 14:0x UK: a box landing never fanned
# out, so build-3 and build-4 cut branches from a tip 23 hours old)
mirror_master "$(git rev-parse "$REMOTE/master")" "$(git remote get-url "$REMOTE" 2>/dev/null)"; exit 0
fi
echo "merge-to-master: try $i: the push was rejected (master moved or the hook was red); again"
if push_race "$pushout"; then NOVERIFY=1; echo "merge-to-master: try $i: master moved under the push (the ref's compare-and-swap lost); the hook passed, the next try pushes without re-running it"; else echo "merge-to-master: try $i: the push was refused by the hook or failed; again" >&2; fi
else
echo "merge-to-master: the merge of $BRANCH onto ${TIP:0:8} does not apply cleanly; resolve on the branch (git merge origin/master) and retry" >&2
git worktree remove --force "$W"; exit 1

View file

@ -0,0 +1,4 @@
{
"suite": "FIN",
"text": "F04 (Review B), the founder's ruling 8 October 19:57 UK: the recovery lock is kept and is always labelled 'recovery', never 'final', on every surface (the checkpoint field, the explorer, receipts, the light client, the oracle, the site)"
}

View file

@ -0,0 +1,4 @@
{
"suite": "OPS",
"text": "F14 (Review B), the founder's ruling 8 October 19:57 UK: the public miner ships from 2.0.2 without remote jobs; our own fleet runs the lab build with its own signing root"
}

View file

@ -0,0 +1,4 @@
{
"suite": "UX",
"text": "F14 (Review B), the founder's ruling 8 October 19:57 UK: the public miner ships from 2.0.2 without remote jobs; our own fleet runs the lab build with its own signing root"
}

View file

@ -175,6 +175,9 @@ tree_checks() {
run "P01 part A, the million-vector driver: a clean run is PASS, one wrong hash or one unanswered nonce is FAIL naming it (self-test, a fake worker)" python3 tools/ci/p01-vectors.py --self-test
run "the proving outcome ledger (review B F08): every claimed job ends in one outcome; the report's self-test reads a log and a state file to known numbers" python3 tools/fleet/prover-outcomes.py --self-test
run "the registry's evidence rules: a PASS names evidence that exists, a touched evidence file moves with its row, stale evidence never reads PASS, a run_status needs the approval (self-test)" bash tools/ci/registry-evidence-check.sh --self-test
run "F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)" bash tools/ci/proof-rule-bypass-check.sh --self-test
run "the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)" python3 tools/ci/test-map-merge.py --self-test
run "the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)" bash tools/ci/review-suite-check.sh
run "the public ledger (docs/ledger-public.md) is what docs/fud-ledger.md generates: one row per item, no commit ids, times or team names (self-test first)" bash -c 'node tools/ledger/export-public.mjs --self-test && node tools/ledger/export-public.mjs --check'
run "the ledger page reads both entry heading forms (M1 and AP-F8-1) so no in-house pass row is dropped from /ledger (known-failed first)" node tools/ledger-page.mjs --self-test
run "every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)" bash tools/ci/workflow-timeouts-check.sh --self-test

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
# F02 (Review B, 8 October 2026): the proof-rule test bypass (IGNEUM_TEST_SKIP_PROOF_RULE, read in the node fork's
# consensus/src/pipeline/body_processor/body_validation_in_context.rs) must never be compiled into a release build. Two rules:
# source: every occurrence of the bypass name in a .rs file of the fork sits in a file under tests/ or inside an item guarded by
# #[cfg(test)] or #[cfg(feature = "<f>")] / cfg!(feature = "<f>") where <f> is NOT in the crate's default features
# (the guard must appear in the 12 lines above the occurrence, inside the same item)
# binary: a release node binary (igneumd), when given, does not contain the bypass name as a string (strings | grep)
# tools/ci/proof-rule-bypass-check.sh <fork tree> [<release igneumd>] exit 0 clean, 1 red (every occurrence named), 2 bad args
# tools/ci/proof-rule-bypass-check.sh --self-test
set -euo pipefail
NAME="${PROOF_RULE_BYPASS_NAME:-IGNEUM_TEST_SKIP_PROOF_RULE}"
HERE="$(cd "$(dirname "$0")" && pwd -P)"; ME="$HERE/$(basename "$0")"
source_rule() { # <tree> -> prints "red <file>:<line> <why>" lines; returns 1 when any
local tree="$1" rc=0 f n guard feat crate_toml defaults
while IFS=: read -r f n _; do
[ -n "$f" ] || continue
case "$f" in */tests/*|*/benches/*) continue ;; esac
case "$(sed -n "${n}p" "$f")" in *"//"*"$NAME"*) if ! sed -n "${n}p" "$f" | grep -qE "env::var|env!\(|\"$NAME\""; then continue; fi ;; esac
guard=$(awk -v n="$n" 'NR>=n-12 && NR<n' "$f" | grep -oE '#\[cfg\(test\)\]|cfg\(feature *= *"[^"]+"\)|cfg!\(feature *= *"[^"]+"\)|cfg\(any\([^)]*feature *= *"[^"]+"[^)]*\)\)' | tail -1 || true)
if [ -z "$guard" ]; then echo "red $f:$n $NAME read with no cfg(test) or cfg(feature) guard in the 12 lines above"; rc=1; continue; fi
case "$guard" in '#[cfg(test)]') continue ;; esac
feat=$(printf '%s' "$guard" | grep -oE 'feature *= *"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"/\1/')
crate_toml=$(d="$(dirname "$f")"; while [ "$d" != / ] && [ ! -f "$d/Cargo.toml" ]; do d=$(dirname "$d"); done; echo "$d/Cargo.toml")
defaults=$(awk '/^\[features\]/{f=1;next} /^\[/{f=0} f && /^default *=/' "$crate_toml" 2>/dev/null || true)
case "$defaults" in *"\"$feat\""*) echo "red $f:$n guarded by feature \"$feat\", which is in the crate's default features ($crate_toml)"; rc=1 ;; esac
done < <(grep -rn --include='*.rs' -F "$NAME" "$tree" 2>/dev/null || true)
return $rc
}
binary_rule() { # <igneumd> -> 1 when the string is inside
local bin="$1"
if strings "$bin" 2>/dev/null | grep -qF "$NAME"; then echo "red $bin carries the string $NAME: the bypass is compiled in"; return 1; fi
return 0
}
if [ "${1:-}" = --self-test ]; then
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; fails=0
mk() { mkdir -p "$d/$1/src"; printf '[package]\nname = "c"\nversion = "0.1.0"\n[features]\ndefault = [%s]\ntest-bypass = []\n' "$2" > "$d/$1/Cargo.toml"; printf '%s\n' "$3" > "$d/$1/src/lib.rs"; }
mk unguarded '' 'fn f() -> bool { std::env::var("IGNEUM_TEST_SKIP_PROOF_RULE").is_ok() }'
mk guarded '' '#[cfg(feature = "test-bypass")]
fn f() -> bool { std::env::var("IGNEUM_TEST_SKIP_PROOF_RULE").is_ok() }
#[cfg(not(feature = "test-bypass"))]
fn f() -> bool { false }'
mk default-feature '"test-bypass"' '#[cfg(feature = "test-bypass")]
fn f() -> bool { std::env::var("IGNEUM_TEST_SKIP_PROOF_RULE").is_ok() }'
mk cfgtest '' '#[cfg(test)]
mod t { fn f() -> bool { std::env::var("IGNEUM_TEST_SKIP_PROOF_RULE").is_ok() } }'
mk comment-only '' '// the bypass IGNEUM_TEST_SKIP_PROOF_RULE is gone from this crate
fn f() -> bool { false }'
out=$(bash "$ME" "$d/unguarded" 2>&1) && { echo "self-test failed: an unguarded env read passed"; fails=1; }; case "$out" in *"no cfg(test) or cfg(feature) guard"*) ;; *) echo "self-test failed: the unguarded read was not named: $out"; fails=1 ;; esac
bash "$ME" "$d/guarded" >/dev/null 2>&1 || { echo "self-test failed: a read under a non-default feature was refused: $(bash "$ME" "$d/guarded" 2>&1)"; fails=1; }
out=$(bash "$ME" "$d/default-feature" 2>&1) && { echo "self-test failed: a read under a DEFAULT feature passed"; fails=1; }; case "$out" in *"default features"*) ;; *) echo "self-test failed: the default feature was not named: $out"; fails=1 ;; esac
bash "$ME" "$d/cfgtest" >/dev/null 2>&1 || { echo "self-test failed: a read under #[cfg(test)] was refused"; fails=1; }
bash "$ME" "$d/comment-only" >/dev/null 2>&1 || { echo "self-test failed: a comment naming the bypass was refused"; fails=1; }
printf 'ELF\0\0igneumd IGNEUM_TEST_SKIP_PROOF_RULE\0' > "$d/bad.bin"; printf 'ELF\0\0igneumd clean\0' > "$d/good.bin"
out=$(bash "$ME" "$d/guarded" "$d/bad.bin" 2>&1) && { echo "self-test failed: a binary carrying the bypass string passed"; fails=1; }; case "$out" in *"compiled in"*) ;; *) echo "self-test failed: the binary was not named: $out"; fails=1 ;; esac
bash "$ME" "$d/guarded" "$d/good.bin" >/dev/null 2>&1 || { echo "self-test failed: a clean binary was refused"; fails=1; }
[ "$fails" = 0 ] && echo "self-test passed: an env read of the bypass with no guard, or under a default feature, is red and named; a read under cfg(test) or a non-default feature passes; a comment passes; a release binary carrying the bypass string is red, a clean one passes"
exit $fails
fi
[ $# -ge 1 ] || { echo "usage: proof-rule-bypass-check.sh <fork tree> [<release igneumd>] | --self-test" >&2; exit 2; }
rc=0; out=$(source_rule "$1") || rc=1
[ -n "${2:-}" ] && { bout=$(binary_rule "$2") || rc=1; out="$out${bout:+
$bout}"; }
printf '%s\n' "$out" | sed -n 's/^red /proof-rule-bypass: RED: /p' | grep . || true
[ "$rc" = 0 ] && echo "proof-rule-bypass: every $NAME read is under cfg(test) or a non-default feature${2:+; the release binary carries no bypass string}"
exit $rc

5
tools/ci/review-suite-check.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# The REV suite of the registry matches Review B's findings and dispatch (tools/ci/review-suite.mjs --check), self-test first.
set -euo pipefail
node tools/ci/review-suite.mjs --self-test >/dev/null
node tools/ci/review-suite.mjs --findings docs/analysis/review-2026-10-08-b/findings.json --dispatch docs/analysis/review-2026-10-08-b/dispatch.md --prefix REV --check

75
tools/ci/review-suite.mjs Normal file
View file

@ -0,0 +1,75 @@
#!/usr/bin/env node
// The REV suite of the acceptance registry: one case per required regression of an external review's findings.json, the owner from
// its dispatch.md table, the finding id and priority carried as fields, status NOT RUN until a lane records a run through the batch
// tools (main through the coordinator, 8 October 2026, 19:5x UK: Review B's 44 regressions). The registry's one structural edit
// per review: generated here, never by hand; --check refuses a registry whose REV suite differs from the generator's output.
//
// node tools/ci/review-suite.mjs --findings <findings.json> --dispatch <dispatch.md> --prefix REV [--write | --check]
// node tools/ci/review-suite.mjs --self-test
import fs from 'node:fs'; import path from 'node:path';
const args = process.argv.slice(2); const arg = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
const ROOT = process.env.TEST_RECORD_ROOT || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
const REG = process.env.TEST_REGISTRY || path.join(ROOT, 'docs/plans/igneum-2.0-test-registry.json');
function owners(dispatchMd) { // "| F01 title | P0 | owner a, owner b | ..." -> {F01: "owner a, owner b"}
const out = {};
for (const line of dispatchMd.split('\n')) {
const m = line.match(/^\|\s*(F\d+)\b[^|]*\|\s*([^|]*)\|\s*([^|]*)\|/); if (m) out[m[1]] = m[3].trim();
}
return out;
}
function suite(findings, dispatchMd, prefix, sourceNote) {
const own = owners(dispatchMd); const tests = [];
for (const f of findings.findings || []) {
(f.required_regressions || []).forEach((line, i) => {
tests.push({ id: `${prefix}-${f.id}-${i + 1}`, title: line, setup: `The regression ${f.id} requires (review finding ${f.id}: ${f.title}).`, steps: [line],
accept: line, evidence: 'The run record of the regression as its owner lane records it through tools/ci/test-record.mjs.', priority: String(f.priority || '').split(' ')[0] || 'P1',
profile: 'P00', cadence: 'Every release candidate', method: 'Automated + independent review', status: 'NOT RUN', source: [f.id], gate: 'Review findings closed',
owner: own[f.id] || 'unassigned', manual_page: null, finding: f.id, finding_title: f.title, finding_priority: f.priority, owner_lane: own[f.id] || 'unassigned', run_status: 'NOT RUN' });
});
}
return { code: prefix, title: `${prefix}: the external review's required regressions`, source: sourceNote, gate: 'Review findings closed', owner: 'the owner lanes per the dispatch table', fixtures: ['F0', 'F5'],
summary: `${tests.length} regressions from ${(findings.findings || []).length} findings; each reads NOT RUN until its owner lane records a run`, tests };
}
function merge(reg, s) { // replace the suite of the same code, keeping live fields of cases that already exist
const old = (reg.suites || []).find((x) => x.code === s.code); const live = new Map((old?.tests || []).map((t) => [t.id, t]));
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record']) if (k in o) t[k] = o[k]; }
reg.suites = [...(reg.suites || []).filter((x) => x.code !== s.code), s]; return reg;
}
if (args.includes('--self-test')) {
let fails = 0;
const findings = { findings: [{ id: 'F01', title: 'A', priority: 'P0 - blocker', required_regressions: ['r one', 'r two'] }, { id: 'F02', title: 'B', priority: 'P1 - x', required_regressions: ['r three'] }] };
const dispatch = '| Finding | Priority | Owner | Default |\n|---|---|---|---|\n| F01 A | P0 | lane x, lane y | d |\n| F02 B | P1 | lane z | d |\n';
const s = suite(findings, dispatch, 'REV', 'test');
if (!(s.tests.length === 3 && s.tests[0].id === 'REV-F01-1' && s.tests[2].id === 'REV-F02-1')) { console.log(`self-test failed: the ids are not <prefix>-<finding>-<n>: ${s.tests.map((t) => t.id)}`); fails = 1; }
if (!(s.tests[0].title === 'r one' && s.tests[0].accept === 'r one')) { console.log('self-test failed: the title and accept are not the regression line verbatim'); fails = 1; }
if (!(s.tests[0].owner_lane === 'lane x, lane y' && s.tests[2].owner_lane === 'lane z')) { console.log(`self-test failed: owners not read from the dispatch table: ${s.tests.map((t) => t.owner_lane)}`); fails = 1; }
if (!(s.tests[0].finding === 'F01' && s.tests[0].priority === 'P0' && s.tests[0].run_status === 'NOT RUN' && s.tests[0].method.includes('Automated'))) { console.log('self-test failed: finding, priority, NOT RUN or method missing'); fails = 1; }
const reg = { suites: [{ code: 'GOV', tests: [] }, { code: 'REV', tests: [{ id: 'REV-F01-1', run_status: 'RUNNING', run_id: 'r9' }] }] };
const m = merge(JSON.parse(JSON.stringify(reg)), s); const rev = m.suites.find((x) => x.code === 'REV');
if (!(m.suites.length === 2 && rev.tests.length === 3 && rev.tests[0].run_status === 'RUNNING' && rev.tests[0].run_id === 'r9' && rev.tests[1].run_status === 'NOT RUN')) { console.log('self-test failed: a regenerated suite did not keep the existing case\'s live fields or dropped another suite'); fails = 1; }
const map = { cells: { c1: { cases: ['REV-F01-1'] } }, not_run: { 'REV-F02-1': 'old' } }; const n = mapReasons(map, s);
if (!(n === 2 && !('REV-F01-1' in map.not_run) && /lane z/.test(map.not_run['REV-F02-1']) && /lane x/.test(map.not_run['REV-F01-2']))) { console.log(`self-test failed: the map's NOT RUN reasons: ${JSON.stringify(map.not_run)} n=${n}`); fails = 1; }
if (!fails) console.log('self-test passed: one case per required regression with the id <prefix>-<finding>-<n>, the line verbatim as title and accept, the owner from the dispatch table, finding and priority carried, NOT RUN; regenerating keeps live fields and the other suites; every unmapped case gets a NOT RUN reason naming its owner lane in the map, a mapped one loses it');
process.exit(fails);
}
const findings = JSON.parse(fs.readFileSync(arg('--findings'), 'utf8')); const dispatch = fs.readFileSync(arg('--dispatch'), 'utf8'); const prefix = arg('--prefix') || 'REV';
const s = suite(findings, dispatch, prefix, `${path.relative(ROOT, arg('--findings'))} and ${path.relative(ROOT, arg('--dispatch'))}`);
const reg = JSON.parse(fs.readFileSync(REG, 'utf8'));
if (args.includes('--check')) {
const cur = (reg.suites || []).find((x) => x.code === prefix); const want = merge(JSON.parse(JSON.stringify(reg)), s).suites.find((x) => x.code === prefix);
const canon = (o) => JSON.stringify(o, (k, v) => (v && typeof v === 'object' && !Array.isArray(v)) ? Object.fromEntries(Object.keys(v).sort().map((x) => [x, v[x]])) : v);
if (canon(cur) !== canon(want)) { console.error(`review-suite: the registry's ${prefix} suite differs from the generator's output; run --write and commit`); process.exit(1); }
console.log(`review-suite: the ${prefix} suite matches its findings (${s.tests.length} cases)`); process.exit(0);
}
const MAP = process.env.TEST_MAP || path.join(ROOT, 'tools/ci/test-map.json');
function mapReasons(map, s) { // every generated case with no cell reads NOT RUN with the owner lane named; a case a cell maps loses its reason
const mapped = new Set(Object.values(map.cells || {}).flatMap((c) => c.cases || [])); map.not_run = map.not_run || {}; let n = 0;
for (const t of s.tests) { if (mapped.has(t.id)) { delete map.not_run[t.id]; continue; } map.not_run[t.id] = `${t.finding} (${t.priority}, the external review): the regression's harness is the owner lane's (${t.owner_lane}); not yet named in the map`; n++; }
return n;
}
if (args.includes('--write')) {
fs.writeFileSync(REG, JSON.stringify(merge(reg, s), null, 2) + '\n');
let n = 0; if (fs.existsSync(MAP)) { const map = JSON.parse(fs.readFileSync(MAP, 'utf8')); n = mapReasons(map, s); fs.writeFileSync(MAP, JSON.stringify(map, null, 2) + '\n'); }
console.log(`review-suite: ${prefix} written, ${s.tests.length} cases from ${(findings.findings || []).length} findings; ${n} NOT RUN reasons in the map`); process.exit(0);
}
console.error('usage: review-suite.mjs --findings f --dispatch d [--prefix REV] --write|--check | --self-test'); process.exit(2);

44
tools/ci/test-map-merge.py Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""A structural three-way merge of tools/ci/test-map.json (8 October 2026, 20:0x UK): two lanes adding cells on adjacent lines
collide as text; as objects they do not. Result = master's map, plus every cell (and not_run entry) the branch added or changed
against the base, minus the cells the branch removed that master left as the base had them. Everything else of the map (title,
registry, rule) is master's. Usage: test-map-merge.py <base.json> <master.json> <branch.json> <out.json>; --self-test."""
import json, sys, tempfile, os
def merge(base, master, branch):
out = json.loads(json.dumps(master))
for key in ("cells", "not_run"):
b, m, r = base.get(key, {}), master.get(key, {}), branch.get(key, {})
res = dict(m)
for k, v in r.items():
if k not in b or b[k] != v:
res[k] = v
for k in b:
if k not in r and k in m and m[k] == b[k]:
del res[k]
out[key] = res
return out
def self_test():
fails = 0
base = {"title": "t", "cells": {"c1": {"a": 1}, "c2": {"a": 2}, "c9": {"a": 9}}, "not_run": {"X-5": "none"}}
master = {"title": "t2", "cells": {"c1": {"a": 1}, "c2": {"a": 2}, "c9": {"a": 9}, "c4": {"a": 4}}, "not_run": {"X-5": "none", "X-6": "m"}}
branch = {"title": "t", "cells": {"c1": {"a": 1}, "c2": {"a": 22}, "c3": {"a": 3}}, "not_run": {}} # adds c3, changes c2, removes c9, clears X-5
r = merge(base, master, branch)
want_cells = {"c1": {"a": 1}, "c2": {"a": 22}, "c4": {"a": 4}, "c3": {"a": 3}}
if r["cells"] != want_cells: print("self-test failed: cells:", r["cells"]); fails = 1
if r["not_run"] != {"X-6": "m"}: print("self-test failed: not_run:", r["not_run"]); fails = 1
if r["title"] != "t2": print("self-test failed: master's other fields not kept"); fails = 1
# master changed c9 too: the branch's removal does not win
master2 = json.loads(json.dumps(master)); master2["cells"]["c9"] = {"a": 99}
if "c9" not in merge(base, master2, branch)["cells"]: print("self-test failed: a cell master changed was removed by the branch"); fails = 1
if not fails: print("self-test passed: master's map plus the branch's added and changed cells and not_run entries, minus what the branch removed and master left alone; master's other fields kept")
return fails
if __name__ == "__main__":
if "--self-test" in sys.argv: sys.exit(self_test())
base, master, branch, out = (json.load(open(p)) for p in sys.argv[1:4]), None, None, None
b, m, r = base
res = merge(b, m, r)
with open(sys.argv[4], "w") as f: json.dump(res, f, indent=2); f.write("\n")
print(f"test-map-merge: {len(res.get('cells', {}))} cells, {len(res.get('not_run', {}))} NOT RUN reasons")

View file

@ -519,6 +519,50 @@
"LEAD-04": "observation window",
"LEAD-05": "observation window",
"LEAD-06": "observation window",
"LEAD-08": "observation window"
"LEAD-08": "observation window",
"REV-F01-1": "F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map",
"REV-F01-2": "F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map",
"REV-F01-3": "F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map",
"REV-F01-4": "F01 (P0, the external review): the regression's harness is the owner lane's (proving lane, node lane); not yet named in the map",
"REV-F02-1": "F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
"REV-F02-2": "F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
"REV-F02-3": "F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
"REV-F03-1": "F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
"REV-F03-2": "F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
"REV-F03-3": "F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
"REV-F04-1": "F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
"REV-F04-2": "F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
"REV-F04-3": "F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
"REV-F04-4": "F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
"REV-F05-1": "F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map",
"REV-F05-2": "F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map",
"REV-F05-3": "F05 (P1, the external review): the regression's harness is the owner lane's (hash lane, adversary lane, floor lane 3); not yet named in the map",
"REV-F06-1": "F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map",
"REV-F06-2": "F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map",
"REV-F06-3": "F06 (P1, the external review): the regression's harness is the owner lane's (hash lane, research lane D); not yet named in the map",
"REV-F07-1": "F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map",
"REV-F07-2": "F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map",
"REV-F07-3": "F07 (P1, the external review): the regression's harness is the owner lane's (app lane, fleet lane); not yet named in the map",
"REV-F08-1": "F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map",
"REV-F08-2": "F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map",
"REV-F08-3": "F08 (P1, the external review): the regression's harness is the owner lane's (proving lane, fleet lane, site (ops page)); not yet named in the map",
"REV-F09-1": "F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map",
"REV-F09-2": "F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map",
"REV-F09-3": "F09 (P1, the external review): the regression's harness is the owner lane's (research lane, floor lane 3, coordinator); not yet named in the map",
"REV-F10-1": "F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map",
"REV-F10-2": "F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map",
"REV-F10-3": "F10 (P2, the external review): the regression's harness is the owner lane's (worker lane (new)); not yet named in the map",
"REV-F11-1": "F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map",
"REV-F11-2": "F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map",
"REV-F11-3": "F11 (P2, the external review): the regression's harness is the owner lane's (Ember lane (new)); not yet named in the map",
"REV-F12-1": "F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F12-2": "F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F12-3": "F12 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F13-1": "F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F13-2": "F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F13-3": "F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"REV-F14-1": "F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map",
"REV-F14-2": "F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map",
"REV-F14-3": "F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map"
}
}

View file

@ -10,8 +10,10 @@
// node tools/ci/test-record.mjs --record <batch.json> batch: {run_id, manifest_sha, evidence_dir, cells:[{cell, status, evidence}]}
// each cell's case ids come from the map; the registry gains status/run/evidence/manifest
// node tools/ci/test-record.mjs --cases <cell> the case ids a matrix cell answers (for the matrix scripts' column)
// node tools/ci/test-record.mjs --note <suite code> "<text>" append a dated note to a suite's notes (a ruling, a review finding's
// disposition); never a case's accept text
// node tools/ci/test-record.mjs --self-test
import fs from 'node:fs'; import path from 'node:path';
import fs from 'node:fs'; import path from 'node:path'; import child_process from 'node:child_process';
const args = process.argv.slice(2); const arg = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
const ROOT = process.env.TEST_RECORD_ROOT || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
const REG = process.env.TEST_REGISTRY || path.join(ROOT, 'docs/plans/igneum-2.0-test-registry.json');
@ -45,7 +47,9 @@ function record(reg, map, batch) {
touched.push(id);
}
}
for (const [id, reason] of Object.entries(map.not_run || {})) { const c = byId.get(id); if (c && (!c.run_status || c.run_status === 'NOT RUN')) { c.run_status = 'NOT RUN'; c.evidence_record = { reason, at: now }; c.updated = now; } }
// a NOT RUN row is stamped only when its status or reason changes (8 October 2026, 20:1x UK: re-stamping every NOT RUN row on every
// run made each lane's landing collide on 39 rows it never touched)
for (const [id, reason] of Object.entries(map.not_run || {})) { const c = byId.get(id); if (c && (!c.run_status || c.run_status === 'NOT RUN') && !(c.run_status === 'NOT RUN' && c.evidence_record?.reason === reason)) { c.run_status = 'NOT RUN'; c.evidence_record = { reason, at: now }; c.updated = now; } }
return touched;
}
const acceptSnapshot = (reg) => JSON.stringify(casesOf(reg).map((c) => { const o = { id: idOf(c) }; for (const k of ACCEPT_KEYS) if (k in c) o[k] = c[k]; return o; }));
@ -63,17 +67,31 @@ if (args.includes('--self-test')) {
if (acceptSnapshot(reg) !== before) { console.log('self-test failed: a record changed an accept text'); fails = 1; }
if (!(touched.length === 1 && reg.cases[0].run_status === 'PASS' && reg.cases[0].run_id === 'r1' && reg.cases[0].evidence_record.manifest_sha === 'abc' && reg.cases[0].evidence_path === '/e/pow.log' && reg.cases[0].updated)) { console.log(`self-test failed: the run was not written to the mapped case's live fields: ${JSON.stringify(reg.cases[0])}`); fails = 1; }
if (!(reg.cases[1].run_status === 'NOT RUN' && /corpus/.test(reg.cases[1].evidence_record.reason))) { console.log('self-test failed: an unmapped Automated case did not read NOT RUN with its reason'); fails = 1; }
const stamp1 = reg.cases[1].updated; record(reg, map, { run_id: 'r1b', manifest_sha: 'abc', evidence_dir: '/e', cells: [{ cell: 'pow', status: 'PASS', evidence: '/e/pow.log' }] });
if (reg.cases[1].updated !== stamp1) { console.log('self-test failed: an unchanged NOT RUN row was re-stamped on a later run'); fails = 1; }
if (reg.cases[2].run_status) { console.log('self-test failed: a manual case was given a run status'); fails = 1; }
const regS = { suites: [{ code: 'X', tests: [{ id: 'X-1', method: 'Automated', accept: 'a' }] }] }; if (casesOf(regS).length !== 1) { console.log('self-test failed: the suites/tests registry shape was not read'); fails = 1; }
const regN = { suites: [{ code: 'FIN', tests: [{ id: 'F-1', method: 'Automated', accept: 'keep' }] }] }; fs.writeFileSync(`${d}/regn.json`, JSON.stringify(regN));
const nr = child_process.spawnSync(process.execPath, [new URL(import.meta.url).pathname, '--note', 'FIN', 'the ruling'], { env: { ...process.env, TEST_REGISTRY: `${d}/regn.json`, TEST_MAP: `${d}/map.json` }, encoding: 'utf8' });
const regN2 = JSON.parse(fs.readFileSync(`${d}/regn.json`, 'utf8'));
if (!(nr.status === 0 && regN2.suites[0].notes?.length === 1 && regN2.suites[0].notes[0].text === 'the ruling' && regN2.suites[0].tests[0].accept === 'keep')) { console.log(`self-test failed: --note did not append a dated note to the suite and keep the accept text: ${nr.stdout} ${nr.stderr}`); fails = 1; }
let threw = false; try { record(reg, map, { run_id: 'r2', manifest_sha: 'x', cells: [{ cell: 'ghost', status: 'PASS' }] }); } catch { threw = true; }
if (!threw) { console.log('self-test failed: a batch naming a cell not in the map was accepted'); fails = 1; }
fs.rmSync(d, { recursive: true, force: true });
if (!fails) console.log('self-test passed: a complete map checks; an unknown case id and an unmapped Automated case are refused; a run batch writes run_status, run_id, evidence_path, updated and the evidence record to the mapped cases only, leaves every accept text byte-identical, gives an unmapped Automated case NOT RUN with its reason and a manual case nothing; a batch naming an unknown cell is refused');
if (!fails) console.log('self-test passed: a complete map checks; an unknown case id and an unmapped Automated case are refused; a run batch writes run_status, run_id, evidence_path, updated and the evidence record to the mapped cases only, leaves every accept text byte-identical, gives an unmapped Automated case NOT RUN with its reason and a manual case nothing; a batch naming an unknown cell is refused; --note appends a dated note to a suite and never touches an accept text; an unchanged NOT RUN row is not re-stamped');
process.exit(fails);
}
const reg = load(REG); const map = load(MAP);
if (args.includes('--check')) { const r = check(reg, map); for (const l of r.lines) console.error(`test-record: ${l}`); console.log(`test-record: ${r.automated} Automated cases, ${r.mapped} mapped to cells, ${r.notRun} NOT RUN with a reason${r.bad ? `, ${r.bad} problems` : ''}`); process.exit(r.bad ? 1 : 0); }
if (arg('--cases')) { console.log(((map.cells || {})[arg('--cases')]?.cases || []).join(',')); process.exit(0); }
if (arg('--note-file')) { const n = load(arg('--note-file')); args.push('--note', n.suite, n.text); }
if (arg('--note')) {
const code = arg('--note'); const text = args[args.indexOf('--note') + 2]; const suite = (reg.suites || []).find((s) => s.code === code);
if (!suite || !text) { console.error(`test-record: --note needs a suite code in the registry and a text (got ${code}, ${text ? 'text' : 'no text'})`); process.exit(2); }
const before = acceptSnapshot(reg); suite.notes = suite.notes || []; suite.notes.push({ at: new Date().toISOString(), text });
if (acceptSnapshot(reg) !== before) { console.error('test-record: REFUSED: the note would change an accept text'); process.exit(1); }
fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); console.log(`test-record: note ${suite.notes.length} on ${code}: ${text.slice(0, 80)}`); process.exit(0);
}
if (arg('--record')) {
const batch = load(arg('--record')); const before = acceptSnapshot(reg); const touched = record(reg, map, batch);
if (acceptSnapshot(reg) !== before) { console.error('test-record: REFUSED: the record would change an accept text'); process.exit(1); }