From f5a5b6c354e0b2b0dd3351dc2d8c5cde51daf8ec Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 03:31:03 +0000 Subject: [PATCH 1/2] Make CI call a gate's root alias, and give every check alias a caller The lint.yml invocation note now states the rule: a gate step runs its root check:* alias, and direct node stays only for a different invocation, a job without pnpm, or a legacy step with no alias yet. The gates that pinned their own direct wiring (aggregator roster, CI filter parity, doc frontmatter, docs nav label, the three symbol-anchor corpora) now run through new or existing aliases, and their self-tests pin both the step and the alias value. The symbol-anchor collector and its three sweep steps become one alias step per corpus. check-changeset-fixed.mjs gains a --self-test, run through a new check:changeset-fixed alias in validate-deps. check:pm-closed-card-sweep is removed: its only CI run is in a job without pnpm, which keeps the direct self-test line. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01ADHkmQq9EYQdTQwmRutQTS --- .github/workflows/half-state-patrol.yml | 4 +- .github/workflows/lint.yml | 162 ++++++----------- .github/workflows/validate-deps.yml | 3 +- package.json | 6 +- scripts/check-adr-symbol-anchors.mjs | 10 +- scripts/check-aggregator-roster.mjs | 18 +- scripts/check-changeset-fixed.mjs | 163 ++++++++++++++++++ scripts/check-ci-filter-parity.mjs | 31 ++-- scripts/check-doc-frontmatter.mjs | 31 ++-- scripts/check-docs-nav-label.mjs | 18 +- scripts/check-scripts-symbol-anchors.mjs | 10 +- .../check-spec-docblock-symbol-anchors.mjs | 10 +- 12 files changed, 323 insertions(+), 143 deletions(-) diff --git a/.github/workflows/half-state-patrol.yml b/.github/workflows/half-state-patrol.yml index c206e1bccab..500d1e4d95c 100644 --- a/.github/workflows/half-state-patrol.yml +++ b/.github/workflows/half-state-patrol.yml @@ -356,8 +356,8 @@ jobs: # The judge's own cases first, and the sweep only if they hold: this # step WRITES to other people's cards, and a broken predicate that # still runs is the one failure mode that cannot be undone by the next - # run. `check:pm-closed-card-sweep` is the same command under a dev - # -facing name; this is the invocation CI holds. + # run. It has no root alias: this job sets up no pnpm, so this line is + # the one definition (the GATE INVOCATION IDIOM note in lint.yml). set +e node scripts/pm/sweep-closed-cards.mjs --self-test > "$RUNNER_TEMP/closed-cards-selftest.log" 2>&1 selftest=$? diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1827a5bfb8a..8cdc67d96a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -108,48 +108,40 @@ concurrency: # ───────────────────────────────────────────────────────────────────────────── # ⭐ GATE INVOCATION IDIOM — stated ONCE, here. The steps below point at it. # -# Most gate steps in this file run their script directly: +# A gate step runs its root `package.json` alias: # -# run: node scripts/check-.mjs +# run: pnpm check: # -# rather than through a `pnpm check:*` alias. That is a deliberate in-repo -# idiom with its own justification, not a workaround: +# so `package.json` holds the one definition of the command, and a fix to the +# alias is a fix to CI (#26091, #26086). A gate that is new, or whose step is +# being rewired, gets a `check:*` key and CI calls that key. The gates that pin +# their own wiring (check-aggregator-roster, check-ci-filter-parity, +# check-doc-frontmatter, check-docs-nav-label, check-system-context-census) +# assert both halves in their self-tests: this file runs `pnpm check:`, +# and the root alias is exactly `--self-test` then the gate. So the step and the +# alias cannot drift apart without a red. # -# * Many gate steps in this file already use it — it is the local precedent. -# * `scripts/pm/dispatch-gates.mjs` derives gate families from EITHER -# spelling, so the direct form loses no discovery. -# * It adds no key to the root manifest. -# * It is pinned rather than incidental: check-aggregator-roster, -# check-ci-filter-parity and check-doc-frontmatter each assert in their own -# self-test that `node ` appears in THIS file, so silently rewiring -# one of them to an alias reddens its gate. +# A step still runs `node scripts/…` directly in three cases, and only these: # -# ⚠️ It is NOT because root `package.json` is off limits. This file used to say -# that, in one shape or another, at nineteen separate sites, and it is FALSE. -# The declared territory of the #9465 @changesets/cli v3 lane inside that file -# is scoped by the lane's own parenthetical, verbatim from the issue body: +# * it runs a DIFFERENT invocation from any alias: other arguments +# (`--base "$MERGE_BASE"`, `--advisory`, `--census`), or one leg only (the +# self-test half of a gate whose scan runs elsewhere). An alias is a copy +# only when it is the same command, and a copy is what this rule removes; +# * its job sets up no pnpm (an install-free job elsewhere, such as +# check-links.yml or board-snapshot.yml, keeps the direct form); +# * it predates this rule and its script has no alias yet. Those steps are +# not swept (#25919 「存量不横扫」); each gains an alias when its step is +# next rewired. Their comments say "invoked as `node` rather than through a +# `pnpm check:*` alias" and point here. # -# root `package.json` (the `@changesets/cli` range and the `version` script) +# `scripts/pm/dispatch-gates.mjs` derives gate families from either spelling, +# so neither form loses discovery. # -# That parenthetical is SCOPING, not illustrative: one dependency range and one -# script key. It does not cover the file, and it does not cover a new `check:*` -# key — so adding `check:` to the root manifest would have been allowed -# all along. The steps below take the direct form on the merits listed above; -# they are not fenced out of the alternative. -# -# Stated once here, and POINTED AT rather than restated, because the over-broad -# reading propagates by copying — measured, not supposed. A dev agent wrote the -# same sentence into a new comment and a new script's docblock and said plainly: -# "I copied the phrasing from neighbouring comments instead of checking it." -# The count was 15 sites when #10894 was filed on 2026-08-21 and 19 by -# 2026-08-24, four of them added AFTER the card was filed and two of those -# within ninety minutes of it being picked up. Nineteen CORRECTED copies would -# re-arm the identical mechanism: a claim that reads as verified because it -# reads exactly like the verified ones beside it. So there is one statement of -# this idiom — this one — and every step refers to it. -# -# Changing this note? It is prose, and no gate can red on a wrong sentence -# here; that is precisely how nineteen wrong ones accumulated. Check any claim +# ⚠️ Root `package.json` was never off limits. The #9465 @changesets/cli v3 +# lane's territory in that file is one dependency range and one script key, +# verbatim from that issue: root `package.json` (the `@changesets/cli` range +# and the `version` script). A new `check:*` key was always allowed; the +# opposite reading once spread to nineteen comments by copying. Check any claim # about the #9465 fence against #9465's own body before you write it down. # ───────────────────────────────────────────────────────────────────────────── @@ -1867,9 +1859,7 @@ jobs: - name: Docs frontmatter parses id: docs-frontmatter-parses continue-on-error: true - run: | - node scripts/check-doc-frontmatter.mjs --self-test - node scripts/check-doc-frontmatter.mjs + run: pnpm check:doc-frontmatter # The docs site's short sidebar label (`navTitle`) reaches the PAGE TREE # and nothing else (#12311). A doc page's `title` serves the SERP title, @@ -1894,15 +1884,12 @@ jobs: # an upgrade does the gate says migrate instead of letting ours become the # thing nobody migrated. # - # Invoked as `node` rather than a `pnpm check:*` alias: see the GATE - # INVOCATION IDIOM note at the top of this file. Filesystem read plus one - # type-stripped import; sub-second. + # Runs its root alias, per the GATE INVOCATION IDIOM note at the top of + # this file. Filesystem read plus one type-stripped import; sub-second. - name: Docs nav label stays out of the title surfaces id: docs-nav-label-stays-out-of-the-title continue-on-error: true - run: | - node scripts/check-docs-nav-label.mjs --self-test - node scripts/check-docs-nav-label.mjs + run: pnpm check:docs-nav-label # #13548 tenant-audit census. `content/docs/permissions/tenant-audit-census.mdx` # carries the population `SqlDriver.auditMissingTenant` acts on: every @@ -2094,9 +2081,9 @@ jobs: # job's check-run is advisory with no signal anywhere (#5617), while a # failing step here is read by everyone — and enforcement later needs # only the flag flip, not a Settings change. - # Invoked as `node` rather than a `pnpm check:*` alias, same as the - # frontmatter step above: see the GATE INVOCATION IDIOM note at the top of - # this file. + # Invoked as `node` rather than a `pnpm check:*` alias (it has none yet, + # and its scan runs `--advisory`): see the GATE INVOCATION IDIOM note at + # the top of this file. - name: Route spellings taught in prose match the ledgers (advisory) id: route-spellings-taught-in-prose-match continue-on-error: true @@ -2488,69 +2475,30 @@ jobs: # #13788 (1,647 platform-checklist citations) joins by registering its own # corpus — ⛔ not by forking the resolver. # - # Invoked as `node` rather than a pnpm alias per the GATE INVOCATION IDIOM - # at the top of this file; the gate's own --self-test asserts BOTH lines - # below are present, so rewiring it silently reddens it. - # - # ⚠️ The two self-tests go through a `run_self_test` COLLECTOR, not a bare - # sequence. Under `bash -e` the first non-zero exit aborts the step, so a - # chained second self-test is neither green nor red — it never runs - # (#10814). The collector runs both unconditionally and fails at the end - # naming every one that broke. - # - # ⛔ AND THE PRODUCTION SWEEP IS A SEPARATE STEP, which is not cosmetic. - # `check-step-collectors` drives this very block under a real `bash -e` - # with a STUB planted at each collected command's script path, and holds - # "all green => every command runs" as an equality. A trailing - # `node scripts/check-adr-symbol-anchors.mjs` in the same block re-invokes - # that same stub, so the harness counts 3 executions against 2 collected - # commands and the gate reds (3/2) — which is exactly what it did here. - # Both existing collectors in this file carry self-tests ONLY, for the - # same reason. Keep the sweep out of the collector block. - - name: Symbol-anchor gate self-tests - id: symbol-anchor-gate-self-tests - continue-on-error: true - run: | - failed="" - run_self_test() { - echo "-- $*" - if "$@"; then - echo "PASS $*" - else - echo "FAIL $*" - failed="${failed} $*"$'\n' - fi - return 0 - } - run_self_test node scripts/symbol-anchors.mjs --self-test - run_self_test node scripts/check-adr-symbol-anchors.mjs --self-test - run_self_test node scripts/check-scripts-symbol-anchors.mjs --self-test - run_self_test node scripts/check-spec-docblock-symbol-anchors.mjs --self-test - if [ -n "$failed" ]; then - echo "" - echo "Symbol-anchor self-tests — the following FAILED:" - printf "%s" "$failed" - exit 1 - fi - + # Each corpus runs its root alias, per the GATE INVOCATION IDIOM note at + # the top of this file: `pnpm check:-symbol-anchors` is the shared + # resolver's `--self-test`, then the corpus's own `--self-test`, then its + # sweep. One step per corpus, each with an `id:` and `continue-on-error`, + # so a red corpus never hides the other two — the job the `run_self_test` + # collector that used to precede these steps did (#10814). The shared + # resolver's self-test (~0.2 s) runs in each step; a red there reds all + # three, which is what a broken resolver means. Each corpus gate's own + # --self-test asserts its step and its alias, so rewiring one silently + # reddens it. - name: ADR symbol anchors resolve (no line numbers survive) id: adr-symbol-anchors-resolve-no-line-numbers continue-on-error: true - run: node scripts/check-adr-symbol-anchors.mjs + run: pnpm check:adr-symbol-anchors # The SECOND corpus (#15765), and it is a `defineCorpus` registration over - # `scripts/**` comment prose — ⛔ not a second resolver. Its own sweep is a - # separate step for the same `check-step-collectors` reason stated above: - # a production sweep inside the collector block re-invokes the stub the - # harness plants and the count comes out 4/3. + # `scripts/**` comment prose — ⛔ not a second resolver. - name: scripts/** symbol anchors resolve (no line citations on tracked targets survive) id: scripts-symbol-anchors-resolve-no-line continue-on-error: true - run: node scripts/check-scripts-symbol-anchors.mjs + run: pnpm check:scripts-symbol-anchors # The THIRD corpus (#17065) — `packages/spec/src/**` doc blocks, again a - # `defineCorpus` registration and ⛔ not a second resolver. Separate sweep - # step for the same `check-step-collectors` reason stated above. + # `defineCorpus` registration and ⛔ not a second resolver. # # ⭐ Its exit contract is a PINNED DAY-ONE RESIDUAL, not a clean tree: the # census (7 findings across 6 of 1,317 files, taken in reporting mode @@ -2562,7 +2510,7 @@ jobs: - name: packages/spec/src/** doc-block symbol anchors resolve (no NEW line citations on tracked targets) id: packages-spec-src-doc-block-symbol-anchors continue-on-error: true - run: node scripts/check-spec-docblock-symbol-anchors.mjs + run: pnpm check:spec-docblock-symbol-anchors # NOTE: the standing platform test checklist (docs/qa/platform-checklist/) # is validated by `pnpm check:platform-checklist`, but by MAINTAINER @@ -3519,9 +3467,7 @@ jobs: - name: Aggregator roster gate id: aggregator-roster-gate continue-on-error: true - run: | - node scripts/check-aggregator-roster.mjs --self-test - node scripts/check-aggregator-roster.mjs + run: pnpm check:aggregator-roster # Rationale: `scripts/ci/lane-verdict.mjs` header (#23191). The verdict # every lane above and below ends with reads each gate's `outcome`, never @@ -3652,9 +3598,7 @@ jobs: - name: CI filter parity (cross-package Layer C) id: ci-filter-parity-cross-package-layer-c continue-on-error: true - run: | - node scripts/check-ci-filter-parity.mjs --self-test - node scripts/check-ci-filter-parity.mjs + run: pnpm check:ci-filter-parity # The READING half of the gate above (#10046). `--union-into` appends the # cross-package scans to the `turbo ls` document ci.yml is about to shard, @@ -4129,7 +4073,7 @@ jobs: # # ⛔ The manifest alias is NOT the verdict. It runs `--self-test` and # nothing else — the shape every credential-needing gate in this manifest - # uses (`check:pm-half-states`, `check:pm-closed-card-sweep`), because a + # uses (`check:pm-half-states`, for one), because a # live mode needs a board and a credential. Wiring the alias ALONE would # run the self-test twice and scan nothing. Both spellings are here on # purpose, self-test first: `check-self-test-wired` requires CI to run the diff --git a/.github/workflows/validate-deps.yml b/.github/workflows/validate-deps.yml index 5fc86aab793..7b58409faf5 100644 --- a/.github/workflows/validate-deps.yml +++ b/.github/workflows/validate-deps.yml @@ -81,8 +81,9 @@ jobs: run: | pnpm install --frozen-lockfile --prefer-offline + # Through its root alias, so its --self-test runs first (#26091). - name: Verify Changesets "fixed" group covers every public package - run: node scripts/check-changeset-fixed.mjs + run: pnpm check:changeset-fixed # pnpm overrides pin what THIS workspace resolves, but they do not ship # with published packages — downstream installs see only the declared diff --git a/package.json b/package.json index 208b2b2614d..376f4ff4900 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "check:widget-option-census": "node scripts/check-widget-option-census.mjs --self-test && node scripts/check-widget-option-census.mjs", "check:doc-authoring": "node scripts/check-doc-authoring.mjs --self-test && node scripts/check-doc-authoring.mjs", "check:doc-anchors": "node scripts/check-doc-anchors.mjs --self-test && node scripts/check-doc-anchors.mjs", + "check:doc-frontmatter": "node scripts/check-doc-frontmatter.mjs --self-test && node scripts/check-doc-frontmatter.mjs", + "check:docs-nav-label": "node scripts/check-docs-nav-label.mjs --self-test && node scripts/check-docs-nav-label.mjs", "check:docs-single-h1": "node scripts/check-docs-single-h1.mjs --self-test && node scripts/check-docs-single-h1.mjs", "check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs", "check:docs-redirects": "node scripts/check-docs-redirects.mjs --self-test && node scripts/check-docs-redirects.mjs", @@ -80,7 +82,6 @@ "check:issue-citations": "node scripts/check-issue-citations.mjs --self-test", "check:pm-record-recognisers": "node scripts/pm/record-recognisers.mjs --self-test", "check:pm-prior-rulings": "node scripts/pm/check-prior-rulings.mjs --self-test", - "check:pm-closed-card-sweep": "node scripts/pm/sweep-closed-cards.mjs --self-test", "check:pm-stale-finding": "node scripts/pm/sweep-stale-finding.mjs --self-test", "check:pm-write-pace": "node scripts/pm/write-pace.mjs --self-test", "check:pm-fleet-token": "node scripts/pm/fleet-token.mjs --self-test", @@ -144,6 +145,7 @@ "check:startup-registry-verdict": "node scripts/check-startup-registry-verdict.mjs --self-test && node scripts/check-startup-registry-verdict.mjs", "check:dev-prereqs": "node scripts/check-dev-prereqs.mjs --self-test && node scripts/check-dev-prereqs.mjs", "check:empty-changeset": "node scripts/check-empty-changeset.mjs --self-test && node scripts/check-empty-changeset.mjs", + "check:changeset-fixed": "node scripts/check-changeset-fixed.mjs --self-test && node scripts/check-changeset-fixed.mjs", "check:adr-0087-registration": "node scripts/check-adr-0087-registration.mjs --self-test && node scripts/check-adr-0087-registration.mjs", "check:changeset-gate-self-tests": "node scripts/check-empty-changeset.mjs --self-test && node scripts/check-adr-0087-registration.mjs --self-test && node scripts/check-changeset-no-major.mjs --self-test", "check:lockstep-package-count": "node scripts/check-lockstep-package-count.mjs --self-test && node scripts/check-lockstep-package-count.mjs", @@ -163,6 +165,8 @@ "check:select-gate-families": "bash scripts/ci/select-gate-families.selftest.sh", "check:shard-attestation": "node scripts/check-shard-attestation.mjs --self-test && node scripts/check-shard-attestation.mjs", "check:required-contexts": "node scripts/check-required-contexts.mjs --self-test && node scripts/check-required-contexts.mjs", + "check:aggregator-roster": "node scripts/check-aggregator-roster.mjs --self-test && node scripts/check-aggregator-roster.mjs", + "check:ci-filter-parity": "node scripts/check-ci-filter-parity.mjs --self-test && node scripts/check-ci-filter-parity.mjs", "check:cross-package-test-inputs": "node scripts/check-cross-package-test-inputs.mjs --self-test && node scripts/check-cross-package-test-inputs.mjs", "check:live-db-isolation": "node scripts/check-live-db-isolation.mjs --self-test && node scripts/check-live-db-isolation.mjs", "check:tier-file-adoption": "node scripts/check-tier-file-adoption.mjs --self-test && node scripts/check-tier-file-adoption.mjs", diff --git a/scripts/check-adr-symbol-anchors.mjs b/scripts/check-adr-symbol-anchors.mjs index a629aa344f4..c3fa956f902 100644 --- a/scripts/check-adr-symbol-anchors.mjs +++ b/scripts/check-adr-symbol-anchors.mjs @@ -331,8 +331,14 @@ export function selfTest() { // 4. The gate is wired to run. A gate nothing invokes is this repo's most // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); - check(workflow.includes('node scripts/check-adr-symbol-anchors.mjs'), 'lint.yml must invoke this gate'); - check(workflow.includes('node scripts/check-adr-symbol-anchors.mjs --self-test'), 'lint.yml must invoke this gate\'s --self-test'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin + // the step AND the alias: shared resolver self-test, this self-test, sweep. + check(/^\s*run: pnpm check:adr-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:adr-symbol-anchors`'); + const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:adr-symbol-anchors']; + check( + alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs', + `root package.json's check:adr-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + ); // 5. The census declaration is intact, INCLUDING the one-way error direction // the ruling ordered recorded (point 5). diff --git a/scripts/check-aggregator-roster.mjs b/scripts/check-aggregator-roster.mjs index 9ecf16566d0..fca48f413ee 100644 --- a/scripts/check-aggregator-roster.mjs +++ b/scripts/check-aggregator-roster.mjs @@ -663,8 +663,22 @@ async function selfTest() { { const lint = sources['lint.yml']; const self = 'scripts/check-aggregator-roster.mjs'; - assert(lint.includes(`node ${self}\n`), `wiring: lint.yml invokes ${self} directly (lint.yml's GATE INVOCATION IDIOM note, not a package.json fence)`); - assert(lint.includes(`node ${self} --self-test`), 'wiring: lint.yml runs the --self-test half too'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so the + // alias is where both legs live: pin the step AND the alias value. + assert( + /^\s*run: pnpm check:aggregator-roster\s*$/m.test(lint), + `wiring: lint.yml runs ${self} through its root alias \`pnpm check:aggregator-roster\``, + ); + let alias = null; + try { + alias = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).scripts?.['check:aggregator-roster'] ?? null; + } catch (err) { + failures.push(`cannot read root package.json to verify wiring: ${err?.code ?? err?.message}`); + } + assert( + alias === `node ${self} --self-test && node ${self}`, + `wiring: root package.json's check:aggregator-roster runs the --self-test leg, then the gate -- got ${JSON.stringify(alias)}`, + ); } // ── The floor: every declared battery RAN, and ran its cases (#13489) ─── diff --git a/scripts/check-changeset-fixed.mjs b/scripts/check-changeset-fixed.mjs index db4910a521f..205d8ee3ad0 100644 --- a/scripts/check-changeset-fixed.mjs +++ b/scripts/check-changeset-fixed.mjs @@ -5,6 +5,7 @@ * released out of lockstep with the rest of the monorepo. * * Run: node scripts/check-changeset-fixed.mjs + * node scripts/check-changeset-fixed.mjs --self-test # the rules against fixtures, no tree read * * Exits with code 1 (and a clear diff) if: * - A public (non-private) workspace package is missing from the @@ -113,7 +114,169 @@ export function judge(config, { publicNames, privateNames }, declared = PRIVATE_ return { missing, stale, declaredProblems }; } +// ── --self-test ────────────────────────────────────────────────────────────── +// +// Shape copied from `scripts/check-node-version.mjs` (AGENTS.md, "Writing a +// --self-test"): a literal cases table whose row LABELS are the battery roster, +// each floored at 1 and registered as the first statement of the loop body; the +// roster's own size pinned; and a verdict flag the dispatch refuses to run +// without. `judge` only: fixtures in, findings out, no tree read. + +let selfTestReachedVerdict = false; + +const SELF_TEST_BATTERIES = Object.freeze({ + 'every public package in the group → GREEN': 1, + 'a group split over several arrays is read as one → GREEN': 1, + 'a public package outside the group → RED (missing)': 1, + 'a config with no fixed group misses every public package → RED': 1, + 'a group name no workspace package has → RED (stale)': 1, + 'an undeclared private package in the group → RED (stale)': 1, + 'a declared private member in the group, privatePackages.version true → GREEN': 1, + 'a declared private member outside the group → RED': 1, + 'a declared private member while privatePackages.version is not true → RED': 1, + 'a declared member whose manifest is public → RED': 1, + 'a declared member absent from the tree is stale only if the group names it': 1, + 'every shipped PRIVATE_FIXED_MEMBERS row carries its reason': 1, +}); + +const SELF_TEST_BATTERY_FLOOR = 12; + +function selfTest() { + const ws = (publicNames, privateNames = []) => ({ publicNames: [...publicNames].sort(), privateNames: new Set(privateNames) }); + const DECLARED = Object.freeze({ 'ext-x': 'a fixture reason' }); + const verdict = (config, packages, declared = DECLARED) => { + const { missing, stale, declaredProblems } = judge(config, packages, declared); + return { missing, stale, declared: declaredProblems.length }; + }; + const green = { missing: [], stale: [], declared: 0 }; + + const cases = [ + { label: 'every public package in the group → GREEN', got: () => verdict({ fixed: [['@a/one', '@a/two']] }, ws(['@a/one', '@a/two'])), expect: green }, + { + label: 'a group split over several arrays is read as one → GREEN', + got: () => verdict({ fixed: [['@a/one'], ['@a/two']] }, ws(['@a/one', '@a/two'])), + expect: green, + }, + { + label: 'a public package outside the group → RED (missing)', + got: () => verdict({ fixed: [['@a/one']] }, ws(['@a/one', '@a/two'])), + expect: { missing: ['@a/two'], stale: [], declared: 0 }, + }, + { + label: 'a config with no fixed group misses every public package → RED', + got: () => verdict({}, ws(['@a/one', '@a/two'])), + expect: { missing: ['@a/one', '@a/two'], stale: [], declared: 0 }, + }, + { + label: 'a group name no workspace package has → RED (stale)', + got: () => verdict({ fixed: [['@a/one', '@a/gone']] }, ws(['@a/one'])), + expect: { missing: [], stale: ['@a/gone'], declared: 0 }, + }, + { + label: 'an undeclared private package in the group → RED (stale)', + got: () => verdict({ fixed: [['@a/one', 'tool-y']], privatePackages: { version: true } }, ws(['@a/one'], ['tool-y'])), + expect: { missing: [], stale: ['tool-y'], declared: 0 }, + }, + { + label: 'a declared private member in the group, privatePackages.version true → GREEN', + got: () => verdict({ fixed: [['@a/one', 'ext-x']], privatePackages: { version: true } }, ws(['@a/one'], ['ext-x'])), + expect: green, + }, + { + label: 'a declared private member outside the group → RED', + got: () => verdict({ fixed: [['@a/one']], privatePackages: { version: true } }, ws(['@a/one'], ['ext-x'])), + expect: { missing: [], stale: [], declared: 1 }, + }, + { + label: 'a declared private member while privatePackages.version is not true → RED', + got: () => [ + verdict({ fixed: [['@a/one', 'ext-x']] }, ws(['@a/one'], ['ext-x'])), + verdict({ fixed: [['@a/one', 'ext-x']], privatePackages: { version: false } }, ws(['@a/one'], ['ext-x'])), + ], + expect: [{ missing: [], stale: [], declared: 1 }, { missing: [], stale: [], declared: 1 }], + }, + { + label: 'a declared member whose manifest is public → RED', + got: () => verdict({ fixed: [['@a/one', 'ext-x']], privatePackages: { version: true } }, ws(['@a/one', 'ext-x'])), + expect: { missing: [], stale: [], declared: 1 }, + }, + { + label: 'a declared member absent from the tree is stale only if the group names it', + got: () => [verdict({ fixed: [['@a/one']] }, ws(['@a/one'])), verdict({ fixed: [['@a/one', 'ext-x']] }, ws(['@a/one']))], + expect: [green, { missing: [], stale: ['ext-x'], declared: 0 }], + }, + { + label: 'every shipped PRIVATE_FIXED_MEMBERS row carries its reason', + got: () => Object.entries(PRIVATE_FIXED_MEMBERS).filter(([, reason]) => typeof reason !== 'string' || reason.trim() === '').map(([name]) => name), + expect: [], + }, + ]; + + const batterySeen = new Map(); + let failed = 0; + for (const c of cases) { + batterySeen.set(c.label, (batterySeen.get(c.label) ?? 0) + 1); + const got = c.got(); + if (JSON.stringify(got) === JSON.stringify(c.expect)) { + console.log(` ✓ ${c.label}`); + } else { + console.error(` ✗ ${c.label}\n expected ${JSON.stringify(c.expect)}\n got ${JSON.stringify(got)}`); + failed += 1; + } + } + + // The floor: every declared row RAN, evaluated before the verdict so the + // success line can only follow a run whose registered rows equal the roster. + const floorFailure = (message) => { + console.error(`✗ self-test floor: ${message}`); + failed += 1; + }; + const declared = Object.keys(SELF_TEST_BATTERIES); + if (declared.length < SELF_TEST_BATTERY_FLOOR) { + floorFailure( + `SELF_TEST_BATTERIES declares ${declared.length} batteries, below the pinned ${SELF_TEST_BATTERY_FLOOR} -- ` + + 'a battery deleted from the roster takes its own floor with it.', + ); + } + const labels = cases.map((c) => c.label); + const duplicated = [...new Set(labels.filter((label, i) => labels.indexOf(label) !== i))]; + if (duplicated.length > 0) { + floorFailure(`the cases table uses ${duplicated.map((l) => JSON.stringify(l)).join(', ')} as a row label more than once.`); + } + for (const [label, count] of batterySeen) { + if (!Object.hasOwn(SELF_TEST_BATTERIES, label)) { + floorFailure(`self-test battery "${label}" registered ${count} case(s) but is not declared in SELF_TEST_BATTERIES.`); + } + } + for (const label of declared) { + const count = batterySeen.get(label) ?? 0; + if (count < SELF_TEST_BATTERIES[label]) { + floorFailure(`self-test battery "${label}" DID NOT RUN -- ${count} case(s) registered, ${SELF_TEST_BATTERIES[label]} pinned.`); + } + } + + if (failed > 0) { + console.error(`\n✗ check-changeset-fixed self-test: ${failed} failure(s) (cases and floor).`); + process.exit(1); + } + console.log(`\n✓ check-changeset-fixed self-test: ${cases.length} cases pass.`); + selfTestReachedVerdict = true; +} + function main() { + if (process.argv.includes('--self-test')) { + selfTest(); + if (!selfTestReachedVerdict) { + console.error( + '\n✗ check-changeset-fixed self-test: selfTest() returned without reaching its verdict,\n' + + 'so no success line was printed. Exiting 0 here would report a self-test\n' + + 'that never finished as a self-test that passed.\n', + ); + process.exit(1); + } + return; + } + const packages = listPackageNames(); const { missing, stale, declaredProblems } = judge(readConfig(), packages); diff --git a/scripts/check-ci-filter-parity.mjs b/scripts/check-ci-filter-parity.mjs index 9162ef38272..44654807bc8 100644 --- a/scripts/check-ci-filter-parity.mjs +++ b/scripts/check-ci-filter-parity.mjs @@ -202,14 +202,11 @@ * * ## Wiring * - * Invoked from `.github/workflows/lint.yml` as `node scripts/...` directly, both - * legs, rather than through a `pnpm check:*` alias: see the GATE INVOCATION - * IDIOM note at the top of that file, which states the reasons once. It is NOT - * because root `package.json` is off limits -- that reading of the #9465 fence - * is false, and the note carries the fence's verbatim scope so this docblock - * does not have to: restating it is how the wrong reading spread (#10894). - * The self-test asserts that wiring against the workflow text -- a gate that - * exists and is not scheduled is the same dormant shape from the other side. + * Invoked from `.github/workflows/lint.yml` through its root alias, which runs + * both legs: see the GATE INVOCATION IDIOM note at the top of that file, which + * states the rule once. The self-test asserts that wiring against the workflow + * text and the alias value -- a gate that exists and is not scheduled is the + * same dormant shape from the other side. */ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; @@ -1226,8 +1223,22 @@ export async function selfTest() { failures.push(`cannot read .github/workflows/lint.yml to verify wiring: ${err?.code ?? err?.message}`); } if (lint !== null) { - assert(lint.includes(`node ${SELF}\n`), `wiring: lint.yml invokes ${SELF} directly (lint.yml's GATE INVOCATION IDIOM note, not a package.json fence)`); - assert(lint.includes(`node ${SELF} --self-test`), 'wiring: lint.yml runs the --self-test leg too'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so the + // alias is where both legs live: pin the step AND the alias value. + assert( + /^\s*run: pnpm check:ci-filter-parity\s*$/m.test(lint), + `wiring: lint.yml runs ${SELF} through its root alias \`pnpm check:ci-filter-parity\``, + ); + let alias = null; + try { + alias = JSON.parse(readFileSync(join(REPO_ROOT, 'package.json'), 'utf8')).scripts?.['check:ci-filter-parity'] ?? null; + } catch (err) { + failures.push(`cannot read root package.json to verify wiring: ${err?.code ?? err?.message}`); + } + assert( + alias === `node ${SELF} --self-test && node ${SELF}`, + `wiring: root package.json's check:ci-filter-parity runs the --self-test leg, then the gate -- got ${JSON.stringify(alias)}`, + ); } // The checked-in ci.yml, which the real-tree cases of (9) and (10) read. diff --git a/scripts/check-doc-frontmatter.mjs b/scripts/check-doc-frontmatter.mjs index 0aed3bf35df..4a7e120122d 100644 --- a/scripts/check-doc-frontmatter.mjs +++ b/scripts/check-doc-frontmatter.mjs @@ -227,14 +227,11 @@ * * ## Wiring * - * Invoked from `.github/workflows/lint.yml` as `node scripts/...` directly, both - * legs, rather than through a `pnpm check:*` alias: see the GATE INVOCATION - * IDIOM note at the top of that file, which states the reasons once. It is NOT - * because root `package.json` is off limits -- that reading of the #9465 fence - * is false, and the note carries the fence's verbatim scope so this docblock - * does not have to: restating it is how the wrong reading spread (#10894). - * The self-test asserts that wiring against the workflow text -- a gate that - * exists and is not scheduled is the same dormant shape from the other side. + * Invoked from `.github/workflows/lint.yml` through its root alias, which runs + * both legs: see the GATE INVOCATION IDIOM note at the top of that file, which + * states the rule once. The self-test asserts that wiring against the workflow + * text and the alias value -- a gate that exists and is not scheduled is the + * same dormant shape from the other side. * * Adding the second root needed NO workflow edit: the step already invokes this * script, and `ROOTS` is read from here. `lint.yml` is the repo's busiest file @@ -1553,8 +1550,22 @@ export async function selfTest() { failures.push(`cannot read .github/workflows/lint.yml to verify wiring: ${err.code ?? err.message}`); } if (lint !== null) { - assert(lint.includes(`node ${SELF}\n`), `wiring: lint.yml invokes ${SELF} directly (lint.yml's GATE INVOCATION IDIOM note, not a package.json fence)`); - assert(lint.includes(`node ${SELF} --self-test`), 'wiring: lint.yml runs the --self-test leg too'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so the + // alias is where both legs live: pin the step AND the alias value. + assert( + /^\s*run: pnpm check:doc-frontmatter\s*$/m.test(lint), + `wiring: lint.yml runs ${SELF} through its root alias \`pnpm check:doc-frontmatter\``, + ); + let alias = null; + try { + alias = JSON.parse(readFileSync(join(REPO_ROOT, 'package.json'), 'utf8')).scripts?.['check:doc-frontmatter'] ?? null; + } catch (err) { + failures.push(`cannot read root package.json to verify wiring: ${err?.code ?? err?.message}`); + } + assert( + alias === `node ${SELF} --self-test && node ${SELF}`, + `wiring: root package.json's check:doc-frontmatter runs the --self-test leg, then the gate -- got ${JSON.stringify(alias)}`, + ); } // ── (11) an unmet prerequisite REFUSES, it does not blame the battery ──── diff --git a/scripts/check-docs-nav-label.mjs b/scripts/check-docs-nav-label.mjs index 17b91df6773..99979a4bc3c 100644 --- a/scripts/check-docs-nav-label.mjs +++ b/scripts/check-docs-nav-label.mjs @@ -736,8 +736,22 @@ export async function selfTest() { failures.push(`cannot read .github/workflows/lint.yml to verify wiring: ${err.code ?? err.message}`); } if (lint !== null) { - assert(lint.includes(`node ${SELF}\n`), `wiring: lint.yml invokes ${SELF} directly (lint.yml's GATE INVOCATION IDIOM note)`); - assert(lint.includes(`node ${SELF} --self-test`), 'wiring: lint.yml runs the --self-test leg too'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so the + // alias is where both legs live: pin the step AND the alias value. + assert( + /^\s*run: pnpm check:docs-nav-label\s*$/m.test(lint), + `wiring: lint.yml runs ${SELF} through its root alias \`pnpm check:docs-nav-label\``, + ); + let alias = null; + try { + alias = JSON.parse(readFileSync(join(REPO_ROOT, 'package.json'), 'utf8')).scripts?.['check:docs-nav-label'] ?? null; + } catch (err) { + failures.push(`cannot read root package.json to verify wiring: ${err?.code ?? err?.message}`); + } + assert( + alias === `node ${SELF} --self-test && node ${SELF}`, + `wiring: root package.json's check:docs-nav-label runs the --self-test leg, then the gate -- got ${JSON.stringify(alias)}`, + ); } // ── The floor: every declared battery RAN, and ran its cases (#13489) ─── diff --git a/scripts/check-scripts-symbol-anchors.mjs b/scripts/check-scripts-symbol-anchors.mjs index ed49c51adff..94d73d4b7ff 100644 --- a/scripts/check-scripts-symbol-anchors.mjs +++ b/scripts/check-scripts-symbol-anchors.mjs @@ -473,8 +473,14 @@ export function selfTest() { // 6. The gate is wired to run. A gate nothing invokes is this repo's most // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); - check(workflow.includes('node scripts/check-scripts-symbol-anchors.mjs'), 'lint.yml must invoke this gate'); - check(workflow.includes('node scripts/check-scripts-symbol-anchors.mjs --self-test'), 'lint.yml must invoke this gate\'s --self-test'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin + // the step AND the alias: shared resolver self-test, this self-test, sweep. + check(/^\s*run: pnpm check:scripts-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:scripts-symbol-anchors`'); + const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:scripts-symbol-anchors']; + check( + alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs', + `root package.json's check:scripts-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + ); // 7. The census declaration is intact, and its three instruments stay // ordered — the ordering IS the reading (raw counts code, comment prose diff --git a/scripts/check-spec-docblock-symbol-anchors.mjs b/scripts/check-spec-docblock-symbol-anchors.mjs index 2d2d39e123e..fb6d785815b 100644 --- a/scripts/check-spec-docblock-symbol-anchors.mjs +++ b/scripts/check-spec-docblock-symbol-anchors.mjs @@ -1271,8 +1271,14 @@ export function selfTest() { // 6. The gate is wired to run. A gate nothing invokes is this repo's most // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); - check(workflow.includes('node scripts/check-spec-docblock-symbol-anchors.mjs'), 'lint.yml must invoke this gate'); - check(workflow.includes('node scripts/check-spec-docblock-symbol-anchors.mjs --self-test'), 'lint.yml must invoke this gate\'s --self-test'); + // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin + // the step AND the alias: shared resolver self-test, this self-test, sweep. + check(/^\s*run: pnpm check:spec-docblock-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:spec-docblock-symbol-anchors`'); + const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:spec-docblock-symbol-anchors']; + check( + alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs', + `root package.json's check:spec-docblock-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + ); // 7. The census declaration is intact, and its three instruments stay // ordered — the ordering IS the reading (raw counts code, comment prose From e6ae9dede854d9b53bf0886c6c89f5b75531adc6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 03:54:41 +0000 Subject: [PATCH 2/2] Give the shared symbol-anchor resolver its own alias and step Each corpus alias bundled the resolver's self-test, which made the resolver's no-path-population marker the corpus family's declaration while the corpus names paths. The resolver now has check:symbol-anchors and its own lint step; the corpus aliases run their self-test and sweep. Both release.yml lanes run check:changeset-fixed through the alias too, so the script has one family, reached from validate-deps on pull_request. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01ADHkmQq9EYQdTQwmRutQTS --- .github/workflows/lint.yml | 26 ++++++++++++------- .github/workflows/release.yml | 4 +-- package.json | 7 ++--- scripts/check-adr-symbol-anchors.mjs | 6 ++--- scripts/check-scripts-symbol-anchors.mjs | 6 ++--- .../check-spec-docblock-symbol-anchors.mjs | 6 ++--- 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8cdc67d96a9..ba9c912c3a6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2475,16 +2475,22 @@ jobs: # #13788 (1,647 platform-checklist citations) joins by registering its own # corpus — ⛔ not by forking the resolver. # - # Each corpus runs its root alias, per the GATE INVOCATION IDIOM note at - # the top of this file: `pnpm check:-symbol-anchors` is the shared - # resolver's `--self-test`, then the corpus's own `--self-test`, then its - # sweep. One step per corpus, each with an `id:` and `continue-on-error`, - # so a red corpus never hides the other two — the job the `run_self_test` - # collector that used to precede these steps did (#10814). The shared - # resolver's self-test (~0.2 s) runs in each step; a red there reds all - # three, which is what a broken resolver means. Each corpus gate's own - # --self-test asserts its step and its alias, so rewiring one silently - # reddens it. + # Every step here runs a root alias, per the GATE INVOCATION IDIOM note at + # the top of this file: `pnpm check:symbol-anchors` is the shared + # resolver's `--self-test`, and each `pnpm check:-symbol-anchors` + # is that corpus's own `--self-test`, then its sweep. One step each, each + # with an `id:` and `continue-on-error`, so a red one never hides the + # others — the job the `run_self_test` collector that used to hold the + # four self-tests did (#10814). Each corpus gate's own --self-test asserts + # its step and its alias, so rewiring one silently reddens it. The + # resolver keeps its own step and alias because it declares no path + # population of its own (its `dispatch-gates: no-path-population` marker) + # while every corpus does. + - name: Symbol-anchor resolver self-test + id: symbol-anchor-resolver-self-test + continue-on-error: true + run: pnpm check:symbol-anchors + - name: ADR symbol anchors resolve (no line numbers survive) id: adr-symbol-anchors-resolve-no-line-numbers continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96a87038a69..d38fcba7587 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -320,7 +320,7 @@ jobs: # (the publish lane runs it again — a gate on one lane is not a gate on # the other). - name: Verify Changesets "fixed" group covers every public package - run: node scripts/check-changeset-fixed.mjs + run: pnpm check:changeset-fixed # ══════════════════════════════════════════════════════════════════════ # POST-VERSION VALIDATION — THE ONLY GATE THE RELEASE COMMIT EVER PASSES @@ -1853,7 +1853,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Verify Changesets "fixed" group covers every public package - run: node scripts/check-changeset-fixed.mjs + run: pnpm check:changeset-fixed # ────────────────────────────────────────────────────────────────────── # ⛔ TOMBSTONE — no objectui pin is read, built or checked here, and none diff --git a/package.json b/package.json index 376f4ff4900..aab6db3d8f5 100644 --- a/package.json +++ b/package.json @@ -111,9 +111,10 @@ "check:workspace-manifest-cycles": "node scripts/check-workspace-manifest-cycles.mjs --self-test && node scripts/check-workspace-manifest-cycles.mjs", "check:adr-anchors": "node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs", "check:adr-links": "node scripts/check-adr-links.mjs --self-test && node scripts/check-adr-links.mjs", - "check:adr-symbol-anchors": "node scripts/symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs", - "check:scripts-symbol-anchors": "node scripts/symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs", - "check:spec-docblock-symbol-anchors": "node scripts/symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs", + "check:symbol-anchors": "node scripts/symbol-anchors.mjs --self-test", + "check:adr-symbol-anchors": "node scripts/check-adr-symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs", + "check:scripts-symbol-anchors": "node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs", + "check:spec-docblock-symbol-anchors": "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs", "check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs --self-test && node scripts/check-platform-checklist.mjs", "gen:checklist-status": "node scripts/gen-checklist-status.mjs --self-test && node scripts/gen-checklist-status.mjs", "check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs", diff --git a/scripts/check-adr-symbol-anchors.mjs b/scripts/check-adr-symbol-anchors.mjs index c3fa956f902..e0f5ddb99bf 100644 --- a/scripts/check-adr-symbol-anchors.mjs +++ b/scripts/check-adr-symbol-anchors.mjs @@ -332,12 +332,12 @@ export function selfTest() { // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin - // the step AND the alias: shared resolver self-test, this self-test, sweep. + // the step AND the alias: this self-test, then the sweep. check(/^\s*run: pnpm check:adr-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:adr-symbol-anchors`'); const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:adr-symbol-anchors']; check( - alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs', - `root package.json's check:adr-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + alias === 'node scripts/check-adr-symbol-anchors.mjs --self-test && node scripts/check-adr-symbol-anchors.mjs', + `root package.json's check:adr-symbol-anchors must run this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, ); // 5. The census declaration is intact, INCLUDING the one-way error direction diff --git a/scripts/check-scripts-symbol-anchors.mjs b/scripts/check-scripts-symbol-anchors.mjs index 94d73d4b7ff..350ccd0cb7c 100644 --- a/scripts/check-scripts-symbol-anchors.mjs +++ b/scripts/check-scripts-symbol-anchors.mjs @@ -474,12 +474,12 @@ export function selfTest() { // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin - // the step AND the alias: shared resolver self-test, this self-test, sweep. + // the step AND the alias: this self-test, then the sweep. check(/^\s*run: pnpm check:scripts-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:scripts-symbol-anchors`'); const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:scripts-symbol-anchors']; check( - alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs', - `root package.json's check:scripts-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + alias === 'node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs', + `root package.json's check:scripts-symbol-anchors must run this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, ); // 7. The census declaration is intact, and its three instruments stay diff --git a/scripts/check-spec-docblock-symbol-anchors.mjs b/scripts/check-spec-docblock-symbol-anchors.mjs index fb6d785815b..4369001d570 100644 --- a/scripts/check-spec-docblock-symbol-anchors.mjs +++ b/scripts/check-spec-docblock-symbol-anchors.mjs @@ -1272,12 +1272,12 @@ export function selfTest() { // carded defect class, and renaming a step silently detaches it. const workflow = readFileSync('.github/workflows/lint.yml', 'utf8'); // CI runs the root alias (lint.yml's GATE INVOCATION IDIOM note), so pin - // the step AND the alias: shared resolver self-test, this self-test, sweep. + // the step AND the alias: this self-test, then the sweep. check(/^\s*run: pnpm check:spec-docblock-symbol-anchors\s*$/m.test(workflow), 'lint.yml must run this gate through `pnpm check:spec-docblock-symbol-anchors`'); const alias = JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['check:spec-docblock-symbol-anchors']; check( - alias === 'node scripts/symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs', - `root package.json's check:spec-docblock-symbol-anchors must run the resolver's and this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, + alias === 'node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs', + `root package.json's check:spec-docblock-symbol-anchors must run this gate's --self-test, then the sweep -- got ${JSON.stringify(alias)}`, ); // 7. The census declaration is intact, and its three instruments stay