Skip to content

Commit a141904

Browse files
committed
test(scripts): class-3 assertion floors at the verdict site for four self-tests
The class-3 shape: the body that REGISTERS cases is not the body that prints the verdict, so the floor is evaluated at the verdict site — the place that prints the green line — with roster, sink and ledger at module scope. Copies the placement PR #15309 settled. - check-osv-exemptions: recipe A over the 17 literal table rows; floor at the verdict site in main()'s --self-test branch. Boolean `passed` converted to a `failures` counter so the floor shares the counted sink (sink/verdict lines only). - typecheck-configs: recipe A over BOTH literal tables under one roster (11 keys, size pin 11); check in the --self-test dispatch block, so the two importers that fold this self-test in never reach it. - check-comment-mask-corpus: concise arrow sink converted to a block-bodied helper calling registerCase(); one hoisted battery, floor 12; check at the verdict site in selfTest(), so the production sweep in main() — which calls runSelfTestCases() too — never reaches it. SELF_TEST_CASE_COUNT and its production read are untouched. - check-workspace-manifest-cycles: Tier B multi-battery, one battery per named section banner (7), floors measured per battery on a run; check at the verdict site in runSelfTest(). Every floor is read off a run with the roster pinned at 9999, never counted from source. No assertion condition, message or argument is changed. Also adds the one ENTRY_BY_HAND row the instrument needs to probe check-workspace-manifest-cycles at all: it defines two self-test-shaped functions and reads NOT MEASURED without it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
1 parent 1036776 commit a141904

5 files changed

Lines changed: 660 additions & 8 deletions

