Repository navigation
Expand file tree
/
Copy pathdispatch-gates.data.mjs
More file actions
1433 lines (1416 loc) · 93.4 KB
/
Copy pathdispatch-gates.data.mjs
File metadata and controls
1433 lines (1416 loc) · 93.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
/**
* dispatch-gates.data — the hand-written tables `dispatch-gates.mjs` reads, as DATA and nothing else.
*
* The gate ROSTER is not here, and never was anywhere: which paths feed which `check:*` families is
* derived from `.github/workflows/*.yml`, the composite actions they `uses:`, `package.json` and the
* gate scripts' own sources, on every run of the engine beside this file. What IS hand-written is
* below: the marker grammar vocabularies, the ledgers (whole-tree residue, compound anchors, governed
* reads, escapable literals), the change-kind roster (convention-triggered gates, named by the KIND
* of change rather than by a path), the model-tier globs, the tier ladder words and the generated-module
* sources (a git-ignored module's committed inputs). Each table keeps the docblock that governs it; a
* row's reason is part of the row.
*
* ⛔ No logic. A row that needs a function names it — a predicate by its key (`matches`, `except`),
* a tier by its name (`tier`) — and the engine resolves the name at load, refusing one it does not
* know. A new gate family under a kind is one row here, still only on the maintainer's words.
*
* ⛔ Not a watch surface. No gate family resolves to this file and no gate imports it, so the paths
* its rows spell are never read as a population; the engine declares its own inherited population
* from what it OPENS, which is why moving these rows here moved no derivation.
*/
/**
* The THREE population markers' grammar, in ONE spelling, and the reading of
* whether the reason one of them captures is WHOLE (#18422).
*
* ## The defect this exists for
*
* Every population marker below captured its reason with `(\S.*)$` under the
* `m` flag, so the capture ends at the FIRST NEWLINE. A reason an author wraps
* across two or three comment lines — which is what a comment that long looks
* like in every file in this tree — was captured as line ONE, and nothing
* refused it: `wholeTreePopulationRefusal` checks that a reason EXISTS and that
* a root walk BACKS it, never that it is whole. Measured on card #17472: a
* three-line reason rendered to the seat as "…and the verdict is", a sentence
* that simply stops. The failure mode is the expensive kind — a truncated
* reason reads as a complete sentence that merely ends oddly, and the reason is
* the ONE thing a seat reads off that row when deciding whether a family
* belongs on its card.
*
* ## The contract: the reason is WHOLE, or the declaration is RED
*
* A declaration OWNS the line it is written on and nothing else. It is
* terminated by a blank line, a blank comment line, a non-comment line, EOF, or
* another `dispatch-gates:` declaration. A comment line immediately below it,
* in the SAME comment form, carrying text that is not a new `dispatch-gates:`
* key, is read as a CONTINUATION of the reason — and a continued reason is
* refused, naming the file and the line that continues it.
*
* ⛔ The remedy is NOT a marker that consumes a comment BLOCK. Nothing in the
* text can tell a wrapped reason from an unrelated comment written under the
* declaration, so a block-consuming marker would silently make the next
* paragraph part of a seat-facing reason — the same class of defect pointed the
* other way, and the coin toss this file refuses everywhere else. Refusing is
* decidable; swallowing is a guess. Measured over the tree at the time of
* writing: 27 declarations across 25 gate files, 26 of them already writing the
* whole reason on one line (up to 1215 characters of it), and exactly one
* wrapped — so the one-line spelling is what this convention already IS, and
* the refusal names the one declaration that was being cut.
*
* ## Why the grammar is built here rather than written out three times
*
* The continuation reading has to agree with the capture about what a marker
* line IS, down to the comment form. Two spellings of one grammar drift
* silently — the exact failure `declaredNoCheckFamiliesReason`'s docblock
* prices one level up — so the pattern each marker uses and the pattern the
* continuation reading uses come out of this one function. Group 1 is the
* comment form (`//` or `#`), group 2 is the reason; the form is captured
* rather than discarded because a `#` line under a `//` declaration is not a
* comment in the same language and cannot be continuing it.
*/
export const POPULATION_MARKER_KEYS = Object.freeze(['no-path-population', 'whole-tree-population', 'wide-population']);
/**
* The COMMENT FORMS a `dispatch-gates:` declaration may be written in, in ONE
* roster — and the HEAD every marker pattern below is built out of it: the
* indent, the form, and the key that follows it.
*
* ## The defect the roster was widened for (#18661)
*
* The alternation listed `//` and `#` and nothing else, so a declaration
* written in the file's own BLOCK-comment idiom parsed as NOTHING: not
* refused, not printed, not counted. Measured on `origin/main` 95b21b33be,
* `declaredNoPathPopulation` read `null` over both of this tree's block-form
* declarations — `scripts/symbol-anchors.mjs` (a slash-star opener) and
* `scripts/release-verify-npm.mjs` (a star-prefixed line inside a docblock) —
* and both families sat in the residue's `undetermined` bucket with `hints=0`,
* the exact bucket the marker exists to split them out of. A dropped
* declaration printed IDENTICALLY to one nobody ever wrote, which is the one
* shape neither side will go and check: its author believes they explained the
* emptiness, its reader believes nobody ever did.
*
* ## Two KINDS of form, because they answer the wholeness question differently
*
* `line` — `//` and `#`. Each line is its OWN comment. The line under a
* declaration is a SEPARATE comment, and nothing in the text says whether it
* belongs to the reason or is an unrelated remark. So the declaration owns
* its line, and a comment line under it is refused as a CUT (#18422) —
* unchanged here, byte for byte, and `populationReasonCutRefusal`'s text
* still states it.
*
* `block` — a slash-star opener (one star or two) and the star-prefixed
* CONTINUATION lines inside it. The whole comment is ONE comment and its
* internal newlines are FORMATTING, not comment boundaries, so a reason that
* wraps is decidable rather than a guess: inside a block, the next
* star-prefixed line is a continuation BY CONSTRUCTION. What the block form
* therefore cannot do is cross any of the three places a block-comment author
* signals a new thought — each one pinned in the self-test:
*
* the CLOSING delimiter the comment is over
* a blank star-only line the author ended the paragraph
* the next star-@tag line the docblock's tag section begins
*
* plus the two the line forms already carry: another `dispatch-gates:` key,
* and EOF. A line INSIDE the block carrying text with no star prefix is none
* of the five — it is reason text this walk cannot read — so it is recorded
* as a CUT and refused, the same direction and for the same reason the line
* forms are refused in.
*
* ⚠️ The residual asymmetry, named here rather than left to be found: a second
* sentence written on the very next star line, with no blank line between it
* and the declaration, IS swallowed into the reason. That is OVER-inclusion,
* and it reaches the seat as a reason that says too much — visible on the row.
* The truncation #18422 refused is UNDER-inclusion, and reaches the seat as a
* sentence that merely ends oddly — invisible. The block idiom's own paragraph
* break is the text that separates the two, and it is what a block-comment
* author already writes; the line forms have no such text, which is why they
* refuse instead.
*
* ## One alternation, one roster
*
* The form alternation is the half a reader has to be able to change in ONE
* place: a form this roster does not list parses as nothing at all, silently,
* and widening it in one builder while the other kept its own copy would fix
* half the markers and leave the other half reading exactly as they did. Both
* builders below and the continuation reading come out of this roster. Group 1
* of every pattern is the form, because the reading has to know a `#` line is
* not continuing a `//` one — and now also which KIND of form it is reading.
*
* ⚠️ Order is load-bearing: the two-star opener is listed BEFORE the one-star
* opener, so a `/**` docblock opener captures whole instead of matching `/*`
* and stranding its second star in front of the key.
*
* ⛔ Every example in the docblocks below is written with the docblock's OWN
* star prefix AND the example's own form — two openers on one line. That is
* not decoration: exactly ONE opener is what this grammar and the
* unparsed-form probe both accept, so a two-opener line is documentation to
* both of them and can never be read as a live declaration about this file.
*/
export const MARKER_COMMENT_FORMS = Object.freeze([
Object.freeze({ label: '//', kind: 'line', open: '\\/\\/' }),
Object.freeze({ label: '#', kind: 'line', open: '#' }),
Object.freeze({ label: '/**', kind: 'block', open: '\\/\\*\\*' }),
Object.freeze({ label: '/*', kind: 'block', open: '\\/\\*' }),
Object.freeze({ label: '*', kind: 'block', open: '\\*' }),
]);
/**
* The marker keys whose files are NOT JavaScript, and the comment forms those
* files really have (#18662).
*
* The roster above is the set of idioms a `dispatch-gates:` declaration may be
* written in across this tree; it is not a claim that every one of them is a
* comment in every LANGUAGE a declaration is read out of. `no-check-families`
* is read out of workflow YAML, where `#` is the only comment there is: a
* `//` or slash-star line in a workflow is document content, and reading a
* declaration off one would be reading it off text the workflow's own parser
* never treats as a remark. So the alternation this key's pattern is built
* from is the roster FILTERED to the forms its language has — never a second
* roster, and never a second pattern.
*
* A key absent from this table gets the whole roster, which is the answer for
* every marker read out of a JavaScript or shell source.
*
* ⛔ This table may only ever NARROW: it names a subset of the labels in
* `MARKER_COMMENT_FORMS`, and a label that is not one of them throws below
* rather than silently contributing nothing to the alternation — a form set
* that quietly emptied would make every declaration of that key parse as
* nothing at all, which is precisely the #18661 failure one level up.
*/
export const MARKER_KEY_FORMS = Object.freeze({
'no-check-families': Object.freeze(['#']),
});
/**
* The REASON-TAIL markers — the keys whose grammar is head + `-- <reason>`,
* with nothing between the key and the separator (#18662).
*
* The three population keys were the whole roster until this card, and the
* builder is still spelled `populationMarkerPattern` because `population*` is
* what this machinery is CALLED everywhere it is exported
* (`populationReasonContinuation`, `populationReasonCutRefusal`) and a rename
* would move the names a reader greps for without moving a single behaviour.
* ⚠️ The ROSTER, not the name, is the authority on which keys it serves:
* `no-check-families` has exactly this grammar and is built here rather than
* out of the fourth hand-written copy of the pattern it used to be — which is
* what left its reason outside the #18422 wholeness reading for two cards.
*/
export const REASON_TAIL_MARKER_KEYS = Object.freeze([...POPULATION_MARKER_KEYS, 'no-check-families']);
/**
* The PATH-LIST markers' grammar, in one spelling, for the same reason the
* reason-only markers have one (#18673).
*
* A path-list declaration names files BEFORE its reason —
* `dispatch-gates: <key> <path> [<path> ...] -- <reason>` — so it cannot come
* out of `populationMarkerPattern`, whose tail is a bare reason. What it CAN
* share is the head: the indent, the comment form and the key. Group 1 is the
* comment form, group 2 the path list, group 3 the reason.
*
* The `--` is SPACE-delimited on both sides here (never `[ \t]*`), because a
* path may legitimately contain one and a bare separator would split it.
*
* ⚠️ `local-env` shares this grammar with a list of ENVIRONMENT NAMES in the
* path position (#20278). The grammar is LIST-then-reason and never reads what
* the list holds; each key's own reader does (`declaredLocalEnv` refuses a
* token that is not an env name). The roster keeps its name for the reason
* `REASON_TAIL_MARKER_KEYS` states: a rename would move what a reader greps for
* without moving a behaviour, and a third builder would be the copy of this
* pattern the refusal below forbids.
*/
export const PATH_LIST_MARKER_KEYS = Object.freeze(['inherited-population', 'self-test-reads', 'local-env']);
/**
* The WHOLE-TREE RESIDUE ledger (#15312) — the families whose own source sweeps
* the repository root, that this derivation can place on NO card, and that are
* deliberately not in the whole-tree bucket above.
*
* ## The defect
*
* `check:driver-memory-census` counts a `vi.mock` of a frozen driver package as
* a module binding wherever in the tree it is written, and names no path
* literal anywhere in its source — so this derivation scored it `undetermined`
* for every card and no `--commands` harvest could contain it. A seat derived
* its family, ran 57 of them with 53 green, and CI's lint job then failed on
* the one gate the derivation could not offer. The gate was right; what was
* missing was a way for a seat to be TOLD about it before the push, which is
* exactly what the whole-tree channel above exists to be. It now declares.
*
* ⚠️ Fixing that one gate is not the fix. The question worth answering is
* "which gates does CI run that no derivation can name", mechanically, so the
* NEXT one reds here rather than in CI a cycle later. This table and the live
* case in `--self-test` are that answer for the class the card names.
*
* ## The population, and why the closure subtraction is load-bearing
*
* A family is IN it when three things hold at once: its own source carries a
* recognised repo-root walk (`repoRootWalkSpelling` — the same predicate that
* vouches for a whole-tree declaration), it declares NEITHER marker, and no
* path in the tree OUTSIDE the gate's own file closure can place it. That last
* subtraction is not a detail: every family matches the card that edits the
* gate itself, through the identity key, so counting that as "the derivation
* can name it" would score this whole population green on a card nobody files.
*
* ## Why this class and not "every gate CI runs"
*
* The wider question was MEASURED for this card rather than guessed at: on
* cd1f8ee96, of the 186 gate scripts CI runs, 44 are named by no derivation at
* all. The bulk of them are SUBTREE walkers — seeded at `packages`, `apps`,
* `examples` through a runtime constant no source scan reads — whose remedy is
* the ordinary `ROOT_DIR_WATCH_HINTS` declaration, one per-gate judgement each.
* A table that swallowed all 44 would be forty rows nobody revisits, which is
* the shape `artifactRosterLines` refuses for its own members. So this holds
* the class the card named, the rest is a filed follow-up, and the boundary is
* STATED here rather than left to be inferred from what the table happens to
* contain.
*
* ## Maintaining this table
*
* A member this table does not list reds `check:pm-dispatch-gates`, and there
* are exactly two honest repairs. If the gate really does read the whole tree,
* give it the `whole-tree-population` marker: it then leaves this population by
* DECLARING, which is the outcome this table exists to push toward. If it does
* not, add a row saying what it reads INSTEAD, so a reader can check the claim
* against the gate. ⛔ Never a row that only says "not whole-tree" — that is
* the reason-less opt-out both markers refuse, and it reads exactly like a
* placeholder nobody will revisit. A listed family that stops being a member
* reds too: a stale exclusion is an exclusion nobody is measuring any more.
*/
export const ROOT_WALK_RESIDUE_LEDGER = [
[
'scripts/check-skill-frame-freshness.mjs --self-test',
'lint.yml runs the SELF-TEST HALF and never the scan — its step is named that, and the step comment states why '
+ '(the pnpm script would drag the scan in with it). The repo-root default parameter belongs to the scan half '
+ 'CI does not schedule, so a whole-tree row for this family would advertise work no workflow performs.',
],
// ⚖️ `scripts/check-console-intercept-disarm.mjs` and its `--self-test` LEFT this
// population under #29353 and their two rows are gone with them — a listed
// family that stops being a member reds here. They left BY PATH: the gate now
// declares what its scan opens, a package-root `package.json` and vitest
// config under each workspace glob, as `ROOT_DIR_WATCH_HINTS` in its own
// source, and its scan reds when that declaration drifts from
// `pnpm-workspace.yaml`. The rows had stated the population right and drawn
// the wrong conclusion from it: an EDITED package-root config moves the gate
// as surely as a new package does, so a card editing one learned of the gate
// only from CI (PR #29345 paid one fix round).
// ⚖️ `check:pm-half-states` LEFT this population under #16904's D2 and its row
// is gone with it — a listed family that stops being a member reds here, and
// a stale exclusion is an exclusion nobody measures. It is now placeable BY
// PATH, and truthfully: `check-half-states.mjs`'s self-test reads a sibling
// source as program text — `scripts/pm/check-prior-rulings.mjs` — to pin
// that the prior-ruling line's writer still prints the key the patrol greps.
// So a card touching that sibling really does owe this gate, which is the
// opposite of the #15753 placement this row was written about: that one came
// from a noise-floor constant and said the opposite of what the constant
// declares, while this literal is exactly what the self-test reads.
// ⚖️ `scripts/symbol-anchors.mjs --self-test` LEFT this population under
// #18661 and its row is gone with it — a listed family that stops being a
// member reds here, and a stale exclusion is an exclusion nobody measures.
// It left through the OUTCOME this table exists to push toward: it DECLARES.
// The declaration was there the whole time — a `no-path-population` marker
// written in that file's own block-comment idiom, in a form the marker
// grammar did not list, so it read back `null` and the family arrived here
// looking like a gate whose emptiness nobody had examined. This row was the
// price of that silence: a hand-written exclusion, carrying by hand the
// reading the gate's own source already carried, for a family that was never
// a member of this population at all. Widening the form set (see
// `MARKER_COMMENT_FORMS`) is what let the declaration be read; deleting the
// row is the other half of the same landing.
];
/**
* Every COMPOUND-name declaration the self-test anchor matches, tree-wide, and
* whether that match is what the anchor MEANT.
*
* ## The defect this ledger answers
*
* `SELF_TEST_DECL` decides "this is a self-test" from the declaration's NAME.
* A name is not a role, so the anchor also fires on production code whose name
* merely spells self-test — and when it does, `maskSelfTests` blanks that
* production body, and `extractWatchHints` never sees the paths in it. The
* failure direction is SILENCE: a hint that is never extracted cannot be
* missed, so a gate family quietly stops being derived for a file it really
* opens.
*
* The specimen that opened this is `maskSelfTests` itself, six lines above:
* `mask` + `Self` + `Test` + `s` matches, so this module's masker blanks its own
* body whenever the module scans itself.
*
* ## The census, re-derived on this tree
*
* 324 code-position matches over the tracked JS/TS corpus, 24 of them under
* `ui/` since the objectui import (objectstack#25803). 290 are the bare
* `selfTest`; the remaining 35 carry compound names over 29 distinct spellings,
* and they are the rows below. 24 are genuine self-test batteries — the
* anchor firing on them is the anchor working. ELEVEN are production code:
*
* scripts/check-self-test-wired.mjs carriesSelfTest
* scripts/check-self-test-workflow-commands.mjs runSelfTest
* scripts/check-step-collectors.mjs selfTestTargets
* scripts/check-step-collectors.mjs selfTestDiscoveries
* scripts/measure-durability-swallow-family.mjs selfTestMode
* scripts/measure-self-test-floor.mjs selfTestDefs
* scripts/pm/dispatch-gates.mjs selfTestOnlyCallables
* scripts/pm/dispatch-gates.mjs maskSelfTests
* scripts/pm/dispatch-gates.mjs selfTestCaseLines
* scripts/pm/dispatch-gates.mjs selfTestOnlyInvocation
* scripts/pm/dispatch-gates.mjs declaredSelfTestReads
*
* Every one of them is a gate that REASONS ABOUT self-tests, which is why they
* cluster: a tool that finds, spawns, counts or masks other scripts' self-tests
* names its functions after the thing it handles, and the anchor cannot tell
* "runs a self-test" from "is one".
*
* ## What it costs today: nothing, MEASURED, and that is the whole point
*
* Neutralising each of the ten one at a time and re-extracting moves no hint
* in any of the six files. The claim is therefore live rather than recalled —
* and it is exactly the kind of claim that stops being true without anything
* going red, which is what the pin in this module's self-test exists to catch.
*
* The same measurement, redone over the table's current 24 genuine rows,
* is still NOT zero, and that asymmetry is what makes the classification
* load-bearing rather than decorative: `fixtureSelfTest` drops
* `packages/spec/spec-changes.json` and `prePushIsArmedSelfTest` drops
* `.githooks/pre-push`, both fixture paths in `scripts/check-regen-pending.mjs`,
* both correctly refused. So "no
* compound-name match may contribute a hint" is FALSE as a blanket invariant;
* the invariant holds only over the accidental half, and only a classification
* can name that half.
*
* ## ⛔ Why the anchor is NOT narrowed, and why nothing is special-cased
*
* The obvious repairs were both refuted by the census rather than judged:
*
* - **Narrowing the name pattern is impossible.** `runSelfTest` is a GENUINE
* entry point in `scripts/check-turbo-task-graph.mjs`, reached only from
* that file's `--self-test` guard, and ACCIDENTAL in
* `scripts/check-self-test-workflow-commands.mjs`, where it is exported and
* spawns other scripts' self-tests from the gate body. One spelling, both
* classes. No predicate over the name can separate them, so any narrowing
* that excludes the accidental one also unmasks a real self-test battery and
* readmits its fixture paths as hints — the fabricated-lead family this
* whole masker exists to refuse, traded for a silence that costs nothing.
* - **Special-casing this module's own path fixes four rows of ten.** The
* other six live in five other files, so the objection that a rename
* "fixes one instance and leaves the class" applies to it too, one file
* wider — and it would make the tool's self-scan differ from every other
* scan, which is a hazard of its own.
*
* ⇒ What ships is neither. The anchor keeps firing on all 35, the mask keeps
* blanking all 35, and the cost of the eleven accidental ones is MEASURED on
* every run instead of asserted in prose. Silence was the defect; the remedy is
* noise on the day it starts costing something.
*
* ## Maintaining this table
*
* A compound-name declaration this table does not list reds
* `check:pm-dispatch-gates`. Classify it and add a row: `accidental: false` if
* it is a self-test battery (its fixtures SHOULD be masked away), `true` if it
* is production code the anchor caught by accident — in which case the pin then
* measures, and keeps measuring, that masking it costs no hint. ⛔ Do not
* "repair" a red by renaming the function to dodge the anchor: the row is the
* record, and the next accidental name is the one nobody will notice.
*
* The TOTAL / GENUINE / ACCIDENTAL counts stated above are pinned the same
* way (#15310): `--self-test` computes them fresh from this table and checks
* the docblock's own prose against that computation, never against a second
* hand-typed constant. Prose that drifts from the table reds there, instead
* of drifting further unnoticed the way it had — twice — by the time #15310
* measured it.
*/
export const COMPOUND_ANCHOR_LEDGER = [
['packages/lint/scripts/check-doc-formula-expressions.mjs', 'specSelfTest', false],
['packages/lint/scripts/check-doc-formula-expressions.mjs', 'fieldRuleSelfTest', false],
// #26076: the ADR index generator's battery, reached from its `--self-test` guard and run again
// (never exited on) by its report-only `--report` mode, so a broken generator warns in CI instead
// of going quiet. A battery, so its fixture filenames are masked away as hints — genuine.
['scripts/adr-index.mjs', 'runSelfTest', false],
['scripts/audits/14744-before-update-per-row-value-census.mjs', 'runSelfTest', false],
['scripts/check-comment-mask-corpus.mjs', 'runSelfTestCases', false],
['scripts/check-doc-authoring.mjs', 'selfTestRule3', false],
['scripts/check-doc-authoring.mjs', 'selfTestPackagesProse', false],
['scripts/check-durability-degradation-log-level.mjs', 'checkSelfTestFloor', false],
['scripts/check-durability-degradation-log-level.mjs', 'selfTestReadSeams', false],
['scripts/check-platform-checklist.mjs', 'selfTestTrapVocabulary', false],
['scripts/check-platform-checklist.mjs', 'selfTestProvisioningUse', false],
['scripts/check-platform-checklist.mjs', 'selfTestUnreferencedRecipes', false],
['scripts/check-platform-checklist.mjs', 'selfTestMetaCallSpelling', false],
['scripts/check-platform-checklist.mjs', 'selfTestLineCitationBinding', false],
['scripts/check-platform-checklist.mjs', 'selfTestSymbolAnchors', false],
['scripts/check-platform-checklist.mjs', 'selfTestPlannedStatus', false],
['scripts/check-regen-pending.mjs', 'fixtureSelfTest', false],
['scripts/check-regen-pending.mjs', 'prePushIsArmedSelfTest', false],
['scripts/check-regen-pending.mjs', 'decisionTableSelfTest', false],
['scripts/check-turbo-task-graph.mjs', 'runSelfTest', false],
['scripts/check-workspace-manifest-cycles.mjs', 'runSelfTest', false],
['ui/scripts/__tests__/helpers/child-verdict.ts', 'selfTestCases', false],
['ui/scripts/check-comment-mask-corpus.mjs', 'runSelfTestCases', false],
['ui/scripts/check-pre-install-import-graph.mjs', 'runSelfTest', false],
['scripts/check-self-test-wired.mjs', 'carriesSelfTest', true],
['scripts/check-self-test-workflow-commands.mjs', 'runSelfTest', true],
['scripts/check-step-collectors.mjs', 'selfTestTargets', true],
['scripts/check-step-collectors.mjs', 'selfTestDiscoveries', true],
['scripts/measure-durability-swallow-family.mjs', 'selfTestMode', true],
['scripts/measure-self-test-floor.mjs', 'selfTestDefs', true],
['scripts/pm/dispatch-gates.mjs', 'selfTestOnlyCallables', true],
['scripts/pm/dispatch-gates.mjs', 'maskSelfTests', true],
['scripts/pm/dispatch-gates.mjs', 'selfTestCaseLines', true],
['scripts/pm/dispatch-gates.mjs', 'selfTestOnlyInvocation', true],
['scripts/pm/dispatch-gates.mjs', 'declaredSelfTestReads', true],
];
/**
* The governed reads this tree carries, pinned — a FLOOR the next card lowers,
* never a list this derivation reads (#18673).
*
* Three rows at the landing of this card, measured by `governedReadCensus`:
*
* scripts/check-commit-card-trailers.mjs .claude/agents/os-dev.md undeclared
* scripts/pm/check-expected-skips.mjs .claude/skills/pm-dispatch/SKILL.md DECLARED
* scripts/pm/check-settings-deny-roster.mjs .claude/settings.json undeclared
*
* ## Why the two undeclared rows are not declared HERE
*
* Both are already MATCHED for the file they read, through a key this card did
* not add: each spells its governed path in its MODULE BODY as well as in its
* self-test, so `extractWatchHints` sees it and the derivation names the family
* for a card touching it — measured, both ways, at this landing. They cost
* nothing today, and declaring them would widen this PR onto two gates it was
* not dispatched to. They are listed so the next reader inherits the
* measurement rather than re-deriving it.
*
* ## What each column makes fail
*
* A row whose READ disappears reds (the census no longer finds it) — which is
* what makes the pin on this class unsatisfiable by deleting the read. A NEW
* governed read reds until it is classified here. A row whose `declared` flag
* changes reds, in both directions: a declaration added is a floor to lower,
* a declaration deleted is this card's defect coming back.
*
* ## The fourth row: a gate whose SUBJECT is a published skill
*
* `scripts/check-skill-top-level-keys.mjs` reconciles the top-level key
* enumeration in the published platform skill against the stack schema, so
* the governed file is its input, not a fixture; it spells that path in its
* module body and is MATCHED for it the same way the two undeclared rows
* above are, so `declared` is false for the same reason.
*
* ## The fifth row: the mailbox doorbell's spelling pin
*
* `scripts/pm/record-recognisers.mjs` reads the seven doorbell keys off the
* pm-dispatch references' `project-forms.md` in its self-test, to pin each
* key's template verbatim in the protocol text — a population claim on a file
* a card can edit, the `check-expected-skips` shape exactly. It is DECLARED
* (`self-test-reads`), so a card touching that file derives
* `check:pm-record-recognisers`.
*
* ## The sixth row: the review claim line's spelling pin
*
* The same self-test reads the pm-dispatch references' `contract-review.md`
* to pin the `Contract-review-claim:` template and its two-hour lapse verbatim
* in the rule text — the same population claim, on a second governed file. The
* one `self-test-reads` line names both paths, so a card touching either file
* derives `check:pm-record-recognisers`.
*/
export const GOVERNED_READ_FLOOR = Object.freeze([
Object.freeze({ script: 'scripts/check-commit-card-trailers.mjs', file: '.claude/agents/os-dev.md', declared: false }),
Object.freeze({
script: 'scripts/check-skill-top-level-keys.mjs',
file: 'skills/objectstack-platform/SKILL.md',
declared: false,
}),
Object.freeze({
script: 'scripts/pm/check-expected-skips.mjs',
file: '.claude/skills/pm-dispatch/SKILL.md',
declared: true,
}),
Object.freeze({
script: 'scripts/pm/check-settings-deny-roster.mjs',
file: '.claude/settings.json',
declared: false,
}),
Object.freeze({
script: 'scripts/pm/record-recognisers.mjs',
file: '.claude/skills/pm-dispatch/references/project-forms.md',
declared: true,
}),
Object.freeze({
script: 'scripts/pm/record-recognisers.mjs',
file: '.claude/skills/pm-dispatch/references/contract-review.md',
declared: true,
}),
]);
/**
* ⛔ SHRINK-ONLY. The gates whose declared population is a bare top-level word
* the tree HAS, and which have not declared the subtree spelling for it.
*
* It is a DEBT list, not an exception list, and the same property makes it safe
* that makes `KNOWN_IMPORT_UNSAFE` safe (#10665): every entry has one remedy —
* declare the subtree spelling beside the literal, the `ROOT_DIR_WATCH_HINTS`
* idiom — and no entry records a judgement anyone has to re-make later. There
* is no supported route in the other direction: a family this rule newly
* reaches is a FAILURE with that one remedy, never a new line in here. An entry
* whose gate has since taken the escape fails as STALE and names itself, which
* is what stops the list from rotting into an allowlist nobody re-reads.
*
* Both halves are asserted in this file's self-test, against the live tree,
* and `check:pm-dispatch-gates` runs that self-test on every pull request. So a
* gate written tomorrow that spells a bare root word fails at AUTHORING time
* rather than landing invisible — which is the half of this class the six
* historical instances could not fix, because each of them was archaeology.
*
* ⚠️ Spelling rule for a new row: it must not become a watch hint of THIS file.
* `extractWatchHints` reads any quoted span carrying a separator, so a family
* keyed by a direct script path (`node scripts/check-x.mjs`) would enter this
* file's own declared population as a path it does not read — the same trap
* `DEFAULT_BASE_REF` is assembled in two halves to avoid. Spell such a row so
* it carries no separator, or join it at runtime. A self-test case below holds
* this, so the rule fails rather than needing to be remembered.
*/
export const ESCAPABLE_LITERAL_LEDGER_ROWS = Object.freeze([
]);
/**
* Gates that fire on what a change IS, keyed by a mechanically-detectable
* convention. Everything else in this script is derived at runtime and lists
* nothing; this table is the one exception, and it is bounded on purpose.
*
* ## Why these cannot be derived like the rest
*
* The path derivation matches a gate when the gate's own source names a
* directory that covers your file. Every gate here computes its population
* instead of naming it, so no source carries a literal to match:
*
* - the two type-check gates — one lints a glob set that lives in the shared
* ESLint config, the other walks the workspace members — sit permanently
* in the "undetermined" bucket;
* - the three test-file RATCHETS (`check:query-options-erasure`,
* `check:engine-double-contract`, `check:where-matcher`) each walk the tree
* for `*.test.*` files and reconcile the count against a baseline JSON. For
* two of the three, what the source names is that baseline — an artifact
* roster, never the population — so those two score `silent` for every card
* in the tree, and they HAVE hints, so the "undetermined" bucket never sees
* them either. Before this entry named them they were printed in NEITHER
* half of the output for every card in the tree.
*
* ⛔ Their hint sets are NOT transcribed here, and a freshly re-measured
* copy must not be put back. The copy that used to sit here listed all
* three sets and read as measured; by the time anyone re-derived it,
* exactly one of its five claims — the `check:engine-double-contract` row —
* was still true. It named a git ref as a hint for two of the rows, a class
* `isNonPathNamespace` refuses (this file's own self-test pins that
* refusal); it gave `check:query-options-erasure` a one-hint set its source
* has since outgrown; and it drew the conclusion below from both. Not one
* of those drifts touched THIS file, so nothing here could have reported
* them. `--residue` prints every family's live `names:` set and re-derives
* it on every run: that is the authority for this question, and a reader
* who wants the sets should run it rather than trust a paragraph.
*
* ⚠ `check:where-matcher` is the exception to the paragraph above, and it
* stays in this entry anyway. Since #13231 its source declares its
* `*.test.ts` population as a literal, so the ORDINARY path derivation
* MATCHES it for a test file under `packages/` and it is no longer silent.
* That is the shape the `check:cross-package-test-inputs` measurement in
* the deletion criterion below describes, and it is answered the same way:
* the declaration is set-equal to that gate's own walk, which is rooted at
* `packages/`, so it reaches no test file outside that root, while the KIND
* reaches every one — and the KIND reaches a card dispatched BEFORE its
* code exists, which no path derivation can. Two routes to one gate is
* redundancy, not a defect; the KIND is the load-bearing one. That gate's
* own source says the same thing in the docblock above its literal, so
* neither side of the pair asserts it alone.
*
* Every membership claim in the two paragraphs above is re-derived in
* `--self-test` against the live tree rather than restated here, so a tree
* that moves one turns a case RED instead of leaving this prose quietly
* false — which is the failure this entry has now paid for twice;
* - `check:i18n` walks `packages/` at runtime for files NAMED
* `i18n-extract.config.ts` and re-extracts each owning package's bundles.
* Its source names only three hints (measured, post-#9144): the shared
* walk module (SURFACE_MODULE) and the two metadata-registry coupling
* constants below — none of them the OWNING-PACKAGE population this entry
* answers for. So it still matches nothing on an ordinary object/field
* edit AND, having hints, never reaches the "undetermined" bucket either:
* before this entry existed, an edit to
* `packages/services/service-messaging/src/objects/` — which regenerates
* that package's four bundles — printed the gate in NEITHER half of the
* output. A gate the derivation cannot mention at all is the one shape
* this script must not produce; it cost a PR a CI round.
*
* No per-card gate list derived from paths can ever name these, however the
* derivation improves.
*
* ## Why a named table and not a wider heuristic
*
* The tempting generalisation — scan every discovered check script for
* `*.test.ts`-shaped literals and call those the test-sensitive gates — was
* measured against this tree and names 22 families, because a script's source
* mentions test paths in its fixtures, its self-test and its comments.
* "Mentions a test file" is not "counts test files", and 22 leads is the same
* as none. So the pair is written down, and the cost of writing it down is paid
* back by the two properties below.
*
* ## Why the two ratchets below joined this entry (#8632)
*
* `check:engine-double-contract` and `check:where-matcher` were handed to the
* PM's judgment in this file's closing prose instead of being derived. Three
* measured instances, all of them CI rounds, say that boundary was in the wrong
* place — and the deciding evidence is not the incidents but a structural
* identity with an entry that was already here.
*
* All three ratchets discover their population the same way: a walk collecting
* `*.test.*` files (`scripts/check-engine-double-contract.mjs`, `walk` at ~line
* 342, `/\.(test|spec)\.(ts|tsx|mts)$/`; `scripts/check-where-matcher-
* conformance.mjs`, corpus walk at ~line 562, `/\.test\.ts$/`), reconciled
* against a shrink-only baseline. `check:query-options-erasure` has sat in this
* entry for exactly that reason. Naming one of three and calling the other two a
* judgment call was an inconsistency in this table, not a considered line.
*
* The noise objection recorded on the card — a path-level trigger fires on test
* files that contain no fake engine at all — is real and is answered by what
* these gates cost to run rather than by narrowing the trigger. Each is a
* whole-tree shrink-only ratchet: one invocation answers for the entire tree,
* needs no build, and prints the offending file and line when it fails. A seat
* that runs one needlessly loses seconds; a seat that is never prompted loses a
* CI round, which is what all three instances did. The trigger deliberately does
* NOT read the file's contents to confirm a double is present: a card is
* dispatched BEFORE its code exists, so the double the gate will object to is
* usually not on disk at derivation time — the second instance added one to a
* file that already had one, the first added the file itself.
*
* This is the shape the "22 leads" note rejects a heuristic for, and it survives
* that objection because the trigger is the gates' own population test, mirrored
* (`isTestFilePath`), not a guess at which scripts look test-flavoured.
*
* ## What the two i18n entries still refuse to list
*
* Neither `matches` enumerates anything. The first walks for the packages that
* own a bundle, the second for the tree's metadata form modules, and both walks
* are the GATE'S OWN, imported from `scripts/i18n-bundle-surface.mjs` rather
* than mirrored here. That import is the fix for a defect this file used to
* carry in its own comment: `findI18nBundlePackages` was a hand-written copy
* described as mirroring `findConfigs` "exactly", which is a second contract
* with no way to report the day it stopped agreeing. What is written down here
* is the KIND, not its population, so a tenth package growing a bundle — or an
* eighteenth form module — is matched by the next run with nothing to update.
*
* ## Why the SECOND i18n entry exists (#9116)
*
* The owning-package entry answers for one of a bundle's two producers. The
* `objects` half is enumerated by the config's own package, so the owning
* package is the trigger surface. The `metadataForms` half is registry-driven
* and identical for every stack, so exactly ONE package commits that baseline
* (`platform-objects`; every other config passes `--no-metadata-forms`) while
* its source sits in `packages/spec`, which owns no extract config and which
* the gate's walk never reaches.
*
* Measured, and paid for once: PR #9113 added two form entries in
* `packages/spec/src/data/`, four `platform-objects` metadata-form bundles
* moved, `check:i18n` reddened on CI, and the dev's diff-derived gate union
* could not have named the family — the path derivation misses it for the
* reason stated above, and the owning-package entry does not cover
* `packages/spec`. Cost: one CI round trip plus a patch commit. The invariant
* the card states is the one this entry restores — a gate a diff can move must
* be derivable FROM that diff; `undetermined` is an honest unknown, not a
* standing blind spot on a known edge.
*
* Its applicability is read, never assumed: `metadataFormsSurfaceIsExtracted`
* asks the configs' own documented flags whether any package still commits that
* baseline. The day the last one opts out, no form module can move a committed
* bundle and this entry stops firing on its own.
*
* ## Why there is no THIRD i18n entry, for the type-registry edge (#9144)
*
* `walkMetadataForms` has a second edge the SECOND entry above does not reach:
* `DEFAULT_METADATA_TYPE_REGISTRY` (packages/spec/src/kernel/metadata-plugin.
* zod.ts) supplies `metadataForms.<type>.label`/`.description` for EVERY
* registry entry, including form-less types, and `METADATA_FORM_REGISTRY`
* itself (packages/spec/src/system/metadata-form-registry.ts, the map, not
* the `*.form.ts` leaves it points at) decides which types get section/field
* labels at all. Editing either moves the same bundles PR #9113 paid for —
* but unlike the `.form.ts` leaves, neither file carries a filename the
* `.form.ts` convention (or any convention) distinguishes, so a KIND entry
* here would need to invent one for exactly two files.
*
* That is not the same shape as the two entries above: this is not a
* runtime-enumerated population at all, it is two SPECIFIC, KNOWN files —
* the shape `SURFACE_MODULE` and `check-type-check-coverage.mjs`'s
* `ROOT_PROGRAM_COUPLED_SCRIPT` already use. So it is closed there instead:
* `check-i18n-bundles.mjs` declares both paths as bare module-body coupling
* constants (`METADATA_TYPE_REGISTRY_MODULE` / `METADATA_FORM_REGISTRY_
* MODULE`), which the ORDINARY path-literal derivation now reads directly off
* that gate's own source — no `CHANGE_KIND_GATES` entry, no `matches`
* function, nothing here to keep in sync. See that pair's doc comment in
* check-i18n-bundles.mjs for the full reasoning, and this file's own
* self-test for the live pins that keep the constants honest as the coupling
* they are: manual, per-file, and silently rottable if nothing watched it.
*
* ## Why the ROOT-program entry does not weaken the ledger's discipline (#9873)
*
* `check-type-check-coverage.mjs` states, in its own source, the very thesis
* the card for this entry was filed to argue: "The root program is everything
* outside packages/apps/examples, so no list here can ever be complete -- add a
* constant when a coupling has actually been measured, the way this one was."
* That is a deliberate policy, written by the gate's author before the card
* existed, and the entry below is a proposal against it. So it owes an answer
* rather than a shrug.
*
* The answer is that the two lists answer different questions, and only one of
* them is a measurement.
*
* The gate's ledger records MAGNITUDE. `ROOT_PROGRAM_COUPLED_SCRIPT` does not
* merely say "this file is in the program" — it carries a measured claim, that
* the file accounts for 29 of that entry's 80 errors, and the ledger note
* spends that number. A rule that manufactured such constants automatically
* really would register couplings nobody had measured, and the refusal is
* right. This entry adds no constant, moves no count and asserts no magnitude:
* the ledger keeps exactly the one measured coupling it has today, with every
* reference to it intact.
*
* This table records RELEVANCE — which gate a seat is told to run before it
* pushes. That claim needs no measurement to be true, because it is already
* settled by a file the repo maintains for another purpose entirely: a path is
* in the root program when the root tsconfig does not exclude it. Nothing here
* is kept in sync by hand, so there is no second contract to rot, and the
* measured-couplings rule the gate states about ITS list is untouched.
*
* What leaving the two questions merged cost, once, in the expensive direction:
* PR #9853 added a single file under a directory this entry now covers, derived
* its gate union with this script at final head, ran both gates the run named
* and reported them green — then CI failed the type-debt ratchet with 19 new
* errors from that one file. The gate worked exactly as designed. Nobody could
* know to run it, and the repair most available at that point is the one the
* gate's own text calls maintainer-only.
*
* ⚠ Its known limit, stated here rather than discovered later: `exclude` drops
* files only from tsc's INITIAL WALK, so sources under an excluded directory
* that a root script IMPORTS are pulled into the program anyway — the root's
* DEBT note recorded 4 of its 80 errors arriving exactly that way, from the
* showcase example, before the root graduated. A path-shaped trigger cannot see an import graph,
* so a card editing only such a file is still not sent here. That is
* deliberately NOT closed on this card: the direction is the safe one (this
* entry under-covers rather than over-covers), and closing it means resolving
* the program INCLUDING imports, which is a different tool than a path
* predicate and a different card's scope.
*
* ## How these entries stay honest
*
* - Every `name` here is resolved against the families actually discovered in
* the workflows at runtime. A gate that is renamed, retired or dropped from
* CI does not silently stop being suggested — the run prints it as STALE and
* says to fix this table. A hand-written list that reports its own rot is a
* different object from one that quietly ages.
* - Every `name` here is an INVOCATION, not a script. One check script can be
* wired into CI under two package scripts that answer different questions, and
* a rationale that names the script instead of the invocation sends a seat to
* a command which cannot reproduce the failure it describes.
* `check:type-check-coverage` and `check:type-check-debt` are one file
* (`scripts/check-type-check-coverage.mjs`); only the second passes
* `--re-measure`, which is the half a new test file's type errors move. This
* entry named the first while explaining the second, so a dev seat ran it in
* good faith, reported the union green, and CI found four new type errors.
* Swept over this tree when that was fixed: the workflows discover 96
* families resolving to 73 distinct script files, and 8 of those files are
* reached by more than one family — 7 of the 8 in the other shape, a `check:`
* script beside a direct `node scripts/check-x.mjs` step in a second
* workflow, which `derive` discovers as its own family and prints with its
* own runnable invocation. The pair below is the only one where two ROOT
* SCRIPTS differ by a flag, so this is a one-off today and what generalises
* is the rule, not the fix.
* - Prose in a `why` is a MODULE-BODY string, so it is scanned for watch hints
* like any other literal — comment masking cannot reach it. The ratchet
* entry's remedy command therefore spells its `--filter` values unquoted (and
* says to quote them for the shell): measured, the shell-quoted spelling adds
* both of its glob filter values (the two `./packages` globs, one flat and
* one nested) to THIS file's own hint set as hints. That spelling is now
* LOAD-BEARING rather than
* merely tidy: it used to be inert as well, because `hintCovers` refused a
* hint that COLLAPSED to a bare top-level directory, and since #9626 that
* refusal reads the hint as written — `packages/*` carries a separator, so
* quoting it here would make this file's own prose match every card under
* `packages/`. Leave the filter values unquoted. A gate list that fabricates
* hints out of its own explanations is the failure this whole script is
* written against, and this is the one place in the tree where the trade is
* live rather than hypothetical.
* - Every `name` here is checked against the LIVE workflows by the self-test,
* not only by the run that happens to print it. The STALE branch reports rot
* to whoever is looking at the output; the self-test case makes the same rot
* fail CI, because this table's names are the one enumerable list in the file
* and an enumerable list is one a guard can hold.
* - Each entry is deletable, with a stated criterion:
* - test-file entry: when a gate on it grows a discoverable path literal,
* the ordinary derivation names it and its line becomes redundant. For the
* three ratchets that means a literal naming their POPULATION — the baseline
* path each already carries is their own output, and a card editing a
* baseline matches through it today without making the gate derivable for
* anybody else.
*
* ⚠ "A literal naming their POPULATION" is the whole of that criterion,
* and one gate in this kind now fails it while READING as satisfied.
* Measured on this tree (#11199, the day PR #12300 landed): of the 2773
* tracked test files, the hint route names `check:cross-package-test-
* inputs` for 2760 of them — 99.5%, against 0–3.3% for its five siblings
* in this same kind — because #12300 taught `hintCovers` to read a glob in
* a non-final segment and the deep `packages` glob for TypeScript files
* came back to life. (That glob is not spelled here: its own wildcard
* closes a block comment.) The hint
* is neither this gate's population nor even its own literal: it is
* INHERITED from the declaration table the gate imports, where it is ONE
* package's declared turbo `inputs` glob (`@objectstack/core`'s, wide
* because a single pin test there walks the whole repo with `git
* ls-files`). It is a row the gate JUDGES, not a population the gate
* DECLARES — so it narrows the day that package's declaration narrows,
* which is the direction the gate's own repair advice pushes. And even at
* 99.5% it reaches no test file outside `packages/**` (10 tracked today,
* all under `examples/**`), none with a `.tsx` suffix (3 today, all in
* client-react), and none under `apps/**` the day one arrives — while
* the KIND reaches every one of them, because the trigger really is "a
* test file's content changed, full stop". Both routes are kept (two
* routes to one gate is redundancy, not a defect); the KIND is the
* load-bearing one. The residue and the inheritance are pinned in the
* self-test, so the next reader re-points a red case instead of
* re-deriving this paragraph.
* - i18n entry: when `check-i18n-bundles.mjs` stops discovering its targets
* at runtime and names its POPULATION in its own source — a literal each
* owning package path starts with — the path half matches and this entry
* is redundant. Growing more prerequisite paths does not qualify; that is
* what it already has.
* - metadata-form entry: when every extract config passes
* `--no-metadata-forms`, no form module can move a committed bundle. That
* day the entry stops firing by itself (its `matches` reads the flags), so
* delete it only once the opt-out is the permanent shape rather than a
* transient one.
* - error-code entry: when the vocabulary gate's own source declares the
* population it walks in a form this derivation can read — which today
* means the bare-root ledger row for it moving off REFUSE-WIDE to a
* recorded subtree spelling — the ordinary path match names it and this
* entry is redundant. ⛔ Growing the gate's own SHAPES table does NOT
* qualify: more stamp positions make the gate see more, and change nothing
* about whether a dispatch brief can NAME it. ⛔ Nor does this predicate
* going quiet on a given card: it reads content, so silence about a file
* nobody can read yet is not evidence in either direction.
* - status entry: on the same criterion as the error-code entry. The
* conformance gate's source would have to declare the population it walks
* in a form this derivation can read, and that means its bare-root ledger
* row moving off REFUSE-WIDE, which takes a ruling. ⛔ Growing the gate's
* derivation rules does NOT qualify, and ⛔ neither does this predicate
* going quiet on a card, for the reason given above.
* - durability-seam entry: when the durability guard's own source declares
* the population its log-level rule walks in a form this derivation can
* read, the ordinary path match names it and this entry is redundant. Today
* that population is the bare package root, so it would take a ruling that
* moves that root off the wide-population channel. ⛔ Growing the guard's
* DURABILITY_CRITICAL_CALLEES does NOT qualify: the predicate already reads
* that table, so a new callee widens this entry's net and does nothing to
* let a path name the gate. ⛔ Neither does this predicate going quiet on a
* card, for the reason given under the error-code entry.
* - root-program entry: when the gate's own source names its root population
* in a form this derivation can read — a positive literal, or a generated
* manifest of the resolved program — the ordinary path match names it and
* this entry is redundant. Growing more measured coupling constants does
* NOT qualify: each names one file, and this entry exists for the files
* that have no constant yet, which is every new one.
* - gate-script entry: when BOTH gates it names declare the population they
* judge in a form this derivation can read, the ordinary path match names
* them and this entry is redundant. Neither can today, and the reasons
* differ, so the criterion is met only when both move:
* `bare-root-worklist` walks every family's own files and declares nothing
* (deliberately — recognising its species needs a heuristic over constant
* NAMES, and #10705 refused to put one on the path that derives every PR's
* gate list, which is why this entry names the gate rather than importing
* its verdicts); `check:pm-dispatch-gates` declares three tracked FILES,
* an artifact roster this tool itself flags as "the shape that reads as a
* clearance and is not", so it reaches a card by gate-script identity
* alone. ⛔ Growing more roster entries does NOT qualify — that is what it
* already has. ⛔ Nor does either gate happening to go quiet: three of the
* five measured instances involved a green that proved nothing, because a
* sweep that cannot see your file is not evidence about your file.
* - ui-scripts-suite entry: when a workflow runs the `ui/scripts/__tests__`
* suite as a step of its own, under an invocation the discovery reads, the
* row can name that family instead of standing in for a CI job — and when
* the `Test UI` job stops running the suite at all, the binding goes STALE
* by itself. ⛔ A path predicate that narrows the four roots to the files
* each test "really" reads does NOT qualify: that map is a second thing
* that goes stale, and the run is cheap enough not to need one (measured
* below).
*
*
* Delete an entry the day its criterion is met, not before.
*
* ## Why the ui-scripts-suite entry stands in for a CI JOB (#26318)
*
* The 180 test files under `ui/scripts/__tests__/` (178 that run, 2 skipped)
* belong to no package and run
* only inside the 16 `Test UI` shards, as the "always-on" files the affected-test
* selector adds to every run because no package owns them. They pin facts a
* change under ui package source, the Console app, the objectui skill bundle or
* `ui/scripts` itself moves — turbo build inputs, the skill-examples floor, the
* SchemaRenderer channel keys — and no derived family named them: measured on
* PR #26274's head 1ef5c482, three reds there were that PR's own and had passed
* every derived local gate.
*
* Every other gate in this table is a family the workflows run, resolved by
* name. This one cannot be: the only step that runs the suite is the `Test UI`
* job's vitest line, which carries a shard and the selector's filter list, and
* no workflow spells a narrower invocation. So its gate declares the job and the
* runner prefix it narrows (`ciJob`, `ciRunner`), and the engine's
* `ciJobRunsRunner` resolves it against the live workflows the same way a family
* is resolved: a job renamed or a runner respelled renders it STALE, and the
* census guard in the self-test reds on the PR that does it.
*
* It names the WHOLE suite, never an affected subset: a subset map is a second
* list that rots. Measured at 6390fe52 on a 4-vCPU container, after the seven
* @objectstack packages the job builds: 178 files passing in 284 s and 286 s
* (two runs; the card's "about a minute" was read elsewhere), nearly all of it
* module import — so a subset would save minutes, not a CI round trip, and the
* reach it would have to model is the tree itself: by a static scan of each
* file and the helpers it imports, 165 of the 180 read ui package source, 141
* the ui apps, 16 the objectui skill bundle and 170 ui/scripts. What it does not see is
* stated rather than hidden: a ui path outside the four roots (a ui package's
* tests outside `src`, its `package.json`, a root config) is not named here,
* though CI's `Test UI` job still schedules the suite for it — the full battery
* on `main` remains the backstop for that, as it is for the selector's own
* misses.
*/
export const CHANGE_KIND_ROWS = [
{
kind: 'adds or edits a test file',
matches: 'test-file',
gates: [
{
name: 'check:query-options-erasure',
why: 'its test-surface ceiling counts sites in *.test/*.spec files, so new test code moves it',
},
{
name: 'check:type-check-coverage',
why: "the STRUCTURAL half: a package whose test files sit outside every tsc program accounting for it must carry a TEST_DEBT entry, so a new test file no tsconfig reaches moves this one. It re-measures no count — the ratchet is the invocation below",
},
{
name: 'check:type-check-debt',
why: "the RATCHET half, and the invocation CI runs for it: `--re-measure` re-runs tsc per ledger entry and fails when a count drifts up, so a new test file that does not typecheck cleanly moves it. Needs the workspace closure BUILT — on an unbuilt worktree it refuses outright, and that throw means NOT MEASURED, never `not applicable to me`. Build first, exactly as lint.yml does: pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/* (quote the filter values for your shell)",
},
{
name: 'check:engine-double-contract',
why: 'it walks every *.test.* file for fake engine doubles and fails when one declares delete()/update() without routing through assertEngineDeleteDispatch/assertEngineUpdateDispatch, against a shrink-only per-file baseline. A new double, or a new test file carrying one, moves it — and so does a delegating pass-through seam wrapping a real engine, which is the reading that missed it twice. Repair by fixing the double, never by raising the baseline. Cheap and whole-tree: one run answers for the whole repo and names the file and line',
},
{
name: 'check:cross-package-test-inputs',