Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 157 additions & 1 deletion scripts/check-comment-mask-corpus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,138 @@ async function main(argv) {
// Self-test -- the comparator, not the corpus
// ---------------------------------------------------------------------------

// ── The self-test's own battery roster and floor (#13489) ──────────────────
//
// `failures.length === 0` used to be this self-test's ONLY success condition,
// so "every case held" and "the cases never ran" printed the same line — and
// the `SELF_TEST_CASE_COUNT` the sweep's green line prints is DERIVED from the
// same array, so a deleted case shrinks the printed number with it and the gate
// stays green. Closed the way PR #13487 validated on check-doc-authoring: what
// is pinned is the registered NAMES, not a number.
//
// This file declares ONE battery, opened at the top of `runSelfTestCases()`'s
// 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: that is a judgement per comment this transplant does not make. The
// hoisted single battery is the shape PRs #14896, #15003 and #15217 landed for
// this case.
//
// ── Why the LEDGER is module-level and the CHECK sits at the verdict site ──
//
// This gate splits its self-test in two: `runSelfTestCases()` REGISTERS and
// returns its cases, and `selfTest()` DECIDES — it prints the per-case lines,
// the red line or the green one. There is no verdict site inside the
// registering body, so the floor is evaluated where the green line already is
// (inside `selfTest()`, reached only from the `--self-test` branch of the
// dispatch). The ledger it reads therefore has to outlive
// `runSelfTestCases()`'s frame — hence module scope rather than the local map
// the single-body recipe closes over. Only the CHECK's location moves;
// attribution and scope are untouched. This is the class-3 placement PR #15309
// settled.
//
// ⚠️ `main()` — the PRODUCTION path — also calls `runSelfTestCases()`, on every
// sweep, so the registrations happen there too. The floor is deliberately NOT
// evaluated on that path: it lives in `selfTest()`, which the sweep never
// calls. Scoping it that way is what keeps a corpus sweep from acquiring a
// refusal that belongs to the `--self-test` verdict.
//
// ⛔ The floor is NOT placed at the end of `runSelfTestCases()` before its
// `return`: an early return anywhere above that line would skip the check
// entirely — the exact defect the #13798 verdict handshake exists to catch —
// coupling hole 1 to hole 2 after the card ruled them orthogonal. It would also
// fire on every production sweep. Evaluated at the verdict site, the same early
// return lands as a count BELOW the floor and reds, in `--self-test` alone.
//
// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3
// keeps a total "right" the moment a sibling grows.
//
// The count is a FLOOR, not an equality — adding cases is ordinary work and must
// not red. A battery BELOW its floor means cases stopped running; the remedy is
// to find what stopped registering.
const SELF_TEST_BATTERIES = Object.freeze({
'check-comment-mask-corpus self-test': 12,
});

// 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 a case 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)';

// The battery ledger, read by `batteryFloorFailures()` below from the OTHER
// function.
//
// ⚠️ Named for the roster's role, deliberately NOT with a self-test spelling:
// `check:pm-dispatch-gates` anchors on a top-level declaration whose NAME spells
// self-test and every such name owes a row in its COMPOUND_ANCHOR_LEDGER. This
// machinery holds no fixtures to mask and reads no path literal, so the accurate
// name is the one that says `battery`.
const batterySeen = new Map();
let openBattery = null;

/** Open a battery. Every case registered after this line is attributed to it. */
function battery(name) {
openBattery = name;
}

/** Called by `runSelfTestCases()`'s own case sink, once per case. */
function registerCase() {
const name = openBattery ?? UNATTRIBUTED_BATTERY;
batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1);
}