‎scripts/check-comment-mask-corpus.mjs‎

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,138 @@ async function main(argv) {
400400
// Self-test -- the comparator, not the corpus
401401
// ---------------------------------------------------------------------------
402402

403+
// ── The self-test's own battery roster and floor (#13489) ──────────────────
404+
//
405+
// `failures.length === 0` used to be this self-test's ONLY success condition,
406+
// so "every case held" and "the cases never ran" printed the same line — and
407+
// the `SELF_TEST_CASE_COUNT` the sweep's green line prints is DERIVED from the
408+
// same array, so a deleted case shrinks the printed number with it and the gate
409+
// stays green. Closed the way PR #13487 validated on check-doc-authoring: what
410+
// is pinned is the registered NAMES, not a number.
411+
//
412+
// This file declares ONE battery, opened at the top of `runSelfTestCases()`'s
413+
// body. It carries ZERO named section banners — fewer than the two the
414+
// sectioning criterion needs — and ⛔ a comment is NOT promoted to a section
415+
// head: that is a judgement per comment this transplant does not make. The
416+
// hoisted single battery is the shape PRs #14896, #15003 and #15217 landed for
417+
// this case.
418+
//
419+
// ── Why the LEDGER is module-level and the CHECK sits at the verdict site ──
420+
//
421+
// This gate splits its self-test in two: `runSelfTestCases()` REGISTERS and
422+
// returns its cases, and `selfTest()` DECIDES — it prints the per-case lines,
423+
// the red line or the green one. There is no verdict site inside the
424+
// registering body, so the floor is evaluated where the green line already is
425+
// (inside `selfTest()`, reached only from the `--self-test` branch of the
426+
// dispatch). The ledger it reads therefore has to outlive
427+
// `runSelfTestCases()`'s frame — hence module scope rather than the local map
428+
// the single-body recipe closes over. Only the CHECK's location moves;
429+
// attribution and scope are untouched. This is the class-3 placement PR #15309
430+
// settled.
431+
//
432+
// ⚠️ `main()` — the PRODUCTION path — also calls `runSelfTestCases()`, on every
433+
// sweep, so the registrations happen there too. The floor is deliberately NOT
434+
// evaluated on that path: it lives in `selfTest()`, which the sweep never
435+
// calls. Scoping it that way is what keeps a corpus sweep from acquiring a
436+
// refusal that belongs to the `--self-test` verdict.
437+
//
438+
// ⛔ The floor is NOT placed at the end of `runSelfTestCases()` before its
439+
// `return`: an early return anywhere above that line would skip the check
440+
// entirely — the exact defect the #13798 verdict handshake exists to catch —
441+
// coupling hole 1 to hole 2 after the card ruled them orthogonal. It would also
442+
// fire on every production sweep. Evaluated at the verdict site, the same early
443+
// return lands as a count BELOW the floor and reds, in `--self-test` alone.
444+
//
445+
// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3
446+
// keeps a total "right" the moment a sibling grows.
447+
//
448+
// The count is a FLOOR, not an equality — adding cases is ordinary work and must
449+
// not red. A battery BELOW its floor means cases stopped running; the remedy is
450+
// to find what stopped registering.
451+
const SELF_TEST_BATTERIES = Object.freeze({
452+
'check-comment-mask-corpus self-test': 12,
453+
});
454+
455+
// DELETING an entry silences that battery's floor exactly as effectively as
456+
// zeroing it, so the roster's own size is pinned too.
457+
const SELF_TEST_BATTERY_FLOOR = 1;
458+
459+
// The key a case is filed under when no battery is open. It is not a declared
460+
// battery, so it reds by the same set difference rather than silently inflating
461+
// whichever battery happened to run last.
462+
const UNATTRIBUTED_BATTERY = '(no battery open)';
463+
464+
// The battery ledger, read by `batteryFloorFailures()` below from the OTHER
465+
// function.
466+
//
467+
// ⚠️ Named for the roster's role, deliberately NOT with a self-test spelling:
468+
// `check:pm-dispatch-gates` anchors on a top-level declaration whose NAME spells
469+
// self-test and every such name owes a row in its COMPOUND_ANCHOR_LEDGER. This
470+
// machinery holds no fixtures to mask and reads no path literal, so the accurate
471+
// name is the one that says `battery`.
472+
const batterySeen = new Map();
473+
let openBattery = null;
474+
475+
/** Open a battery. Every case registered after this line is attributed to it. */
476+
function battery(name) {
477+
openBattery = name;
478+
}
479+
480+
/** Called by `runSelfTestCases()`'s own case sink, once per case. */
481+
function registerCase() {
482+
const name = openBattery ?? UNATTRIBUTED_BATTERY;
483+
batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1);
484+
}
485+
486+
/**
487+
* The floor: every declared battery RAN, and ran its cases (#13489).
488+
*
489+
* Guards the registrations made by **`runSelfTestCases()`** — the body whose
490+
* case sink `ok()` routes through `registerCase()`. It is called from
491+
* `selfTest()` immediately before the success line, so that line can only be
492+
* printed by a run in which the set of batteries that registered cases EQUALS
493+
* the set declared, each at or above its own count. A set difference says WHICH
494+
* battery stopped; a count says only that something did.
495+
*
496+
* @returns {string[]} floor breaches; empty means the floor held
497+
*/
498+
function batteryFloorFailures() {
499+
const declared = Object.keys(SELF_TEST_BATTERIES);
500+
const problems = [];
501+
if (declared.length < SELF_TEST_BATTERY_FLOOR) {
502+
problems.push(
503+
`SELF_TEST_BATTERIES declares ${declared.length} batteries, below the pinned `
504+
+ `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
505+
);
506+
}
507+
for (const [name, count] of batterySeen) {
508+
if (declared.includes(name)) continue;
509+
problems.push(
510+
`self-test battery "${name}" registered ${count} case(s) but is not declared in `
511+
+ 'SELF_TEST_BATTERIES — a case attributed to no declared battery is one nothing floors.',
512+
);
513+
}
514+
for (const name of declared) {
515+
const count = batterySeen.get(name) ?? 0;
516+
if (count >= SELF_TEST_BATTERIES[name]) continue;
517+
problems.push(
518+
count === 0
519+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. `
520+
+ 'The verdict below would have claimed those cases hold.'
521+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of `
522+
+ `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`,
523+
);
524+
}
525+
if (problems.length) {
526+
problems.push(
527+
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the '
528+
+ 'number. Find what stopped registering (an early return, a deleted block, a guard that now '
529+
+ 'skips) and restore it.',
530+
);
531+
}
532+
return problems;
533+
}
534+
403535
/**
404536
* What these cases hold: that a disagreement is REPORTED, in both directions,
405537
* and that the shebang reconciliation is applied. They run against the real
@@ -411,10 +543,16 @@ async function main(argv) {
411543
export let SELF_TEST_CASE_COUNT = 0;
412544

413545
async function runSelfTestCases(parse) {
546+
// The single hoisted battery this body's cases are attributed to. The floor
547+
// that reads them is evaluated at the verdict site in `selfTest()`.
548+
battery('check-comment-mask-corpus self-test');
414549
const { scanSource } = await import('./js-comment-mask.mjs');
415550
const failures = [];
416551
const cases = [];
417-
const ok = (label, condition) => cases.push({ label, condition: Boolean(condition) });
552+
const ok = (label, condition) => {
553+
registerCase();
554+
cases.push({ label, condition: Boolean(condition) });
555+
};
418556

419557
const flagNothing = (source) => ({ comment: new Uint8Array(source.length) });
420558
const flagEverything = (source) => ({ comment: new Uint8Array(source.length).fill(1) });
@@ -492,6 +630,24 @@ export async function selfTest() {
492630
console.error(`\n${failures.length}/${cases.length} self-test case(s) failed.`);
493631
process.exit(EXIT_DISAGREEMENT);
494632
}
633+
634+
// ── The assertion floor, at the verdict site (#13489) ─────────────────────
635+
// `runSelfTestCases()` registers but does not decide, so the floor over ITS
636+
// registrations is evaluated here, after every case has had its chance and
637+
// immediately before the success line — the only place a run that registered
638+
// nothing can still be stopped from reporting that every case held. It sits
639+
// in `selfTest()`, not in the registering body, so the production sweep in
640+
// `main()` — which calls `runSelfTestCases()` too — never reaches it.
641+
const floorProblems = batteryFloorFailures();
642+
if (floorProblems.length) {
643+
console.error(
644+
`\n✗ check-comment-mask-corpus self-test: the assertion floor over runSelfTestCases()'s `
645+
+ `registrations was breached (${floorProblems.length} problem(s)); every case that DID run passed.`,
646+
);
647+
for (const problem of floorProblems) console.error(` - ${problem}`);
648+
process.exit(EXIT_DISAGREEMENT);
649+
}
650+
495651
console.log(`\nAll ${cases.length} self-test cases passed.`);
496652

497653
return SELF_TEST_VERDICT;

‎scripts/check-osv-exemptions.mjs‎

Lines changed: 167 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,153 @@ function validateLedger(text, today) {
377377
return { problems, count: entries.length };
378378
}
379379

380-
/** @returns {{ passed: boolean, lines: string[] }} */
380+
// ── The self-test's own battery roster and floor (#13489) ──────────────────
381+
//
382+
// `passed` used to be this self-test's ONLY success condition, so "every case
383+
// held" and "the cases never ran" printed the same line. Closed the way PR
384+
// #13487 validated on check-doc-authoring: what is pinned is the registered
385+
// NAMES, not a number.
386+
//
387+
// This self-test is TABLE-DRIVEN — one literal `cases` table, one loop over it,
388+
// and a sink that writes only when a case FAILS. Routing THAT sink through
389+
// `registerCase()` would register a case only when it fails: a fully green run
390+
// would register 0 and every battery would read DID NOT RUN, the floor inverted
391+
// rather than installed. So the roster is the table's own rows. Each row's
392+
// `name` is a declared battery, verbatim, with a floor of 1, and
393+
// `registerCase(name)` is the FIRST statement of the driving loop body — so the
394+
// case is attributed to the row actually being run, whatever that row asserts
395+
// afterwards. There is no `battery()` opener: for a table-driven self-test the
396+
// ROW is the battery, so attribution is the loop variable rather than a
397+
// most-recently-opened section.
398+
//
399+
// ⛔ A pinned TOTAL is not the repair, and neither is a roster DERIVED from the
400+
// table: `cases.length` moves with the table, so a deleted row would delete its
401+
// own floor. The roster below is a LITERAL the table is checked against, which
402+
// is what lets a deleted or renamed row name ITSELF in the refusal.
403+
//
404+
// The counts are a FLOOR, not an equality — a row that grows into several
405+
// registrations must not red. 1 is the honest floor for a table row: the loop
406+
// reaches it exactly once per run.
407+
//
408+
// ── Why the LEDGER is module-level and the CHECK sits at the verdict site ──
409+
//
410+
// This gate splits its self-test in two: `selfTest()` REGISTERS and returns its
411+
// failure count, and `main()` DECIDES — it prints the per-case lines, the red
412+
// line or the green one. There is no verdict site inside the registering body,
413+
// so the floor is evaluated where the green line already is (inside `main()`'s
414+
// `--self-test` branch, so it can never fire on a production run). The ledger it
415+
// reads therefore has to outlive `selfTest()`'s frame — hence module scope
416+
// rather than the local map the single-body recipe closes over. Only the CHECK's
417+
// location moves; attribution and scope are untouched. This is the class-3
418+
// placement PR #15309 settled.
419+
//
420+
// ⛔ The floor is NOT placed at the end of `selfTest()` before its `return`: an
421+
// early return anywhere above that line would skip the check entirely — the
422+
// exact defect the #13798 verdict handshake exists to catch — coupling hole 1
423+
// to hole 2 after the card ruled them orthogonal. Evaluated at the verdict site,
424+
// the same early return lands as a count BELOW the floor and reds.
425+
const SELF_TEST_BATTERIES = Object.freeze({
426+
'missing/empty ledger → green': 1,
427+
'comments-only ledger (zero exemptions) → green': 1,
428+
'well-formed exemption inside the window → green': 1,
429+
'multi-line reason → green': 1,
430+
'expired ignoreUntil → red': 1,
431+
'ignoreUntil == today → red (the scanner already stopped ignoring it)': 1,
432+
'missing ignoreUntil → red': 1,
433+
'quoted ignoreUntil → red': 1,
434+
'ignoreUntil beyond the ceiling → red': 1,
435+
'reason without an advisory link → red': 1,
436+
'reason that is only a link → red': 1,
437+
'untouched template placeholders → red': 1,
438+
'missing reason → red': 1,
439+
'unknown key → red': 1,
440+
'duplicate id → red': 1,
441+
'[[PackageOverrides]] escape hatch → red': 1,
442+
'top-level key outside a table → red': 1,
443+
});
444+
445+
// DELETING an entry silences that battery's floor exactly as effectively as
446+
// zeroing it, so the roster's own size is pinned too. This pin is also half of
447+
// the duplicate-label refusal: two rows sharing a name collapse to ONE key in
448+
// the literal above, so the roster falls below this number; the table
449+
// cross-check in `batteryFloorFailures()` is the other half, and names WHICH
450+
// label collided.
451+
const SELF_TEST_BATTERY_FLOOR = 17;
452+
453+
// The ledger `batteryFloorFailures()` reads from the OTHER function, and the
454+
// row labels the table actually presented on this run — both module-level
455+
// because the body that fills them is not the body that reads them.
456+
//
457+
// ⚠️ Named for the roster's role, deliberately NOT with a self-test spelling:
458+
// `check:pm-dispatch-gates` anchors on a top-level declaration whose NAME spells
459+
// self-test and every such name owes a row in its COMPOUND_ANCHOR_LEDGER. This
460+
// machinery holds no fixtures to mask and reads no path literal, so the accurate
461+
// name is the one that says `battery`.
462+
const batterySeen = new Map();
463+
let batteryRowLabels = [];
464+
465+
/** Called by `selfTest()`'s driving loop, once per row, before the row runs. */
466+
function registerCase(name) {
467+
batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1);
468+
}
469+
470+
/**
471+
* The floor: every declared row RAN, and ran its case (#13489).
472+
*
473+
* Guards the registrations made by **`selfTest()`** — the body whose driving
474+
* loop calls `registerCase()`. It is called from `main()`'s `--self-test`
475+
* branch immediately before the success line, so that line can only be printed
476+
* by a run in which the set of rows that registered EQUALS the set declared,
477+
* each at or above its own count. A set difference says WHICH row stopped; a
478+
* count says only that something did.
479+
*
480+
* @returns {string[]} floor breaches; empty means the floor held
481+
*/
482+
function batteryFloorFailures() {
483+
const declared = Object.keys(SELF_TEST_BATTERIES);
484+
const problems = [];
485+
if (declared.length < SELF_TEST_BATTERY_FLOOR) {
486+
problems.push(
487+
`SELF_TEST_BATTERIES declares ${declared.length} batteries, below the pinned ` +
488+
`${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
489+
);
490+
}
491+
const duplicated = [...new Set(batteryRowLabels.filter((name, i) => batteryRowLabels.indexOf(name) !== i))];
492+
if (duplicated.length > 0) {
493+
problems.push(
494+
`the cases table uses ${duplicated.map((n) => JSON.stringify(n)).join(', ')} as a row label more than once — ` +
495+
'two rows sharing a label are ONE battery, so the second can stop running while the first keeps the floor met.',
496+
);
497+
}
498+
for (const [name, count] of batterySeen) {
499+
if (declared.includes(name)) continue;
500+
problems.push(
501+
`self-test battery "${name}" registered ${count} case(s) but is not declared in ` +
502+
'SELF_TEST_BATTERIES — a case attributed to no declared battery is one nothing floors.',
503+
);
504+
}
505+
for (const name of declared) {
506+
const count = batterySeen.get(name) ?? 0;
507+
if (count >= SELF_TEST_BATTERIES[name]) continue;
508+
problems.push(
509+
count === 0
510+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` +
511+
'The verdict below would have claimed that case holds.'
512+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` +
513+
`${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`,
514+
);
515+
}
516+
if (problems.length) {
517+
problems.push(
518+
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' +
519+
'number. Find what stopped registering (a deleted row, a renamed label, a loop that no longer ' +
520+
'reaches it) and restore it.',
521+
);
522+
}
523+
return problems;
524+
}
525+
526+
/** @returns {{ failures: number, lines: string[] }} */
381527
// Set by `selfTest()` only after its verdict is printed, and read at the
382528
// dispatch: a `return` that leaves the function above that line prints nothing
383529
// and still exits 0 — a self-test that never finished, reported as one that
@@ -479,23 +625,27 @@ function selfTest() {
479625
];
480626

481627
const lines = [];
482-
let passed = true;
628+
// The row labels this run actually presented, for the floor's table
629+
// cross-check at the verdict site (#13489).
630+
batteryRowLabels = cases.map((testCase) => testCase.name);
631+
let failures = 0;
483632
for (const testCase of cases) {
633+
registerCase(testCase.name);
484634
const { problems } = validateLedger(testCase.text, today);
485635
let ok;
486636
if (testCase.expect === null) {
487637
ok = problems.length === 0;
488638
} else {
489639
ok = problems.length > 0 && problems.some((p) => testCase.expect.test(p));
490640
}
491-
if (!ok) passed = false;
641+
if (!ok) failures++;
492642
lines.push(
493643
`${ok ? ' ✓' : ' ✗'} ${testCase.name}` +
494644
(ok ? '' : `\n got: ${problems.length === 0 ? '(no problems)' : problems.join('\n ')}`),
495645
);
496646
}
497647
selfTestReachedVerdict = true;
498-
return { passed, lines };
648+
return { failures, lines };
499649
}
500650

501651
function main() {
@@ -512,11 +662,21 @@ function main() {
512662
);
513663
process.exit(1);
514664
}
515-
const { passed, lines } = selfTestResult;
665+
const { failures, lines } = selfTestResult;
516666
console.log('check-osv-exemptions self-test (both directions):');
517667
for (const line of lines) console.log(line);
518-
if (!passed) {
519-
console.error('\n✗ self-test failed — the ledger check does not do what it claims.');
668+
// ── The assertion floor, at the verdict site (#13489) ─────────────────
669+
// `selfTest()` registers but does not decide, so the floor over ITS
670+
// registrations is evaluated here, after every row has had its chance and
671+
// immediately before the success line — the only place a run that
672+
// registered nothing can still be stopped from reporting that every case
673+
// held. Its breaches share this branch's counted sink, so one red line
674+
// covers cases and floor alike.
675+
const floorProblems = batteryFloorFailures();
676+
for (const problem of floorProblems) console.error(`✗ self-test floor: ${problem}`);
677+
const total = failures + floorProblems.length;
678+
if (total > 0) {
679+
console.error(`\n✗ check-osv-exemptions self-test: ${total} failure(s) (cases and floor).`);
520680
process.exit(1);
521681
}
522682
console.log('\n✓ self-test passed: valid ledgers accepted, every convention breach rejected.');

0 commit comments

Comments
 (0)