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
34 changes: 34 additions & 0 deletions scripts/check-ratchet-remedy-authority.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,40 @@ const CONTROL = {
why: 'PR for #11341. Refuses by binding a negation to the verb, over a shrink-only baseline holding only the child-environment leaks that predate the gate; their repair is a card per neighbourhood, so no authority is missing.',
},

// The sixth refusal instance, and the first to arrive because a gate's own
// SHRINK TESTIMONY MOVED rather than because its remedy did (#13489). Its
// remedy sentence is unchanged from before that card and was reachable the
// whole time; what changed is proximity. `anchorFor` reads the ±400 characters
// around every mention of a DECLARED target, and this gate's `⛔ SHRINK-ONLY`
// banner sits at the top of a docblock long enough to push the const's own
// name out of that window. A comment added elsewhere in the file named
// SELF_TEST_RUN_OTHERWISE and its shrink-only nature one line apart, stage 2
// reached the target for the first time, and a remedy that had been offering
// registry growth for as long as it existed became visible.
//
// ⛔ The lesson is about this detector, not about that comment: a gate whose
// testimony sits just outside the window is INDISTINGUISHABLE here from one
// with no ratchet at all, and the difference is a docblock's length. Recorded
// rather than engineered around — the window is a deliberate precision choice
// (widening it was measured and rejected), so the honest response is to know
// that `excluded` can mean "not reached yet".
//
// Refusal — not marking — because the ledger's own docblock already says it:
// "Nothing joins this list to silence a finding: a gate that really does ship
// an unrun self-test gets wired instead." The author-facing message had been
// contradicting that sentence; it now agrees with it. The legitimate row still
// exists and is named, as a maintainer's judgement of evidence rather than as
// an escape from wiring. Both refusal limbs fire on the rewritten sentence:
// the negation bound to the verb, and the act denied as its own subject.
//
// ⚠️ The two shapes are quoted HERE and not in the `why` below. Putting them
// in the string made this file read as an instance of its own convention and
// reddened self-test case (1) — measured, on the first attempt at this entry.
'check-self-test-wired.mjs': {
expect: 'refused',
why: 'PR for #13489. Refuses by both limbs at once over a shrink-only ledger — see the sentence quoted in the comment above — because wiring the self-test is the whole remedy and it is the landing author\'s. Reached for the first time when a comment elsewhere in that file put the ledger\'s name and its shrink-only nature one line apart, inside anchorFor\'s window; the remedy sentence itself is older than this card.',
},

// The tenth, and the fourth to join as a NEW gate rather than by retrofit
// (#13582). Marking rather than refusal, on the same reading as the seventh
// through ninth: there IS a legitimate act here and it has an owner. The gate
Expand Down
164 changes: 157 additions & 7 deletions scripts/check-self-test-wired.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ import { maskComments } from './js-comment-mask.mjs';
const ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..');
const WORKFLOW_DIR = '.github/workflows';

// The token every gate in this farm writes when it names a path belonging to a
// maintainer rather than to the landing author (#8435). Declared per gate by
// convention, and read out of AUTHOR-FACING strings — a comment mentioning it
// tells an author nothing, so it has to live in the message itself.
const RATCHET_AUTHORITY_MARKER = '⛔ MAINTAINER-ONLY';

/** Extensions whose files can be a `scripts/` entry point. */
const SCRIPT_EXT = /\.(mjs|mts|js|sh)$/;

