From de6303c2bf7ef82945b3d328da17dbe250579145 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:44:37 +0000 Subject: [PATCH 1/4] test(scripts): floor measure-test-shard-timings' self-test on a registered battery The 22 bare `if (...) throw ...` assertions in `selfTest()` are each wrapped verbatim in the `check(() => { ... })` thunk PR #15198 landed: the condition is carried in untouched, so no `!` can be dropped while the case still registers and still passes. Zero named section banners, so one hoisted battery (the PR #14896 / #15003 / #15217 shape), floored at the measured 22 with the roster's own size pinned at 1. The floor throws, matching this file's own sink, and is evaluated before the verdict so the success line can only be printed by a run that actually ran its cases. Part of #13799 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/measure-test-shard-timings.mjs | 229 +++++++++++++++++++++---- 1 file changed, 191 insertions(+), 38 deletions(-) diff --git a/scripts/measure-test-shard-timings.mjs b/scripts/measure-test-shard-timings.mjs index d271b125ce..df2fd6424b 100644 --- a/scripts/measure-test-shard-timings.mjs +++ b/scripts/measure-test-shard-timings.mjs @@ -174,6 +174,46 @@ export function buildDataset({ perSummary, fileCounts, provenance }) { }; } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// `--self-test` reaching its verdict 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. +// +// This file's assertions are bare `throw`s rather than calls to an assertion +// helper, so they are counted by the `check(() => { ... })` THUNK PR #15198 +// measured: the existing `if (...) throw ...` is carried into the thunk +// VERBATIM and the condition is never touched. Routing these through a boolean +// helper instead would mean inverting 22 failure conditions by hand, and a +// dropped `!` yields an assertion that still registers its case and still +// passes -- invisible to the very floor being installed here. The throw still +// propagates: this file fails fast on the FIRST broken assertion, as it always +// has. +// +// This file declares ONE battery, opened at the top of the self-test body. Its +// blocks are headed by unmarked prose comments -- it carries ZERO named section +// banners, fewer than the two 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. +// +// 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, never to lower the number. +const SELF_TEST_BATTERIES = Object.freeze({ + 'measure-test-shard-timings self-test': 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 = 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 @@ -181,6 +221,28 @@ export function buildDataset({ perSummary, fileCounts, provenance }) { const SELF_TEST_VERDICT = 'measure-test-shard-timings 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('measure-test-shard-timings self-test'); + // The thunk: it registers the case and then runs the existing site VERBATIM, + // so no assertion condition is inverted or rewritten and the sink keeps its + // own semantics. Registration happens whether or not the site fires, which is + // what makes the count a floor on cases RUN rather than a count of failures. + const check = (fn) => { + registerCase(); + fn(); + }; const summary = (tasks) => ({ tasks }); const testTask = (pkg, start, end, status = 'MISS', exitCode = 0) => ({ taskId: `${pkg}#test`, @@ -190,9 +252,15 @@ function selfTest() { execution: { startTime: start, endTime: end, exitCode }, }); - if (median([3]) !== 3) throw new Error('median: single value'); - if (median([5, 1, 3]) !== 3) throw new Error('median: odd length is not order-dependent'); - if (median([1, 2, 3, 4]) !== 2.5) throw new Error('median: even length averages the middle pair'); + check(() => { + if (median([3]) !== 3) throw new Error('median: single value'); + }); + check(() => { + if (median([5, 1, 3]) !== 3) throw new Error('median: odd length is not order-dependent'); + }); + check(() => { + if (median([1, 2, 3, 4]) !== 2.5) throw new Error('median: even length averages the middle pair'); + }); // A build task in the same summary must not be read as a test duration. const mixed = samplesFromSummary( @@ -202,23 +270,37 @@ function selfTest() { ]), 'f' ); - if (mixed.samples.get('a') !== 2) throw new Error(`task filter: got ${mixed.samples.get('a')}`); - if (mixed.samples.size !== 1) throw new Error('task filter: a non-test task was sampled'); + check(() => { + if (mixed.samples.get('a') !== 2) throw new Error(`task filter: got ${mixed.samples.get('a')}`); + }); + check(() => { + if (mixed.samples.size !== 1) throw new Error('task filter: a non-test task was sampled'); + }); // THE ONE THAT MATTERS: a cache HIT is skipped, never recorded as ~0s. // The control leg first -- a genuine 40ms MISS is a legitimate measurement, // so only the HIT/MISS pair below proves the skip is about the cache status // and not about the window being short. const shortMiss = samplesFromSummary(summary([testTask('a', 0, 40)]), 'f'); - if (shortMiss.samples.get('a') !== 0.04) throw new Error('cache: a short MISS was not recorded'); + check(() => { + if (shortMiss.samples.get('a') !== 0.04) throw new Error('cache: a short MISS was not recorded'); + }); const withHit = samplesFromSummary(summary([testTask('a', 0, 40, 'HIT'), testTask('b', 0, 60_000)]), 'f'); - if (withHit.samples.has('a')) throw new Error('cache: a HIT was recorded as a measurement'); - if (!withHit.skippedCached.includes('a')) throw new Error('cache: a HIT was not reported as skipped'); - if (withHit.samples.get('b') !== 60) throw new Error('cache: the MISS beside it was lost'); + check(() => { + if (withHit.samples.has('a')) throw new Error('cache: a HIT was recorded as a measurement'); + }); + check(() => { + if (!withHit.skippedCached.includes('a')) throw new Error('cache: a HIT was not reported as skipped'); + }); + check(() => { + if (withHit.samples.get('b') !== 60) throw new Error('cache: the MISS beside it was lost'); + }); // A failed suite stopped early; its window is not the package's cost. const failed = samplesFromSummary(summary([testTask('a', 0, 500, 'MISS', 1)]), 'f'); - if (failed.samples.has('a')) throw new Error('exit: a failed suite was recorded as a duration'); + check(() => { + if (failed.samples.has('a')) throw new Error('exit: a failed suite was recorded as a duration'); + }); const threw = (fn) => { try { @@ -228,13 +310,19 @@ function selfTest() { return true; } }; - if (!threw(() => samplesFromSummary({}, 'f'))) throw new Error('shape: a non-summary was accepted'); - if (!threw(() => samplesFromSummary(summary([{ task: 'test', package: '', cache: { status: 'MISS' } }]), 'f'))) { - throw new Error('shape: a nameless test task was accepted'); - } - if (!threw(() => samplesFromSummary(summary([{ task: 'test', package: 'a', cache: { status: 'MISS' } }]), 'f'))) { - throw new Error('shape: a test task with no execution window was accepted'); - } + check(() => { + if (!threw(() => samplesFromSummary({}, 'f'))) throw new Error('shape: a non-summary was accepted'); + }); + check(() => { + if (!threw(() => samplesFromSummary(summary([{ task: 'test', package: '', cache: { status: 'MISS' } }]), 'f'))) { + throw new Error('shape: a nameless test task was accepted'); + } + }); + check(() => { + if (!threw(() => samplesFromSummary(summary([{ task: 'test', package: 'a', cache: { status: 'MISS' } }]), 'f'))) { + throw new Error('shape: a test task with no execution window was accepted'); + } + }); // Merging: the same package sampled by several shards collapses to its median. const merged = buildDataset({ @@ -246,29 +334,39 @@ function selfTest() { fileCounts: new Map([['a', 10], ['big', 50]]), provenance: { measuredAt: 'test' }, }); - if (merged.packages.a !== 20) throw new Error(`merge: expected the median 20, got ${merged.packages.a}`); + check(() => { + if (merged.packages.a !== 20) throw new Error(`merge: expected the median 20, got ${merged.packages.a}`); + }); // rates: a -> 20/10 = 2, big -> 100/50 = 2 => 2 - if (merged.secondsPerTestFileFallback !== 2) { - throw new Error(`fallback rate: got ${merged.secondsPerTestFileFallback}`); - } + check(() => { + if (merged.secondsPerTestFileFallback !== 2) { + throw new Error(`fallback rate: got ${merged.secondsPerTestFileFallback}`); + } + }); // Packages too small to vote on the rate are excluded from it but still kept. const tiny = buildDataset({ perSummary: [samplesFromSummary(summary([testTask('a', 0, 10_000), testTask('t', 0, 300)]), 'f')], fileCounts: new Map([['a', 5], ['t', 1]]), provenance: {}, }); - if (tiny.secondsPerTestFileFallback !== 2) throw new Error(`rate: a 0.3s/1-file package voted (${tiny.secondsPerTestFileFallback})`); - if (tiny.packages.t !== 0.3) throw new Error('rate: the small package was dropped from the dataset'); + check(() => { + if (tiny.secondsPerTestFileFallback !== 2) throw new Error(`rate: a 0.3s/1-file package voted (${tiny.secondsPerTestFileFallback})`); + }); + check(() => { + if (tiny.packages.t !== 0.3) throw new Error('rate: the small package was dropped from the dataset'); + }); - if (!threw(() => - buildDataset({ - perSummary: [samplesFromSummary(summary([testTask('a', 0, 40, 'HIT')]), 'f')], - fileCounts: new Map(), - provenance: {}, - }) - )) { - throw new Error('an all-cached run produced a dataset instead of refusing'); - } + check(() => { + if (!threw(() => + buildDataset({ + perSummary: [samplesFromSummary(summary([testTask('a', 0, 40, 'HIT')]), 'f')], + fileCounts: new Map(), + provenance: {}, + }) + )) { + throw new Error('an all-cached run produced a dataset instead of refusing'); + } + }); // Workspace resolution, at the depth that actually caught a defect. A // one-level scan resolves `packages/*` and returns null for the ~60% of the @@ -276,13 +374,68 @@ function selfTest() { // seven more roots -- silently, as "this package has no test files", which // then skews the fallback rate toward whichever half sits at depth 1. const nested = packageDirForName('@objectstack/driver-turso'); - if (nested === null) throw new Error('workspace: a package nested under packages/drivers/ did not resolve'); - if (path.relative(REPO_ROOT, nested).split(path.sep).length < 3) { - throw new Error(`workspace: expected a nested path, got ${nested}`); - } - if (countTestFiles(nested) === 0) throw new Error('workspace: the resolved nested package reports no test files'); + check(() => { + if (nested === null) throw new Error('workspace: a package nested under packages/drivers/ did not resolve'); + }); + check(() => { + if (path.relative(REPO_ROOT, nested).split(path.sep).length < 3) { + throw new Error(`workspace: expected a nested path, got ${nested}`); + } + }); + check(() => { + if (countTestFiles(nested) === 0) throw new Error('workspace: the resolved nested package reports no test files'); + }); const flat = packageDirForName('@objectstack/spec'); - if (flat === null || path.basename(flat) !== 'spec') throw new Error('workspace: a depth-1 package stopped resolving'); + check(() => { + if (flat === null || path.basename(flat) !== 'spec') throw new Error('workspace: a depth-1 package stopped resolving'); + }); + + // -- 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 cases EQUALS the set declared. A set difference + // names WHICH battery stopped; a count says only that something did. + // + // It THROWS rather than collecting into a `failures` array because that is + // how every other assertion in this file reports: the dispatch below turns an + // unfinished self-test into a non-zero exit, and a floor breach is exactly + // that -- a self-test that did not run what it claims to run. + const floorFailures = []; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorFailures.push( + `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; + floorFailures.push( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES -- a case 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; + floorFailures.push( + 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 (floorFailures.length > 0) { + throw new Error( + `measure-test-shard-timings self-test floor (${floorFailures.length} breach(es)):\n` + + floorFailures.map((f) => ` - ${f}`).join('\n') + + '\n 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('measure-test-shard-timings: self-test OK'); From fe8e5b39ab8e53b803180d5bc90624d7c0dce8f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:49:01 +0000 Subject: [PATCH 2/4] test(scripts): floor measure-position-name-fold-census' self-test on a registered battery The 13 `if (...) problems.push(...)` assertion sites in `selfTest()` are each wrapped verbatim in the `check(() => { ... })` thunk PR #15198 landed. The sink here is FAILURE-ONLY, so routing `problems.push` itself through `registerCase()` would have inverted the floor rather than installing it: a green run would register nothing and the battery would read DID NOT RUN. Zero named banners, so one hoisted battery, floored at the 24 registrations measured on a run (four of the thirteen sites sit inside loops: 9 + 4 + 4 + 2 + 5). The floor lives inside the `!quiet` branch. `main()` gates the PRODUCTION census on `selfTest({ quiet: true })`, which prints no verdict and so makes no claim for a floor to guard; evaluating it there would let a roster edit change what a census run outputs. Both production modes are byte-identical to the base tree. One site stays outside the roster and says so in place: the `catch` arm of the AUDIT_CONTROLS loop ends in `continue`, illegal inside the thunk's arrow function, and rewriting that control flow is exactly what the verbatim rule forbids. Its sibling in the same loop body is floored, so a loop that stops running still reds. Part of #13799 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/measure-position-name-fold-census.mjs | 245 ++++++++++++++---- 1 file changed, 193 insertions(+), 52 deletions(-) diff --git a/scripts/measure-position-name-fold-census.mjs b/scripts/measure-position-name-fold-census.mjs index da07fd2d85..e6fcf147ac 100644 --- a/scripts/measure-position-name-fold-census.mjs +++ b/scripts/measure-position-name-fold-census.mjs @@ -693,75 +693,167 @@ const AUDIT_CONTROLS = [ // handshake is a flag rather than a returned sentinel. let selfTestReachedVerdict = false; +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// Reaching the verdict used to be this self-test's ONLY success condition, so +// "every control held" and "the controls 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. +// +// This file's assertion sites are `if (...) problems.push(...)` -- a +// FAILURE-ONLY sink. Routing `problems.push` itself through `registerCase()` +// would register a case only when a control FAILS: a fully green run would +// register 0 and the battery would read DID NOT RUN, the floor inverted rather +// than installed. So the sites are counted by the `check(() => { ... })` THUNK +// PR #15198 measured: the existing `if (...) problems.push(...)` is carried in +// VERBATIM, no condition is touched, and registration happens whether or not +// the site fires. +// +// This file declares ONE battery, opened at the top of the self-test body: it +// carries ZERO named section banners, fewer than the two the sectioning +// criterion needs, and a comment is NOT promoted to a section head. The hoisted +// single battery is the shape PR #14896, PR #15003 and PR #15217 landed. +// +// ⭐ ONE site stays OUTSIDE the roster, deliberately: the `catch` arm of the +// AUDIT_CONTROLS loop (`audit control threw: ...`) ends in `continue`, which is +// illegal inside the thunk's arrow function. Wrapping it would mean rewriting +// that control flow -- exactly what the verbatim rule forbids. Its sibling in +// the same loop body, `if (!ac.expect(result))`, IS floored, so a loop that +// stops running still reds. This is the residue recipe A's non-table +// assertions carry too (PR #15286's `extra` call). +// +// The count is a FLOOR, not an equality -- adding controls is ordinary work and +// must not red. A battery BELOW its floor means cases stopped running; the +// remedy is to find what stopped registering, never to lower the number. Four +// of the thirteen floored sites sit inside `for` loops, so the count is the +// number of times a site is REACHED on a run, measured on a run. +const SELF_TEST_BATTERIES = Object.freeze({ + 'measure-position-name-fold-census self-test': 24, +}); + +// 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)'; + function selfTest({ quiet = false } = {}) { + // The battery ledger this self-test's floor is evaluated against (#13489). + // `battery()` opens a battery; every site 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('measure-position-name-fold-census self-test'); + // The thunk: it registers the case and then runs the existing site VERBATIM, + // so no control's condition is inverted or rewritten and the `problems` sink + // keeps its own semantics. + const check = (fn) => { + registerCase(); + fn(); + }; const problems = []; const c = census(); for (const name of CONTROLS.builtinPositions) { - if (!c.positions.has(name)) { - problems.push(`parse-integrity: built-in POSITION \`${name}\` was not found. The scanner has drifted` - + ' off the declaration shape; every number below it would be an under-count.'); - } + check(() => { + if (!c.positions.has(name)) { + problems.push(`parse-integrity: built-in POSITION \`${name}\` was not found. The scanner has drifted` + + ' off the declaration shape; every number below it would be an under-count.'); + } + }); } for (const name of CONTROLS.builtinPermissionSets) { - if (!c.permissionSets.has(name)) { - problems.push(`parse-integrity: built-in PERMISSION SET \`${name}\` was not found. Same failure, other axis.`); - } + check(() => { + if (!c.permissionSets.has(name)) { + problems.push(`parse-integrity: built-in PERMISSION SET \`${name}\` was not found. Same failure, other axis.`); + } + }); } const a = CONTROLS.junctionAnchor; - if (!c.bindings.some((b) => b.position === a.position && b.set === a.set)) { - problems.push(`positive control (junction) NOT classified as a junction binding: ` - + `${a.position} -> ${a.set}\n ${a.why}`); - } - if (c.nameFolds.some((f) => f.name === a.position)) { - problems.push(`positive control (junction) was reported as a NAME-DEPENDENCY: ${a.position}\n ${a.why}`); - } + check(() => { + if (!c.bindings.some((b) => b.position === a.position && b.set === a.set)) { + problems.push(`positive control (junction) NOT classified as a junction binding: ` + + `${a.position} -> ${a.set}\n ${a.why}`); + } + }); + check(() => { + if (c.nameFolds.some((f) => f.name === a.position)) { + problems.push(`positive control (junction) was reported as a NAME-DEPENDENCY: ${a.position}\n ${a.why}`); + } + }); const b = CONTROLS.nameFoldAnchor; const fold = c.nameFolds.find((f) => f.name === b.name); - if (!fold) { - problems.push(`positive control (name-fold) NOT detected: ${b.name}\n ${b.why}`); - } else if (!fold.permissionSets.some((s) => s.kind === 'artifact')) { - problems.push(`positive control (name-fold) ${b.name} was detected, but its permission-set half is not` - + ' the composed artifact -- the control is passing for the wrong reason.'); - } + check(() => { + if (!fold) { + problems.push(`positive control (name-fold) NOT detected: ${b.name}\n ${b.why}`); + } else if (!fold.permissionSets.some((s) => s.kind === 'artifact')) { + problems.push(`positive control (name-fold) ${b.name} was detected, but its permission-set half is not` + + ' the composed artifact -- the control is passing for the wrong reason.'); + } + }); const n = CONTROLS.negative; - if (c.nameFolds.some((f) => f.name === n.position)) { - problems.push(`negative control reported as a name-fold: ${n.position}\n ${n.why}`); - } - if (!c.bindings.some((x) => x.position === n.position && x.set === n.set)) { - problems.push(`negative control ${n.position} -> ${n.set} was not seen as a junction binding at all;` - + ' the binder scan is blind, so every junction-bound pair would be misfiled as unbound.'); - } + check(() => { + if (c.nameFolds.some((f) => f.name === n.position)) { + problems.push(`negative control reported as a name-fold: ${n.position}\n ${n.why}`); + } + }); + check(() => { + if (!c.bindings.some((x) => x.position === n.position && x.set === n.set)) { + problems.push(`negative control ${n.position} -> ${n.set} was not seen as a junction binding at all;` + + ' the binder scan is blind, so every junction-bound pair would be misfiled as unbound.'); + } + }); const sb = CONTROLS.secondBinder; - if (!c.bindings.some((x) => x.position === sb.position && x.set === sb.set)) { - problems.push(`second-binder control missed: \`${sb.position} -> ${sb.set}\` is a junction binding declared by` - + ' a binder whose list is NOT named POSITION_PERMISSION_SET_BINDINGS. The scan is recognising one app\'s' - + ' spelling again, so other apps\' junction rows are invisible and their positions read as inert.'); - } - if (c.inert.some((i) => i.name === sb.position)) { - problems.push(`second-binder control: \`${sb.position}\` was reported INERT while a binder binds it.`); - } - if (!c.nameFolds.some((f) => f.name === 'sales_manager')) { - problems.push('a position junction-bound to a DIFFERENT set must still count as a name-fold on its own name' - + ' (sales_manager is bound to crm_sales_user and still folds onto the HotCRM `sales_manager` set).' - + ' Losing this would tell 要点 2 the pair is already governed.'); - } + check(() => { + if (!c.bindings.some((x) => x.position === sb.position && x.set === sb.set)) { + problems.push(`second-binder control missed: \`${sb.position} -> ${sb.set}\` is a junction binding declared by` + + ' a binder whose list is NOT named POSITION_PERMISSION_SET_BINDINGS. The scan is recognising one app\'s' + + ' spelling again, so other apps\' junction rows are invisible and their positions read as inert.'); + } + }); + check(() => { + if (c.inert.some((i) => i.name === sb.position)) { + problems.push(`second-binder control: \`${sb.position}\` was reported INERT while a binder binds it.`); + } + }); + check(() => { + if (!c.nameFolds.some((f) => f.name === 'sales_manager')) { + problems.push('a position junction-bound to a DIFFERENT set must still count as a name-fold on its own name' + + ' (sales_manager is bound to crm_sales_user and still folds onto the HotCRM `sales_manager` set).' + + ' Losing this would tell 要点 2 the pair is already governed.'); + } + }); const bleed = CONTROLS.isDefaultBleed; - if (!c.bindings.some((x) => x.position === 'everyone' && x.set === bleed.bound)) { - problems.push(`regression control: \`${bleed.bound}\` declares \`isDefault: true\` and must appear as an` - + ' `everyone` auto-bind. It does not, so the isDefault scan has stopped seeing declarations it owns.'); - } - for (const set of bleed.unbound) { - if (c.bindings.some((x) => x.position === 'everyone' && x.set === set)) { - problems.push(`regression control fired again: \`everyone -> ${set}\` was reported as a junction binding,` - + ` but ${set} declares no \`isDefault\`. The isDefault look-ahead is reading past the declaration's own` - + ' object literal again -- see CONTROLS.isDefaultBleed.'); + check(() => { + if (!c.bindings.some((x) => x.position === 'everyone' && x.set === bleed.bound)) { + problems.push(`regression control: \`${bleed.bound}\` declares \`isDefault: true\` and must appear as an` + + ' `everyone` auto-bind. It does not, so the isDefault scan has stopped seeing declarations it owns.'); } + }); + for (const set of bleed.unbound) { + check(() => { + if (c.bindings.some((x) => x.position === 'everyone' && x.set === set)) { + problems.push(`regression control fired again: \`everyone -> ${set}\` was reported as a junction binding,` + + ` but ${set} declares no \`isDefault\`. The isDefault look-ahead is reading past the declaration's own` + + ' object literal again -- see CONTROLS.isDefaultBleed.'); + } + }); } for (const ac of AUDIT_CONTROLS) { @@ -772,9 +864,11 @@ function selfTest({ quiet = false } = {}) { problems.push(`audit control threw: ${ac.label} -- ${e?.message}`); continue; } - if (!ac.expect(result)) { - problems.push(`audit control FAILED: ${ac.label}\n got: ${JSON.stringify(result)}`); - } + check(() => { + if (!ac.expect(result)) { + problems.push(`audit control FAILED: ${ac.label}\n got: ${JSON.stringify(result)}`); + } + }); } if (problems.length > 0) { @@ -783,6 +877,53 @@ function selfTest({ quiet = false } = {}) { return 1; } if (!quiet) { + // -- The floor: the declared battery RAN, and ran its cases (#13489) ---- + // + // Evaluated after every site 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 cases EQUALS the set declared. + // + // ⭐ It lives INSIDE the `!quiet` branch on purpose. `main()` gates the + // PRODUCTION path on `selfTest({ quiet: true })`, which prints no verdict + // and therefore makes no claim for a floor to guard; evaluating the floor + // there would let a roster edit change what a census run outputs. The floor + // belongs to the `--self-test` verdict, which is the line that would + // otherwise claim controls hold that never ran. + // + // It reports in this file's own idiom -- a stderr block and a non-zero + // return -- and returns BEFORE `selfTestReachedVerdict` is set, so a breach + // reds through the #13798 handshake as well as through the exit code. + const floorFailures = []; + const declaredBatteries = Object.keys(SELF_TEST_BATTERIES); + if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) { + floorFailures.push( + `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; + floorFailures.push( + `self-test battery "${name}" registered ${count} case(s) but is not declared in ` + + 'SELF_TEST_BATTERIES -- a case 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; + floorFailures.push( + count === 0 + ? `self-test battery "${name}" DID NOT RUN -- 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` + + 'The verdict below would have claimed those controls 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 (floorFailures.length > 0) { + process.stderr.write(`x measure-position-name-fold-census self-test floor (${floorFailures.length} breach(es))\n\n${ + floorFailures.map((f) => ` - ${f}`).join('\n\n')}\n\n 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.\n\n`); + return 1; + } process.stdout.write( `✓ measure-position-name-fold-census self-test: junction anchor (${a.position} -> ${a.set}) classified as` + ` a junction binding and absent from the name-dependency list; name-fold anchor (${b.name}) detected` From 6f2fc4e4635a5ba4f1ef5f05bbb8ca83ee66a826 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:54:19 +0000 Subject: [PATCH 3/4] test(scripts): floor check-override-consistency's self-test on its table rows Recipe A as the pilot PR #15271 settled it: each of the 17 row labels of the literal `cases` table is a declared battery with a floor of 1, `registerCase(name)` is the first statement of the driving loop body, and the roster is a LITERAL the table is cross-checked against so a deleted or renamed row names ITSELF rather than deleting its own floor. Roster size pinned at 17; duplicate labels are refused from both sides. The three `// --- ... ---` comments inside the array are grouping rules, not section heads. The boolean `passed` becomes a `failures` counter so the red line can carry the recipe's wording, `N failure(s) (cases and floor)`; no assertion condition is touched and the green line is byte-identical to the base tree's. Part of #13799 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-override-consistency.mjs | 128 ++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/scripts/check-override-consistency.mjs b/scripts/check-override-consistency.mjs index 142dc2a6aa..c2a6716458 100644 --- a/scripts/check-override-consistency.mjs +++ b/scripts/check-override-consistency.mjs @@ -429,6 +429,63 @@ const FIXTURE_LOCKFILE = [ '', ].join('\n'); +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// `--self-test` reaching its verdict 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 PR #13487 way: what is pinned is the registered NAMES, not a +// number. +// +// This self-test is TABLE-DRIVEN -- one literal `cases` table, one loop over it, +// and a sink that writes only when a case FAILS. Routing THAT sink through +// `registerCase()` would register a case only when it fails: a fully green run +// would register 0 and every battery would read DID NOT RUN, the floor inverted +// rather than installed. So the roster is the table's own rows. Each row LABEL +// is a declared battery, verbatim, with a floor of 1, and `registerCase(name)` +// is the first statement of the driving loop body -- so the case is attributed +// to the row actually being run, whatever that row asserts afterwards. There is +// no `battery()` opener: for a table-driven self-test the ROW is the battery. +// +// The three `// --- ... ---` comments inside the table are grouping rules, not +// section heads, and a comment is NOT promoted to one -- the rows are the +// batteries either way. +// +// A pinned TOTAL is not the repair, and neither is a roster DERIVED from the +// table: `cases.length` moves with the table, so a deleted row would delete its +// own floor. The roster below is a LITERAL the table is checked against, which +// is what lets a deleted or renamed row name ITSELF in the refusal. +// +// The counts are a FLOOR, not an equality -- a row that grows into several +// registrations must not red. 1 is the honest floor for a table row: the loop +// reaches it exactly once per run. +const SELF_TEST_BATTERIES = Object.freeze({ + 'lockfile parses into a consumer index': 1, + 'unparseable lockfile -> census skipped, never a crash': 1, + 'lockfile that is not a mapping -> census skipped': 1, + 'transitive consumer present (snapshot pulls undici) -> NOT reported': 1, + 'workspace importer counts as a consumer (semver) -> NOT reported': 1, + 'zero consumers (nothing pulls form-data) -> REPORTED': 1, + 'census reports only the idle one out of a mixed set': 1, + 'bound equal to the target floor (the #5032 undici shape) -> REPORTED': 1, + 'bound below the target floor (uncovered gap) -> REPORTED': 1, + 'bound above the target version line (the durable shape) -> NOT reported': 1, + 'selector with no upper bound -> NOT reported': 1, + 'inclusive upper bound covers the target -> NOT reported': 1, + 'declared range that reaches the target -> no violation': 1, + 'declared range that cannot reach the target -> violation': 1, + 'no implicit prereleases: ^1.7.0 does not reach 1.7.0-rc.2': 1, + 'declaration outside the selector scope -> override does not apply': 1, + 'declaration inside the selector scope -> override applies': 1, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. This pin is also half of +// the duplicate-label refusal: two rows sharing a label collapse to ONE key in +// the literal above, so the roster falls below this number; the table +// cross-check in the floor block is the other half, and names WHICH label +// collided. +const SELF_TEST_BATTERY_FLOOR = 17; + // 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 @@ -542,9 +599,16 @@ function selfTest() { }, ]; - let passed = true; + // The ledger this self-test's floor is evaluated against (#13489). + const batterySeen = new Map(); + const registerCase = (name) => { + batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1); + }; + + let failures = 0; console.log('check-override-consistency self-test (both directions):'); for (const testCase of cases) { + registerCase(testCase.name); let actual; try { actual = testCase.actual(); @@ -552,15 +616,71 @@ function selfTest() { actual = `threw: ${error.message}`; } const ok = actual === testCase.expect; - if (!ok) passed = false; + if (!ok) failures++; console.log( `${ok ? ' ✓' : ' ✗'} ${testCase.name}` + (ok ? '' : `\n expected ${JSON.stringify(testCase.expect)}, got ${JSON.stringify(actual)}`), ); } - if (!passed) { - console.error('\n✗ self-test failed — this check does not do what it claims.'); + // -- The floor: every declared row RAN, and ran its case (#13489) -------- + // + // Evaluated after every row 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 rows + // that registered EQUALS the set declared. A set difference names WHICH row + // stopped; a count says only that something did. + const floorFailure = (message) => { + console.error(`✗ self-test floor: ${message}`); + failures++; + }; + 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.`, + ); + } + const rowLabels = cases.map((c) => c.name); + const duplicated = [...new Set(rowLabels.filter((name, i) => rowLabels.indexOf(name) !== i))]; + if (duplicated.length > 0) { + floorBreached = true; + floorFailure( + `the cases table uses ${duplicated.map((n) => JSON.stringify(n)).join(', ')} as a row label more than once — ` + + 'two rows sharing a label are ONE battery, so the second can stop running while the first keeps the floor met.', + ); + } + 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 — a case 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 that case holds.' + : `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 (a deleted row, a renamed label, a loop that no longer ' + + 'reaches it) and restore it.', + ); + } + + if (failures) { + console.error(`\n✗ check-override-consistency self-test: ${failures} failure(s) (cases and floor).`); process.exit(1); } console.log( From 9965c88d35c11ec3d22f1e474ef82cc136d20463 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 09:54:19 +0000 Subject: [PATCH 4/4] test(scripts): floor check-skill-line-ratchet's self-test on all 155 table rows Recipe A per the pilot PR #15271: every row label is a declared battery with a floor of 1, `registerCase(name)` is the first statement of the driving loop, the roster is a LITERAL cross-checked against the table, size pinned at 155. The only deleted line is the red-run verdict, reworded to the recipe's `N failure(s) (cases and floor)`; the green line stays byte-identical. The batch-8 census flagged this table's four `...(() => { ... })()` spreads as an IIFE-produced block whose rows could not take a literal roster key. Measured, that premise does not hold here: each IIFE is a SCOPING device that declares local fixture consts and then returns an array of LITERAL [label, actual, expected] rows. No label is a template string or computed, and no row comes from a map/push/loop. Three independent readings agree on 155 -- labels extracted from source, literal row starts, and the `cases.length` the green line prints on a run -- so ALL 155 rows are floored with no residue, rather than the partition the row anticipated. Part of #13799 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/pm/check-skill-line-ratchet.mjs | 268 +++++++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-) diff --git a/scripts/pm/check-skill-line-ratchet.mjs b/scripts/pm/check-skill-line-ratchet.mjs index 5fd99aef30..1cdaba9efd 100644 --- a/scripts/pm/check-skill-line-ratchet.mjs +++ b/scripts/pm/check-skill-line-ratchet.mjs @@ -1488,6 +1488,209 @@ function run() { if (failed) process.exit(1); } +// -- The self-test's own battery roster and floor (#13489) ------------------ +// +// `--self-test` reaching its verdict 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 PR #13487 way: what is pinned is the registered NAMES, not a +// number. +// +// This self-test is TABLE-DRIVEN -- one `cases` table, one loop over it, and a +// sink (`failed++`) that writes only when a case FAILS. Routing THAT sink +// through `registerCase()` would register a case only when it fails: a fully +// green run would register 0 and every battery would read DID NOT RUN, the +// floor inverted rather than installed. So the roster is the table's own rows. +// Each row LABEL is a declared battery, verbatim, with a floor of 1, and +// `registerCase(name)` is the first statement of the driving loop body -- so the +// case is attributed to the row actually being run. There is no `battery()` +// opener: for a table-driven self-test the ROW is the battery. +// +// ⭐ ALL 155 rows are floored, the four `...(() => { ... })()` spreads included. +// Those spreads were flagged in the batch-8 census as an IIFE-produced block +// whose rows could not take a literal roster key. Measured here, that premise +// does not hold for this file: each IIFE is a SCOPING device that declares +// local fixture consts and then `return [...]`s an array of LITERAL +// `[label, actual, expected]` rows. No row label is a template string, none is +// computed, and no row is produced by a `map`/`push`/loop. Three independent +// readings agree on 155 -- the source labels extracted by indentation, the +// literal row starts, and the `cases.length` the green line prints on a run -- +// so nothing here is the `extra`-call residue of PR #15286, and leaving any row +// outside the roster would have been the lossy reading. +// +// A pinned TOTAL is not the repair, and neither is a roster DERIVED from the +// table: `cases.length` moves with the table, so a deleted row would delete its +// own floor. The roster below is a LITERAL the table is checked against, which +// is what lets a deleted or renamed row name ITSELF in the refusal. +// +// The counts are a FLOOR, not an equality -- a row that grows into several +// registrations must not red. 1 is the honest floor for a table row: the loop +// reaches it exactly once per run. +const SELF_TEST_BATTERIES = Object.freeze({ + 'under the ceiling -> green': 1, + 'at the ceiling -> green': 1, + 'over the ceiling -> red': 1, + 'red message names the file': 1, + 'red message names the remedy': 1, + 'red message names the authoring rule': 1, + 'empty read -> red, not a skip': 1, + 'every covered file has a positive ceiling': 1, + 'SKILL.md is covered': 1, + 'the dev-agent definition is covered': 1, + 'all five compressed references are covered': 1, + 'all eight lane/seat job descriptions are covered': 1, + 'the other four skills are covered (#9473)': 1, + 'root AGENTS.md is covered (#9792)': 1, + 'root CLAUDE.md is covered (#9965)': 1, + 'references/compile-surfaces.md is covered (#12098)': 1, + 'every separator-less ceiling declares a root-file watch hint': 1, + 'and the declaration names no file the map does not cover': 1, + 'both root instruction files are declared': 1, + 'the declared form is NOT a CEILINGS key': 1, + 'the published skills/ catalog is deliberately uncovered': 1, + 'budget is 120 bytes': 1, + 'a short ASCII line -> green': 1, + 'a long ASCII line -> RED': 1, + 'a short CJK line -> green': 1, + 'a long CJK line -> RED': 1, + 'the RED message names the budget': 1, + 'the RED message names the line number': 1, + 'the RED message offers NO allowlist to add a line to': 1, + 'no offenders -> green verdict': 1, + 'a long line inside a fence is exempt': 1, + '...and the SAME line outside one is RED': 1, + 'scan: a fenced long line yields no offender': 1, + 'scan: the fence closes again': 1, + 'a long table row is exempt': 1, + '...and the same cells as prose are RED': 1, + 'a long heading is exempt': 1, + '...and the same text as a paragraph is RED': 1, + 'front matter is exempt': 1, + 'scan: front matter closes at the second ---': 1, + 'an anchored Blocked-by: line is exempt': 1, + 'Restart-when: too': 1, + 'Restart-touch: too': 1, + 'but a MID-PROSE mention is not exempt — the escape hatch is line-anchored only': 1, + 'a long blockquote line is exempt': 1, + '...including one indented inside a list': 1, + '...and the same quotation unquoted is RED': 1, + 'a bare over-long URL is exempt': 1, + 'a single over-long code span is exempt': 1, + '...but prose LEADING to that URL is RED (wrap first, URL lands alone)': 1, + 'wrapLine splits a long CJK line': 1, + 'every wrapped CJK segment is within budget': 1, + 'wrapping a CJK line changes NOTHING but whitespace': 1, + 'wrapping an ASCII line changes NOTHING but whitespace': 1, + 'a list continuation is indented under the marker': 1, + 'no continuation line opens a new markdown block': 1, + 'wrapLine is idempotent — its output is the canonical form': 1, + 'wrapLine leaves a short line untouched': 1, + 'wrapLine never breaks inside a code span': 1, + 'wrapLine never breaks inside a 「…」 ruling quote': 1, + '...nor inside a 『…』 one': 1, + 'a quote longer than the budget makes its line unbreakable, not RED': 1, + 'a line OPENING an unterminated 「 is exempt': 1, + 'a line INSIDE an open quote is exempt': 1, + '...and the same line outside one is RED — the exemption is the quote, not the text': 1, + 'a SHORT line inside a quote is simply green, not counted exempt': 1, + 'advanceState opens on an unmatched 「': 1, + 'advanceState closes on the matching 」': 1, + 'a quote opened and closed on ONE line does not open the state': 1, + 'advanceState does not track quotes inside a fence': 1, + 'scan: a two-line quote yields quotation exemptions, not offenders': 1, + 'wrapLine never strands a closing 。 at a line head': 1, + 'an ASCII , after a Han character is a no-break-after mark': 1, + '...and ; and : are the same mark class': 1, + '...but after a LATIN word it stays an ordinary break point': 1, + '...and a mark with a space before it follows nothing': 1, + '...and an atom not ending in one is never the mark': 1, + 'breakLegal refuses the break after a Han+ASCII mark': 1, + '...a mark with no following space was never a break point to begin with': 1, + '...still allows the CJK-to-CJK break one atom earlier': 1, + '...and still allows an ordinary ASCII space break': 1, + 'wrapLine splits the trap line': 1, + 'no wrapped line ends on a Han+ASCII mark — the break is no longer OFFERED': 1, + '...the line it took instead is still within budget': 1, + '...and it moved only whitespace, as every wrap must': 1, + 'the SAME shape with a Latin word before the mark still breaks there': 1, + 'wrapLine is still idempotent under the new rule': 1, + 'a long table row is EXEMPT from the 120-byte line rule': 1, + '...and the same row is METERED by its file pin': 1, + 'at exactly the pin -> green': 1, + 'one byte wider -> RED (widening a cell is the measured defect)': 1, + 'narrower than the pin -> green': 1, + 'a pin of 0 is a measurement — a file with no table row passes it': 1, + '...and the FIRST table row in such a file is RED': 1, + 'a missing pin is RED, not a skip (#4690)': 1, + '...and says so rather than naming a width': 1, + 'the RED message names the width': 1, + 'the RED message names the line': 1, + 'the RED message names the remedy': 1, + 'the RED message names consolidation as the way to pay it down': 1, + 'the RED message offers NO allowlist': 1, + 'the RED message says raising needs a maintainer ruling': 1, + 'scanTableRows finds the widest row': 1, + '...and reports its line number': 1, + 'a `|` line inside a FENCE is not a table row': 1, + 'a `|` line in FRONT MATTER is not a table row': 1, + 'a file with no table row measures 0': 1, + 'every ceilinged file carries a pin': 1, + 'and the pin map names no file the ceiling map does not cover': 1, + 'every pin is a non-negative integer': 1, + 'the published skills/ catalog is uncovered here too': 1, + 'the citation is the ruling this file was given': 1, + 'cross-file move — a raise covered by its sources\' net decrease PASSES': 1, + '...and the green verdict states the arithmetic it read': 1, + 'cross-file move — a raise whose sources did NOT shrink is RED (the whole defect: a `move` that licenses an ordinary raise would run green forever)': 1, + '...and the RED verdict names the raise and the net decrease it fell short of': 1, + '...and sends the author to the ordinary path rather than to a bigger declaration': 1, + 'cross-file move — a raise EXCEEDING the net decrease is RED, even by one line': 1, + '...while a raise exactly equal to it is legal': 1, + 'cross-file move — a declaration citing no ruling is RED, however sound its arithmetic': 1, + '...and the RED verdict spells the citation it wanted': 1, + 'cross-file move — declarations whose net movement is 0 leave the map total unchanged': 1, + 'cross-file move — a declaration whose participants net POSITIVE fails the total (+2 here)': 1, + '...and the RED total names the lines it grew by': 1, + '...and a net-negative move reports the corpus shrinking': 1, + 'cross-file move — one source may not pay for two destinations': 1, + 'cross-file move — a destination lowered back to or below its pre-move ceiling reads as PAID DOWN, never as red (lowering is always legitimate)': 1, + '...and says so rather than reporting an arithmetic it can no longer measure': 1, + 'cross-file move — a SOURCE grown back past what it paid re-opens the move (the loophole: the destination keeps the lines while the payers grow back)': 1, + 'cross-file move — an unknown destination is RED, not a skip (#4690)': 1, + 'cross-file move — an unknown source is RED, not a skip (#4690)': 1, + 'cross-file move — a file may not pay its own raise': 1, + 'cross-file move — a declaration naming no source at all is RED': 1, + 'every live declaration cites the ruling': 1, + 'every live declaration names its destination\'s pre-move ceiling and at least one source': 1, + 'every live participant is a file this map covers': 1, + 'ruled raise — a destination that later took an ordinary ruled raise PASSES with `was` at its literal pre-move value': 1, + '...and the verdict prices the MOVE, not the ruling: +10 against the sources\' net 11': 1, + '...and names the ruled lines it took out, so the arithmetic can be read back': 1, + '...while the SAME tree with the raise unrecorded is the double red this record ends': 1, + '...whose first half reads the ruling as the move\'s: +44 against 11': 1, + '...and which now sends the author to the record rather than to `was`': 1, + 'ruled raise — the map-wide total subtracts it too, and reads the corpus DOWN 1': 1, + '...where the unrecorded twin reports the corpus growing by 33': 1, + 'ruled raise — a record quoting NO ruling is RED (the licence is the maintainer\'s or it does not exist)': 1, + '...and the RED verdict says what it wanted': 1, + '...while an ENGLISH ruling quoted the way this map already quotes one passes': 1, + 'ruled raise — a record with no line count is RED': 1, + 'ruled raise — a NEGATIVE line count is RED: lowering is always legitimate and is never recorded here': 1, + 'ruled raise — a record with no ruling DATE is RED': 1, + 'ruled raise — records claiming MORE lines than the ceiling stands above `was` are RED': 1, + '...which is the loophole that closes: an inflated record would otherwise read the move as paid down and pass forever': 1, + 'ruled raise — a destination whose whole rise is ruled keeps none of it for the move, and reads as paid down': 1, + 'ruled raise — a declaration carrying no record behaves exactly as before (every case above this group is one)': 1, + 'every live ruled-raise record quotes its ruling, dates it, and names a positive line count': 1, +}); + +// DELETING an entry silences that battery's floor exactly as effectively as +// zeroing it, so the roster's own size is pinned too. This pin is also half of +// the duplicate-label refusal: two rows sharing a label collapse to ONE key in +// the literal above, so the roster falls below this number; the table +// cross-check in the floor block is the other half, and names WHICH label +// collided. +const SELF_TEST_BATTERY_FLOOR = 155; + // 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 @@ -1786,14 +1989,77 @@ function selfTest() { ]; })(), ].map((c) => (Array.isArray(c[1]) || (c[1] && typeof c[1] === 'object') ? [c[0], JSON.stringify(c[1]), JSON.stringify(c[2])] : c)); + // The ledger this self-test's floor is evaluated against (#13489). + const batterySeen = new Map(); + const registerCase = (name) => { + batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1); + }; + let failed = 0; for (const [name, actual, expected] of cases) { + registerCase(name); const ok = actual === expected; if (!ok) failed++; console.log(` ${ok ? '✓' : '✗'} ${name}`); } + // -- The floor: every declared row RAN, and ran its case (#13489) -------- + // + // Evaluated after every row 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 rows + // that registered EQUALS the set declared. A set difference names WHICH row + // stopped; a count says only that something did. + const floorFailure = (message) => { + console.error(`✗ self-test floor: ${message}`); + failed++; + }; + 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.`, + ); + } + const rowLabels = cases.map(([name]) => name); + const duplicated = [...new Set(rowLabels.filter((name, i) => rowLabels.indexOf(name) !== i))]; + if (duplicated.length > 0) { + floorBreached = true; + floorFailure( + `the cases table uses ${duplicated.map((n) => JSON.stringify(n)).join(', ')} as a row label more than once — ` + + 'two rows sharing a label are ONE battery, so the second can stop running while the first keeps the floor met.', + ); + } + 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 — a case 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 that case holds.' + : `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 (a deleted row, a renamed label, a loop that no longer ' + + 'reaches it) and restore it.', + ); + } + if (failed) { - console.error(`✗ check-skill-line-ratchet self-test: ${failed} of ${cases.length} case(s) failed.`); + console.error(`✗ check-skill-line-ratchet self-test: ${failed} failure(s) (cases and floor).`); process.exit(1); } console.log(`✓ check-skill-line-ratchet self-test: ${cases.length} cases pass.`);