Skip to content

Commit 2258fc9

Browse files
committed
feat(devx): one recogniser for the verdict-handshake shape (#14968)
`classifyHandshake` reads the masked, line-anchored source and returns `sentinel` / `flag` / `helper` / `none` from the MECHANISM -- the value the self-test hands back and the comparison that consumes it -- rather than from any of the three landed names. `definitionSpan` brace-counts the body instead of taking the first column-0 `}`, which three files close early inside an inline arrow argument. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
1 parent c99449a commit 2258fc9

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

scripts/measure-self-test-floor.mjs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,169 @@ export function probeEarlyReturn(absFile, entry, { timeout = 120000, placement =
603603
}
604604
}
605605

606+
// ---------------------------------------------------------------------------
607+
// Instrument 3 -- WHICH verdict handshake a self-test carries, read from code
608+
// ---------------------------------------------------------------------------
609+
610+
/**
611+
* The extent of one definition's BODY in masked code: from the `{` the anchor
612+
* lands on to the `}` that closes it, by counting braces.
613+
*
614+
* ⛔ NOT "the first `}` at column 0". That rule is the tree's convention for
615+
* where a top-level definition ENDS, and it is wrong often enough to matter:
616+
* `scripts/check-error-code-casing.mjs` closes an inline arrow argument with
617+
* `});` at column 0 inside `selfTest`, 107 lines before the function really
618+
* ends. Read that way, the flag `selfTest` sets as its last act falls OUTSIDE
619+
* its own body and the file classifies `none` -- measured, three files (also
620+
* `check-optional-error-sink-contract`, `check-org-identifier`), all three
621+
* carrying a perfectly ordinary handshake.
622+
*
623+
* Counting is safe HERE and only here because the text is already masked: every
624+
* brace inside a comment, a string, a template or a regex literal has been
625+
* blanked, so the ones that remain are the ones the parser sees.
626+
*/
627+
export function definitionSpan(code, name) {
628+
const pats = [
629+
new RegExp(
630+
`^[ \\t]*(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)` +
631+
`\\s*(?::\\s*[A-Za-z_$][\\w$<>\\[\\]|. ]*\\s*)?\\{`,
632+
'm',
633+
),
634+
new RegExp(`^[ \\t]*(?:export\\s+)?const\\s+${name}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*(?::[^=]*)?=>\\s*\\{`, 'm'),
635+
];
636+
for (const re of pats) {
637+
const m = code.match(re);
638+
if (!m) continue;
639+
const at = m.index + m[0].length;
640+
let depth = 1;
641+
for (let i = at; i < code.length; i++) {
642+
if (code[i] === '{') depth++;
643+
else if (code[i] === '}' && --depth === 0) return { at, end: i };
644+
}
645+
}
646+
return null;
647+
}
648+
649+
/**
650+
* SENTINEL -- the self-test's RETURN VALUE is compared against a NAMED operand:
651+
* `if (selfTest() !== SELF_TEST_VERDICT)`, `if ((await selfTest()) !== ...)`.
652+
*
653+
* BOUNDARY -- the operand must be an IDENTIFIER. A comparison against a LITERAL
654+
* is the ACCIDENT shape this file's header is about: over a self-test that
655+
* returned early, `runSelfTest() === 0` is `undefined === 0` -> false -> exit 1,
656+
* having printed ZERO BYTES. Nothing noticed anything; the arithmetic of a
657+
* comparison against a missing return value did it, and calling that a handshake
658+
* is the mistake the DEFEATED/HELD/ACCIDENT verdict exists to refuse. The word
659+
* literals are excluded for the same reason they are literals.
660+
*
661+
* ⚠️ So `selfTest() !== undefined` would read `none` here, and no file in this
662+
* tree spells it that way today. Left unadmitted rather than written blind --
663+
* the same protocol the DISPATCH criterion publishes for its two unwitnessed
664+
* spellings: widen with a control in both directions, and publish the delta.
665+
*/
666+
const HANDSHAKE_RETURN_COMPARED =
667+
/(?:await\s+)?\(?\s*(?:await\s+)?([A-Za-z_$][\w$]*)\s*\(\s*\)\s*\)?\s*(?:!==|===|!=|==)\s*([A-Za-z_$][\w$]*)\b/g;
668+
const LITERAL_OPERAND = /^(?:undefined|null|true|false|NaN)$/;
669+
670+
/**
671+
* The FLAG and HELPER shapes share one carrier and differ only in WHO reads it.
672+
*
673+
* The carrier is a variable that CROSSES THE BOUNDARY out of the self-test: a
674+
* module-level binding, assigned `true` INSIDE the self-test's body as its last
675+
* act, and read OUTSIDE it by the dispatch. Both halves are structural, and
676+
* both are load-bearing:
677+
*
678+
* MODULE-LEVEL a `let`/`var` at column 0. A binding declared inside the
679+
* self-test cannot outlive the call, so it can carry nothing --
680+
* and the ordinary accumulator `let ok = true` is exactly that.
681+
* Without this half `scripts/check-regen-pending.mjs` reads
682+
* `flag`, on an `ok` set inside `decisionTableSelfTest` and an
683+
* unrelated `(ok) => !ok` arrow PARAMETER 60 lines later. There
684+
* is no scope analysis here; the column-0 declaration is what
685+
* stands in for one.
686+
* CROSSES set inside the body, read outside it. A boolean set and read
687+
* within one function is a local decision, not a handshake.
688+
*
689+
* Then the two shapes:
690+
*
691+
* FLAG the DISPATCH reads it itself, negated -- `if (!selfTestReachedVerdict)`.
692+
* HELPER the dispatch HANDS it to a callee that refuses on its behalf --
693+
* `requireReachedVerdict('selfTest', selfTestReachedVerdict)`. The
694+
* callee must be defined in this file and its body must PRODUCE A
695+
* FAILURE, which is what separates a refusal from any other function
696+
* that happens to take a boolean.
697+
*
698+
* ⭐ The helper is therefore not a third mechanism -- it is the FLAG with its
699+
* refusal factored out of ten inlined copies, which is exactly the merit the
700+
* ruling that admitted it turned on. Recognised as its own shape because the
701+
* census question is "which spelling is this file written in", and answering it
702+
* is this column's whole job.
703+
*/
704+
const MODULE_LEVEL_BINDING = /^(?:let|var)\s+([A-Za-z_$][\w$]*)/gm;
705+
const SET_TRUE = /([A-Za-z_$][\w$]*)\s*=\s*true\b/g;
706+
const negatedRead = (name) => new RegExp(String.raw`!\s*${name}\b`, 'g');
707+
const handedToCall = (name) => new RegExp(String.raw`([A-Za-z_$][\w$]*)\s*\(\s*[^()]*?\b${name}\s*\)`, 'g');
708+
709+
/**
710+
* ⭐ THE ONE RECOGNISER (#14968). Every caller -- `--json`, the human census
711+
* column, the per-shape summary -- reads THIS, so a fifth landed shape is a
712+
* change here and nowhere else. That is the whole point: the repair had landed
713+
* in three spellings, every handshake question was answered by a hand-written
714+
* grep, and three seats in one shift got three different wrong answers from
715+
* three different greps whose completeness nobody could check.
716+
*
717+
* Returns `'sentinel' | 'flag' | 'helper' | 'none'`, read from the MASKED,
718+
* line-anchored source -- the same text `selfTestDefs` and the injection anchor
719+
* read. Masked, because a spelling quoted inside a fixture string or described
720+
* in a docblock is not a handshake the dispatch can perform; this file is full
721+
* of both, and so are the gates that reason about self-tests.
722+
*
723+
* ⛔ NOT keyed on the three landed NAMES. `SELF_TEST_VERDICT`,
724+
* `selfTestReachedVerdict` and `requireReachedVerdict` appear nowhere in this
725+
* function: what it reads is the value handed back and the comparison that
726+
* consumes it. `classifyFloor` keyed on the NAME `SELF_TEST_BATTERIES` once and
727+
* called a fixture floored after its roster had been removed; the control below
728+
* renames every landed spelling out of a fixture and requires the same verdict.
729+
*
730+
* ORDER: sentinel, then helper, then flag. Measured on this base: NO file in the
731+
* census carries two shapes, so the order decides nothing today -- the live
732+
* check below publishes that overlap as a number rather than leaving it assumed.
733+
*
734+
* ⛔ It recognises the shapes; it does not legislate them. Two of the three are
735+
* deliberate -- the flag exists because those self-tests' own exit codes are
736+
* load-bearing, so the handshake cannot BE the return value -- and unifying the
737+
* tree is explicitly not this instrument's business.
738+
*/
739+
export function classifyHandshake(src) {
740+
const code = maskCommentsAndLiterals(src);
741+
742+
for (const m of code.matchAll(HANDSHAKE_RETURN_COMPARED)) {
743+
if (/self.?test/i.test(m[1]) && !LITERAL_OPERAND.test(m[2])) return 'sentinel';
744+
}
745+
746+
const spans = selfTestDefs(src)
747+
.map((name) => definitionSpan(code, name))
748+
.filter((s) => s !== null);
749+
const insideSelfTest = (at) => spans.some((s) => at >= s.at && at < s.end);
750+
751+
const moduleLevel = new Set([...code.matchAll(MODULE_LEVEL_BINDING)].map((m) => m[1]));
752+
const carried = new Set();
753+
for (const m of code.matchAll(SET_TRUE)) {
754+
if (moduleLevel.has(m[1]) && insideSelfTest(m.index)) carried.add(m[1]);
755+
}
756+
757+
let flag = false;
758+
for (const name of carried) {
759+
for (const m of code.matchAll(handedToCall(name))) {
760+
if (insideSelfTest(m.index) || /self.?test/i.test(m[1])) continue;
761+
const def = definitionSpan(code, m[1]);
762+
if (def && PRODUCES_FAILURE.test(code.slice(def.at, def.end))) return 'helper';
763+
}
764+
for (const m of code.matchAll(negatedRead(name))) if (!insideSelfTest(m.index)) flag = true;
765+
}
766+
return flag ? 'flag' : 'none';
767+
}
768+
606769
// ---------------------------------------------------------------------------
607770
// The controls -- run on EVERY invocation, before any number is printed
608771
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)