@@ -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+ / (?: a w a i t \s + ) ? \( ? \s * (?: a w a i t \s + ) ? ( [ A - Z a - z _ $ ] [ \w $ ] * ) \s * \( \s * \) \s * \) ? \s * (?: ! = = | = = = | ! = | = = ) \s * ( [ A - Z a - z _ $ ] [ \w $ ] * ) \b / g;
668+ const LITERAL_OPERAND = / ^ (?: u n d e f i n e d | n u l l | t r u e | f a l s e | N a N ) $ / ;
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 = / ^ (?: l e t | v a r ) \s + ( [ A - Z a - z _ $ ] [ \w $ ] * ) / gm;
705+ const SET_TRUE = / ( [ A - Z a - z _ $ ] [ \w $ ] * ) \s * = \s * t r u e \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 ( / s e l f .? t e s t / 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 ) || / s e l f .? t e s t / 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