@@ -118,6 +118,61 @@ const HERE = dirname(fileURLToPath(import.meta.url));
118118const REPO_ROOT = resolve ( HERE , '..' ) ;
119119const SCRIPTS_DIR = join ( REPO_ROOT , 'scripts' ) ;
120120
121+ /**
122+ * POPULATION DECLARATION -- the corpus this gate's verdict is ABOUT, in the
123+ * subtree spelling `scripts/pm/dispatch-gates.mjs` compares in.
124+ *
125+ * Nothing here reads it; `mirrorFiles()` and `checkPair()` do. It is declared
126+ * anyway, because the dispatch derivation reads SOURCE TEXT and this gate's
127+ * population is discovered by EXTENSION with no path literal anywhere: the walk
128+ * is rooted at `SCRIPTS_DIR`, whose only spelled component is the bare
129+ * single-segment word `scripts` -- no separator, so `extractWatchHints`
130+ * recovers nothing from it, and `hintCovers` would refuse a bare word anyway.
131+ *
132+ * Measured on `ca46f8f12` before this constant existed, on the exact change set
133+ * the defect was filed from:
134+ *
135+ * dispatch-gates --commands -- scripts/js-comment-mask.d.mts \
136+ * scripts/js-comment-mask.mjs
137+ * -> 31 commands, ZERO of them this gate
138+ *
139+ * So the one class of change this gate exists for -- a hand-written `.d.mts`
140+ * moving out of step with the module it mirrors -- was the class the derivation
141+ * never sent here, and the red arrived in CI a cycle late (#15553; the specimen
142+ * is PR #15532, whose 32 derived commands were all green while this gate went
143+ * red on an arity mismatch).
144+ *
145+ * BOTH SIDES are declared, because either side moving breaks the mirror: the
146+ * declaration side is what `mirrorFiles()` walks, and the module side is what
147+ * `checkPair()` imports to read `Function.length` off. A change set naming only
148+ * `js-comment-mask.mjs` can turn this gate red without touching a `.d.mts` at
149+ * all.
150+ *
151+ * ⛔ NOT a hand list of the four mirror pairs: the corpus is DISCOVERED on
152+ * purpose (see this file's header), and a hint list that enumerated today's
153+ * pairs would go quiet on the fifth exactly as the walk would not.
154+ *
155+ * ## The precision this costs, measured rather than asserted
156+ *
157+ * `scripts/**\/*.d.mts` reaches 4 tracked files and this gate reads all 4 --
158+ * 100% precise. `scripts/**\/*.mjs` reaches 214 and this gate reads 4 of them
159+ * -- 1.9%. That second hint is the price of keeping the module side declared
160+ * without a hand list, and it is small in the only currency that matters here:
161+ * the pair of commands `lint.yml` runs costs ~0.18 s of wall clock, and over
162+ * the 36 open PRs on the day this landed it newly named this gate on 10 of them
163+ * (1 through the `.d.mts` hint, 9 through the `.mjs` one). ~1.6 s of fleet
164+ * compute per 36 cards, against a defect class whose alternative is a CI red a
165+ * cycle late. Under `hintCovers`' recorded ruling -- over-naming is loud and
166+ * self-limiting, under-naming is silent -- that trade runs the right way.
167+ *
168+ * Spelled as a LITERAL array, never computed from the walk's extension test:
169+ * the extractor reads SOURCE TEXT, so a built spelling keeps this value
170+ * identical at runtime, keeps every assertion about it green, and contributes
171+ * ZERO hints. `check-watch-hint-literal.mjs` holds that rule fleet-wide; the
172+ * self-test below holds the own-source half.
173+ */
174+ const ROOT_DIR_WATCH_HINTS = [ 'scripts/**/*.d.mts' , 'scripts/**/*.mjs' ] ;
175+
121176/**
122177 * The export spellings this parser recognises, in the words a declaration
123178 * author would write them. Published for the same reason the cross-package
@@ -429,7 +484,7 @@ const SELF_TEST_VERDICT = 'check-declaration-mirrors self-test reached its verdi
429484// not red. A battery BELOW its floor means cases stopped running; the remedy is
430485// to find what stopped registering.
431486const SELF_TEST_BATTERIES = Object . freeze ( {
432- 'check-declaration-mirrors self-test' : 23 ,
487+ 'check-declaration-mirrors self-test' : 29 ,
433488} ) ;
434489
435490// DELETING an entry silences that battery's floor exactly as effectively as
@@ -603,6 +658,70 @@ async function selfTest() {
603658 mirrorFiles ( ) . every ( ( f ) => f . endsWith ( '.d.mts' ) ) ,
604659 ) ;
605660
661+ // ── the population this gate DECLARES to the dispatch derivation (#15553) ──
662+ //
663+ // The walk above is discovered by EXTENSION and spells no path, so before the
664+ // declaration beside `SCRIPTS_DIR` existed a change set naming a mirror --
665+ // either half of one -- derived every other `scripts/` family and not this
666+ // one. Nothing in THIS file can ENFORCE the declaration: `extractWatchHints`
667+ // and `hintCovers` live in another tool entirely, so a wrong or missing one
668+ // runs green here forever. What CAN be held here are the properties a wrong
669+ // one breaks -- it is a live literal the extractor can read, it is not the
670+ // bare root the consumer refuses or over-names on, and its extensions still
671+ // admit every file the walk really opens, on BOTH sides of a mirror.
672+ const declaredRoots = ROOT_DIR_WATCH_HINTS . map ( ( h ) => h . split ( '/' ) [ 0 ] ) ;
673+ const declaredSuffixes = ROOT_DIR_WATCH_HINTS . map ( ( h ) => h . slice ( h . lastIndexOf ( '*' ) + 1 ) ) ;
674+ const admits = ( repoRelative ) =>
675+ ROOT_DIR_WATCH_HINTS . some ( ( h , i ) =>
676+ repoRelative . split ( '/' ) [ 0 ] === declaredRoots [ i ] && repoRelative . endsWith ( declaredSuffixes [ i ] ) ) ;
677+ ok (
678+ 'the gate declares a population at all' ,
679+ Array . isArray ( ROOT_DIR_WATCH_HINTS ) && ROOT_DIR_WATCH_HINTS . length > 0 ,
680+ ) ;
681+ ok (
682+ 'every declared hint is multi-segment, so the consumer does not refuse it as a bare word' ,
683+ ROOT_DIR_WATCH_HINTS . every ( ( h ) => h . split ( '/' ) . filter ( Boolean ) . length > 1 ) ,
684+ ) ;
685+ ok (
686+ 'and none of them is the bare subtree or the repo root, which would name this gate for every '
687+ + 'JSON, Markdown and text file the walk never opens' ,
688+ ROOT_DIR_WATCH_HINTS . every ( ( h ) => ! h . endsWith ( '/**' ) && h !== '.' && h !== '**' ) ,
689+ ) ;
690+ // The card's own point, held against the LIVE walk rather than a fixture: the
691+ // declaration side of every mirror this tree really has is admitted by the
692+ // extensions declared. A fifth pair added tomorrow is walked by `mirrorFiles`
693+ // and covered by the same hint, which is the property a hand list would lose.
694+ ok (
695+ 'every declaration the walk discovers is admitted by a declared hint' ,
696+ mirrorFiles ( ) . length > 0
697+ && mirrorFiles ( ) . every ( ( f ) => admits ( relative ( REPO_ROOT , f ) . split ( sep ) . join ( '/' ) ) ) ,
698+ ) ;
699+ // And the MODULE side, which `checkPair` imports to read `Function.length`
700+ // off: an arity change there reds this gate with no `.d.mts` edited at all,
701+ // so a declaration naming only the declarations would miss half the class.
702+ ok (
703+ 'and so is the module each declaration mirrors, whose arity the gate reads' ,
704+ mirrorFiles ( ) . length > 0
705+ && mirrorFiles ( ) . every ( ( f ) =>
706+ admits ( relative ( REPO_ROOT , f . replace ( / \. d \. m t s $ / , '.mjs' ) ) . split ( sep ) . join ( '/' ) ) ) ,
707+ ) ;
708+ // Read from THIS file's own source, comment-masked and scoped to the
709+ // declaration STATEMENT: a whole-file search finds the spellings the docblock
710+ // above writes and passes on a computed declaration, which is the one
711+ // spelling that keeps every case above green while contributing ZERO hints.
712+ const ownDeclaration = ( ( ) => {
713+ const code = maskComments ( readFileSync ( fileURLToPath ( import . meta. url ) , 'utf8' ) ) ;
714+ const sites = [ ...code . matchAll ( / \b c o n s t \s + R O O T _ D I R _ W A T C H _ H I N T S \s * = \s * ( [ ^ ; ] * ) ; / g) ] ;
715+ return sites . length === 1 ? sites [ 0 ] [ 1 ] : null ;
716+ } ) ( ) ;
717+ ok (
718+ 'the declaration is ONE literal array of quoted strings — a computed spelling keeps this value '
719+ + 'identical at runtime and contributes ZERO hints to the derivation' ,
720+ ownDeclaration !== null
721+ && ROOT_DIR_WATCH_HINTS . every ( ( h ) => ownDeclaration . includes ( `'${ h } '` ) )
722+ && / ^ \s * \[ \s * (?: ' [ ^ ' \\ ] * ' \s * , \s * ) * ' [ ^ ' \\ ] * ' \s * , ? \s * \] \s * $ / . test ( ownDeclaration ) ,
723+ ) ;
724+
606725 // ── The floor: every declared battery RAN, and ran its cases (#13489) ────
607726 //
608727 // Evaluated after every battery has had its chance and BEFORE the verdict, so
0 commit comments