From e7668becda85b09dc82da4356f2e275d35015f15 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 19:35:04 +0000 Subject: [PATCH] The merge normalises master's registry copy on every landing; --check refuses a row outside the four decisions Co-Authored-By: Claude Fable 5.1 --- tools/ci/merge-to-master.sh | 1 + tools/ci/test-record.mjs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tools/ci/merge-to-master.sh b/tools/ci/merge-to-master.sh index bbdc09526..f94578b05 100755 --- a/tools/ci/merge-to-master.sh +++ b/tools/ci/merge-to-master.sh @@ -138,6 +138,7 @@ merge_with_batches() { fi if [ -n "$BATCHES" ]; then git checkout -q "$tip" -- "$REGISTRY_PATH" # master's copy, never the branch's + node tools/ci/test-record.mjs --normalize >/dev/null 2>&1 || true # the master's decision vocabulary and record schema on every landing (idempotent) 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" diff --git a/tools/ci/test-record.mjs b/tools/ci/test-record.mjs index 426d6b8c2..e5836dfb7 100644 --- a/tools/ci/test-record.mjs +++ b/tools/ci/test-record.mjs @@ -47,6 +47,7 @@ function check(reg, map) { if (!isAutomated(c)) continue; const id = idOf(c); if (!mapped.has(id) && !notRun[id]) { out.push(`case ${id} (Automated) has no harness in the map and no NOT RUN reason`); bad++; } } + for (const c of cases) { const st = c.run_status; if (st && !DECISIONS.has(st)) { out.push(`case ${idOf(c)} reads ${st}: the decision vocabulary is NOT RUN, BLOCKED, FAIL, PASS (run --normalize)`); bad++; } } for (const [cell, m] of Object.entries(map.cells || {})) { if (!m.command) { out.push(`map: cell ${cell} has no command`); bad++; } if (!m.box_class) { out.push(`map: cell ${cell} has no box class`); bad++; } if (!Array.isArray(m.fixtures)) { out.push(`map: cell ${cell} has no fixtures list (F0 onward, or empty)`); bad++; } } return { bad, lines: out, automated: cases.filter(isAutomated).length, mapped: [...mapped].length, notRun: Object.keys(notRun).length }; } @@ -118,6 +119,8 @@ if (args.includes('--self-test')) { const map = { cells: { 'pow': { command: 'cargo test --release', box_class: 'suite', fixtures: ['F0'], cases: ['C1'] } }, not_run: { C2: 'no malformed-case corpus yet', C4: 'the hostile-load rig is not built' } }; fs.writeFileSync(`${d}/reg.json`, JSON.stringify(reg)); fs.writeFileSync(`${d}/map.json`, JSON.stringify(map)); let r = check(reg, map); if (r.bad !== 0) { console.log(`self-test failed: a complete map was refused: ${r.lines.join('; ')}`); fails = 1; } + const regV = { cases: [{ id: 'V1', method: 'Automated', accept: 'a', run_status: 'RUNNING' }] }; const rv = check(regV, { cells: { c: { command: 'x', box_class: 'b', fixtures: [], cases: ['V1'] } }, not_run: {} }); + if (!(rv.bad === 1 && /vocabulary/.test(rv.lines[0]))) { console.log(`self-test failed: a RUNNING row passed --check: ${rv.lines}`); fails = 1; } const map2 = { cells: { pow: { ...map.cells.pow, cases: ['C1', 'C9'] } }, not_run: {} }; r = check(reg, map2); if (!(r.bad >= 2 && r.lines.some((l) => l.includes('unknown case C9')) && r.lines.some((l) => l.includes('C2') && l.includes('no harness')))) { console.log(`self-test failed: an unknown id or an unmapped Automated case was not refused: ${r.lines.join('; ')}`); fails = 1; } const before = acceptSnapshot(reg); const touched = record(reg, map, { run_id: 'r1', manifest_sha: 'abc', evidence_dir: '/e', method: 'native', cells: [{ cell: 'pow', status: 'PASS', evidence: '/e/pow.log' }] });