Skip to content

Commit ed39935

Browse files
committed
fix(devx): anchor the self-test floor probe on the real definition
`injectEarlyReturn` took the FIRST match of `function <name>(` against RAW source, so a docblock sentence or a fixture string won a definition. In `scripts/pm/dispatch-gates.mjs` both stand ahead of the real one, and the injection landed inside a template literal: the copy is then a SyntaxError, which exits non-zero AND prints a stack, so the verdict reads HELD — a hold awarded to a gate for the probe having broken its own copy of it. That was recorded as an `ENTRY_BY_HAND` null describing the FILE, and read for months as a property of it. Two rules now make the anchor the definition, and the control fixture carries one decoy of each kind ahead of its real definition so neither rule can be dropped silently: MASKED the match is taken over `maskCommentsAndLiterals(src)` — the existing `scanSource` flags, comments and literals blanked, offsets and line numbers preserved. LINE-START the match must begin a line (`export` / `async` prefixes kept). ⛔ The POPULATION criterion keeps reading `maskComments`: every `--self-test` dispatch names the flag with a string literal, so masking literals there would empty the census rather than shrink it. A control pins the two masks to their opposite answers on the same fixture. Measured: the census is byte-identical to main (178 files, `--json` diff empty), and over all 171 mechanically anchored rows the injection offset does not move — the new rules pick the same byte in every one. Ledger: the `scripts/pm/dispatch-gates.mjs` null becomes `'selfTest'`. Its copy now parses and runs (measured by hand: exit 1, `selfTest() returned without reaching its verdict`), so the row's remaining NOT MEASURED is the BASELINE precondition — the probe writes its copy under `scripts/`, where that gate's own single-site sweep finds the near-duplicate and refuses (#15515, a separate card, not worked around here) — and the row now states that reason instead of a false one about this file. Two stale counts in the same ledger corrected: `check-platform-checklist` dispatches SIX self-test functions, and the docblock's "nine files" is TEN rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
1 parent cf6b671 commit ed39935

1 file changed

Lines changed: 169 additions & 11 deletions

File tree

scripts/measure-self-test-floor.mjs

Lines changed: 169 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ import { tmpdir } from 'node:os';
108108
import { fileURLToPath } from 'node:url';
109109

110110
import { isEntrypoint } from './invoked-as.mjs';
111-
import { maskComments } from './js-comment-mask.mjs';
111+
import { blank, maskComments, scanSource } from './js-comment-mask.mjs';
112112

113113
const ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..');
114114

@@ -244,14 +244,62 @@ export function selfTestDefs(src) {
244244
return [...names];
245245
}
246246

247-
/** Insert `return;` as the first statement of `name`. Returns null when absent. */
247+
/**
248+
* The source a DEFINITION may be anchored in: comments AND the content of every
249+
* string, template and regex literal blanked, every other byte -- and every
250+
* offset and line number -- left exactly where it was, so a match found here
251+
* slices the ORIGINAL text.
252+
*
253+
* ⛔ NOT for the population criterion above, which must keep reading
254+
* `maskComments`. Every `--self-test` dispatch names the flag with a string
255+
* literal, so this mask blanks the dispatch out of every file in the tree; a
256+
* control below pins the two masks to their opposite answers.
257+
*/
258+
export function maskCommentsAndLiterals(source) {
259+
const { comment, literal } = scanSource(source);
260+
const both = new Uint8Array(source.length);
261+
for (let i = 0; i < both.length; i++) both[i] = comment[i] | literal[i];
262+
return blank(source, both);
263+
}
264+
265+
/**
266+
* Insert `return;` as the first statement of `name`. Returns null when absent.
267+
*
268+
* TWO rules make the anchor the DEFINITION rather than the first text that reads
269+
* like one, and NEITHER is redundant -- the control fixture below carries one
270+
* decoy of each kind, in this order, ahead of its real definition:
271+
*
272+
* MASKED the match is taken over `maskCommentsAndLiterals(src)`. Read raw,
273+
* the first `function selfTest() {` in `scripts/pm/dispatch-gates.mjs`
274+
* is a sentence in a docblock and the next is a FIXTURE STRING.
275+
* LINE-START the match must BEGIN a line, `export` / `async` being the only
276+
* prefixes a definition in this tree carries. Masking alone still
277+
* prefers a mid-line named function expression -- a value in an
278+
* object literal is not the function the dispatch calls.
279+
*
280+
* What the first rule costs when it is missing is not an absence: injecting into
281+
* a template literal makes the copy a SyntaxError, and a copy that dies in the
282+
* parser exits non-zero and prints a stack, so `mutatedSpoke` is true and the
283+
* verdict above reads HELD -- a hold awarded to a gate for the probe having
284+
* broken its own copy of it. `scripts/pm/dispatch-gates.mjs` carried an
285+
* `ENTRY_BY_HAND` null for months over exactly this, a limit of the INSTRUMENT
286+
* recorded as a property of the FILE (#14963). Anchored on the definition its
287+
* copy parses and runs -- measured by hand, exit 1 and `selfTest() returned
288+
* without reaching its verdict`. The row it leaves is still NOT MEASURED, but
289+
* for a reason the probe now states itself: see that row.
290+
*/
248291
export function injectEarlyReturn(src, name) {
292+
const code = maskCommentsAndLiterals(src);
249293
const pats = [
250-
new RegExp(`(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)\\s*(?::\\s*[A-Za-z_$][\\w$<>\\[\\]|. ]*\\s*)?\\{`),
251-
new RegExp(`const\\s+${name}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*(?::[^=]*)?=>\\s*\\{`),
294+
new RegExp(
295+
`^[ \\t]*(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)` +
296+
`\\s*(?::\\s*[A-Za-z_$][\\w$<>\\[\\]|. ]*\\s*)?\\{`,
297+
'm',
298+
),
299+
new RegExp(`^[ \\t]*(?:export\\s+)?const\\s+${name}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*(?::[^=]*)?=>\\s*\\{`, 'm'),
252300
];
253301
for (const re of pats) {
254-
const m = src.match(re);
302+
const m = code.match(re);
255303
if (!m) continue;
256304
const at = m.index + m[0].length;
257305
return `${src.slice(0, at)}\n return; /*${PROBE_MARKER}*/\n${src.slice(at)}`;
@@ -436,7 +484,7 @@ const UNRUNNABLE_GATE = [
436484
* The HELPER handshake spelling, reduced: the third of the three landed
437485
* handshake shapes, and the one the probe had never read in either direction.
438486
* Its only carrier under `scripts/` is `check-platform-checklist.mjs`, whose
439-
* `ENTRY_BY_HAND` row is a deliberate `null` (its dispatch calls four self-test
487+
* `ENTRY_BY_HAND` row is a deliberate `null` (its dispatch calls six self-test
440488
* functions and combines their statuses), so every probe run recorded NOT
441489
* MEASURED for it and the sweep said nothing at all about this shape -- not
442490
* held, not defeated (#15371). The other two spellings each have dozens of live
@@ -567,6 +615,61 @@ const NON_DISPATCH_MENTION_GATE = [
567615
'',
568616
].join('\n');
569617

618+
/**
619+
* The DECOY shape, reduced: three texts that read like `function selfTest() {`
620+
* standing AHEAD of the real definition, one for each way the anchor could take
621+
* the wrong one, in the order they occur in `scripts/pm/dispatch-gates.mjs`.
622+
*
623+
* 1. a docblock sentence naming the convention -- a COMMENT;
624+
* 2. a fixture the gate feeds its own scanner -- a TEMPLATE LITERAL;
625+
* 3. a named function expression held as a value -- real CODE, MID-LINE.
626+
*
627+
* Each defeats a different half of the rule, and each fails DIFFERENTLY, which
628+
* is why one fixture carries all three rather than three carrying one:
629+
*
630+
* into (1) the `return;` lands in a comment, the copy behaves exactly as the
631+
* original, and the probe reads `mutation had no observable effect`;
632+
* into (2) it lands inside a template literal, so the copy is a SyntaxError --
633+
* which exits non-zero AND prints a stack, and is therefore scored HELD. The
634+
* flattering direction: a hold awarded to a gate for the probe breaking its
635+
* own copy of it (#14963);
636+
* into (3) it lands in a function nothing calls, and the probe again reads no
637+
* observable effect.
638+
*
639+
* The real definition below them is HOLED -- its dispatch discards the result --
640+
* so the one reading that can only come from anchoring on it is DEFEATED with
641+
* ZERO bytes printed. The fixture is spawned, so that verdict also says the
642+
* mutated copy PARSED and RAN.
643+
*/
644+
const DECOY_ANCHOR_GATE = [
645+
'#!/usr/bin/env node',
646+
'// The convention this tree writes: `function selfTest() {` at column 0.',
647+
'const FIXTURE = `',
648+
'function selfTest() {',
649+
" console.log('a fixture the gate scans, not a definition');",
650+
'}',
651+
'`;',
652+
'const holder = { run: function selfTest() { return FIXTURE.length; } };',
653+
'function selfTest() {',
654+
' const failures = [];',
655+
" if (holder.run() < 1) failures.push('the fixture text went missing');",
656+
" if (failures.length) { console.error(failures.join(String.fromCharCode(10))); process.exit(1); }",
657+
" console.log('fixture self-test: 1 case passes');",
658+
'}',
659+
"if (process.argv.includes('--self-test')) selfTest();",
660+
'',
661+
].join('\n');
662+
663+
/** The definition-shaped text the three decoys and the real definition share. */
664+
const DECOY_ANCHOR_TEXT = 'function selfTest() {';
665+
666+
/**
667+
* The anchor an UNMASKED, UNANCHORED first match takes -- the pre-#14963 rule,
668+
* kept here as the thing the controls below measure against rather than as a
669+
* second implementation of anything: `injectEarlyReturn` never uses it.
670+
*/
671+
const NAIVE_ANCHOR = /(?:async\s+)?function\s+selfTest\s*\([^)]*\)\s*\{/;
672+
570673
/**
571674
* Both instruments, against both directions. Returns the failures; the caller
572675
* refuses on any. Nothing here reads the repo, so a control failure is always
@@ -599,6 +702,41 @@ export function runControls() {
599702
say(DISPATCH.test(NON_DISPATCH_MENTION_GATE),
600703
'CONTROL FIXTURE INVALID: the mention fixture does not match even UNMASKED, so the verdict above says nothing about comments being masked away');
601704

705+
// ⛔ ... and the population criterion must keep reading COMMENT-masked source.
706+
// The mask the injection ANCHOR needs blanks literals too, and every dispatch
707+
// in this tree names the flag with a string literal -- so reading the census
708+
// through that one would not classify a single file generously, it would empty
709+
// the population, taking this instrument's own control fixtures (deliberately
710+
// strings) with it. The two masks are required to answer this OPPOSITELY.
711+
say(!DISPATCH.test(maskCommentsAndLiterals(HOLED_GATE)),
712+
'CONTROL FAILED: the code-only mask that the injection anchor reads still admits a dispatch to the population; the two masks no longer answer differently, and whichever of them the census ends up reading, one of the two questions is being answered with the wrong text');
713+
714+
// The ANCHOR, against every text that reads like a definition without being
715+
// one. What is at stake is not a classification but a WRONG READING: an
716+
// injection into a fixture string makes the copy a SyntaxError, whose non-zero
717+
// exit and stack trace this file's verdict scores HELD (#14963).
718+
const decoyFlags = scanSource(DECOY_ANCHOR_GATE);
719+
const commentDecoy = DECOY_ANCHOR_GATE.indexOf(DECOY_ANCHOR_TEXT);
720+
const literalDecoy = DECOY_ANCHOR_GATE.indexOf(DECOY_ANCHOR_TEXT, commentDecoy + 1);
721+
const midLineDecoy = DECOY_ANCHOR_GATE.indexOf('function selfTest() { return FIXTURE.length');
722+
const realDef = DECOY_ANCHOR_GATE.indexOf('\nfunction selfTest() {\n const failures') + 1;
723+
say(commentDecoy >= 0 && literalDecoy > commentDecoy && midLineDecoy > literalDecoy && realDef > midLineDecoy,
724+
'CONTROL FIXTURE INVALID: the three decoys no longer all stand AHEAD of the real definition, so a first-match anchor would reach the definition however it was spelled and every verdict below passes for the wrong reason');
725+
say(decoyFlags.comment[commentDecoy] === 1,
726+
'CONTROL FIXTURE INVALID: the first decoy is not comment content, so it no longer tests the comment half of the mask');
727+
say(decoyFlags.literal[literalDecoy] === 1,
728+
'CONTROL FIXTURE INVALID: the second decoy is not literal content, so it no longer tests the string/template half of the mask -- the half whose failure produces a SyntaxError and a false HELD');
729+
say(decoyFlags.comment[midLineDecoy] === 0 && decoyFlags.literal[midLineDecoy] === 0,
730+
'CONTROL FIXTURE INVALID: the third decoy is masked away as comment or literal, so it tests the mask a second time instead of the LINE-START rule it is there for');
731+
say(DECOY_ANCHOR_GATE.search(NAIVE_ANCHOR) === commentDecoy,
732+
'CONTROL FIXTURE INVALID: an unmasked first-match anchor no longer lands on a decoy, so the MASK is not what the anchor verdict below is reading');
733+
say(maskCommentsAndLiterals(DECOY_ANCHOR_GATE).search(NAIVE_ANCHOR) === midLineDecoy,
734+
'CONTROL FIXTURE INVALID: masking alone no longer lands on the mid-line decoy, so the LINE-START rule is not what the anchor verdict below is reading -- masking would be carrying it on its own');
735+
const decoyInjected = injectEarlyReturn(DECOY_ANCHOR_GATE, 'selfTest');
736+
say(decoyInjected !== null
737+
&& decoyInjected.includes(`function selfTest() {\n return; /*${PROBE_MARKER}*/\n\n const failures = [];`),
738+
'ANCHOR CONTROL FAILED: the early return was not injected at the REAL definition; a text that merely READS like one -- in a comment, in a fixture string, or mid-line in code -- was preferred over the function the dispatch calls');
739+
602740
// Instrument 1, both directions.
603741
say(classifyFloor(maskComments(HOLED_GATE)) === 'NONE',
604742
'POSITIVE CONTROL FAILED: a self-test deciding success by failures.length alone was not classified NONE');
@@ -637,18 +775,21 @@ export function runControls() {
637775
const sound = join(dir, 'sound-gate.mjs');
638776
const accident = join(dir, 'accident-gate.mjs');
639777
const unrunnable = join(dir, 'unrunnable-gate.mjs');
778+
const decoy = join(dir, 'decoy-anchor-gate.mjs');
640779
const helper = join(dir, 'helper-handshake-gate.mjs');
641780
const helperHoled = join(dir, 'helper-handshake-gate-holed.mjs');
642781
writeFileSync(holed, HOLED_GATE);
643782
writeFileSync(sound, SOUND_GATE);
644783
writeFileSync(accident, ACCIDENT_GATE);
645784
writeFileSync(unrunnable, UNRUNNABLE_GATE);
785+
writeFileSync(decoy, DECOY_ANCHOR_GATE);
646786
writeFileSync(helper, HELPER_HANDSHAKE_GATE);
647787
writeFileSync(helperHoled, HELPER_HANDSHAKE_GATE_HOLED);
648788
const h = probeEarlyReturn(holed, 'selfTest');
649789
const s = probeEarlyReturn(sound, 'selfTest');
650790
const a = probeEarlyReturn(accident, 'runSelfTest');
651791
const u = probeEarlyReturn(unrunnable, 'selfTest');
792+
const dc = probeEarlyReturn(decoy, 'selfTest');
652793
const hh = probeEarlyReturn(helper, 'selfTest');
653794
const hhHoled = probeEarlyReturn(helperHoled, 'selfTest');
654795
say(h.verdict === 'DEFEATED',
@@ -676,6 +817,14 @@ export function runControls() {
676817
// being red, would satisfy the verdict above while testing nothing.
677818
say(u.baselineExit !== 0 && u.baselineBytes > 0 && u.baselineHead !== '',
678819
`POSITIVE CONTROL FAILED: the unrunnable fixture no longer produces the measured shape (baseline exit ${u.baselineExit}, ${u.baselineBytes} byte(s)); the NOT MEASURED verdict above would then be passing for the wrong reason`);
820+
// The anchor, ON DISK. The static assertions above read WHERE the injection
821+
// went; this one reads what the copy then DID -- so it also says the copy
822+
// parsed and ran. Anchored on the fixture string instead, the copy dies in
823+
// the parser: non-zero exit, a stack trace on stderr, verdict HELD.
824+
say(dc.verdict === 'DEFEATED',
825+
`POSITIVE CONTROL FAILED: a known-holed gate whose real definition is preceded by three definition-shaped decoys was read as ${dc.verdict} (${dc.why ?? ''}); anchored on the fixture string this reads HELD, a hold awarded for the probe breaking its own copy`);
826+
say(dc.mutatedBytes === 0,
827+
`POSITIVE CONTROL FAILED: the decoy gate printed ${dc.mutatedBytes} byte(s) when defeated; the copy anchored on the real definition returns before its verdict line and says NOTHING, so anything printed here is the copy failing rather than the gate being silent`);
679828
// The HELPER handshake spelling, both directions, differing by ONE line: the
680829
// dispatch asking `requireReachedVerdict`. Until this fixture the probe had
681830
// read that shape in NEITHER direction -- its single carrier in the tree is
@@ -707,7 +856,7 @@ export function runControls() {
707856
// ---------------------------------------------------------------------------
708857

709858
/**
710-
* ⛔ SHRINK-ONLY. The nine files whose entry cannot be resolved mechanically,
859+
* ⛔ SHRINK-ONLY. The ten files whose entry cannot be resolved mechanically,
711860
* resolved by READING the dispatch site (the ruling's A2.1: the grep is an
712861
* entry point, not the criterion). A `null` entry is NOT MEASURED with the
713862
* stated reason -- never a quiet pass, and never a guess.
@@ -726,15 +875,24 @@ export const ENTRY_BY_HAND = Object.freeze({
726875
// `runSelfTest()` is what the dispatch calls. Probing the inner one records a
727876
// TypeError as a handshake; probing `runSelfTest` reads the real one (#14842).
728877
'scripts/check-workspace-manifest-cycles.mjs': 'runSelfTest',
729-
// The dispatch calls FOUR self-test functions and combines their statuses;
878+
// The dispatch calls SIX self-test functions and combines their statuses;
730879
// there is no single entry an early return leaves, so a one-function probe
731880
// measures a sub-battery and reads a downstream crash as a handshake.
732881
'scripts/check-platform-checklist.mjs': null,
733882
// The self-test is an inline top-level block calling several helpers.
734883
'scripts/check-regen-pending.mjs': null,
735-
// Injecting into this file produces a SyntaxError (the anchor lands inside a
736-
// template literal), so no run of it measures anything.
737-
'scripts/pm/dispatch-gates.mjs': null,
884+
// Four self-test-shaped names in raw source (`selfTest`, `selfTestOnlyCallables`
885+
// and two inside fixtures), so the entry is hand-read -- but for THIS FILE's
886+
// reason. The row was a `null` until #14963, on the claim that injecting here
887+
// can only produce a SyntaxError: the anchor read raw source, where a docblock
888+
// sentence and then a FIXTURE STRING stand ahead of the real definition. That
889+
// was the INSTRUMENT's limit recorded as this file's property. Anchored on the
890+
// definition the copy parses and runs (measured: exit 1, `selfTest() returned
891+
// without reaching its verdict`). The probe still reads NOT MEASURED here and
892+
// now says why itself -- `baseline run failed (exit 1)`, because it writes its
893+
// copy under `scripts/`, where this gate's own single-site sweep finds the
894+
// near-duplicate and refuses (#15515). A separate card, not worked around here.
895+
'scripts/pm/dispatch-gates.mjs': 'selfTest',
738896
});
739897

740898
/**

0 commit comments

Comments
 (0)