From d24610a6686d39df1e213be3eda76443de3a679e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:46:02 +0000 Subject: [PATCH 1/2] test(scripts): floor publish-smoke-pack's self-test with a hoisted single battery `SELF-TEST PASSED (n cases)` was printed on `process.exitCode !== 1` alone, so a self-test whose four cases never registered printed the same line as one where every case held. Recipe b5 (PR #15217): ONE battery opened at the top of the self-test body, named `publish-smoke-pack self-test`, floor = the count measured on a run (4), `SELF_TEST_BATTERIES` size pinned at 1, `registerCase()` called from the existing `check(name, fn)` helper's block body, and a verdict that refuses a below-floor / DID-NOT-RUN / undeclared battery through the same sink the cases use (a `FAIL` line plus the failing exit code). No comment is promoted to a section head; no assertion condition is touched; the verdict handshake (`SELF_TEST_VERDICT`) is unchanged. Floor measured, not counted: the roster was first pinned at 9999 and the breach line named `registered 4 case(s)`. Part of #13799 Co-authored-by: Claude --- scripts/publish-smoke-pack.mjs | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/scripts/publish-smoke-pack.mjs b/scripts/publish-smoke-pack.mjs index 5545a0ce6e..15b1e76c21 100644 --- a/scripts/publish-smoke-pack.mjs +++ b/scripts/publish-smoke-pack.mjs @@ -180,6 +180,41 @@ async function main() { * the derivation) is invisible to any fixture whose names all start with `@`. */ +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// A `cases` list that holds a line per case, ok or FAIL, used to be this +// self-test's ONLY success condition, so "every case held" and "the cases +// never ran" printed the same line. Closed the way PR #13487 validated on +// check-doc-authoring: what is pinned is the registered NAMES, not a +// number. The floor requires the OPENED set to equal the DECLARED set with +// each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'publish-smoke-pack self-test': 4, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + // Returned by `selfTest()` only after its verdict is printed. The dispatch // refuses anything else: a `return` that leaves the function above that line // prints nothing and still exits 0 — a self-test that never finished, reported @@ -187,8 +222,23 @@ async function main() { const SELF_TEST_VERDICT = 'publish-smoke-pack self-test reached its verdict'; function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('publish-smoke-pack self-test'); const cases = []; const check = (name, fn) => { + registerCase(); try { fn(); cases.push(` ok — ${name}`); @@ -248,6 +298,56 @@ function selfTest() { assert(msg.includes('@objectstack/gone'), `the diagnostic does not name the surplus package: ${msg}`); }); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a line in the + // report and the failing exit code — so a breached floor reads exactly like a + // failed case and cannot be printed over by the verdict below. + const floorFailure = (message) => { + cases.push(` FAIL — ${message}`); + process.exitCode = 1; + }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + console.log('publish-smoke-pack self-test'); for (const line of cases) console.log(line); console.log(process.exitCode === 1 ? 'SELF-TEST FAILED' : `SELF-TEST PASSED (${cases.length} cases)`); From 9ecea9ca2f5fe008cf7824457ed505bf299d7607 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:50:52 +0000 Subject: [PATCH 2/2] test(scripts): floor eight more self-tests with a hoisted single battery The b5 recipe (PR #15217) applied to the remaining eight census rows of #13799 batch 8a. Each file gets ONE battery opened at the top of its self-test body, named `BASENAME self-test`, with the floor read off a run (the roster pinned at 9999 first, the breach line naming N), the roster's own size pinned at 1, `registerCase()` called from the block body of the helper the file already has, and a verdict refusing below-floor / DID NOT RUN / undeclared batteries through the file's own failure sink. Floors measured on a run: check-adr-symbol-anchors 17, symbol-anchors 51, check-i18n-walk-parity 23, check-test-completeness 67, checklist-select 17, release-rehearsal-clone 32, render-release-coverage-anchor 10, run-with-stall-guard 41. Two of them are the census's own warning coming true: symbol-anchors has 40 static `assert(` sites but registers 51 (loops), and render-release-coverage-anchor has 8 static `expect(` sites but registers 10. A floor counted from the source would have been wrong in both. Where the helper is module-level (check-adr-symbol-anchors, symbol-anchors, render-release-coverage-anchor) the case sites call a thin in-body wrapper that registers and then defers to the existing assertion, exactly as PR #15156 landed for that shape; no assertion condition is touched. checklist-select also stops transcribing its case count: the success line's hand-typed `17` is now read off a counter (#15305). It renders the same text today, which is what makes the byte comparison across the change readable. check-test-completeness scopes its floor to the loud run, because `selfTest({ quiet: true })` also runs on every production invocation of that gate, where nothing claims a self-test verdict. Part of #13799 Fixes #15305 Co-authored-by: Claude --- scripts/check-adr-symbol-anchors.mjs | 139 ++++++++++++++-- scripts/check-i18n-walk-parity.mjs | 99 ++++++++++- scripts/check-test-completeness.mjs | 108 +++++++++++- scripts/checklist-select.mjs | 105 +++++++++++- scripts/pm/release-rehearsal-clone.mjs | 103 +++++++++++- scripts/render-release-coverage-anchor.mjs | 124 +++++++++++++- scripts/run-with-stall-guard.mjs | 102 +++++++++++- scripts/symbol-anchors.mjs | 185 ++++++++++++++++----- 8 files changed, 894 insertions(+), 71 deletions(-) diff --git a/scripts/check-adr-symbol-anchors.mjs b/scripts/check-adr-symbol-anchors.mjs index 44e5b7a54e..19f0489352 100644 --- a/scripts/check-adr-symbol-anchors.mjs +++ b/scripts/check-adr-symbol-anchors.mjs @@ -153,6 +153,41 @@ function list(root = process.cwd()) { function assert(cond, msg) { if (!cond) { console.error(`❌ check-adr-symbol-anchors --self-test: ${msg}`); process.exit(1); } } +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// A module-level `assert()` that exits on the first failure used to be this +// self-test's ONLY success condition, so "every case held" and "the cases +// never ran" printed the same line. Closed the way PR #13487 validated on +// check-doc-authoring: what is pinned is the registered NAMES, not a +// number. The floor requires the OPENED set to equal the DECLARED set with +// each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'check-adr-symbol-anchors self-test': 17, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + // Returned by `selfTest()` only after its verdict is printed. The dispatch // refuses anything else: a `return` that leaves the function above that line // prints nothing and still exits 0 — a self-test that never finished, reported @@ -160,6 +195,27 @@ function assert(cond, msg) { if (!cond) { console.error(`❌ check-adr-symbol-an const SELF_TEST_VERDICT = 'check-adr-symbol-anchors self-test reached its verdict'; export function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('check-adr-symbol-anchors self-test'); + // A thin in-body wrapper over the module-level `assert`: it attributes the + // case to the open battery and then defers to the existing assertion, whose + // semantics (print and exit 1 on the first failure) are unchanged. + const check = (cond, message) => { + registerCase(); + assert(cond, message); + }; // 1. ⭐ The instrument this gate cannot hold about itself on a clean tree: a // synthetic corpus carrying one of EVERY finding class, plus the healthy // forms, so "no findings" is told apart from "the rule stopped matching". @@ -191,18 +247,18 @@ export function selfTest() { const kinds = findings.map((f) => f.kind); const count = (k) => kinds.filter((x) => x === k).length; - assert(count('line-anchor') === 3, `3 line anchors (plain, hyphen range, EN DASH range) must be found, got ${count('line-anchor')}`); + check(count('line-anchor') === 3, `3 line anchors (plain, hyphen range, EN DASH range) must be found, got ${count('line-anchor')}`); // `noSuchSymbol` (absent) and `commentOnlySymbol` (named only in a // comment — the census's permissiveness, refused here). The vanished FILE // is a different class and is asserted separately below. - assert(count('unresolved-symbol') === 2, `2 unresolved symbols must be found, got ${count('unresolved-symbol')}`); - assert(count('bad-exemption') === 1, `an invalid exemption class must be a finding, got ${count('bad-exemption')}`); - assert(count('unresolved-path') === 1, `a vanished target must be a finding, got ${count('unresolved-path')}`); - assert(counts.exempt === 1, 'a valid exemption must be honoured exactly once'); + check(count('unresolved-symbol') === 2, `2 unresolved symbols must be found, got ${count('unresolved-symbol')}`); + check(count('bad-exemption') === 1, `an invalid exemption class must be a finding, got ${count('bad-exemption')}`); + check(count('unresolved-path') === 1, `a vanished target must be a finding, got ${count('unresolved-path')}`); + check(counts.exempt === 1, 'a valid exemption must be honoured exactly once'); // ...and the healthy record must contribute NOTHING. A rule that fires on // good anchors is as broken as one that misses bad ones. - assert(!findings.some((f) => f.doc.includes('0001-good')), 'the healthy record must produce no findings'); - assert(counts.declaration >= 1 && counts.literal >= 1, 'both resolution classes must be exercised by the fixture'); + check(!findings.some((f) => f.doc.includes('0001-good')), 'the healthy record must produce no findings'); + check(counts.declaration >= 1 && counts.literal >= 1, 'both resolution classes must be exercised by the fixture'); } finally { rmSync(tmp, { recursive: true, force: true }); } @@ -211,10 +267,10 @@ export function selfTest() { // (dispatch-gates / check-declared-population-live), so a wrong entry runs // perfectly green here and shows up only as a dev who was never told this // gate reads their surface. - assert(ROOT_DIR_WATCH_HINTS.every((h) => h.startsWith(ADR_DIR)), 'every watch hint must be under the declared ADR dir'); - assert(existsSync(ADR_DIR), `the declared population must reach the tree: ${ADR_DIR}`); - assert(CORPUS.docRoots.includes(ADR_DIR), 'the corpus must sweep the population this gate declares'); - assert( + check(ROOT_DIR_WATCH_HINTS.every((h) => h.startsWith(ADR_DIR)), 'every watch hint must be under the declared ADR dir'); + check(existsSync(ADR_DIR), `the declared population must reach the tree: ${ADR_DIR}`); + check(CORPUS.docRoots.includes(ADR_DIR), 'the corpus must sweep the population this gate declares'); + check( ROOT_DIR_WATCH_HINTS.every((h) => CORPUS.docRoots.includes(h.replace(/\/\*+$/, ''))), `the declared hints must name the roots the corpus sweeps: ${ROOT_DIR_WATCH_HINTS.join(', ')} vs ${CORPUS.docRoots.join(', ')}`, ); @@ -223,19 +279,68 @@ export function selfTest() { // clean tree from an extractor that silently matches nothing — the exact // failure mode that let 243 rotted anchors sit unnoticed. const live = sweepCorpus(CORPUS); - assert(live.counts.anchors > 300, `the live ADR corpus must yield its anchors, got ${live.counts.anchors}`); - assert(live.counts.symbol > 0, 'the live corpus must contain resolved SYMBOL anchors'); + check(live.counts.anchors > 300, `the live ADR corpus must yield its anchors, got ${live.counts.anchors}`); + check(live.counts.symbol > 0, 'the live corpus must contain resolved SYMBOL anchors'); // 4. The gate is wired to run. A gate nothing invokes is this repo's most // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); - assert(workflow.includes('node scripts/check-adr-symbol-anchors.mjs'), 'lint.yml must invoke this gate'); - assert(workflow.includes('node scripts/check-adr-symbol-anchors.mjs --self-test'), 'lint.yml must invoke this gate\'s --self-test'); + check(workflow.includes('node scripts/check-adr-symbol-anchors.mjs'), 'lint.yml must invoke this gate'); + check(workflow.includes('node scripts/check-adr-symbol-anchors.mjs --self-test'), 'lint.yml must invoke this gate\'s --self-test'); // 5. The census declaration is intact, INCLUDING the one-way error direction // the ruling ordered recorded (point 5). - assert(CENSUS_13556.rotRateIsLowerBound === true, 'the 72.1% figure is a LOWER bound and must be declared as one'); - assert(CENSUS_13556.totalSurface === CENSUS_13556.distinctLineAnchors + CENSUS_13556.continuationAnchors, 'the declared surface must be the sum of its parts'); + check(CENSUS_13556.rotRateIsLowerBound === true, 'the 72.1% figure is a LOWER bound and must be declared as one'); + check(CENSUS_13556.totalSurface === CENSUS_13556.distinctLineAnchors + CENSUS_13556.continuationAnchors, 'the declared surface must be the sum of its parts'); + + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — the module-level + // `assert`, which prints and exits 1 — so a breached floor cannot be printed + // over by the verdict below. + const floorMessages = []; + const floorFailure = (message) => { floorMessages.push(message); }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + assert(!floorBreached, floorMessages.join('\n ')); console.log(`✅ check-adr-symbol-anchors --self-test: every finding class provoked, healthy anchors silent, population live, wiring pinned (${live.counts.anchors} live anchors)`); diff --git a/scripts/check-i18n-walk-parity.mjs b/scripts/check-i18n-walk-parity.mjs index 64a7179f91..a7af2ccac4 100644 --- a/scripts/check-i18n-walk-parity.mjs +++ b/scripts/check-i18n-walk-parity.mjs @@ -521,15 +521,65 @@ const RECORDED_WALKED = [ */ const RECORDED_UNWALKED = ['messages', 'settings', 'settingsCommon']; +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// `failures.length === 0` used to be this self-test's ONLY success +// condition, so "every case held" and "the cases never ran" printed the +// same line. Closed the way PR #13487 validated on check-doc-authoring: +// what is pinned is the registered NAMES, not a number. The floor requires +// the OPENED set to equal the DECLARED set with each battery at or above +// its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'check-i18n-walk-parity self-test': 23, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + let selfTestReachedVerdict = false; function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('check-i18n-walk-parity self-test'); const failures = []; let cases = 0; // Counted, never transcribed: a hand-typed case count in the success line is a // number that goes stale the first time a case is added, and a self-test whose // own report is wrong is the last place to keep one. const eq = (what, got, want) => { + registerCase(); cases += 1; const a = JSON.stringify(got); const b = JSON.stringify(want); @@ -595,8 +645,55 @@ function selfTest() { ledgerRatchetProblems(KNOWN_NO_EXTRACTOR_FACE, LEDGER_CEILING).length, 0); eq('recorded sample: hints are live', DECLARED_WATCH_HINTS.length > 0, true); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — `failures`, read by + // the verdict below — so a breached floor cannot be printed over by the + // success line. + const floorFailure = (message) => { failures.push(message); }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + if (failures.length) { - console.error(`✗ check-i18n-walk-parity self-test: ${failures.length} case(s) failed:\n`); + console.error(`✗ check-i18n-walk-parity self-test: ${failures.length} failure(s) (cases and floor):\n`); for (const f of failures) console.error(` ${f}`); console.error(''); selfTestReachedVerdict = true; diff --git a/scripts/check-test-completeness.mjs b/scripts/check-test-completeness.mjs index 6a3c8abbf3..56ca823d72 100644 --- a/scripts/check-test-completeness.mjs +++ b/scripts/check-test-completeness.mjs @@ -573,6 +573,41 @@ function reportVerdict(verdict) { process.exit(verdict.exit); } +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// Reaching the success line without an `eq` having thrown used to be this +// self-test's ONLY success condition, so "every case held" and "the cases +// never ran" printed the same line. Closed the way PR #13487 validated on +// check-doc-authoring: what is pinned is the registered NAMES, not a +// number. The floor requires the OPENED set to equal the DECLARED set with +// each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'check-test-completeness self-test': 67, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + // Returned by `selfTest()` only after its verdict is printed. The dispatch // refuses anything else: a `return` that leaves the function above that line // prints nothing and still exits 0 — a self-test that never finished, reported @@ -580,7 +615,22 @@ function reportVerdict(verdict) { const SELF_TEST_VERDICT = 'check-test-completeness self-test reached its verdict'; function selfTest({ quiet = false } = {}) { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('check-test-completeness self-test'); const eq = (actual, expected, what) => { + registerCase(); const a = JSON.stringify(actual); const e = JSON.stringify(expected); if (a !== e) throw new Error(`${what}: got ${a}, want ${e}`); @@ -906,7 +956,63 @@ function selfTest({ quiet = false } = {}) { 'exit codes: the refusal code collides with a verdict code', ); - if (!quiet) console.log('check-test-completeness: self-test OK'); + // ⚠️ The floor is scoped to the LOUD run on purpose. `selfTest({ quiet: true })` + // also runs on EVERY production invocation of this gate (see `main()`), and + // there nothing claims a self-test verdict — the floor exists to stop a green + // SELF-TEST VERDICT being printed over cases that never ran, so it is + // evaluated exactly where that verdict is printed. A production run's output + // and exit code are therefore untouched by it. + if (!quiet) { + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a thrown Error — + // so a breached floor cannot be printed over by the success line. + const floorMessages = []; + const floorFailure = (message) => { floorMessages.push(message); }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + if (floorBreached) throw new Error(floorMessages.join('\n ')); + + console.log('check-test-completeness: self-test OK'); + } return SELF_TEST_VERDICT; } diff --git a/scripts/checklist-select.mjs b/scripts/checklist-select.mjs index a0286fad8a..a7d75bcb8a 100644 --- a/scripts/checklist-select.mjs +++ b/scripts/checklist-select.mjs @@ -123,9 +123,58 @@ function isBlocked(it) { // and still exits 0 — a self-test that never finished, reported as one that // passed (#13798). The self-test's own exit code stays load-bearing, so the // handshake is a flag rather than a returned sentinel. +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// Reaching the success line without an `eq` having exited used to be this +// self-test's ONLY success condition, so "every case held" and "the cases +// never ran" printed the same line. Closed the way PR #13487 validated on +// check-doc-authoring: what is pinned is the registered NAMES, not a +// number. The floor requires the OPENED set to equal the DECLARED set with +// each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'checklist-select self-test': 17, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + let selfTestReachedVerdict = false; function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('checklist-select self-test'); const FIX = [ { id: 'a.one', status: 'active', priority: 'P0', surface: 'browser', since: 'v16', source: ['packages/foo/bar.ts'] }, { id: 'a.two', status: 'active', priority: 'P1', surface: 'api', since: 'v16.1', source: ['#3358'], blocked: { by: 'fixture', ref: '#1' } }, @@ -134,7 +183,14 @@ function selfTest() { ]; const COV = { metadataKinds: { hook: { items: ['a.one'] } } }; const ids = (sel) => selectItems(sel, FIX, COV).map((i) => i.id).sort(); + // Counted, never transcribed (#15305): the success line below used to carry a + // hand-typed `17`, a number nothing derived and nothing compared — accurate on + // the day it was typed and silently wrong the first time a case is added or + // removed. It is now read off this counter. + let cases = 0; const eq = (got, want, name) => { + registerCase(); + cases += 1; const g = JSON.stringify(got), w = JSON.stringify(want); if (g !== w) { console.error(`✗ ${name}: got ${g}, want ${w}`); process.exit(1); } }; @@ -156,7 +212,54 @@ function selfTest() { eq(ids('packages/foo/bar.ts'), ['a.one'], 'bare source path (has /) → file: mode'); eq(ids('bar.ts'), ['a.one'], 'bare source basename (code ext) → file: mode'); eq(ids('missing.json'), [], 'unmatched .json name → empty, no throw'); - console.log('✓ checklist-select self-test: 17 cases pass.'); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a `✗` line on stderr + // and exit 1 — so a breached floor cannot be printed over by the success line. + const floorFailure = (message) => { console.error(`✗ ${message}`); }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + if (floorBreached) process.exit(1); + + console.log(`✓ checklist-select self-test: ${cases} cases pass.`); selfTestReachedVerdict = true; process.exit(0); } diff --git a/scripts/pm/release-rehearsal-clone.mjs b/scripts/pm/release-rehearsal-clone.mjs index abc7d00755..f762cdd05d 100755 --- a/scripts/pm/release-rehearsal-clone.mjs +++ b/scripts/pm/release-rehearsal-clone.mjs @@ -538,12 +538,61 @@ function cloneOf(root, source, name, { depth = 0 } = {}) { // and still exits 0 — a self-test that never finished, reported as one that // passed (#13798). The self-test's own exit code stays load-bearing, so the // handshake is a flag rather than a returned sentinel. +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// `failures === 0` used to be this self-test's ONLY success condition, so +// "every case held" and "the cases never ran" printed the same line. Closed +// the way PR #13487 validated on check-doc-authoring: what is pinned is the +// registered NAMES, not a number. The floor requires the OPENED set to +// equal the DECLARED set with each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'release-rehearsal-clone self-test': 32, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + let selfTestReachedVerdict = false; function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('release-rehearsal-clone self-test'); const root = mkdtempSync(join(tmpdir(), 'rehearsal-clone-selftest-')); let failures = 0; const t = (name, cond, extra = '') => { + registerCase(); if (cond) { process.stdout.write(` ✓ ${name}\n`); } else { @@ -667,7 +716,59 @@ function selfTest() { rmSync(root, { recursive: true, force: true }); } - process.stdout.write(failures === 0 ? '\n✓ self-test passed\n' : `\n✗ self-test: ${failures} failure(s)\n`); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a `✗` line on stdout + // and the `failures` tally the verdict reads — so a breached floor cannot be + // printed over by the success line. + const floorFailure = (message) => { + failures += 1; + process.stdout.write(` ✗ ${message}\n`); + }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + + process.stdout.write(failures === 0 + ? '\n✓ self-test passed\n' + : `\n✗ self-test: ${failures} failure(s) (cases and floor)\n`); selfTestReachedVerdict = true; return failures === 0 ? 0 : 1; diff --git a/scripts/render-release-coverage-anchor.mjs b/scripts/render-release-coverage-anchor.mjs index baf675af33..3e9cad98e4 100644 --- a/scripts/render-release-coverage-anchor.mjs +++ b/scripts/render-release-coverage-anchor.mjs @@ -170,9 +170,65 @@ function expect(what, ok) { // and still exits 0 — a self-test that never finished, reported as one that // passed (#13798). The self-test's own exit code stays load-bearing, so the // handshake is a flag rather than a returned sentinel. +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// `failures === 0` used to be this self-test's ONLY success condition, so +// "every case held" and "the cases never ran" printed the same line. Closed +// the way PR #13487 validated on check-doc-authoring: what is pinned is the +// registered NAMES, not a number. The floor requires the OPENED set to +// equal the DECLARED set with each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'render-release-coverage-anchor self-test': 10, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + let selfTestReachedVerdict = false; function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('render-release-coverage-anchor self-test'); + // A thin in-body wrapper over the module-level `expect`: it attributes the case + // to the open battery and then defers to the existing assertion, whose + // semantics (the `assertions` tally, the ok/FAIL line, the `failures` tally) + // are unchanged. + const check = (what, ok) => { + registerCase(); + expect(what, ok); + }; const base = { report: 'check-release-section-coverage: 2 finding(s)', errText: '', @@ -180,13 +236,13 @@ function selfTest() { sweptAt: '2026-08-24T00:00:00.000Z', }; - expect( + check( 'verdict — a non-zero ADVISORY code is did-not-run whatever --strict said, because a broken ' + 'instrument makes the predicate meaningless', verdict({ advisoryCode: 1, strictCode: 0 }) === 'did-not-run' && verdict({ advisoryCode: 1, strictCode: 1 }) === 'did-not-run', ); - expect( + check( 'verdict — instrument healthy + strict 0 is clean; instrument healthy + strict non-zero is ' + 'findings', verdict({ advisoryCode: 0, strictCode: 0 }) === 'clean' @@ -194,34 +250,34 @@ function selfTest() { ); const down = renderBody({ ...base, advisoryCode: 1, strictCode: 0, errText: 'BROKEN INSTRUMENT' }); - expect( + check( 'did-not-run — never renders as a clean corpus (#4690): it carries the DID NOT RUN heading and ' + 'none of the clean body\'s claim', down.includes('THE SWEEP DID NOT RUN') && !down.includes('Every published minor has its section'), ); - expect( + check( 'did-not-run — wraps the gate\'s classified stderr rather than re-wording it', down.includes('BROKEN INSTRUMENT'), ); const clean = renderBody({ ...base, advisoryCode: 0, strictCode: 0, report: 'OK — 5 published minor(s)' }); - expect( + check( 'clean — states the no-findings verdict and carries the gate\'s own OK line', clean.includes('Every published minor has its section') && clean.includes('OK — 5 published minor(s)'), ); const found = renderBody({ ...base, advisoryCode: 0, strictCode: 1 }); - expect( + check( 'findings — names the remedy as a curated write and points at the maintenance process', found.includes('docs/releases-maintenance.md') && found.includes('curated write'), ); - expect( + check( 'findings — wraps the gate\'s authored prose verbatim', found.includes('check-release-section-coverage: 2 finding(s)'), ); for (const [name, body] of [['did-not-run', down], ['clean', clean], ['findings', found]]) { - expect( + check( `${name} — carries the machine-findable marker and the heartbeat, so no branch can lose ` + 'either', body.startsWith(MARKER) && body.includes('Swept 2026-08-24T00:00:00.000Z') && body.includes(HEARTBEAT_NOTE), @@ -231,9 +287,59 @@ function selfTest() { // Counted, never a literal: a hard-coded total silently stops matching the // moment a case is added, and a self-test that misreports its own size is the // first thing a reader stops trusting. + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a `FAIL` line and the + // `failures` tally the verdict reads — so a breached floor cannot be printed + // over by the success line. + const floorFailure = (message) => { + failures += 1; + console.error(` FAIL ${message}`); + }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + console.log(failures === 0 ? `\nOK render-release-coverage-anchor --self-test: ${assertions} assertions pass` - : `\nFAILED ${failures} of ${assertions} assertion(s)`); + : `\nFAILED ${failures} failure(s) (cases and floor) of ${assertions} assertion(s)`); selfTestReachedVerdict = true; return failures === 0 ? 0 : 1; } diff --git a/scripts/run-with-stall-guard.mjs b/scripts/run-with-stall-guard.mjs index 8f7151049b..45239fbae1 100644 --- a/scripts/run-with-stall-guard.mjs +++ b/scripts/run-with-stall-guard.mjs @@ -212,6 +212,41 @@ const DEFER_NOTE_EVERY = 12; const argv = process.argv.slice(2); +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// `failures.length === 0` used to be this self-test's ONLY success +// condition, so "every case held" and "the cases never ran" printed the +// same line. Closed the way PR #13487 validated on check-doc-authoring: +// what is pinned is the registered NAMES, not a number. The floor requires +// the OPENED set to equal the DECLARED set with each battery at or above +// its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'run-with-stall-guard self-test': 41, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + // Set by `selfTest()` only after its verdict is printed, and read at the // dispatch: a `return` that leaves the function above that line prints nothing // and still exits 0 — a self-test that never finished, reported as one that @@ -772,6 +807,20 @@ function runGuard(args, env = {}, { timeoutMs = 90_000, marker = '' } = {}) { /** Exercise the guard against synthetic stalls. Exits 0 / 1; never returns. */ async function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('run-with-stall-guard self-test'); const dir = mkdtempSync(join(tmpdir(), 'stall-guard-selftest-')); const linux = existsSync('/proc'); const failures = []; @@ -783,6 +832,7 @@ async function selfTest() { const WINDOW = ['--stall-minutes', '0.05']; const check = (label, cond, detail) => { + registerCase(); if (cond) { results.push(` ✓ ${label}`); } else { @@ -1099,13 +1149,63 @@ async function selfTest() { try { rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ } } + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — a `✗` line in the + // report and the `failures` list the verdict reads — so a breached floor + // cannot be printed over by the success line. + const floorFailure = (message) => { + failures.push(message); + results.push(` ✗ ${message}`); + }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + console.log('run-with-stall-guard --self-test'); for (const line of results) console.log(line); if (!linux) { console.log(' (process-classification cases skipped: /proc not available)'); } if (failures.length) { - console.error(`\n✗ ${failures.length} self-test case(s) failed — the stall guard does not do what its callers assume.`); + console.error(`\n✗ ${failures.length} failure(s) (cases and floor) — the stall guard does not do what its callers assume.`); process.exit(1); } console.log(`\n✓ ${results.length} case(s) passed — the guard fires, classifies and tears down.`); diff --git a/scripts/symbol-anchors.mjs b/scripts/symbol-anchors.mjs index 8f7f15695e..e5d1377e75 100644 --- a/scripts/symbol-anchors.mjs +++ b/scripts/symbol-anchors.mjs @@ -576,6 +576,41 @@ export function formatFindings(findings) { function assert(cond, msg) { if (!cond) { console.error(`❌ symbol-anchors --self-test: ${msg}`); process.exit(1); } } +// ── The self-test's own battery roster and floor (#13489) ────────────────── +// +// A module-level `assert()` that exits on the first failure used to be this +// self-test's ONLY success condition, so "every case held" and "the cases +// never ran" printed the same line. Closed the way PR #13487 validated on +// check-doc-authoring: what is pinned is the registered NAMES, not a +// number. The floor requires the OPENED set to equal the DECLARED set with +// each battery at or above its own count. +// +// This file declares ONE battery, opened at the top of the self-test body. It +// carries fewer than the two named section banners the sectioning criterion +// needs, and ⛔ a comment is NOT promoted to a section head — that is a +// judgement per comment this transplant does not make. The hoisted single +// battery is the shape PR #14896, PR #15003 and PR #15217 landed for exactly +// this case. +// +// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3 +// keeps a total "right" the moment a sibling grows. +// +// The count is a FLOOR, not an equality — adding cases is ordinary work and must +// not red. A battery BELOW its floor means cases stopped running; the remedy is +// to find what stopped registering. +const SELF_TEST_BATTERIES = Object.freeze({ + 'symbol-anchors self-test': 51, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. +const SELF_TEST_BATTERY_FLOOR = 1; + +// The key an assertion is filed under when no battery is open. It is not a +// declared battery, so it reds by the same set difference rather than silently +// inflating whichever battery happened to run last. +const UNATTRIBUTED_BATTERY = '(no battery open)'; + // Returned by `selfTest()` only after its verdict is printed. The dispatch // refuses anything else: a `return` that leaves the function above that line // prints nothing and still exits 0 — a self-test that never finished, reported @@ -583,6 +618,27 @@ function assert(cond, msg) { if (!cond) { console.error(`❌ symbol-anchors --se const SELF_TEST_VERDICT = 'symbol-anchors self-test reached its verdict'; export function selfTest() { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every assertion below is attributed to the one + // most recently opened, so a section that stops running stops registering and + // names ITSELF at the floor rather than going quiet. + const batterySeen = new Map(); + let openBattery = null; + const battery = (name) => { + openBattery = name; + }; + const registerCase = () => { + const b = openBattery ?? UNATTRIBUTED_BATTERY; + batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1); + }; + battery('symbol-anchors self-test'); + // A thin in-body wrapper over the module-level `assert`: it attributes the + // case to the open battery and then defers to the existing assertion, whose + // semantics (print and exit 1 on the first failure) are unchanged. + const check = (cond, message) => { + registerCase(); + assert(cond, message); + }; // 1. Declaration sites, one per supported spelling. Each is provoked in BOTH // directions -- a rule that only ever says "found" is not a rule. const ts = [ @@ -604,32 +660,32 @@ export function selfTest() { ].join('\n'); for (const name of ['registerApp', 'SqlDriver', 'Shape', 'Verdict', 'Posture', 'RESERVED_NAMESPACES', 'helperName', 'destructured', 'stateMachines', 'quoted_key', 'methodShorthand', 'accessorName']) { - assert(symbolResolutionClass(ts, 'x.ts', name) === 'declaration', `"${name}" should resolve as a declaration`); + check(symbolResolutionClass(ts, 'x.ts', name) === 'declaration', `"${name}" should resolve as a declaration`); } - assert(symbolResolutionClass(ts, 'x.ts', 'sys_metadata') === 'literal', 'a quoted data identifier resolves as `literal`, not `declaration`'); - assert(symbolResolutionClass(ts, 'x.ts', 'notPresentAnywhere') === null, 'an absent symbol must NOT resolve'); + check(symbolResolutionClass(ts, 'x.ts', 'sys_metadata') === 'literal', 'a quoted data identifier resolves as `literal`, not `declaration`'); + check(symbolResolutionClass(ts, 'x.ts', 'notPresentAnywhere') === null, 'an absent symbol must NOT resolve'); // 2. ⭐ The census caveat, enforced: prose is not resolution. A symbol named // only in a comment is exactly the false green that made 72.1% a LOWER // bound, so it is provoked directly. const commented = '// registerApp is described here but not defined\nconst other = 1;\n'; - assert(symbolResolutionClass(commented, 'x.ts', 'registerApp') === null, 'a symbol named only in a comment must NOT resolve'); + check(symbolResolutionClass(commented, 'x.ts', 'registerApp') === null, 'a symbol named only in a comment must NOT resolve'); const blockCommented = '/* interface Shape { } */\nconst other = 1;\n'; - assert(symbolResolutionClass(blockCommented, 'x.ts', 'Shape') === null, 'a declaration inside a block comment must NOT resolve'); + check(symbolResolutionClass(blockCommented, 'x.ts', 'Shape') === null, 'a declaration inside a block comment must NOT resolve'); // ...and the stripper must not be fooled by a `//` living inside a string. const slashInString = 'const url = "https://example.com";\nexport function afterTheString() {}\n'; - assert(symbolResolutionClass(slashInString, 'x.ts', 'afterTheString') === 'declaration', '`//` inside a string must not eat the rest of the file'); + check(symbolResolutionClass(slashInString, 'x.ts', 'afterTheString') === 'declaration', '`//` inside a string must not eat the rest of the file'); // 3. Substring is not resolution -- the other half of the same caveat. - assert(symbolResolutionClass('const registerApplication = 1;', 'x.ts', 'registerApp') === null, 'a symbol must not resolve as a substring of a longer identifier'); - assert(symbolResolutionClass('const names = ["sys_metadata_extra"];', 'x.ts', 'sys_metadata') === null, 'the `literal` class is a WHOLE-token match'); + check(symbolResolutionClass('const registerApplication = 1;', 'x.ts', 'registerApp') === null, 'a symbol must not resolve as a substring of a longer identifier'); + check(symbolResolutionClass('const names = ["sys_metadata_extra"];', 'x.ts', 'sys_metadata') === null, 'the `literal` class is a WHOLE-token match'); // 4. Markdown headings and keyed formats. - assert(symbolResolutionClass('## Overlay whitelist\n', 'a.md', 'Overlay whitelist') === 'declaration', 'a markdown heading resolves by text'); - assert(symbolResolutionClass('## Overlay whitelist\n', 'a.md', 'overlay-whitelist') === 'declaration', 'a markdown heading resolves by slug'); - assert(symbolResolutionClass('## Something else\n', 'a.md', 'overlay-whitelist') === null, 'an absent heading must NOT resolve'); - assert(symbolResolutionClass('{ "compilerOptions": { } }', 'a.json', 'compilerOptions') === 'declaration', 'a JSON key resolves'); - assert(symbolResolutionClass('{ "other": 1 }', 'a.json', 'compilerOptions') === null, 'an absent JSON key must NOT resolve'); + check(symbolResolutionClass('## Overlay whitelist\n', 'a.md', 'Overlay whitelist') === 'declaration', 'a markdown heading resolves by text'); + check(symbolResolutionClass('## Overlay whitelist\n', 'a.md', 'overlay-whitelist') === 'declaration', 'a markdown heading resolves by slug'); + check(symbolResolutionClass('## Something else\n', 'a.md', 'overlay-whitelist') === null, 'an absent heading must NOT resolve'); + check(symbolResolutionClass('{ "compilerOptions": { } }', 'a.json', 'compilerOptions') === 'declaration', 'a JSON key resolves'); + check(symbolResolutionClass('{ "other": 1 }', 'a.json', 'compilerOptions') === null, 'an absent JSON key must NOT resolve'); // 5. Extraction: every grammar form, and every ⛔ line-number spelling the // census found in the corpus (plain, hyphen range, EN DASH range, and a @@ -654,52 +710,101 @@ export function selfTest() { ].join('\n'); const { anchors, lineAnchors } = extractAnchors(doc); const sym = anchors.filter((a) => a.symbol); - assert(sym.length === 3, `expected 3 symbol anchors, got ${sym.length}`); - assert(anchors.some((a) => a.continuation && a.symbol === 'installPackage' && a.path.endsWith('engine.ts')), 'a continuation must inherit the preceding path'); - assert(anchors.some((a) => a.repo === 'objectui' && a.symbol === 'BaseSchema'), 'a cross-repo anchor keeps its repo'); - assert(anchors.some((a) => !a.symbol && a.path.endsWith('object.zod.ts')), 'a file-level anchor is an anchor'); + check(sym.length === 3, `expected 3 symbol anchors, got ${sym.length}`); + check(anchors.some((a) => a.continuation && a.symbol === 'installPackage' && a.path.endsWith('engine.ts')), 'a continuation must inherit the preceding path'); + check(anchors.some((a) => a.repo === 'objectui' && a.symbol === 'BaseSchema'), 'a cross-repo anchor keeps its repo'); + check(anchors.some((a) => !a.symbol && a.path.endsWith('object.zod.ts')), 'a file-level anchor is an anchor'); const live = lineAnchors.filter((l) => !l.exempt); /* ⭐ EVERY spelling in the corpus, by name. The comma and `+` forms are here * because the #13556 census's own extractor missed them, and a gate that * inherits that blind spot reports a rotted corpus as clean. */ const rawOf = (needle) => live.filter((l) => l.raw.includes(needle)); - assert(rawOf(':4901').length === 1, 'a plain line anchor must be found'); - assert(rawOf(':28-76').length === 1, 'a hyphen range must be found'); - assert(rawOf('459–463').length === 1, 'an EN DASH range must be found'); - assert(rawOf(':2920').length === 1, 'a continuation parent must be found'); - assert(rawOf(':2933').length === 1, 'a bare continuation must be found'); - assert(rawOf(':13,346-389').length === 1, 'a COMMA list must be found'); - assert(rawOf(':29-39,147-152').length === 1, 'a comma list of RANGES must be found'); - assert(rawOf(':2214+').length === 1, 'an open-ended `+` anchor must be found'); - assert(rawOf(':595+').length === 1, 'an open-ended `+` continuation must be found'); - assert(rawOf('audit-plugin.ts:40').length === 1, 'a BARE, un-spanned line anchor in prose must be found'); - assert(rawOf(':2956/2991').length === 1, 'a SLASH list must be found'); - assert(rawOf('builder-block.ts:42').length === 1, 'an anchor SHARING a code span with other text must be found'); - assert(rawOf('~`326`').length === 1, 'the TILDE bare-number form must be found'); - assert(extractAnchors('a status `403` and a size `4096` on a `packages/x/y.ts#sym` line').lineAnchors.length === 0, + check(rawOf(':4901').length === 1, 'a plain line anchor must be found'); + check(rawOf(':28-76').length === 1, 'a hyphen range must be found'); + check(rawOf('459–463').length === 1, 'an EN DASH range must be found'); + check(rawOf(':2920').length === 1, 'a continuation parent must be found'); + check(rawOf(':2933').length === 1, 'a bare continuation must be found'); + check(rawOf(':13,346-389').length === 1, 'a COMMA list must be found'); + check(rawOf(':29-39,147-152').length === 1, 'a comma list of RANGES must be found'); + check(rawOf(':2214+').length === 1, 'an open-ended `+` anchor must be found'); + check(rawOf(':595+').length === 1, 'an open-ended `+` continuation must be found'); + check(rawOf('audit-plugin.ts:40').length === 1, 'a BARE, un-spanned line anchor in prose must be found'); + check(rawOf(':2956/2991').length === 1, 'a SLASH list must be found'); + check(rawOf('builder-block.ts:42').length === 1, 'an anchor SHARING a code span with other text must be found'); + check(rawOf('~`326`').length === 1, 'the TILDE bare-number form must be found'); + check(extractAnchors('a status `403` and a size `4096` on a `packages/x/y.ts#sym` line').lineAnchors.length === 0, 'an UNTILDED bare number must NOT be read as a line anchor — it is an HTTP status or a byte count far more often than a line'); - assert(live.length === 14, `expected 14 live line anchors across every spelling, got ${live.length}: ${live.map((l) => l.raw).join(' ')}`); - assert(lineAnchors.some((l) => l.exempt === 'HISTORICAL'), 'the exemption marker must be read'); - assert(!lineAnchors.some((l) => l.raw.includes('file.ts:12')), 'ordinary fenced content must stay invisible to the extractor'); - assert(rawOf('rule-validator.ts:378').length === 1, 'a fenced COMMENT HEADER naming a path IS an anchor and must be found'); + check(live.length === 14, `expected 14 live line anchors across every spelling, got ${live.length}: ${live.map((l) => l.raw).join(' ')}`); + check(lineAnchors.some((l) => l.exempt === 'HISTORICAL'), 'the exemption marker must be read'); + check(!lineAnchors.some((l) => l.raw.includes('file.ts:12')), 'ordinary fenced content must stay invisible to the extractor'); + check(rawOf('rule-validator.ts:378').length === 1, 'a fenced COMMENT HEADER naming a path IS an anchor and must be found'); // 6. An exemption governs the anchor it FOLLOWS and does not spill leftwards // onto an earlier, unexcused one. const spill = 'First `a/b.ts:10` then `c/d.ts:20` '; const spilled = extractAnchors(spill).lineAnchors; - assert(spilled.find((l) => l.raw.includes('a/b.ts'))?.exempt === null, 'an exemption must not cover an earlier anchor'); - assert(spilled.find((l) => l.raw.includes('c/d.ts'))?.exempt === 'HISTORICAL', 'an exemption must cover the anchor it follows'); + check(spilled.find((l) => l.raw.includes('a/b.ts'))?.exempt === null, 'an exemption must not cover an earlier anchor'); + check(spilled.find((l) => l.raw.includes('c/d.ts'))?.exempt === 'HISTORICAL', 'an exemption must cover the anchor it follows'); // 7. An invalid exemption class is a finding, not a silent pass -- otherwise // a typo is a way to switch the gate off. const bogus = extractAnchors('`a/b.ts:10` ').lineAnchors; - assert(bogus[0].exempt === null, 'an unrecognised exemption class must not be honoured'); - assert(bogus[0].exemptRaw !== null, 'an unrecognised exemption must still be CARRIED, so it reports as a bad exemption rather than as a plain line anchor'); + check(bogus[0].exempt === null, 'an unrecognised exemption class must not be honoured'); + check(bogus[0].exemptRaw !== null, 'an unrecognised exemption must still be CARRIED, so it reports as a bad exemption rather than as a plain line anchor'); // 8. defineCorpus refuses a corpus that would sweep nothing. let threw = false; try { defineCorpus({ id: 'x', label: 'x', docRoots: [] }); } catch { threw = true; } - assert(threw, 'defineCorpus must refuse an empty docRoots'); + check(threw, 'defineCorpus must refuse an empty docRoots'); + + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── + // + // Evaluated after every battery has had its chance and BEFORE the verdict, so + // the success line below can only be printed by a run in which the set of + // batteries that registered assertions EQUALS the set declared. A set + // difference names WHICH battery stopped; a count says only that something did. + // The floor's refusal joins the SAME sink the cases use — the module-level + // `assert`, which prints and exits 1 — so a breached floor cannot be printed + // over by the verdict below. + const floorMessages = []; + const floorFailure = (message) => { floorMessages.push(message); }; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + let floorBreached = false; + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorBreached = true; + floorFailure( + `SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` + + `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`, + ); + } + for (const [name, count] of batterySeen) { + if (declaredBatteries.includes(name)) continue; + floorBreached = true; + floorFailure( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.', + ); + } + for (const name of declaredBatteries) { + const count = batterySeen.get(name) ?? 0; + if (count >= SELF_TEST_BATTERIES[name]) continue; + floorBreached = true; + floorFailure( + count === 0 + ? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those cases hold.' + : `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` + + `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`, + ); + } + if (floorBreached) { + floorFailure( + 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' + + 'number. Find what stopped registering (an early return, a deleted block, a guard that now ' + + 'skips) and restore it.', + ); + } + assert(!floorBreached, floorMessages.join('\n ')); console.log('✅ symbol-anchors --self-test: grammar, both resolution classes, comment/substring rejection, every ⛔ line-number spelling, exemption scoping and corpus registration verified');