Skip to content

Commit 169cfb9

Browse files
committed
fix(pm): census the self-test name anchor's accidental matches and pin their cost
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
1 parent 8125673 commit 169cfb9

1 file changed

Lines changed: 277 additions & 7 deletions

File tree

scripts/pm/dispatch-gates.mjs

Lines changed: 277 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,13 +1818,22 @@ export { maskComments };
18181818
* Re-derived at 6193e576d across the 169 scripts under `scripts/` that carry
18191819
* one — 185 declarations: 111 `function selfTest(`, 28
18201820
* `export function selfTest(`, 19 `async function selfTest(`, 8
1821-
* `export async function selfTest(`, and 19 compound names — one of which is
1822-
* this module's own `maskSelfTests`, so this file's masker blanks its own body
1823-
* whenever it scans itself. That was true long before the helper follow below
1824-
* existed and it costs nothing today, because none of the functions it reaches
1825-
* spells a path; it is recorded because it is the kind of thing that stops
1826-
* being free the day one of them does. The load-bearing half holds: 185 of 185
1827-
* at column 0, and a column-0 scan for the const/arrow spelling finds ZERO.
1821+
* `export async function selfTest(`, and 19 compound names. The load-bearing
1822+
* half holds: 185 of 185 at column 0, and a column-0 scan for the const/arrow
1823+
* spelling finds ZERO.
1824+
*
1825+
* ⚠ A name is not a role, so this anchor also fires on production code whose
1826+
* name merely spells self-test — `maskSelfTests` six hundred lines below is one,
1827+
* which is why this file's masker blanks its own body whenever it scans itself.
1828+
* ⛔ That is NOT one specimen: it is a class with seven live members over four
1829+
* files, and this docblock claimed it was one until a census counted them. The
1830+
* census, what each member costs, why this pattern is deliberately NOT narrowed
1831+
* to exclude them, and the live pin that keeps their cost at zero all live at
1832+
* `COMPOUND_ANCHOR_LEDGER`. Read that table before touching this pattern; the
1833+
* short version is that one spelling, `runSelfTest`, is a genuine entry point in
1834+
* one script and production code in another, so no name predicate can separate
1835+
* the two classes and narrowing this anchor can only trade a silence that costs
1836+
* nothing for a fabricated lead that does.
18281837
* ⚠ The counts in this paragraph read 61/53/7/4 when it was written and had
18291838
* gone stale by a factor of three before anyone re-read them — re-derive them
18301839
* rather than quoting them; only the two PROPERTIES are what this pattern
@@ -2143,6 +2152,188 @@ export function maskSelfTests(source) {
21432152
return blank(source, flags);
21442153
}
21452154