Expand Down Expand Up @@ -263,10 +269,13 @@ export function auditPopulation({ carriers, named, selfTested, ledger }) {
' `--self-test`, but no workflow ever executes it with that flag. An unrun\n' +
' self-test is a phantom check, and for a gate whose defect class is its\n' +
' matching rule it is also the ONLY instrument the rule has (#11150).\n' +
` Wire \`node ${script} --self-test\` into the step that runs it. If the\n` +
' self-test is already run some other way — the script drives another tool\'s,\n' +
' or its ordinary run executes its own cases — add a SELF_TEST_RUN_OTHERWISE\n' +
' row in scripts/check-self-test-wired.mjs naming the evidence for that.',
` Wire \`node ${script} --self-test\` into the step that runs it. That is the\n` +
' whole remedy, and it is the landing author\'s.\n' +
' ⛔ Do not add a SELF_TEST_RUN_OTHERWISE row to clear this. That ledger is\n' +
' shrink-only; adding an entry is not the fix, it is this finding written down\n' +
' somewhere quieter. A row records that a self-test genuinely IS run another\n' +
' way — a wrapper drives it, or the ordinary run executes its own cases — and\n' +
` judging that evidence is ${RATCHET_AUTHORITY_MARKER}, never a way out of wiring.`,
});
}
return findings;
Expand Down Expand Up @@ -410,14 +419,89 @@ function main() {
// --self-test
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// The self-test's own battery registry, floor and verdict (#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 dispatch below was `if (--self-test) selfTest()`, which discards the
// call's completion: an early `return` anywhere above the verdict printed
// NOTHING and still exited 0. Measured on 597020aa5 by injecting `return;` as
// the first statement of `selfTest()` -- exit 0, zero bytes of output, in the
// gate whose whole subject is that a shipped self-test actually runs.
//
// Both holes are closed the way PR #13487 validated on check-doc-authoring:
// what is pinned is the registered NAMES, not a number. Every section opens
// with `battery('<name>')`, every `ok()` is attributed to the battery most
// recently opened, and the floor requires the OPENED set to equal the DECLARED
// set with each battery at or above its own count. A set difference names
// WHICH battery stopped running; a count says only that something did.
//
// ⛔ A merely non-zero count is not the repair, and neither is a pinned TOTAL:
// a battery dropping from 9 cases to 3 keeps a total "right" for the wrong
// reason the moment a sibling grows.
//
// The counts are a FLOOR, not an equality: adding cases is ordinary work and
// must not red. A battery BELOW its floor means cases stopped running, and the
// remedy is to find what stopped registering -- never to lower the number.
//
// Measured on 597020aa5 by instrumenting `ok` and printing the per-battery
// tally: 7 / 4 / 4 / 4 / 9 / 15, 43 in total.
//
// Five of the six are pinned at what they measure. `live ledger` is not, and
// deliberately: its case count is `1 + one per SELF_TEST_RUN_OTHERWISE row's
// worth`, and that list is ⛔ SHRINK-ONLY by design -- deleting a row is the
// outcome the list exists to reach. A floor at today's 15 would turn every
// legitimate shrink red and train the next author to edit the floor, which is
// the one habit these floors exist to prevent. So what is pinned there is the
// invariant that does NOT move with the list: the structural case ran AND at
// least one row was actually audited (1 + 3). A loop that stops iterating, or
// a ledger that empties without the structural case noticing, still reds.
const SELF_TEST_BATTERIES = Object.freeze({
'comment mask': 7,
'right boundary': 4,
'alias resolution': 4,
'population verdict': 4,
'ledger hygiene': 9,
'live ledger': 4,
});

// DELETING an entry silences that battery's floor exactly as effectively as
// zeroing it, so the registry's own size is pinned too. Adding a battery raises
// this number; removing one is the same ⛔ deliberate edit as lowering a count.
const SELF_TEST_BATTERY_FLOOR = 6;

// 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 the floor has been evaluated and the
// verdict printed. The dispatch refuses anything else: a `return` that leaves
// the function early prints nothing and exits 0, which is the same
// nothing-ran-nothing-complained pass one level up.
const SELF_TEST_VERDICT = 'check-self-test-wired self-test reached its verdict';

function selfTest() {
const failures = [];
const seen = new Map();
let openBattery = null;
// Declare the battery the following assertions belong to. The name must be a
// key of SELF_TEST_BATTERIES -- an unknown one reds by set difference,
// naming itself, rather than being counted somewhere it is not floored.
const battery = (name) => {
openBattery = name;
};
const ok = (cond, label) => {
const b = openBattery ?? UNATTRIBUTED_BATTERY;
seen.set(b, (seen.get(b) ?? 0) + 1);
if (!cond) failures.push(label);
};
const wf = (text, name = 'lint.yml') => [{ name, text }];

// ── Prose never decides anything, in either direction ────────────────────
battery('comment mask');
ok(
!carriesSelfTest('scripts/x.mjs', '// run it with --self-test sometimes\nconst a = 1;\n'),
'a `--self-test` that exists only in a JS comment was read as an implementation',
Expand Down Expand Up @@ -447,6 +531,7 @@ function selfTest() {
}

// ── Right boundary: the defect class this gate is itself in the family for ─
battery('right boundary');
{
const got = collectInvocations(wf(' - run: node scripts/g.mjs --self-test-extra\n'), {});
ok(got.named.has('scripts/g.mjs'), 'the script was not seen at all — the boundary case would test nothing');
Expand All @@ -468,6 +553,7 @@ function selfTest() {
}

// ── Aliases: reached only when a workflow actually names them ────────────
battery('alias resolution');
{
const pkg = {
'check:thing': 'node scripts/thing.mjs --self-test && node scripts/thing.mjs',
Expand All @@ -491,6 +577,7 @@ function selfTest() {
}

// ── The population verdict, both directions ──────────────────────────────
battery('population verdict');
{
const carriers = new Set(['scripts/g.mjs']);
const run = (text, ledger = []) => {
Expand All @@ -511,6 +598,7 @@ function selfTest() {
}

// ── Ledger hygiene: every row must still be true, and still be needed ────
battery('ledger hygiene');
{
const carriers = new Set(['scripts/w.mjs', 'scripts/t.mjs']);
const sources = {
Expand Down Expand Up @@ -561,6 +649,7 @@ function selfTest() {
}

// ── The live ledger, checked against the real tree ───────────────────────
battery('live ledger');
{
const sourceOf = (relPath) => {
try {
Expand All @@ -582,18 +671,79 @@ function selfTest() {
}
}

// ── The floor: every declared battery RAN, and ran its cases ─────────────
//
// Evaluated here, after every battery has had its chance and BEFORE the
// verdict -- so the line below can only be printed by a run in which the set
// of batteries that registered assertions equals the set declared.
const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);
const totalCases = [...seen.values()].reduce((a, b) => a + b, 0);
let floorBreached = false;
if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) {
floorBreached = true;
failures.push(
`SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` +
`${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the registry takes its own floor with it.`,
);
}
for (const [name, count] of seen) {
if (declaredBatteries.includes(name)) continue;
floorBreached = true;
failures.push(
`self-test battery "${name}" registered ${count} case(s) but is not declared in ` +
'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.',
);
}
for (const name of declaredBatteries) {
const count = seen.get(name) ?? 0;
if (count >= SELF_TEST_BATTERIES[name]) continue;
floorBreached = true;
failures.push(
count === 0
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` +
'The verdict below would have claimed they 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 (failures.length > 0) {
console.error('check-self-test-wired --self-test FAILED:');
for (const f of failures) console.error(` - ${f}`);
if (floorBreached) {
console.error(
'\nA battery at or below its floor means cases STOPPED RUNNING — the battery is the bug,\n' +
'not the number. Find what stopped registering (an early return, a deleted block, a guard\n' +
'that now skips) and restore it. Raising a floor after ADDING cases is ordinary work;\n' +
`LOWERING one is ${RATCHET_AUTHORITY_MARKER}, not a co-equal option — "the count\n` +
'legitimately moved" and "something stopped running" need different edits, and only a\n' +
'measurement tells them apart.\n',
);
}
process.exit(1);
}
console.log(
`check-self-test-wired --self-test: ${SELF_TEST_RUN_OTHERWISE.length} live ledger row(s) verified, plus the ` +
'comment mask, the right boundary, alias resolution and both audit directions.',
'comment mask, the right boundary, alias resolution and both audit directions' +
` — ${declaredBatteries.length} declared batteries, ${totalCases} cases registered, every battery at or` +
' above its pinned floor.',
);
return SELF_TEST_VERDICT;
}

if (isEntrypoint(import.meta.url)) {
if (process.argv.includes('--self-test')) selfTest();
else main();
if (process.argv.includes('--self-test')) {
// ⛔ Never `selfTest();` bare, and never `return selfTest()`. A `return`
// anywhere above that verdict prints nothing, evaluates no floor and exits
// 0 — the same nothing-ran-nothing-complained pass the battery floor
// refuses, one level up (#13489).
if (selfTest() !== SELF_TEST_VERDICT) {
console.error(
'\n✗ check-self-test-wired self-test: selfTest() returned without reaching its verdict, so no\n' +
'battery floor was evaluated and no success line was printed. Exiting 0 here would report a\n' +
'self-test that never finished as a self-test that passed.\n',
);
process.exit(1);
}
} else main();
}
Loading
Loading