@@ -19,6 +19,54 @@ concurrency:
1919 group : lint-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
2020 cancel-in-progress : true
2121
22+ # ─────────────────────────────────────────────────────────────────────────────
23+ # ⭐ GATE INVOCATION IDIOM — stated ONCE, here. The steps below point at it.
24+ #
25+ # Most gate steps in this file run their script directly:
26+ #
27+ # run: node scripts/check-<thing>.mjs
28+ #
29+ # rather than through a `pnpm check:*` alias. That is a deliberate in-repo
30+ # idiom with its own justification, not a workaround:
31+ #
32+ # * Many gate steps in this file already use it — it is the local precedent.
33+ # * `scripts/pm/dispatch-gates.mjs` derives gate families from EITHER
34+ # spelling, so the direct form loses no discovery.
35+ # * It adds no key to the root manifest.
36+ # * It is pinned rather than incidental: check-aggregator-roster,
37+ # check-ci-filter-parity and check-doc-frontmatter each assert in their own
38+ # self-test that `node <self>` appears in THIS file, so silently rewiring
39+ # one of them to an alias reddens its gate.
40+ #
41+ # ⚠️ It is NOT because root `package.json` is off limits. This file used to say
42+ # that, in one shape or another, at nineteen separate sites, and it is FALSE.
43+ # The declared territory of the #9465 @changesets/cli v3 lane inside that file
44+ # is scoped by the lane's own parenthetical, verbatim from the issue body:
45+ #
46+ # root `package.json` (the `@changesets/cli` range and the `version` script)
47+ #
48+ # That parenthetical is SCOPING, not illustrative: one dependency range and one
49+ # script key. It does not cover the file, and it does not cover a new `check:*`
50+ # key — so adding `check:<thing>` to the root manifest would have been allowed
51+ # all along. The steps below take the direct form on the merits listed above;
52+ # they are not fenced out of the alternative.
53+ #
54+ # Stated once here, and POINTED AT rather than restated, because the over-broad
55+ # reading propagates by copying — measured, not supposed. A dev agent wrote the
56+ # same sentence into a new comment and a new script's docblock and said plainly:
57+ # "I copied the phrasing from neighbouring comments instead of checking it."
58+ # The count was 15 sites when #10894 was filed on 2026-08-21 and 19 by
59+ # 2026-08-24, four of them added AFTER the card was filed and two of those
60+ # within ninety minutes of it being picked up. Nineteen CORRECTED copies would
61+ # re-arm the identical mechanism: a claim that reads as verified because it
62+ # reads exactly like the verified ones beside it. So there is one statement of
63+ # this idiom — this one — and every step refers to it.
64+ #
65+ # Changing this note? It is prose, and no gate can red on a wrong sentence
66+ # here; that is precisely how nineteen wrong ones accumulated. Check any claim
67+ # about the #9465 fence against #9465's own body before you write it down.
68+ # ─────────────────────────────────────────────────────────────────────────────
69+
2270jobs :
2371
2472 lint :
@@ -176,11 +224,8 @@ jobs:
176224 #
177225 # What runs here is the self-tests, not the modules' callers: the callers
178226 # are the two gate steps below, which already run on every PR.
179- # Invoked as `node` rather than through `pnpm check:*`: those aliases
180- # belong in root package.json, declared territory of the @changesets/cli
181- # v3 migration lane (#9465) while it runs — same shape as the
182- # `node scripts/…` steps later in this lane. dispatch-gates.mjs derives
183- # gate families from either spelling.
227+ # Invoked as `node` rather than through `pnpm check:*`: see the GATE
228+ # INVOCATION IDIOM note at the top of this file.
184229 # `ts-parse` spawns ~20 real node children (~10 s measured, and the spawns
185230 # are the point — they pin that a caller's try/catch cannot swallow the
186231 # refusal); the other two are in-process fixtures, ~0.5 s combined.
@@ -297,9 +342,9 @@ jobs:
297342 # contract (scripts/check-required-contexts.mjs) that only a
298343 # maintainer-present Settings change can enrol, and an unenrolled job is
299344 # an advisory gate — #5617 verbatim. This job is already required.
300- # Invoked as `node` rather than `pnpm check:*` for the same reason as the
301- # other `node scripts/…` steps in this lane: that alias is a line in root
302- # package.json, inside the @changesets/cli v3 fence (#9465) .
345+ # Invoked as `node` rather than `pnpm check:*`, same as the other
346+ # `node scripts/…` steps in this lane: see the GATE INVOCATION IDIOM note
347+ # at the top of this file .
303348 # Runs its own comparator self-test first — 12 cases proving the
304349 # comparison can still REPORT, because "0 files disagree" and "the
305350 # comparison is broken" otherwise print the same line.
@@ -547,11 +592,9 @@ jobs:
547592 # from this step reddens instead of going quiet. Temp-dir fixtures, no
548593 # network, ~1.7 s.
549594 #
550- # Invoked as `node` rather than through a `pnpm check:*` alias: that alias
551- # belongs in root package.json, which is declared territory of the
552- # @changesets/cli v3 migration lane (#9465) while it runs. Same shape as
553- # check-links.yml's ADR-link step, and dispatch-gates.mjs derives gate
554- # families from either spelling.
595+ # Invoked as `node` rather than through a `pnpm check:*` alias: see the
596+ # GATE INVOCATION IDIOM note at the top of this file. Same shape as
597+ # check-links.yml's ADR-link step.
555598 - name : Release-rehearsal clone preflight self-test
556599 run : node scripts/pm/release-rehearsal-clone.mjs --self-test
557600
@@ -586,9 +629,8 @@ jobs:
586629 # until now NOTHING in this repo ran, and an unrun self-test is a phantom
587630 # check — the AGENTS.md rule about `@ts-expect-error` in an uncompiled
588631 # file, wearing a different hat. Invoked as `node`/`bash` rather than
589- # through `pnpm check:*`: that alias belongs in root package.json, which
590- # is declared territory of the @changesets/cli v3 migration lane (#9465)
591- # while it runs. Temp-dir fixtures, no network, ~6 s in total.
632+ # through `pnpm check:*`: see the GATE INVOCATION IDIOM note at the top of
633+ # this file. Temp-dir fixtures, no network, ~6 s in total.
592634 #
593635 # (`check-governed-merges.mjs`'s own cases run in the
594636 # `pnpm check:pm-governed-merges` step above, which is already its
@@ -685,19 +727,9 @@ jobs:
685727 # commands executing when the first fails, 3 of 3 when none does. A harness
686728 # that cannot reproduce the defect cannot certify the fix.
687729 #
688- # Invoked as `node scripts/…` rather than through a `pnpm check:*` alias,
689- # on the precedent this job already sets: several gate steps here are
690- # invoked directly, and dispatch-gates.mjs derives gate families from
691- # either spelling, so the direct form loses no discovery and adds no key
692- # to the root manifest.
693- #
694- # ⚠️ NOT because root package.json is off limits. The #9465 changeset lane
695- # fences that file's `@changesets/cli` range and its `version` script — the
696- # parenthetical in the issue body is scoping, not illustrative — and not
697- # the file, so a `check:step-collectors` key would have been allowed. Said
698- # plainly because the over-broad reading is easy to acquire and then
699- # propagates as a constraint nobody actually has, which is this step's own
700- # defect class wearing a different hat: a claim that reads as verified.
730+ # Invoked as `node scripts/…` rather than through a `pnpm check:*` alias:
731+ # see the GATE INVOCATION IDIOM note at the top of this file, which is
732+ # where the correction this comment used to carry now lives in full.
701733 # Temp-dir fixtures, no network, ~1 s.
702734 - name : Step-collector gate (self-tests that mask each other)
703735 run : |
@@ -918,10 +950,9 @@ jobs:
918950 # naming what could not be read (#4690 / #7484).
919951 #
920952 # Invoked as `node` rather than through a `pnpm check:*` alias, same as
921- # the aggregator-roster step below: that alias belongs in root
922- # package.json, declared territory of the @changesets/cli v3 migration
923- # lane (#9465) while it runs. Dependency-free filesystem read, sub-second
924- # over the whole corpus.
953+ # the aggregator-roster step below: see the GATE INVOCATION IDIOM note at
954+ # the top of this file. Dependency-free filesystem read, sub-second over
955+ # the whole corpus.
925956 - name : Docs frontmatter parses
926957 run : |
927958 node scripts/check-doc-frontmatter.mjs --self-test
@@ -1009,8 +1040,8 @@ jobs:
10091040 # failing step here is read by everyone — and enforcement later needs
10101041 # only the flag flip, not a Settings change.
10111042 # Invoked as `node` rather than a `pnpm check:*` alias, same as the
1012- # frontmatter step above: the alias is a line in root package.json,
1013- # inside the @changesets/cli v3 fence (#9465) while that lane runs .
1043+ # frontmatter step above: see the GATE INVOCATION IDIOM note at the top of
1044+ # this file .
10141045 - name : Route spellings taught in prose match the ledgers (advisory)
10151046 run : |
10161047 node scripts/check-doc-route-spelling.mjs --self-test
@@ -1074,10 +1105,9 @@ jobs:
10741105 # re-narrowing the scope REFUSES instead of reporting a clean corpus.
10751106 #
10761107 # Invoked as `node` rather than through a `pnpm check:*` alias, same as the
1077- # doc-frontmatter and aggregator-roster steps: that alias belongs in root
1078- # package.json, declared territory of the @changesets/cli v3 migration lane
1079- # (#9465) while it runs. Dependency-free filesystem read, sub-second over
1080- # the whole corpus.
1108+ # doc-frontmatter and aggregator-roster steps: see the GATE INVOCATION
1109+ # IDIOM note at the top of this file. Dependency-free filesystem read,
1110+ # sub-second over the whole corpus.
10811111 #
10821112 # Its --self-test runs FIRST and is where the rule is observed FAILING:
10831113 # this PR swept the live corpus, so a passing run over real data cannot
@@ -1361,10 +1391,8 @@ jobs:
13611391 # them — the #9604 precedent, re-measured.
13621392 #
13631393 # Invoked as `node scripts/…` rather than through a `pnpm check:*` alias,
1364- # same as the other direct steps in this lane: that alias is a line in root
1365- # package.json, inside the @changesets/cli v3 fence (#9465), and the direct
1366- # form loses no discovery — dispatch-gates.mjs derives gate families from
1367- # either spelling. Precedent: PR #10698.
1394+ # same as the other direct steps in this lane: see the GATE INVOCATION
1395+ # IDIOM note at the top of this file. Precedent: PR #10698.
13681396 #
13691397 # Runs its own --self-test first, and that leg is load-bearing: this gate
13701398 # computes its own population, so a block parser that stopped matching
@@ -1812,7 +1840,8 @@ jobs:
18121840 # instrument is fatal here in every mode. `--strict` promotes findings to
18131841 # exit 1 for a caller that owns the remedy; nothing on a PR passes it.
18141842 #
1815- # No `pnpm check:*` alias: root package.json is inside the #9465 fence.
1843+ # No `pnpm check:*` alias: see the GATE INVOCATION IDIOM note at the top
1844+ # of this file.
18161845 - name : Release section-coverage guard
18171846 run : |
18181847 node scripts/check-release-section-coverage.mjs --self-test
@@ -1935,8 +1964,8 @@ jobs:
19351964 # entry without one.
19361965 #
19371966 # Invoked as `node` rather than through a `pnpm check:*` alias, same as the
1938- # other `node scripts/...` steps in this lane: that alias is a line in root
1939- # package.json, inside the @changesets/cli v3 fence (#9465) .
1967+ # other `node scripts/...` steps in this lane: see the GATE INVOCATION
1968+ # IDIOM note at the top of this file .
19401969 #
19411970 # Its --self-test runs first and is not ceremony. The gate is GREEN OVER AN
19421971 # EMPTY EXECUTABLE POPULATION -- every mention of the verb on this tree is
@@ -1996,11 +2025,9 @@ jobs:
19962025 # as a fresh one, and this comment is what the next reader checks the
19972026 # harness against. Prose classes survive an M16; a count does not.
19982027 #
1999- # Invoked as `node` rather than through a `pnpm check:*` alias: that alias
2000- # belongs in root package.json, declared territory of the @changesets/cli
2001- # v3 migration lane (#9465) while it runs. Same shape as the
2002- # release-rehearsal step above; dispatch-gates.mjs derives gate families
2003- # from either spelling. No network, no build; ~0.2 s.
2028+ # Invoked as `node` rather than through a `pnpm check:*` alias: see the
2029+ # GATE INVOCATION IDIOM note at the top of this file. Same shape as the
2030+ # release-rehearsal step above. No network, no build; ~0.2 s.
20042031 - name : Cross-repo closer outcome contract
20052032 run : |
20062033 node scripts/check-cross-repo-closer-outcome.mjs --self-test
@@ -2025,10 +2052,8 @@ jobs:
20252052 # is exactly the change that would miss one.
20262053 #
20272054 # Invoked as `node` rather than a `pnpm check:*` alias for the same reason
2028- # as the step above: that alias belongs in root package.json, declared
2029- # territory of the @changesets/cli v3 migration lane (#9465) while it
2030- # runs. dispatch-gates.mjs derives gate families from either spelling.
2031- # No network, no build; ~1 s, most of it the tracked-file sweep.
2055+ # as the step above: see the GATE INVOCATION IDIOM note at the top of this
2056+ # file. No network, no build; ~1 s, most of it the tracked-file sweep.
20322057 - name : Closing-keyword parser parity
20332058 run : |
20342059 node scripts/check-closing-keyword-parity.mjs --self-test
@@ -2067,8 +2092,8 @@ jobs:
20672092 # matching nothing.
20682093 #
20692094 # Invoked as `node` rather than through a `pnpm check:*` alias, same
2070- # reason as the step above: root package.json is declared territory of
2071- # the @changesets/cli v3 migration lane (#9465) while it runs .
2095+ # reason as the step above: see the GATE INVOCATION IDIOM note at the top
2096+ # of this file .
20722097 - name : Merge-queue triage outcome contract
20732098 run : |
20742099 node scripts/check-merge-queue-triage-outcome.mjs --self-test
@@ -2114,9 +2139,8 @@ jobs:
21142139 # state is a refusal rather than a quiet pass — no aggregator found, a
21152140 # workflow that will not parse, or a roster that resolves to zero members
21162141 # all exit 1 naming what could not be read (#4690).
2117- # Invoked as `node` rather than through a `pnpm check:*` alias: that
2118- # alias belongs in root package.json, which is declared territory of the
2119- # @changesets/cli v3 migration lane (#9465) while it runs. Reads two YAML
2142+ # Invoked as `node` rather than through a `pnpm check:*` alias: see the
2143+ # GATE INVOCATION IDIOM note at the top of this file. Reads two YAML
21202144 # files; sub-second.
21212145 - name : Aggregator roster gate
21222146 run : |
@@ -2225,10 +2249,9 @@ jobs:
22252249 # either filter has left the OR, and reports a `crosspkg` entry that
22262250 # covers no declaration any more.
22272251 # Invoked as `node` rather than through a `pnpm check:*` alias for the
2228- # same reason as the steps below: that alias belongs in root package.json,
2229- # declared territory of the @changesets/cli v3 lane (#9465) while it runs.
2230- # dispatch-gates.mjs derives gate families from either spelling. Two YAML
2231- # parses and a string comparison; milliseconds.
2252+ # same reason as the steps below: see the GATE INVOCATION IDIOM note at
2253+ # the top of this file. Two YAML parses and a string comparison;
2254+ # milliseconds.
22322255 - name : CI filter parity (cross-package Layer C)
22332256 run : |
22342257 node scripts/check-ci-filter-parity.mjs --self-test
@@ -2247,10 +2270,8 @@ jobs:
22472270 # and the reader-side refusal that now catches that needs a live pin of its
22482271 # own or it rots the same way.
22492272 # Invoked as `node` rather than through a `pnpm check:*` alias for the same
2250- # reason as the release-rehearsal self-test above: that alias belongs in
2251- # root package.json, declared territory of the @changesets/cli v3 lane
2252- # (#9465) while it runs. dispatch-gates.mjs derives gate families from
2253- # either spelling. Pure functions, no IO, milliseconds.
2273+ # reason as the release-rehearsal self-test above: see the GATE INVOCATION
2274+ # IDIOM note at the top of this file. Pure functions, no IO, milliseconds.
22542275 - name : Shard partitioner self-test
22552276 run : node scripts/partition-test-shards.mjs --self-test
22562277
@@ -2270,10 +2291,8 @@ jobs:
22702291 # corpus (every `scripts/**/*.d.mts`), so a third mirror added tomorrow is
22712292 # covered by existing rather than by anyone remembering to enrol it.
22722293 # Invoked as `node` rather than through a `pnpm check:*` alias for the
2273- # same reason as the two steps above: that alias belongs in root
2274- # package.json, declared territory of the @changesets/cli v3 lane (#9465)
2275- # while it runs. dispatch-gates.mjs derives gate families from either
2276- # spelling. Imports two small modules; milliseconds.
2294+ # same reason as the two steps above: see the GATE INVOCATION IDIOM note
2295+ # at the top of this file. Imports two small modules; milliseconds.
22772296 - name : Hand-written declaration mirrors
22782297 run : |
22792298 node scripts/check-declaration-mirrors.mjs --self-test
@@ -2493,8 +2512,8 @@ jobs:
24932512 # pointing at #10371 which owns their repair; the list refuses new members
24942513 # outright, so nothing can join it to get CI green.
24952514 # Invoked as `node` rather than through a `pnpm check:*` alias, same as
2496- # the other `node scripts/…` steps in this lane: that alias is a line in
2497- # root package.json, inside the @changesets/cli v3 fence (#9465) .
2515+ # the other `node scripts/…` steps in this lane: see the GATE INVOCATION
2516+ # IDIOM note at the top of this file .
24982517 # Runs its own --self-test first, and that leg is not decoration: this
24992518 # gate computes its own population, so a scan that stopped matching would
25002519 # print a confident green over a tree it never read (#4690's family). The
@@ -3215,8 +3234,8 @@ jobs:
32153234 # SUCCESS as well as failure, which is the visibility half of #10473 — every
32163235 # PR's log carries each file's distance from its ceiling.
32173236 #
3218- # Invoked as `node`, NOT through an npm script: root package.json is fenced
3219- # (#9465), and this gate needs no wiring there — same shape as the two
3237+ # Invoked as `node`, NOT through an npm script — see the GATE INVOCATION
3238+ # IDIOM note at the top of this file; same shape as the two
32203239 # `node scripts/…` steps earlier in this lane. Static text only, no build, so
32213240 # it belongs in this pre-build group. No paths filter, for the standard reason:
32223241 # a filter on skills/** would go dormant on the PR that edits the ceilings.
0 commit comments