2155+
/**
2156+
* The source extensions the name-anchor census below reads. The anchor is a
2157+
* JavaScript declaration shape, so the corpus is the tree's JavaScript and
2158+
* TypeScript, and nothing else.
2159+
*/
2160+
const ANCHOR_CENSUS_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
2161+
2162+
/**
2163+
* The bare name this tree's self-test convention spells. Every other name the
2164+
* anchor matches is COMPOUND, and a compound name is the only place the anchor
2165+
* can be wrong in either direction.
2166+
*/
2167+
const BARE_ENTRY_POINT_NAME = 'selfTest';
2168+
2169+
/**
2170+
* Every COMPOUND-name declaration the self-test anchor matches, tree-wide, and
2171+
* whether that match is what the anchor MEANT.
2172+
*
2173+
* ## The defect this ledger answers
2174+
*
2175+
* `SELF_TEST_DECL` decides "this is a self-test" from the declaration's NAME.
2176+
* A name is not a role, so the anchor also fires on production code whose name
2177+
* merely spells self-test — and when it does, `maskSelfTests` blanks that
2178+
* production body, and `extractWatchHints` never sees the paths in it. The
2179+
* failure direction is SILENCE: a hint that is never extracted cannot be
2180+
* missed, so a gate family quietly stops being derived for a file it really
2181+
* opens.
2182+
*
2183+
* The specimen that opened this is `maskSelfTests` itself, six lines above:
2184+
* `mask` + `Self` + `Test` + `s` matches, so this module's masker blanks its own
2185+
* body whenever the module scans itself.
2186+
*
2187+
* ## The census, re-derived on this tree
2188+
*
2189+
* 209 code-position matches over the tracked JS/TS corpus. 187 are the bare
2190+
* `selfTest`; the remaining 22 carry compound names over 21 distinct spellings,
2191+
* and they are the rows below. Fifteen are genuine self-test batteries — the
2192+
* anchor firing on them is the anchor working. SEVEN are production code:
2193+
*
2194+
* scripts/check-self-test-wired.mjs carriesSelfTest
2195+
* scripts/check-self-test-workflow-commands.mjs runSelfTest
2196+
* scripts/check-step-collectors.mjs selfTestTargets
2197+
* scripts/check-step-collectors.mjs selfTestDiscoveries
2198+
* scripts/measure-self-test-floor.mjs selfTestDefs
2199+
* scripts/pm/dispatch-gates.mjs selfTestOnlyCallables
2200+
* scripts/pm/dispatch-gates.mjs maskSelfTests
2201+
*
2202+
* Every one of them is a gate that REASONS ABOUT self-tests, which is why they
2203+
* cluster: a tool that finds, spawns, counts or masks other scripts' self-tests
2204+
* names its functions after the thing it handles, and the anchor cannot tell
2205+
* "runs a self-test" from "is one".
2206+
*
2207+
* ## What it costs today: nothing, MEASURED, and that is the whole point
2208+
*
2209+
* Neutralising each of the seven one at a time and re-extracting moves no hint
2210+
* in any of the four files. The claim is therefore live rather than recalled —
2211+
* and it is exactly the kind of claim that stops being true without anything
2212+
* going red, which is what the pin in this module's self-test exists to catch.
2213+
*
2214+
* The same measurement over the fifteen genuine rows is NOT zero, and that
2215+
* asymmetry is what makes the classification load-bearing rather than
2216+
* decorative: `fixtureSelfTest` drops `packages/spec/spec-changes.json` and
2217+
* `prePushIsArmedSelfTest` drops `.githooks/pre-push`, both fixture paths in
2218+
* `scripts/check-regen-pending.mjs`, both correctly refused. So "no
2219+
* compound-name match may contribute a hint" is FALSE as a blanket invariant;
2220+
* the invariant holds only over the accidental half, and only a classification
2221+
* can name that half.
2222+
*
2223+
* ## ⛔ Why the anchor is NOT narrowed, and why nothing is special-cased
2224+
*
2225+
* The obvious repairs were both refuted by the census rather than judged:
2226+
*
2227+
* - **Narrowing the name pattern is impossible.** `runSelfTest` is a GENUINE
2228+
* entry point in `scripts/check-turbo-task-graph.mjs`, reached only from
2229+
* that file's `--self-test` guard, and ACCIDENTAL in
2230+
* `scripts/check-self-test-workflow-commands.mjs`, where it is exported and
2231+
* spawns other scripts' self-tests from the gate body. One spelling, both
2232+
* classes. No predicate over the name can separate them, so any narrowing
2233+
* that excludes the accidental one also unmasks a real self-test battery and
2234+
* readmits its fixture paths as hints — the fabricated-lead family this
2235+
* whole masker exists to refuse, traded for a silence that costs nothing.
2236+
* - **Special-casing this module's own path fixes two rows of seven.** The
2237+
* other five live in three other files, so the objection that a rename
2238+
* "fixes one instance and leaves the class" applies to it too, one file
2239+
* wider — and it would make the tool's self-scan differ from every other
2240+
* scan, which is a hazard of its own.
2241+
*
2242+
* ⇒ What ships is neither. The anchor keeps firing on all 22, the mask keeps
2243+
* blanking all 22, and the cost of the seven accidental ones is MEASURED on
2244+
* every run instead of asserted in prose. Silence was the defect; the remedy is
2245+
* noise on the day it starts costing something.
2246+
*
2247+
* ## Maintaining this table
2248+
*
2249+
* A compound-name declaration this table does not list reds
2250+
* `check:pm-dispatch-gates`. Classify it and add a row: `accidental: false` if
2251+
* it is a self-test battery (its fixtures SHOULD be masked away), `true` if it
2252+
* is production code the anchor caught by accident — in which case the pin then
2253+
* measures, and keeps measuring, that masking it costs no hint. ⛔ Do not
2254+
* "repair" a red by renaming the function to dodge the anchor: the row is the
2255+
* record, and the next accidental name is the one nobody will notice.
2256+
*/
2257+
const COMPOUND_ANCHOR_LEDGER = [
2258+
['packages/lint/scripts/check-doc-formula-expressions.mjs', 'specSelfTest', false],
2259+
['packages/lint/scripts/check-doc-formula-expressions.mjs', 'fieldRuleSelfTest', false],
2260+
['scripts/check-comment-mask-corpus.mjs', 'runSelfTestCases', false],
2261+
['scripts/check-doc-authoring.mjs', 'selfTestRule3', false],
2262+
['scripts/check-doc-authoring.mjs', 'selfTestPackagesProse', false],
2263+
['scripts/check-durability-degradation-log-level.mjs', 'selfTestReadSeams', false],
2264+
['scripts/check-platform-checklist.mjs', 'selfTestTrapVocabulary', false],
2265+
['scripts/check-platform-checklist.mjs', 'selfTestProvisioningUse', false],
2266+
['scripts/check-platform-checklist.mjs', 'selfTestUnreferencedRecipes', false],
2267+
['scripts/check-platform-checklist.mjs', 'selfTestMetaCallSpelling', false],
2268+
['scripts/check-platform-checklist.mjs', 'selfTestSourceLineCitations', false],
2269+
['scripts/check-regen-pending.mjs', 'fixtureSelfTest', false],
2270+
['scripts/check-regen-pending.mjs', 'prePushIsArmedSelfTest', false],
2271+
['scripts/check-regen-pending.mjs', 'decisionTableSelfTest', false],
2272+
['scripts/check-turbo-task-graph.mjs', 'runSelfTest', false],
2273+
['scripts/check-self-test-wired.mjs', 'carriesSelfTest', true],
2274+
['scripts/check-self-test-workflow-commands.mjs', 'runSelfTest', true],
2275+
['scripts/check-step-collectors.mjs', 'selfTestTargets', true],
2276+
['scripts/check-step-collectors.mjs', 'selfTestDiscoveries', true],
2277+
['scripts/measure-self-test-floor.mjs', 'selfTestDefs', true],
2278+
['scripts/pm/dispatch-gates.mjs', 'selfTestOnlyCallables', true],
2279+
['scripts/pm/dispatch-gates.mjs', 'maskSelfTests', true],
2280+
];
2281+
2282+
/**
2283+
* The ledger as `"<file>::<name>"` keys. `runSelfTest` alone proves the key has
2284+
* to carry the file: that one spelling is a genuine entry point in one script
2285+
* and production code in another, so a name-keyed ledger could not hold both
2286+
* verdicts at once — the same reason the anchor itself cannot be narrowed.
2287+
*/
2288+
export const COMPOUND_ANCHOR_KEYS = new Map(
2289+
COMPOUND_ANCHOR_LEDGER.map(([file, name, accidental]) => [`${file}::${name}`, accidental]),
2290+
);
2291+
2292+
/**
2293+
* Every COMPOUND-name declaration the self-test anchor matches in `source`, at
2294+
* CODE positions only.
2295+
*
2296+
* Comments are masked first for the same reason `maskSelfTests` composes them
2297+
* first: this tree's docblocks quote declaration shapes at column 0, and a
2298+
* quoted one is prose, not a declaration. A match inside a string literal is
2299+
* excluded by the same means the mask uses — `scanSource`'s literal map — so a
2300+
* self-test fixture that BUILDS a module source cannot enter the census as if
2301+
* it were a declaration of the file holding it.
2302+
*/
2303+
export function compoundAnchorDecls(source) {
2304+
const scan = scanSource(source);
2305+
const decommented = maskComments(source);
2306+
const out = [];
2307+
for (const m of decommented.matchAll(SELF_TEST_DECL)) {
2308+
if (scan.comment[m.index] || scan.literal[m.index]) continue;
2309+
const name = m[0].match(/function[ \t]+([A-Za-z0-9_$]+)/)[1];
2310+
if (name === BARE_ENTRY_POINT_NAME) continue;
2311+
out.push({ name, index: m.index, line: decommented.slice(0, m.index).split('\n').length });
2312+
}
2313+
return out;
2314+
}
2315+
2316+
/**
2317+
* `source` with one declaration renamed so the self-test anchor no longer sees
2318+
* it — the counterfactual the accidental half is measured against.
2319+
*
2320+
* The rename replaces the self-test token INSIDE the identifier rather than
2321+
* appending or truncating, because the anchor matches the token anywhere in the
2322+
* name: a mangle that leaves any spelling of it behind is a mutation that does
2323+
* not land, and a mutation that does not land reads exactly like a clean
2324+
* measurement. Both directions are asserted by the caller, which refuses unless
2325+
* the anchored-declaration count drops by exactly one.
2326+
*
2327+
* Word-anchored so a longer identifier sharing the prefix is untouched —
2328+
* renaming `runSelfTest` must not also rewrite `runSelfTestCases`.
2329+
*/
2330+
export function withoutAnchor(source, name) {
2331+
const replacement = name.replace(/[Ss]elf[_]?[Tt]est/, 'Probe');
2332+
if (/[Ss]elf[_]?[Tt]est/.test(replacement)) return null;
2333+
if (new RegExp(`\\b${replacement}\\b`).test(source)) return null;
2334+
return source.replace(new RegExp(`\\b${name}\\b`, 'g'), replacement);
2335+
}
2336+
21462337
/**
21472338
* The IANA top-level media types. A closed registry, not a heuristic: these ten
21482339
* are the whole of it, so a two-segment literal headed by one of them is a MIME
@@ -9895,6 +10086,85 @@ function selfTest() {
989510086
!transitiveHints.includes('packages/branch/fixture.ts'),
989610087
);
989710088

10089+
// ── The anchor fires on NAMES, so it also fires on production code ───────
10090+
//
10091+
// Live, over the tracked tree, against `COMPOUND_ANCHOR_LEDGER`. The census
10092+
// and the argument for measuring rather than narrowing are at that table;
10093+
// what runs here is the half that can go red.
10094+
//
10095+
// Read the three assertions as one instrument. The first says the population
10096+
// has not moved under the table. The second says masking the accidental half
10097+
// still costs no hint — the claim the ledger makes in prose, re-measured on
10098+
// every run, so the day someone writes a path literal into `maskSelfTests`,
10099+
// `carriesSelfTest` or any other accidental row, THIS goes red and names the
10100+
// hint instead of dropping it in silence. The third is the control that makes
10101+
// the second mean anything: a counterfactual that silently failed to rename
10102+
// would report "no hint moves" for every row, which is indistinguishable from
10103+
// a pass, so at least one GENUINE row must be seen to move a hint.
10104+
{
10105+
const census = new Map();
10106+
for (const rel of trackedFiles()) {
10107+
if (!ANCHOR_CENSUS_EXTENSIONS.test(rel)) continue;
10108+
let text;
10109+
try {
10110+
text = readFileSync(join(ROOT, rel), 'utf8');
10111+
} catch {
10112+
continue;
10113+
}
10114+
if (!/[Ss]elf[_]?[Tt]est/.test(text)) continue;
10115+
for (const decl of compoundAnchorDecls(text)) census.set(`${rel}::${decl.name}`, rel);
10116+
}
10117+
const unlisted = [...census.keys()].filter((k) => !COMPOUND_ANCHOR_KEYS.has(k)).sort();
10118+
const stale = [...COMPOUND_ANCHOR_KEYS.keys()].filter((k) => !census.has(k)).sort();
10119+
t(
10120+
`every compound self-test NAME the anchor matches is classified in COMPOUND_ANCHOR_LEDGER` +
10121+
(unlisted.length ? ` — unlisted: ${unlisted.join(', ')}` : '') +
10122+
(stale.length ? ` — listed but gone: ${stale.join(', ')}` : ''),
10123+
unlisted.length === 0 && stale.length === 0,
10124+
);
10125+
10126+
const costly = [];
10127+
const movers = [];
10128+
for (const [key, accidental] of COMPOUND_ANCHOR_KEYS) {
10129+
const rel = census.get(key);
10130+
if (!rel) continue;
10131+
const name = key.slice(rel.length + 2);
10132+
const src = readFileSync(join(ROOT, rel), 'utf8');
10133+
const alt = withoutAnchor(src, name);
10134+
if (alt === null || compoundAnchorDecls(alt).length !== compoundAnchorDecls(src).length - 1) {
10135+
costly.push(`${key} (the counterfactual rename did not land — this row was NOT measured)`);
10136+
continue;
10137+
}
10138+
const before = extractWatchHints(src, rel);
10139+
const after = extractWatchHints(alt, rel);
10140+
const dropped = after.filter((h) => !before.includes(h));
10141+
if (dropped.length === 0) continue;
10142+
if (accidental) costly.push(`${key} now hides ${JSON.stringify(dropped)}`);
10143+
else movers.push(key);
10144+
}
10145+
t(
10146+
'masking an ACCIDENTAL name match still costs this tree no watch hint' +
10147+
(costly.length ? ` — ${costly.join('; ')}` : ''),
10148+
costly.length === 0,
10149+
);
10150+
t(
10151+
'control: at least one GENUINE self-test battery is seen to lose a fixture hint, so the ' +
10152+
'measurement above is an instrument and not a broken rename reporting zero everywhere',
10153+
movers.length > 0,
10154+
);
10155+
}
10156+
10157+
// The card's own specimen, pinned by identity: this module's masker and the
10158+
// helper predicate beside it are both named into the anchor's population, so a
10159+
// rename that "fixes" either one has to move the ledger row rather than the
10160+
// problem.
10161+
{
10162+
const selfCensus = compoundAnchorDecls(readFileSync(join(ROOT, 'scripts/pm/dispatch-gates.mjs'), 'utf8'));
10163+
const names = selfCensus.map((d) => d.name);
10164+
t("this module's own masker is in the anchor's population", names.includes('maskSelfTests'));
10165+
t('…and so is the reachability helper beside it', names.includes('selfTestOnlyCallables'));
10166+
}
10167+
989810168
// A population DECLARED for this very scanner is referenced by no executing
989910169
// code — being unreferenced is what such a declaration IS. Extending the mask
990010170
// to value declarations was implemented and REFUSED on this evidence: over

0 commit comments

Comments
 (0)