/**
* The floor: every declared battery RAN, and ran its cases (#13489).
*
* Guards the registrations made by **`runSelfTestCases()`** — the body whose
* case sink `ok()` routes through `registerCase()`. It is called from
* `selfTest()` immediately before the success line, so that line can only be
* printed by a run in which the set of batteries that registered cases EQUALS
* the set declared, each at or above its own count. A set difference says WHICH
* battery stopped; a count says only that something did.
*
* @returns {string[]} floor breaches; empty means the floor held
*/
function batteryFloorFailures() {
const declared = Object.keys(SELF_TEST_BATTERIES);
const problems = [];
if (declared.length < SELF_TEST_BATTERY_FLOOR) {
problems.push(
`SELF_TEST_BATTERIES declares ${declared.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 (declared.includes(name)) continue;
problems.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 declared) {
const count = batterySeen.get(name) ?? 0;
if (count >= SELF_TEST_BATTERIES[name]) continue;
problems.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 (problems.length) {
problems.push(
'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.',
);
}
return problems;
}

/**
* What these cases hold: that a disagreement is REPORTED, in both directions,
* and that the shebang reconciliation is applied. They run against the real
Expand All @@ -411,10 +543,16 @@ async function main(argv) {
export let SELF_TEST_CASE_COUNT = 0;

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

const flagNothing = (source) => ({ comment: new Uint8Array(source.length) });
const flagEverything = (source) => ({ comment: new Uint8Array(source.length).fill(1) });
Expand Down Expand Up @@ -492,6 +630,24 @@ export async function selfTest() {
console.error(`\n${failures.length}/${cases.length} self-test case(s) failed.`);
process.exit(EXIT_DISAGREEMENT);
}

// ── The assertion floor, at the verdict site (#13489) ─────────────────────
// `runSelfTestCases()` registers but does not decide, so the floor over ITS
// registrations is evaluated here, after every case has had its chance and
// immediately before the success line — the only place a run that registered
// nothing can still be stopped from reporting that every case held. It sits
// in `selfTest()`, not in the registering body, so the production sweep in
// `main()` — which calls `runSelfTestCases()` too — never reaches it.
const floorProblems = batteryFloorFailures();
if (floorProblems.length) {
console.error(
`\n✗ check-comment-mask-corpus self-test: the assertion floor over runSelfTestCases()'s `
+ `registrations was breached (${floorProblems.length} problem(s)); every case that DID run passed.`,
);
for (const problem of floorProblems) console.error(` - ${problem}`);
process.exit(EXIT_DISAGREEMENT);
}

console.log(`\nAll ${cases.length} self-test cases passed.`);

return SELF_TEST_VERDICT;
Expand Down
174 changes: 167 additions & 7 deletions scripts/check-osv-exemptions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,153 @@ function validateLedger(text, today) {
return { problems, count: entries.length };
}

/** @returns {{ passed: boolean, lines: string[] }} */
// ── The self-test's own battery roster and floor (#13489) ──────────────────
//
// `passed` 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 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's
// `name` 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, so attribution is the loop variable rather than a
// most-recently-opened section.
//
// ⛔ 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.
//
// ── Why the LEDGER is module-level and the CHECK sits at the verdict site ──
//
// This gate splits its self-test in two: `selfTest()` REGISTERS and returns its
// failure count, and `main()` DECIDES — it prints the per-case lines, the red
// line or the green one. There is no verdict site inside the registering body,
// so the floor is evaluated where the green line already is (inside `main()`'s
// `--self-test` branch, so it can never fire on a production run). The ledger it
// reads therefore has to outlive `selfTest()`'s frame — hence module scope
// rather than the local map the single-body recipe closes over. Only the CHECK's
// location moves; attribution and scope are untouched. This is the class-3
// placement PR #15309 settled.
//
// ⛔ The floor is NOT placed at the end of `selfTest()` before its `return`: an
// early return anywhere above that line would skip the check entirely — the
// exact defect the #13798 verdict handshake exists to catch — coupling hole 1
// to hole 2 after the card ruled them orthogonal. Evaluated at the verdict site,
// the same early return lands as a count BELOW the floor and reds.
const SELF_TEST_BATTERIES = Object.freeze({
'missing/empty ledger → green': 1,
'comments-only ledger (zero exemptions) → green': 1,
'well-formed exemption inside the window → green': 1,
'multi-line reason → green': 1,
'expired ignoreUntil → red': 1,
'ignoreUntil == today → red (the scanner already stopped ignoring it)': 1,
'missing ignoreUntil → red': 1,
'quoted ignoreUntil → red': 1,
'ignoreUntil beyond the ceiling → red': 1,
'reason without an advisory link → red': 1,
'reason that is only a link → red': 1,
'untouched template placeholders → red': 1,
'missing reason → red': 1,
'unknown key → red': 1,
'duplicate id → red': 1,
'[[PackageOverrides]] escape hatch → red': 1,
'top-level key outside a table → red': 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 name collapse to ONE key in
// the literal above, so the roster falls below this number; the table
// cross-check in `batteryFloorFailures()` is the other half, and names WHICH
// label collided.
const SELF_TEST_BATTERY_FLOOR = 17;

// The ledger `batteryFloorFailures()` reads from the OTHER function, and the
// row labels the table actually presented on this run — both module-level
// because the body that fills them is not the body that reads them.
//
// ⚠️ Named for the roster's role, deliberately NOT with a self-test spelling:
// `check:pm-dispatch-gates` anchors on a top-level declaration whose NAME spells
// self-test and every such name owes a row in its COMPOUND_ANCHOR_LEDGER. This
// machinery holds no fixtures to mask and reads no path literal, so the accurate
// name is the one that says `battery`.
const batterySeen = new Map();
let batteryRowLabels = [];

/** Called by `selfTest()`'s driving loop, once per row, before the row runs. */
function registerCase(name) {
batterySeen.set(name, (batterySeen.get(name) ?? 0) + 1);
}

/**
* The floor: every declared row RAN, and ran its case (#13489).
*
* Guards the registrations made by **`selfTest()`** — the body whose driving
* loop calls `registerCase()`. It is called from `main()`'s `--self-test`
* branch immediately before the success line, so that line can only be printed
* by a run in which the set of rows that registered EQUALS the set declared,
* each at or above its own count. A set difference says WHICH row stopped; a
* count says only that something did.
*
* @returns {string[]} floor breaches; empty means the floor held
*/
function batteryFloorFailures() {
const declared = Object.keys(SELF_TEST_BATTERIES);
const problems = [];
if (declared.length < SELF_TEST_BATTERY_FLOOR) {
problems.push(
`SELF_TEST_BATTERIES declares ${declared.length} batteries, below the pinned ` +
`${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
);
}
const duplicated = [...new Set(batteryRowLabels.filter((name, i) => batteryRowLabels.indexOf(name) !== i))];
if (duplicated.length > 0) {
problems.push(
`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 (declared.includes(name)) continue;
problems.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 declared) {
const count = batterySeen.get(name) ?? 0;
if (count >= SELF_TEST_BATTERIES[name]) continue;
problems.push(
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 (problems.length) {
problems.push(
'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.',
);
}
return problems;
}

/** @returns {{ failures: number, lines: string[] }} */
// Set by `selfTest()` only after its verdict is printed, and read at the
// dispatch: a `return` that leaves the function above that line prints nothing
// and still exits 0 — a self-test that never finished, reported as one that
Expand Down Expand Up @@ -479,23 +625,27 @@ function selfTest() {
];

const lines = [];
let passed = true;
// The row labels this run actually presented, for the floor's table
// cross-check at the verdict site (#13489).
batteryRowLabels = cases.map((testCase) => testCase.name);
let failures = 0;
for (const testCase of cases) {
registerCase(testCase.name);
const { problems } = validateLedger(testCase.text, today);
let ok;
if (testCase.expect === null) {
ok = problems.length === 0;
} else {
ok = problems.length > 0 && problems.some((p) => testCase.expect.test(p));
}
if (!ok) passed = false;
if (!ok) failures++;
lines.push(
`${ok ? ' ✓' : ' ✗'} ${testCase.name}` +
(ok ? '' : `\n got: ${problems.length === 0 ? '(no problems)' : problems.join('\n ')}`),
);
}
selfTestReachedVerdict = true;
return { passed, lines };
return { failures, lines };
}

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