@@ -4172,18 +4172,21 @@ const BARE_ENTRY_POINT_NAME = 'selfTest';
41724172 *
41734173 * ## The census, re-derived on this tree
41744174 *
4175- * 209 code-position matches over the tracked JS/TS corpus. 187 are the bare
4176- * `selfTest`; the remaining 22 carry compound names over 21 distinct spellings,
4177- * and they are the rows below. Fifteen are genuine self-test batteries — the
4178- * anchor firing on them is the anchor working. SEVEN are production code:
4175+ * 252 code-position matches over the tracked JS/TS corpus. 223 are the bare
4176+ * `selfTest`; the remaining 29 carry compound names over 26 distinct spellings,
4177+ * and they are the rows below. Nineteen are genuine self-test batteries — the
4178+ * anchor firing on them is the anchor working. TEN are production code:
41794179 *
4180- * scripts/check-self-test-wired.mjs carriesSelfTest
4180+ * scripts/check-self-test-wired.mjs carriesSelfTest
41814181 * scripts/check-self-test-workflow-commands.mjs runSelfTest
4182- * scripts/check-step-collectors.mjs selfTestTargets
4183- * scripts/check-step-collectors.mjs selfTestDiscoveries
4184- * scripts/measure-self-test-floor.mjs selfTestDefs
4185- * scripts/pm/dispatch-gates.mjs selfTestOnlyCallables
4186- * scripts/pm/dispatch-gates.mjs maskSelfTests
4182+ * scripts/check-step-collectors.mjs selfTestTargets
4183+ * scripts/check-step-collectors.mjs selfTestDiscoveries
4184+ * scripts/measure-durability-swallow-family.mjs selfTestMode
4185+ * scripts/measure-self-test-floor.mjs selfTestDefs
4186+ * scripts/pm/dispatch-gates.mjs selfTestOnlyCallables
4187+ * scripts/pm/dispatch-gates.mjs maskSelfTests
4188+ * scripts/pm/dispatch-gates.mjs selfTestCaseLines
4189+ * scripts/pm/dispatch-gates.mjs selfTestOnlyInvocation
41874190 *
41884191 * Every one of them is a gate that REASONS ABOUT self-tests, which is why they
41894192 * cluster: a tool that finds, spawns, counts or masks other scripts' self-tests
@@ -4192,16 +4195,17 @@ const BARE_ENTRY_POINT_NAME = 'selfTest';
41924195 *
41934196 * ## What it costs today: nothing, MEASURED, and that is the whole point
41944197 *
4195- * Neutralising each of the seven one at a time and re-extracting moves no hint
4196- * in any of the four files. The claim is therefore live rather than recalled —
4198+ * Neutralising each of the ten one at a time and re-extracting moves no hint
4199+ * in any of the six files. The claim is therefore live rather than recalled —
41974200 * and it is exactly the kind of claim that stops being true without anything
41984201 * going red, which is what the pin in this module's self-test exists to catch.
41994202 *
4200- * The same measurement over the fifteen genuine rows is NOT zero, and that
4201- * asymmetry is what makes the classification load-bearing rather than
4202- * decorative: `fixtureSelfTest` drops `packages/spec/spec-changes.json` and
4203- * `prePushIsArmedSelfTest` drops `.githooks/pre-push`, both fixture paths in
4204- * `scripts/check-regen-pending.mjs`, both correctly refused. So "no
4203+ * The same measurement, redone over the table's current nineteen genuine rows,
4204+ * is still NOT zero, and that asymmetry is what makes the classification
4205+ * load-bearing rather than decorative: `fixtureSelfTest` drops
4206+ * `packages/spec/spec-changes.json` and `prePushIsArmedSelfTest` drops
4207+ * `.githooks/pre-push`, both fixture paths in `scripts/check-regen-pending.mjs`,
4208+ * both correctly refused. So "no
42054209 * compound-name match may contribute a hint" is FALSE as a blanket invariant;
42064210 * the invariant holds only over the accidental half, and only a classification
42074211 * can name that half.
@@ -4219,14 +4223,14 @@ const BARE_ENTRY_POINT_NAME = 'selfTest';
42194223 * that excludes the accidental one also unmasks a real self-test battery and
42204224 * readmits its fixture paths as hints — the fabricated-lead family this
42214225 * whole masker exists to refuse, traded for a silence that costs nothing.
4222- * - **Special-casing this module's own path fixes two rows of seven .** The
4223- * other five live in three other files, so the objection that a rename
4226+ * - **Special-casing this module's own path fixes four rows of ten .** The
4227+ * other six live in five other files, so the objection that a rename
42244228 * "fixes one instance and leaves the class" applies to it too, one file
42254229 * wider — and it would make the tool's self-scan differ from every other
42264230 * scan, which is a hazard of its own.
42274231 *
4228- * ⇒ What ships is neither. The anchor keeps firing on all 22 , the mask keeps
4229- * blanking all 22 , and the cost of the seven accidental ones is MEASURED on
4232+ * ⇒ What ships is neither. The anchor keeps firing on all 29 , the mask keeps
4233+ * blanking all 29 , and the cost of the ten accidental ones is MEASURED on
42304234 * every run instead of asserted in prose. Silence was the defect; the remedy is
42314235 * noise on the day it starts costing something.
42324236 *
@@ -4239,6 +4243,13 @@ const BARE_ENTRY_POINT_NAME = 'selfTest';
42394243 * measures, and keeps measuring, that masking it costs no hint. ⛔ Do not
42404244 * "repair" a red by renaming the function to dodge the anchor: the row is the
42414245 * record, and the next accidental name is the one nobody will notice.
4246+ *
4247+ * The TOTAL / GENUINE / ACCIDENTAL counts stated above are pinned the same
4248+ * way (#15310): `--self-test` computes them fresh from this table and checks
4249+ * the docblock's own prose against that computation, never against a second
4250+ * hand-typed constant. Prose that drifts from the table reds there, instead
4251+ * of drifting further unnoticed the way it had — twice — by the time #15310
4252+ * measured it.
42424253 */
42434254const COMPOUND_ANCHOR_LEDGER = [
42444255 ['packages/lint/scripts/check-doc-formula-expressions.mjs', 'specSelfTest', false],
@@ -15508,6 +15519,86 @@ function selfTest() {
1550815519 t('…and so is the reachability helper beside it', names.includes('selfTestOnlyCallables'));
1550915520 }
1551015521
15522+ // #15310 — the docblock above states this table's TOTAL / GENUINE /
15523+ // ACCIDENTAL composition in prose, and prose does not move when a row is
15524+ // added: three integers that agree with EACH OTHER while jointly
15525+ // disagreeing with the table is exactly the shape that let this drift twice
15526+ // without ever looking wrong. The declared numbers are read out of this
15527+ // module's own docblock text, never re-typed as a second constant here, and
15528+ // checked against a count taken fresh from COMPOUND_ANCHOR_LEDGER itself —
15529+ // so a row added without touching the docblock reds here, and an edit to
15530+ // any unrelated line changes neither side and stays green.
15531+ {
15532+ const ownSource = readFileSync(nodePath.join(ROOT, 'scripts/pm/dispatch-gates.mjs'), 'utf8');
15533+ const ledgerAt = ownSource.indexOf('const COMPOUND_ANCHOR_LEDGER = [');
15534+ const before = ledgerAt < 0 ? '' : ownSource.slice(0, ledgerAt);
15535+ const blockStart = before.lastIndexOf('/**');
15536+ const blockEnd = before.lastIndexOf('*/');
15537+ // Line-wrapped JSDoc prose carries a `\n * ` between words that happen to
15538+ // fall on a line break — flattened to single spaces so a future rewrap of
15539+ // this paragraph cannot itself make a true reading look false.
15540+ const prose =
15541+ blockStart < 0 || blockEnd < 0
15542+ ? ''
15543+ : ownSource
15544+ .slice(blockStart, blockEnd)
15545+ .replace(/\n[ \t]*\*[ \t]?/g, ' ')
15546+ .replace(/[ \t]+/g, ' ');
15547+
15548+ const total = COMPOUND_ANCHOR_LEDGER.length;
15549+ const genuine = COMPOUND_ANCHOR_LEDGER.filter(([, , accidental]) => !accidental).length;
15550+ const accidental = COMPOUND_ANCHOR_LEDGER.length - genuine;
15551+ const distinctSpellings = new Set(COMPOUND_ANCHOR_LEDGER.map(([, name]) => name)).size;
15552+
15553+ // Only as wide as the words this docblock actually spells; extending it is
15554+ // a deliberate edit, not silent tolerance for a new spelling.
15555+ const NUMBER_WORDS = {
15556+ zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9,
15557+ ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16,
15558+ seventeen: 17, eighteen: 18, nineteen: 19, twenty: 20,
15559+ };
15560+ const asCount = (word) => (/^\d+$/.test(word) ? Number(word) : NUMBER_WORDS[String(word).toLowerCase()]);
15561+
15562+ const remaining = prose.match(/the remaining (\d+) carry compound names/);
15563+ const allFiring = prose.match(/keeps firing on all (\d+), the mask keeps blanking all (\d+)/);
15564+ const genuineLine = prose.match(/([A-Za-z]+) are genuine self-test batteries/);
15565+ const accidentalLine = prose.match(/([A-Za-z]+) are production code:/);
15566+ const spellingsLine = prose.match(/over (\d+) distinct spellings/);
15567+
15568+ t(
15569+ 'the docblock\'s TOTAL row count — "the remaining N carry compound names" and both "all N" claims — ' +
15570+ `agrees with the table (table: ${total}; declared: ` +
15571+ `${remaining ? remaining[1] : '<not found>'}/${allFiring ? allFiring[1] : '<not found>'}/` +
15572+ `${allFiring ? allFiring[2] : '<not found>'})`,
15573+ remaining !== null
15574+ && allFiring !== null
15575+ && Number(remaining[1]) === total
15576+ && Number(allFiring[1]) === total
15577+ && Number(allFiring[2]) === total,
15578+ );
15579+ t(
15580+ "the docblock's GENUINE count (\"N are genuine self-test batteries\") agrees with the table " +
15581+ `(table: ${genuine}; declared: ${genuineLine ? genuineLine[1] : '<not found>'})`,
15582+ genuineLine !== null && asCount(genuineLine[1]) === genuine,
15583+ );
15584+ t(
15585+ "the docblock's ACCIDENTAL count (\"N are production code:\") agrees with the table " +
15586+ `(table: ${accidental}; declared: ${accidentalLine ? accidentalLine[1] : '<not found>'})`,
15587+ accidentalLine !== null && asCount(accidentalLine[1]) === accidental,
15588+ );
15589+ // Distinct NAMES, not rows: `runSelfTest` is a genuine entry point in one
15590+ // file and an accidental one in another, so it is one spelling occupying
15591+ // two rows — the same reason COMPOUND_ANCHOR_KEYS has to carry the file in
15592+ // its key. This is derivable from the table exactly like the three above,
15593+ // so it is pinned the same way rather than left as the one clause in this
15594+ // paragraph a future row could still drift without going red.
15595+ t(
15596+ "the docblock's distinct-spellings count (\"over N distinct spellings\") agrees with the table " +
15597+ `(table: ${distinctSpellings}; declared: ${spellingsLine ? spellingsLine[1] : '<not found>'})`,
15598+ spellingsLine !== null && Number(spellingsLine[1]) === distinctSpellings,
15599+ );
15600+ }
15601+
1551115602 // A population DECLARED for this very scanner is referenced by no executing
1551215603 // code — being unreferenced is what such a declaration IS. Extending the mask
1551315604 // to value declarations was implemented and REFUSED on this evidence: over
0 commit comments