From b519d96bdb18a8439912f54f5df9e26ece35a055 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 03:52:28 +0000 Subject: [PATCH 1/6] test(scripts): give check-clause2-carriers a battery roster and floor Part of #13799 (batch 3). The concise-arrow sink `t` gets a block body so it can call registerCase(); 12 batteries / 150 cases pinned. Case count before == after (150), measured against the branch point. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/pm/check-clause2-carriers.mjs | 111 +++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/scripts/pm/check-clause2-carriers.mjs b/scripts/pm/check-clause2-carriers.mjs index e6d9df44ff..2eef6ff038 100644 --- a/scripts/pm/check-clause2-carriers.mjs +++ b/scripts/pm/check-clause2-carriers.mjs @@ -207,6 +207,43 @@ import { // dispatch-gates: no-path-population -- this gate reads no file in the tree at all; its whole input is the GitHub API (PRs, their labels, and the claim comments on their cards), so no card's file surface can predict it and the honest derivation is a repo-wide undetermined one (#13519) +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// `failed.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. Every section opens with `battery('')`, +// every assertion is attributed to the battery most recently opened, and the +// floor requires the OPENED set to equal the DECLARED set with each battery at +// or above its own count. +// +// The counts are 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({ + 'the declaration reader: the fixed spelling, and everything that is not': 15, + 'the card-level declaration: four states, none collapsed into another': 9, + 'C1, replaying the 2026-08-31 measured table': 12, + 'C2, the row this file exists for': 14, + 'C3, the direction a carrier comparison cannot see': 7, + '#14155: the COMPLETED state, and the three it must stay distinct from': 18, + 'the event reader itself': 9, + 'the cost bound, stated as one predicate both sides read': 6, + 'C4: the independence clause\'s carrier (maintainer 2026-09-01 「同意 A」)': 52, + 'the #13910 specimen, end to end': 2, + 'pairing, derived from the same relation H8/H31 read': 3, + 'the exit register is distinct in every direction it must be': 3, +}); + +// 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 = 12; + +// 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)'; + const API = 'https://api.github.com'; const TOKEN = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? ''; const SELF_PATH = fileURLToPath(import.meta.url); @@ -1297,11 +1334,28 @@ const CLAIM = (extra) => ({ let selfTestReachedVerdict = false; 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); + }; const cases = []; - const t = (name, ok, detail) => cases.push({ name, ok: Boolean(ok), detail }); + const t = (name, ok, detail) => { + registerCase(); + cases.push({ name, ok: Boolean(ok), detail }); + }; const says = (s, frag) => typeof s === 'string' && s.includes(frag); // -- the declaration reader: the fixed spelling, and everything that is not -- + battery('the declaration reader: the fixed spelling, and everything that is not'); t('the two fixed spellings read as declarations', readClause2Line('Clause-②: yes')?.value === 'yes' && readClause2Line('Clause-②: no')?.value === 'no'); t('a blockquoted claim line still reads (SKILL.md writes the claim as a blockquote)', readClause2Line('> Clause-②: no')?.value === 'no'); t('a bulleted, bolded, backticked line still reads — decoration is markdown, not meaning', readClause2Line('- **`Clause-②`**: **`yes`**')?.value === 'yes'); @@ -1323,6 +1377,7 @@ export function selfTest() { t('⛔ the reader never invents a value from an adjacent word', readClause2Line('this card is clause 2 yes in substance')?.kind !== 'declared'); // -- the card-level declaration: four states, none collapsed into another --- + battery('the card-level declaration: four states, none collapsed into another'); t('a claim comment carrying the line reads DECLARED', cardDeclaration([CLAIM('Clause-②: no')]).state === 'declared'); t('…and keeps the value', cardDeclaration([CLAIM('Clause-②: yes')]).value === 'yes'); t('a thread with no claim comment at all reads ABSENT', cardDeclaration([{ body: 'just a comment', created_at: '2026-08-31T10:00:00Z' }]).state === 'absent'); @@ -1337,6 +1392,7 @@ export function selfTest() { ]).value === 'no'); // -- C1, replaying the 2026-08-31 measured table --------------------------- + battery('C1, replaying the 2026-08-31 measured table'); const L = CONTRACT_REVIEW_LABEL; const pair = (o) => ({ pr: 13910, card: 13476, draft: true, prLabels: [], cardLabels: [], cardComments: [CLAIM('Clause-②: no')], ...o }); // The seven pairs the filing cards tabulated, at the moment a human looked. @@ -1364,6 +1420,7 @@ export function selfTest() { t('C1 never prescribes a write from this script', says(split[5], '自查放行')); // -- C2, the row this file exists for -------------------------------------- + battery('C2, the row this file exists for'); const absent = c2DeclarationUnreadable(pair({ cardComments: [CLAIM('Domain: x')] })); t('a card with no Clause-② line in its claim comment produces a C2 row', typeof absent === 'string'); t('…and says NO READING in as many words', says(absent, 'NO READING')); @@ -1383,6 +1440,7 @@ export function selfTest() { t('…and the unjudged accounting names the thread that could not be read', says(pairUnjudged(pair({ cardComments: null })), 'comment thread')); // -- C3, the direction a carrier comparison cannot see ---------------------- + battery('C3, the direction a carrier comparison cannot see'); // A declared `yes`, bare on both carriers — C3's candidate shape. The event // stream is what says WHICH of the four states this is. const declaredYes = (o) => pair({ prLabels: [], cardLabels: [], cardComments: [CLAIM('Clause-②: yes')], ...o }); @@ -1410,6 +1468,7 @@ export function selfTest() { t('an unreadable carrier never manufactures a C3 row', c3DeclaredYesUngated(pair({ cardLabels: null, cardComments: [CLAIM('Clause-②: yes')] })) === null); // -- #14155: the COMPLETED state, and the three it must stay distinct from -- + battery('#14155: the COMPLETED state, and the three it must stay distinct from'); const completed = declaredYes({ pr: 13864, card: 13657, @@ -1452,6 +1511,7 @@ export function selfTest() { t('labels bare but the stream ending on a HANG is a disagreement, reported not resolved', says(c3DeclaredYesUngated(declaredYes({ cardEvents: [CARD_HUNG], prEvents: [PR_HUNG, PR_CLEARED] })), 'the labels say bare, the events say hung')); // -- the event reader itself ------------------------------------------------ + battery('the event reader itself'); t('an empty stream reads NEVER HUNG — readable, and nothing was hung', carrierGateHistory([]).state === 'never-hung'); t('a null stream reads UNREADABLE — the distinction the whole row turns on', carrierGateHistory(null).state === 'unreadable'); t('the LAST event decides, and the reader sorts rather than trusting arrival order', carrierGateHistory([CARD_CLEARED, CARD_HUNG]).state === 'cleared' && carrierGateHistory([CARD_HUNG, CARD_CLEARED]).state === 'cleared'); @@ -1463,6 +1523,7 @@ export function selfTest() { t('the gate label is the sibling\'s constant, not a second spelling', carrierGateHistory([EV('labeled', '2026-08-31T16:55:54Z')]).state === 'hung'); // -- the cost bound, stated as one predicate both sides read --------------- + battery('the cost bound, stated as one predicate both sides read'); t('a declared `no` owes NO event stream — the cost bound', needsGateHistory(pair({ cardComments: [CLAIM('Clause-②: no')] })) === false); t('a pair still carrying the gate owes none either', needsGateHistory(pair({ prLabels: [L], cardLabels: [L], cardComments: [CLAIM('Clause-②: yes')] })) === false); t('an ABSENT declaration owes none — C2 already owns that pair', needsGateHistory(pair({ cardComments: [CLAIM('Domain: x')] })) === false); @@ -1471,6 +1532,7 @@ export function selfTest() { t('the page cap exists, because the stream arrives OLDEST FIRST and a short read loses the removal', Number.isInteger(EVENT_PAGE_CAP) && EVENT_PAGE_CAP > 0); // -- C4: the independence clause's carrier (maintainer 2026-09-01 「同意 A」) -- + battery('C4: the independence clause\'s carrier (maintainer 2026-09-01 「同意 A」)'); // The fixtures are the measured verdict shape, not an invented one: the live // verdicts open a fenced block whose first line is `VERDICT: PASS`, with // `REVIEWED-HEAD:` beside it, and the session IDs are the two real ones from @@ -1566,23 +1628,70 @@ export function selfTest() { t('…and a self-reviewed pair is adverse even when every carrier reading is clean', pairRows(reviewed([VERDICT(SELF_PAIR)])).map((r) => r.code).join(',') === 'C4'); // -- the #13910 specimen, end to end --------------------------------------- + battery('the #13910 specimen, end to end'); const specimen = pair({ pr: 13910, card: 13476, prLabels: [], cardLabels: [L], cardComments: [CLAIM('Domain: `domain:engine`')] }); const specimenRows = pairRows(specimen).map((r) => r.code); t('the measured #13910 specimen produces BOTH the split row and the no-reading row', specimenRows.includes('C1') && specimenRows.includes('C2'), JSON.stringify(specimenRows)); t('…and no C3 row, because that card declared nothing at all', !specimenRows.includes('C3')); // -- pairing, derived from the same relation H8/H31 read -------------------- + battery('pairing, derived from the same relation H8/H31 read'); const prRow = (n, card, ref) => ({ number: n, draft: true, labels: [], body: `Fixes #${card}\n`, head: { ref } }); t('a PR body naming its card pairs with it', derivePairs([prRow(13910, 13476, 'claude/issue-13476-x')], [13476]).length === 1); t('a PR body naming another card does NOT pair on a stale branch name', derivePairs([prRow(13910, 9999, 'claude/issue-13476-x')], [13476]).length === 0); t('a branch name is the fallback when the body says nothing', derivePairs([{ number: 1, labels: [], body: 'no refs here', head: { ref: 'claude/issue-13476-x' } }], [13476]).length === 1); // -- the exit register is distinct in every direction it must be ----------- + battery('the exit register is distinct in every direction it must be'); const codes = [EXIT_OK, EXIT_USAGE, EXIT_INCOMPLETE, EXIT_PREREQUISITE_NOT_MET, EXIT_PAIR_ADVERSE]; t('every exit code is distinct — a verdict can never be read as an environment complaint', new Set(codes).size === codes.length, JSON.stringify(codes)); t('the adverse-pair code is NOT the prerequisite code', EXIT_PAIR_ADVERSE !== EXIT_PREREQUISITE_NOT_MET); t('the prerequisite code is the sibling\'s, imported rather than re-picked', EXIT_PREREQUISITE_NOT_MET === 3); + // -- 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. + const floorFailure = (message) => { cases.push({ name: message, ok: false }); }; + 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.', + ); + } + const failed = cases.filter((c) => !c.ok); for (const c of failed) console.error(` ✗ ${c.name}${c.detail ? ` — ${c.detail}` : ''}`); if (failed.length) { From d0fd81298f3968700a3167fbabbf23a1443aace2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 03:56:12 +0000 Subject: [PATCH 2/6] test(scripts): give check-adr-links a battery roster and floor Part of #13799 (batch 3). The module-level `assert` keeps its semantics (print and exit 1 on the first failure); a thin in-body `check()` wrapper attributes each case to the open battery and defers to it. 4 batteries / 23 cases pinned, and the floor reports through that same `assert`. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-adr-links.mjs | 137 +++++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 19 deletions(-) diff --git a/scripts/check-adr-links.mjs b/scripts/check-adr-links.mjs index 88c0d140e0..09e41584ce 100644 --- a/scripts/check-adr-links.mjs +++ b/scripts/check-adr-links.mjs @@ -356,6 +356,35 @@ function runCheck() { ); } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// A pass 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. Every section opens with `battery('')`, every +// assertion is attributed to the battery most recently opened, and the floor +// requires the OPENED set to equal the DECLARED set with each battery at or +// above its own count. +// +// The counts are 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({ + 'Discrimination: verbatim link shapes are invisible; prose ones are not.': 9, + 'Reverse verification on a real directory: the dead one IS reported, the': 5, + 'The ADR-0046 pin. The three illustrative strings must still BE in the': 8, + 'Stale-baseline detection is what makes KNOWN_DEAD_TARGETS shrink-only.': 1, +}); + +// 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 = 4; + +// 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)'; + /* ------------------------------------------------------------------ self-test */ function assert(cond, message) { @@ -372,7 +401,28 @@ function assert(cond, message) { const SELF_TEST_VERDICT = 'check-adr-links 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); + }; + // 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. Discrimination: verbatim link shapes are invisible; prose ones are not. + battery('Discrimination: verbatim link shapes are invisible; prose ones are not.'); const doc = [ '# Fixture', '', @@ -393,17 +443,18 @@ function selfTest() { 'External [site](https://example.com/x.md) and an [anchor](#section).', ].join('\n'); const targets = extractRelativeLinks(doc).map((l) => l.target); - assert(targets.includes('./0001-real.md'), 'prose link was not extracted'); - assert(targets.includes('./0002-missing.md'), 'second prose link was not extracted'); - assert(!targets.includes('./crm_lead_guide.md'), 'code-span link leaked into the extraction'); - assert(!targets.includes('./crm_lead_guide.md#qualification'), 'fenced-block link leaked into the extraction'); - assert(!targets.includes('…'), 'code-span image link leaked into the extraction'); - assert(!targets.includes('./nope.md'), 'tilde-fenced link leaked into the extraction'); - assert(!targets.some((t) => t.startsWith('http')), 'an http(s) destination was treated as repo-relative'); - assert(targets.length === 2, `expected exactly 2 prose destinations, got ${targets.length}: ${targets.join(', ')}`); - assert(extractRelativeLinks(doc)[0].line === 3, 'line numbers did not survive verbatim stripping'); + check(targets.includes('./0001-real.md'), 'prose link was not extracted'); + check(targets.includes('./0002-missing.md'), 'second prose link was not extracted'); + check(!targets.includes('./crm_lead_guide.md'), 'code-span link leaked into the extraction'); + check(!targets.includes('./crm_lead_guide.md#qualification'), 'fenced-block link leaked into the extraction'); + check(!targets.includes('…'), 'code-span image link leaked into the extraction'); + check(!targets.includes('./nope.md'), 'tilde-fenced link leaked into the extraction'); + check(!targets.some((t) => t.startsWith('http')), 'an http(s) destination was treated as repo-relative'); + check(targets.length === 2, `expected exactly 2 prose destinations, got ${targets.length}: ${targets.join(', ')}`); + check(extractRelativeLinks(doc)[0].line === 3, 'line numbers did not survive verbatim stripping'); // 2. Reverse verification on a real directory: the dead one IS reported, the + battery('Reverse verification on a real directory: the dead one IS reported, the'); // illustrative ones are not, and removing the dead one turns the sweep green. const tmp = mkdtempSync(join(tmpdir(), 'adr-links-')); try { @@ -412,25 +463,26 @@ function selfTest() { writeFileSync(join(dir, '0001-real.md'), '# Real\n'); writeFileSync(join(dir, '0003-linker.md'), doc); const red = sweep('records', tmp); - assert(red.checked === 2, `self-test sweep census expected 2, got ${red.checked}`); - assert(red.findings.length === 1, `expected exactly 1 finding, got ${red.findings.length}`); - assert(red.findings[0].target === './0002-missing.md', `wrong finding: ${red.findings[0].target}`); + check(red.checked === 2, `self-test sweep census expected 2, got ${red.checked}`); + check(red.findings.length === 1, `expected exactly 1 finding, got ${red.findings.length}`); + check(red.findings[0].target === './0002-missing.md', `wrong finding: ${red.findings[0].target}`); writeFileSync(join(dir, '0002-missing.md'), '# Now it exists\n'); const green = sweep('records', tmp); - assert(green.findings.length === 0, `expected 0 findings after the target appeared, got ${green.findings.length}`); - assert(green.checked === 2, `census must not change when a target appears: ${green.checked}`); + check(green.findings.length === 0, `expected 0 findings after the target appeared, got ${green.findings.length}`); + check(green.checked === 2, `census must not change when a target appears: ${green.checked}`); } finally { rmSync(tmp, { recursive: true, force: true }); } // 3. The ADR-0046 pin. The three illustrative strings must still BE in the + battery('The ADR-0046 pin. The three illustrative strings must still BE in the'); // record (otherwise this pin is measuring nothing), and none of them may // reach the extractor. const conventionPath = join(process.cwd(), CONVENTION_RECORD); - assert(existsSync(conventionPath), `${CONVENTION_RECORD} is missing — re-point this pin at the record that now documents the link convention (#6592)`); + check(existsSync(conventionPath), `${CONVENTION_RECORD} is missing — re-point this pin at the record that now documents the link convention (#6592)`); const convention = readFileSync(conventionPath, 'utf8'); for (const target of CONVENTION_ILLUSTRATIVE_TARGETS) { - assert( + check( convention.includes(target), `${CONVENTION_RECORD} no longer contains the illustrative destination ${JSON.stringify(target)}. ` + `This pin has gone stale — re-point it at whatever now documents the package-docs link convention (#6592); ` + @@ -439,24 +491,71 @@ function selfTest() { } const conventionTargets = extractRelativeLinks(convention).map((l) => l.target); for (const target of CONVENTION_ILLUSTRATIVE_TARGETS) { - assert( + check( !conventionTargets.includes(target), `${CONVENTION_RECORD}'s illustrative destination ${JSON.stringify(target)} was extracted as a real link. ` + `That record DEFINES the package-docs link convention and names a doc that deliberately does not exist here; ` + `the gate must skip fenced blocks and code spans, not the file (#6592).`, ); } - assert(conventionTargets.length > 0, `${CONVENTION_RECORD} yielded no real links at all — the extractor is over-stripping`); + check(conventionTargets.length > 0, `${CONVENTION_RECORD} yielded no real links at all — the extractor is over-stripping`); // 4. Stale-baseline detection is what makes KNOWN_DEAD_TARGETS shrink-only. + battery('Stale-baseline detection is what makes KNOWN_DEAD_TARGETS shrink-only.'); const live = new Set(sweep().findings.map(keyOf)); const staleEntries = KNOWN_DEAD_TARGETS.filter((e) => !live.has(keyOf(e))); - assert( + check( staleEntries.length === 0, `KNOWN_DEAD_TARGETS has ${staleEntries.length} entr(y/ies) that no longer match a broken link — delete them:\n` + staleEntries.map((e) => ` ${e.file} -> ${e.target}`).join('\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. + 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-links --self-test: discrimination, census, ADR-0046 pin and baseline staleness all verified'); return SELF_TEST_VERDICT; From 9683881a92cc7627775848dd0e56724029b26039 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 03:57:05 +0000 Subject: [PATCH 3/6] test(scripts): give check-doc-anchors a battery roster and floor Part of #13799 (batch 3). Same thin in-body `check()` wrapper over the module-level `assert`; 8 batteries / 22 cases pinned. Gate mode still green on the tree (298 fragment links across 409 sources). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-doc-anchors.mjs | 149 +++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/scripts/check-doc-anchors.mjs b/scripts/check-doc-anchors.mjs index 9f9eb73c21..f3fa9c6b7f 100644 --- a/scripts/check-doc-anchors.mjs +++ b/scripts/check-doc-anchors.mjs @@ -524,6 +524,39 @@ function idOf(headingLine) { return headingIds(headingLine)[0]; } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// A pass 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. Every section opens with `battery('')`, every +// assertion is attributed to the battery most recently opened, and the floor +// requires the OPENED set to equal the DECLARED set with each battery at or +// above its own count. +// +// The counts are 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({ + 'Slug parity with the renderer, on the two shapes that actually broke.': 2, + 'The inline shapes the flattener has to get right.': 8, + 'Duplicate headings get the renderer\'s counter suffixes.': 1, + 'Headings that are not headings.': 2, + 'Extraction discriminates prose from examples.': 1, + 'End to end, on a synthetic corpus: a good anchor is silent, a dead one': 3, + 'The census guard is load-bearing: it is the only thing that tells a': 1, + 'The dispatch-gates declaration (#9979). Enforcement cannot hold any of': 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 = 8; + +// 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 @@ -531,44 +564,70 @@ function idOf(headingLine) { const SELF_TEST_VERDICT = 'check-doc-anchors 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); + }; + // 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. Slug parity with the renderer, on the two shapes that actually broke. + battery('Slug parity with the renderer, on the two shapes that actually broke.'); for (const [heading, expected] of PARITY_PINS) { - assert(idOf(heading) === expected, `parity pin failed: ${heading}\n expected ${expected}\n got ${idOf(heading)}`); + check(idOf(heading) === expected, `parity pin failed: ${heading}\n expected ${expected}\n got ${idOf(heading)}`); } // 2. The inline shapes the flattener has to get right. - assert(idOf('## `os login --json` is NDJSON — the one exception') === 'os-login---json-is-ndjson--the-one-exception', 'code span + em dash'); - assert(idOf('## `client.data` — CRUD & Batch') === 'clientdata--crud--batch', 'code span + ampersand'); - assert(idOf('## **Bold** and *emphasis*') === 'bold-and-emphasis', 'emphasis delimiters are syntax'); - assert(idOf('## The `snake_case` rule') === 'the-snake_case-rule', 'intraword underscore is literal text'); - assert(idOf('## See [the CLI](/docs/deployment/cli) first') === 'see-the-cli-first', 'a link contributes its text, not its href'); - assert(idOf('## Closing hashes ##') === 'closing-hashes', 'ATX closing sequence'); - assert(idOf('## Dynamic approvers (#3447) [#dynamic-approvers-3447]') === 'dynamic-approvers-3447', 'fumadocs custom id'); - assert(idOf('## A `[#not-an-id]` code span') === 'a-not-an-id-code-span', 'a custom id inside a code span is not a custom id'); + battery('The inline shapes the flattener has to get right.'); + check(idOf('## `os login --json` is NDJSON — the one exception') === 'os-login---json-is-ndjson--the-one-exception', 'code span + em dash'); + check(idOf('## `client.data` — CRUD & Batch') === 'clientdata--crud--batch', 'code span + ampersand'); + check(idOf('## **Bold** and *emphasis*') === 'bold-and-emphasis', 'emphasis delimiters are syntax'); + check(idOf('## The `snake_case` rule') === 'the-snake_case-rule', 'intraword underscore is literal text'); + check(idOf('## See [the CLI](/docs/deployment/cli) first') === 'see-the-cli-first', 'a link contributes its text, not its href'); + check(idOf('## Closing hashes ##') === 'closing-hashes', 'ATX closing sequence'); + check(idOf('## Dynamic approvers (#3447) [#dynamic-approvers-3447]') === 'dynamic-approvers-3447', 'fumadocs custom id'); + check(idOf('## A `[#not-an-id]` code span') === 'a-not-an-id-code-span', 'a custom id inside a code span is not a custom id'); // 3. Duplicate headings get the renderer's counter suffixes. + battery('Duplicate headings get the renderer\'s counter suffixes.'); const dupes = headingIds('## Overview\n## Overview\n## Overview\n'); - assert( + check( dupes.join(',') === 'overview,overview-1,overview-2', `duplicate-heading counters drifted: ${dupes.join(',')}`, ); // 4. Headings that are not headings. - assert(headingIds('```md\n## Not a heading\n```\n').length === 0, 'a heading inside a fence is not a heading'); - assert(headingIds('---\ntitle: Frontmatter\n---\n\n## Real\n').join(',') === 'real', 'frontmatter is not a heading'); + battery('Headings that are not headings.'); + check(headingIds('```md\n## Not a heading\n```\n').length === 0, 'a heading inside a fence is not a heading'); + check(headingIds('---\ntitle: Frontmatter\n---\n\n## Real\n').join(',') === 'real', 'frontmatter is not a heading'); // 5. Extraction discriminates prose from examples. + battery('Extraction discriminates prose from examples.'); const extracted = extractFragmentLinks( 'See [a](/docs/x#good) and `[b](/docs/x#in-a-code-span)`.\n' + '```md\n[c](/docs/x#in-a-fence)\n```\n' + 'External [d](https://example.com/y#skipped), page-only [e](/docs/x#).\n', ); - assert( + check( extracted.map((l) => l.fragment).join(',') === 'good', `extractor picked up the wrong links: ${extracted.map((l) => l.dest).join(', ')}`, ); // 6. End to end, on a synthetic corpus: a good anchor is silent, a dead one + battery('End to end, on a synthetic corpus: a good anchor is silent, a dead one'); // is found, and an unresolvable page is its own class. A discrimination // rule that only ever says "nothing found" looks exactly like a broken // extractor, so both directions are provoked. @@ -588,44 +647,92 @@ function selfTest() { '[no page](/docs/gone#anywhere)\n', ); const result = sweep(tmp); - assert(result.checked === 3, `expected 3 checked fragment links, got ${result.checked}`); - assert(result.broken.length === 1 && result.broken[0].fragment === 'overlay-whitelist', 'the dead anchor was not reported'); - assert(result.unresolved.length === 1 && result.unresolved[0].path === '/docs/gone', 'the unresolvable page was not reported'); + check(result.checked === 3, `expected 3 checked fragment links, got ${result.checked}`); + check(result.broken.length === 1 && result.broken[0].fragment === 'overlay-whitelist', 'the dead anchor was not reported'); + check(result.unresolved.length === 1 && result.unresolved[0].path === '/docs/gone', 'the unresolvable page was not reported'); } finally { rmSync(tmp, { recursive: true, force: true }); } // 7. The census guard is load-bearing: it is the only thing that tells a + battery('The census guard is load-bearing: it is the only thing that tells a'); // clean corpus apart from an extractor that matches nothing. const live = sweep(); - assert(live.checked > 0, 'the real corpus yielded zero fragment links — the extractor is over-stripping'); + check(live.checked > 0, 'the real corpus yielded zero fragment links — the extractor is over-stripping'); // 8. The dispatch-gates declaration (#9979). Enforcement cannot hold any of + battery('The dispatch-gates declaration (#9979). Enforcement cannot hold any of'); // these: the declaration is read by another tool entirely, so a wrong or // missing entry runs perfectly green here and shows up only as a dev // dispatched on a README.md / ARCHITECTURE.md card who is not told that // this REQUIRED gate — the repo's only fragment coverage — reads it. - assert( + check( EXTRA_SOURCES.every((f) => ROOT_FILE_WATCH_HINTS.includes(`${f}/**`)), `every extra source declares a root-file watch hint: ${EXTRA_SOURCES.join(', ')} vs ${ROOT_FILE_WATCH_HINTS.join(', ')}`, ); - assert( + check( ROOT_FILE_WATCH_HINTS.every((h) => EXTRA_SOURCES.includes(h.replace(/\/\*+$/, ''))), `the declaration names no file this gate does not read: ${ROOT_FILE_WATCH_HINTS.join(', ')}`, ); // Provenance, never a lookup key: `listSources` joins each EXTRA_SOURCES // entry with the repo root, so the glob form there would drop both from the // sweep — and `existsSync` would drop them SILENTLY. - assert( + check( !EXTRA_SOURCES.some((f) => ROOT_FILE_WATCH_HINTS.includes(f)), 'the declared form is NOT an EXTRA_SOURCES entry — it would silently empty the extra-source half of the sweep', ); // The population the declaration claims is the one the gate really reads. - assert( + check( ROOT_FILE_WATCH_HINTS.every((h) => listSources(process.cwd()).includes(h.replace(/\/\*+$/, ''))), 'the declared root files are in the live source population this gate sweeps', ); + // -- 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. + 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-doc-anchors --self-test: slug parity, custom ids, duplicate counters, extraction discrimination and both finding classes verified (${live.checked} live fragment links)`, ); From 0f6391d52f913a440a7cacde090f40e315f12766 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 03:58:06 +0000 Subject: [PATCH 4/6] test(scripts): give check-kernel-hook-pairs a battery roster and floor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #13799 (batch 3). Third of the module-level-`assert` trio, same thin in-body wrapper; 10 batteries / 16 cases pinned. Its verdict line prints a LITERAL "10 cases" that tracks nothing and undercounts the 16 assertions the body actually runs — left as found (the card's scope is the floor, and the handshake stays exactly as landed), recorded instead. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-kernel-hook-pairs.mjs | 143 ++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/scripts/check-kernel-hook-pairs.mjs b/scripts/check-kernel-hook-pairs.mjs index 32d0da19e7..9d913a0eb3 100644 --- a/scripts/check-kernel-hook-pairs.mjs +++ b/scripts/check-kernel-hook-pairs.mjs @@ -305,6 +305,41 @@ function assert(condition, message) { } } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// A pass 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. Every section opens with `battery('')`, every +// assertion is attributed to the battery most recently opened, and the floor +// requires the OPENED set to equal the DECLARED set with each battery at or +// above its own count. +// +// The counts are 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({ + 'A hook pinned on both sides passes.': 1, + 'Pinned on neither side ⇒ one problem naming BOTH files.': 5, + 'Pinned on ONE side ⇒ still red, naming only the side that lacks it.': 3, + 'Every dispatch flavour is recognised, including the shared module': 1, + 'Subscription is NOT dispatch. A plugin consuming `kernel:ready` owes': 1, + 'A `get(…)` that is not a `hooks.get(…)` is not a dispatch — the': 1, + 'Non-`kernel:` hooks are out of scope: `data:beforeInsert` and friends': 1, + 'A dynamic hook name cannot be resolved statically and is not guessed —': 1, + 'Any title form carries the pin: `describe` counts, and so does a': 1, + 'The name must appear in a TITLE. A hook that only shows up in a test': 1, +}); + +// 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 = 10; + +// 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 @@ -312,6 +347,26 @@ function assert(condition, message) { const SELF_TEST_VERDICT = 'check-kernel-hook-pairs 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); + }; + // 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); + }; const PINNED = (hook) => ` describe('Kernel', () => { it('fails the boot when a ${hook} handler throws', async () => {}); @@ -319,43 +374,47 @@ function selfTest() { `; // 1. A hook pinned on both sides passes. + battery('A hook pinned on both sides passes.'); { const { problems } = audit({ sources: { 'kernel.ts': `await this.context.trigger('kernel:ready');` }, tests: { 'kernel.test.ts': PINNED('kernel:ready'), 'lite-kernel.test.ts': PINNED('kernel:ready') }, }); - assert(problems.length === 0, `a hook pinned on both sides passes (got ${problems.length})`); + check(problems.length === 0, `a hook pinned on both sides passes (got ${problems.length})`); } // 2. Pinned on neither side ⇒ one problem naming BOTH files. + battery('Pinned on neither side ⇒ one problem naming BOTH files.'); { const { problems } = audit({ sources: { 'kernel.ts': `await this.context.trigger('kernel:probe');` }, tests: { 'kernel.test.ts': PINNED('kernel:ready'), 'lite-kernel.test.ts': PINNED('kernel:ready') }, }); - assert(problems.length === 1, `an unpinned hook is one problem (got ${problems.length})`); - assert(problems[0].includes('kernel:probe'), 'the problem names the hook'); - assert(problems[0].includes('kernel.test.ts'), 'the problem names kernel.test.ts as missing'); - assert(problems[0].includes('lite-kernel.test.ts'), 'the problem names lite-kernel.test.ts as missing'); - assert(problems[0].includes('kernel.ts:1 (trigger)'), 'the problem quotes the dispatch site'); + check(problems.length === 1, `an unpinned hook is one problem (got ${problems.length})`); + check(problems[0].includes('kernel:probe'), 'the problem names the hook'); + check(problems[0].includes('kernel.test.ts'), 'the problem names kernel.test.ts as missing'); + check(problems[0].includes('lite-kernel.test.ts'), 'the problem names lite-kernel.test.ts as missing'); + check(problems[0].includes('kernel.ts:1 (trigger)'), 'the problem quotes the dispatch site'); } // 3. Pinned on ONE side ⇒ still red, naming only the side that lacks it. + battery('Pinned on ONE side ⇒ still red, naming only the side that lacks it.'); // This is the whole point: the pair is the unit, not the presence. { const { problems } = audit({ sources: { 'kernel.ts': `await this.context.trigger('kernel:probe');` }, tests: { 'kernel.test.ts': PINNED('kernel:probe'), 'lite-kernel.test.ts': PINNED('kernel:ready') }, }); - assert(problems.length === 1, `a half-pinned hook is still a problem (got ${problems.length})`); - assert(problems[0].includes('lite-kernel.test.ts'), 'the missing side is named'); - assert( + check(problems.length === 1, `a half-pinned hook is still a problem (got ${problems.length})`); + check(problems[0].includes('lite-kernel.test.ts'), 'the missing side is named'); + check( !problems[0].includes('in: kernel.test.ts'), 'the side that DOES pin it is not reported as missing', ); } // 4. Every dispatch flavour is recognised, including the shared module + battery('Every dispatch flavour is recognised, including the shared module'); // #5282 introduced — a callee missing from the vocabulary is a hook the // gate cannot see at all, which is worse than a false positive. { @@ -374,53 +433,58 @@ function selfTest() { }, tests: { 'kernel.test.ts': '', 'lite-kernel.test.ts': '' }, }); - assert( + check( [...dispatches.keys()].sort().join(',') === 'kernel:a,kernel:b,kernel:c,kernel:d,kernel:e,kernel:f', `every dispatch flavour is seen (got: ${[...dispatches.keys()].sort().join(',')})`, ); } // 5. Subscription is NOT dispatch. A plugin consuming `kernel:ready` owes + battery('Subscription is NOT dispatch. A plugin consuming `kernel:ready` owes'); // no pin — the kernels decide what a hook means, plugins only listen. { const { dispatches } = audit({ sources: { 'fallbacks/x.ts': `ctx.hook('kernel:ready', async () => {});` }, tests: { 'kernel.test.ts': '', 'lite-kernel.test.ts': '' }, }); - assert(dispatches.size === 0, `ctx.hook() is a subscription, not a dispatch (got ${dispatches.size})`); + check(dispatches.size === 0, `ctx.hook() is a subscription, not a dispatch (got ${dispatches.size})`); } // 6. A `get(…)` that is not a `hooks.get(…)` is not a dispatch — the + battery('A `get(…)` that is not a `hooks.get(…)` is not a dispatch — the'); // vocabulary's most collision-prone name stays scoped to its receiver. { const { dispatches } = audit({ sources: { 'kernel.ts': `const svc = this.services.get('kernel:ready');` }, tests: { 'kernel.test.ts': '', 'lite-kernel.test.ts': '' }, }); - assert(dispatches.size === 0, `services.get() is not a hook dispatch (got ${dispatches.size})`); + check(dispatches.size === 0, `services.get() is not a hook dispatch (got ${dispatches.size})`); } // 7. Non-`kernel:` hooks are out of scope: `data:beforeInsert` and friends + battery('Non-`kernel:` hooks are out of scope: `data:beforeInsert` and friends'); // are per-plugin vocabularies with no cross-kernel meaning to keep equal. { const { dispatches } = audit({ sources: { 'kernel.ts': `await this.context.trigger('data:beforeInsert', doc);` }, tests: { 'kernel.test.ts': '', 'lite-kernel.test.ts': '' }, }); - assert(dispatches.size === 0, `only kernel:* hooks are paired (got ${dispatches.size})`); + check(dispatches.size === 0, `only kernel:* hooks are paired (got ${dispatches.size})`); } // 8. A dynamic hook name cannot be resolved statically and is not guessed — + battery('A dynamic hook name cannot be resolved statically and is not guessed —'); // that is the generic `trigger(name)` seam every hook flows through. { const { dispatches } = audit({ sources: { 'kernel-base.ts': `await dispatchHookPropagating(name, this.hooks.get(name) || [], undefined, args);` }, tests: { 'kernel.test.ts': '', 'lite-kernel.test.ts': '' }, }); - assert(dispatches.size === 0, `a variable hook name is not invented (got ${dispatches.size})`); + check(dispatches.size === 0, `a variable hook name is not invented (got ${dispatches.size})`); } // 9. Any title form carries the pin: `describe` counts, and so does a + battery('Any title form carries the pin: `describe` counts, and so does a'); // modified `it.each(…)`. The gate judges pairing, never title style. { const { problems } = audit({ @@ -430,10 +494,11 @@ function selfTest() { 'lite-kernel.test.ts': `it.each([1])('kernel:probe propagates (%i)', () => {});`, }, }); - assert(problems.length === 0, `describe() and it.each() titles both pin (got: ${problems[0] ?? ''})`); + check(problems.length === 0, `describe() and it.each() titles both pin (got: ${problems[0] ?? ''})`); } // 10. The name must appear in a TITLE. A hook that only shows up in a test + battery('The name must appear in a TITLE. A hook that only shows up in a test'); // BODY is incidental setup for some other assertion, not a pin. { const { problems } = audit({ @@ -443,8 +508,54 @@ function selfTest() { 'lite-kernel.test.ts': `it('boots', () => { ctx.hook('kernel:probe', fn); });`, }, }); - assert(problems.length === 1, `a body mention is not a named pin (got ${problems.length})`); + check(problems.length === 1, `a body mention is not a named pin (got ${problems.length})`); + } + + // -- 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. + 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('✓ self-test: 10 cases'); From 3e787d35585a9c3bdfcdace89de4b68465e00324 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 03:59:54 +0000 Subject: [PATCH 5/6] test(scripts): give check-published-files a battery roster and floor Part of #13799 (batch 3). The 7 inline `failures.push(...)` sites now route through one block-bodied `expect()` that registers the case whether it holds or not; message text is unchanged. 2 batteries / 59 cases, the first hoisted because cases run before the first banner. The count reconciles against the tables: 21 + 12 + 4 and 12 + 6 + 3 + 1. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-published-files.mjs | 128 ++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 15 deletions(-) diff --git a/scripts/check-published-files.mjs b/scripts/check-published-files.mjs index 6a392a8546..43fa396a64 100644 --- a/scripts/check-published-files.mjs +++ b/scripts/check-published-files.mjs @@ -424,6 +424,33 @@ function exportsVerdict(manifest) { * through. Both failures are silent, so they get asserted rather than assumed. */ +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// A pass 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. Every section opens with `battery('')`, every +// assertion is attributed to the battery most recently opened, and the floor +// requires the OPENED set to equal the DECLARED set with each battery at or +// above its own count. +// +// The counts are 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({ + 'the dispatch-gates declaration (#10542)': 37, + 'GATED and its census floor (#12879)': 22, +}); + +// 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 = 2; + +// 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 @@ -431,6 +458,32 @@ function exportsVerdict(manifest) { const SELF_TEST_VERDICT = 'check-published-files 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); + }; + // The one in-body assertion helper the 7 inline `failures.push(...)` sites + // now route through. The message is built the same way it always was; the + // only change is that a case is COUNTED whether it holds or not, which is + // what lets the floor below tell "held" from "never ran". + const expect = (ok, message) => { + registerCase(); + if (!ok) failures.push(message); + }; + // Cases run before the first banner, so the first battery is opened at the + // top of the body and that banner carries no second opener — PR #13487's own + // shape, as batches 1b and 2 landed it. + battery('the dispatch-gates declaration (#10542)'); + const failures = []; const cases = [ ['dist', 'dist/index.js', true], ['dist', 'dist/nested/deep/chunk.js', true], @@ -474,18 +527,13 @@ function selfTest() { ['dist/index.js', null], ['README.md', null], ]; - const failures = []; for (const [pattern, path, expected] of cases) { const actual = matcher(pattern)(path); - if (actual !== expected) { - failures.push(`matcher("${pattern}")("${path}") === ${actual}, expected ${expected}`); - } + expect(actual === expected, `matcher("${pattern}")("${path}") === ${actual}, expected ${expected}`); } for (const [path, expected] of forbidden) { const actual = FORBIDDEN.find((f) => f.test(path))?.label ?? null; - if (actual !== expected) { - failures.push(`FORBIDDEN("${path}") === ${actual}, expected ${expected}`); - } + expect(actual === expected, `FORBIDDEN("${path}") === ${actual}, expected ${expected}`); } // ── the dispatch-gates declaration (#10542) ─────────────────────────────── @@ -518,10 +566,11 @@ function selfTest() { ], ]; for (const [name, ok] of declarationCases) { - if (!ok) failures.push(`ROOT_DIR_WATCH_HINTS: ${name}`); + expect(ok, `ROOT_DIR_WATCH_HINTS: ${name}`); } // ── GATED and its census floor (#12879) ─────────────────────────────────── + battery('GATED and its census floor (#12879)'); // // The verdict cases pin the two directions apart: a map that is merely ABSENT // leaves everything resolvable, a map that is present and EMPTY leaves nothing @@ -545,16 +594,15 @@ function selfTest() { ]; for (const [label, manifest, expected] of verdictCases) { const actual = exportsVerdict(manifest).ok; - if (actual !== expected) { - failures.push(`exportsVerdict(${label}).ok === ${actual}, expected ${expected}`); - } + expect(actual === expected, `exportsVerdict(${label}).ok === ${actual}, expected ${expected}`); } for (const [label, manifest, expected] of verdictCases) { if (expected) continue; const { lines } = exportsVerdict(manifest); - if (!Array.isArray(lines) || lines.length === 0 || !lines.some((l) => l.startsWith('Fix:'))) { - failures.push(`exportsVerdict(${label}) refuses without a Fix: line`); - } + expect( + Array.isArray(lines) && lines.length > 0 && lines.some((l) => l.startsWith('Fix:')), + `exportsVerdict(${label}) refuses without a Fix: line`, + ); } // The floor is the control, so it is itself controlled -- against the live @@ -593,7 +641,7 @@ function selfTest() { ], ]; for (const [name, ok] of floorCases) { - if (!ok) failures.push(`GATED census: ${name}`); + expect(ok, `GATED census: ${name}`); } // The shared enumerator is a plain module, so no workflow invokes it and it @@ -601,8 +649,58 @@ function selfTest() { // what it must not be). Its coverage is that every gate which consolidated // onto it folds these in, this one included. const enumeratorFailures = workspaceEnumeratorSelfTest({ root: ROOT }); + // The enumerator returns a SET of failures rather than one assertion, so it + // registers as the single case it is here: that the shared enumerator's own + // self-test ran and returned nothing. + expect(enumeratorFailures.length === 0, `the shared workspace enumerator reported ${enumeratorFailures.length} failure(s)`); failures.push(...enumeratorFailures); + // -- 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. + 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.', + ); + } + for (const m of floorMessages) failures.push(m); + if (failures.length > 0) { console.error(`✗ check:published-files --self-test — ${failures.length} failure(s)\n`); for (const f of failures) console.error(` ${f}`); From 6bddfc959485c6fe9aab54c3bd8a816dd54a8c87 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 04:00:54 +0000 Subject: [PATCH 6/6] test(scripts): give ablation-dist-preflight a battery roster and floor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #13799 (batch 3). The 6 inline `failed += 1` sites route through one block-bodied `check()` that keeps both the ✓ and ✗ branches verbatim and counts the case either way. 3 batteries / 36 cases, the first hoisted (10 + 3 + 12 + 1 run before the first banner). Not a repo sweep: with no args it prints usage and exits 2, unchanged from the branch point. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/ablation-dist-preflight.mjs | 159 +++++++++++++++++++++------- 1 file changed, 120 insertions(+), 39 deletions(-) diff --git a/scripts/ablation-dist-preflight.mjs b/scripts/ablation-dist-preflight.mjs index c9ad125b20..fe9b0f151b 100644 --- a/scripts/ablation-dist-preflight.mjs +++ b/scripts/ablation-dist-preflight.mjs @@ -551,6 +551,34 @@ function run(argv) { if (!v.ok || !tv.ok) process.exit(1); } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// A pass 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. Every section opens with `battery('')`, every +// assertion is attributed to the battery most recently opened, and the floor +// requires the OPENED set to equal the DECLARED set with each battery at or +// above its own count. +// +// The counts are 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({ + 'whole-tree accounting: the pure table': 26, + 'porcelain parsing': 3, + 'whole-tree accounting: a real git tree': 7, +}); + +// 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 = 3; + +// 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 @@ -558,6 +586,37 @@ function run(argv) { const SELF_TEST_VERDICT = 'ablation-dist-preflight 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); + }; + // The one in-body assertion helper the 6 inline `failed += 1` sites now route + // through. Each site already had a ✓ branch and a ✗ branch; both are kept + // verbatim, and the only change is that a case is COUNTED either way — which + // is what lets the floor below tell "held" from "never ran". + const check = (label, ok, detail = '') => { + registerCase(); + if (ok) { + console.log(` ✓ ${label}`); + return; + } + console.error(` ✗ ${label}${detail}`); + failed += 1; + }; + // Cases run before the first banner, so the first battery is opened at the + // top of the body and that banner carries no second opener — PR #13487's own + // shape, as batches 1b and 2 landed it. + battery('whole-tree accounting: the pure table'); + let failed = 0; const cases = [ ['missing dist is red', { mode: 'present', distExists: false, scanned: 0, codeHits: 0, mapHits: 0 }, false], ['empty dist is red, not a skip', { mode: 'present', distExists: true, scanned: 0, codeHits: 0, mapHits: 0 }, false], @@ -570,15 +629,9 @@ function selfTest() { ['absent mode: still in code is red', { mode: 'absent', distExists: true, scanned: 9, codeHits: 3, mapHits: 0 }, false], ['absent mode: missing dist still red', { mode: 'absent', distExists: false, scanned: 0, codeHits: 0, mapHits: 0 }, false], ]; - let failed = 0; for (const [label, input, expected] of cases) { const got = verdict(input).ok; - if (got !== expected) { - console.error(` ✗ ${label}: expected ok=${expected}, got ok=${got}`); - failed += 1; - } else { - console.log(` ✓ ${label}`); - } + check(label, got === expected, `: expected ok=${expected}, got ok=${got}`); } // Filesystem leg: a real dist tree where the marker lives only in a sourcemap @@ -597,13 +650,7 @@ function selfTest() { ['scan classifies a map-only token as a map hit', mapOnly.codeHits.length === 0 && mapOnly.mapHits.length === 1], ['map-only scan is judged RED', verdict({ mode: 'present', distExists: true, scanned: mapOnly.scanned, codeHits: 0, mapHits: mapOnly.mapHits.length }).ok === false], ]; - for (const [label, ok] of checks) { - if (ok) console.log(` ✓ ${label}`); - else { - console.error(` ✗ ${label}`); - failed += 1; - } - } + for (const [label, ok] of checks) check(label, ok); } finally { rmSync(tmp, { recursive: true, force: true }); } @@ -631,12 +678,11 @@ function selfTest() { ]; for (const [label, input, expectedOk, expectedLeg] of treeCases) { const got = treeVerdict(input); - if (got.ok !== expectedOk || got.leg !== expectedLeg) { - console.error(` ✗ ${label}: expected ok=${expectedOk} leg=${expectedLeg}, got ok=${got.ok} leg=${got.leg}`); - failed += 1; - } else { - console.log(` ✓ ${label}`); - } + check( + label, + got.ok === expectedOk && got.leg === expectedLeg, + `: expected ok=${expectedOk} leg=${expectedLeg}, got ok=${got.ok} leg=${got.leg}`, + ); } // A red restore leg must NAME the leaked path -- a refusal that does not say @@ -645,14 +691,11 @@ function selfTest() { { const red = treeVerdict({ mode: 'absent', gitReadable: true, files: [f('packages/spec/authorable-surface/data.json', false, true)] }); const named = red.paths.includes('packages/spec/authorable-surface/data.json'); - if (named) console.log(' ✓ a red restore leg names the leaked path'); - else { - console.error(' ✗ a red restore leg names the leaked path'); - failed += 1; - } + check('a red restore leg names the leaked path', named); } // ---- porcelain parsing --------------------------------------------------- + battery('porcelain parsing'); { const Z = String.fromCharCode(0); const parsed = parsePorcelainZ([' M packages/spec/authorable-surface/data.json', '?? scratch note.txt', 'R new/name.ts', 'old/name.ts', ''].join(Z)); @@ -661,16 +704,11 @@ function selfTest() { ['parses an untracked path holding a space, unquoted', parsed[1]?.path === 'scratch note.txt' && parsed[1]?.untracked === true], ['consumes a rename origin record instead of listing it', parsed.length === 3 && parsed[2]?.path === 'new/name.ts'], ]; - for (const [label, ok] of checks) { - if (ok) console.log(` ✓ ${label}`); - else { - console.error(` ✗ ${label}`); - failed += 1; - } - } + for (const [label, ok] of checks) check(label, ok); } // ---- whole-tree accounting: a real git tree ------------------------------- + battery('whole-tree accounting: a real git tree'); // The pure table cannot catch a broken `git status` read or a broken // HEAD-vs-worktree marker probe, and those are the wires that make the // verdict mean anything. This leg replays the measured incident end to end. @@ -732,17 +770,60 @@ function selfTest() { ['git leg: a deleted guard is a mutate leg, not a restore leg', deleteLeg.ok === true && deleteLeg.leg === 'mutate'], ['git leg: an untracked path reds the restore leg', untrackedLeg.ok === false && untrackedLeg.paths.includes('scratch.txt')], ]; - for (const [label, ok] of gitChecks) { - if (ok) console.log(` ✓ ${label}`); - else { - console.error(` ✗ ${label}`); - failed += 1; - } - } + for (const [label, ok] of gitChecks) check(label, ok); } finally { rmSync(repo, { recursive: true, force: 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. + 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.', + ); + } + for (const m of floorMessages) { + console.error(` ✗ ${m}`); + failed += 1; + } + if (failed > 0) { console.error(`✗ ablation-dist-preflight self-test: ${failed} case(s) failed.`); process.exit(1);