Skip to content

fix(pm): declare the runtime-enumerated gate populations dispatch-gates could never name (#10542) - #11189

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10542-runtime-population-gates
Aug 23, 2026
Merged

fix(pm): declare the runtime-enumerated gate populations dispatch-gates could never name (#10542)#11189
os-zhuang merged 1 commit into
mainfrom
claude/issue-10542-runtime-population-gates

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes#10542

scripts/pm/dispatch-gates.mjs derives a gate's relevance from the path literals in the gate's own source. A gate that computes its population at runtime by parsing pnpm-workspace.yaml names no literal at all, so the derivation could name it for no card in the tree. This closes that class per-gate, with the reading the card itself asked for first — and one of those readings disagrees with the card.

The measurement, before and after

Reproduced the way #10542 specifies: build the family table the way derive() does, then ask coveringKey(entry, specimen) for each of the four layout specimens — packages/cli/src/index.ts (flat), packages/services/service-cache/src/index.ts (nested), apps/docs/package.json, examples/showcase/package.json.

 card (filed) BEFORE (main) AFTER
discovered families 119 137 137
... whose gate script reads pnpm-workspace.yaml at runtime 12 15 15
... naming NONE of the four specimens 7 10 9
... of those, rescued by CHANGE_KIND_GATES 2 2 3
... UNRESCUED 5 8 6

The tree moved under the card, in both directions: the class grew from 5 unrescued to 8, and the discovered farm from 119 to 137. check:test-source-alias and check:type-source-resolution still name all four (PR #10540), so the instrument tracks the tree rather than reporting a constant.

Of the 8, two moved out and four are now recorded as correct-by-reading rather than unexamined:

familynow names
pnpm check:published-filesall four specimens, via gate source 'packages/*' / 'apps/*' / 'examples/*'
pnpm check:pnpm-filter-targetsscripts/** cards (e.g. scripts/check-nul-bytes.mjs) — not the four specimens, deliberately
pnpm check:cross-package-test-inputsevery test-file card, through CHANGE_KIND_GATES

Per-gate classification — which refusal each is actually under

Verified against current source, not the card. The decision rule is the one check-examples-live-imports.mjs already sets and pins: declare a root the gate walks wholesale, refuse one it merely traverses to reach a narrow subset — with the precision measured, not asserted.

familywhat it really readsrefusal it sits underverdictaction
check:published-fileswalks and judges every non-build file of every publishable memberbare top-level word (packages, apps, examples) refused as too generic4803 / 5263 named = 91.3% — wholesaledeclares the workspace globs, reconciled against the live workspaceGlobs() parse in its self-test
check:published-readme-exportssame walk, then narrowed to published .md + manifests + the dist type entrysame149 / 5263 = 2.8% — filteredrefuses the declaration, against the card's own triage. Recorded with the measurement and pinned
check:pnpm-filter-targetswalks scripts/ and .github/workflows/ whole; reads one package.json per memberscripts spelled join(root, 'scripts') — a bare word, dropped before hintCovers is consulted228 / 235 = 97.0% for the scripts subtreedeclares the scripts subtree; the per-member manifest half stays a documented blind spot (declaring the workspace globs would name 5263 files to reach ~78 manifests, 1.5%)
check:cross-package-test-inputs (×2 invocations)walks packages/apps/examples for tests reading outside their own packagebare top-level words2611 / 5263 = 49.6% — neither wholesale nor negligiblemoved to CHANGE_KIND_GATES under "adds or edits a test file", which is the granularity it actually judges
check:release-bodyits CI script is --self-testalone; the workspace/CHANGELOG reads belong to the release runzero hints, and the surviving cause is the isNonPathNamespace media-type refusal the card namescorrect as-isno-path-population marker with a reason
check:prerelease-pinspnpm-workspace.yamloverrides and npm, nothing elserepo-root file spelled as a bare filename — the second documented refusal, exactly as the card saysalready covered: prerelease-pin-watch.yml declares pnpm-workspace.yaml in paths:, so the CI-trigger key (#9171) reaches itmarker. A subtree-spelling hint would be a second spelling of one population
check:dev-prereqslint.yml runs --self-test only (the step name says so); the scan is a pnpm dev preflightits one hint, packages/spec, is honest — the freshness half really reads that distcorrect as-is; silent elsewhere is the right verdictnote in the header, no marker — one would contradict the hint, and the new self-test asserts that pair cannot coexist

The card said of the published-files pair that "their population genuinely is the workspace and the #10114 escape applies directly". It applies to one of them. check-published-readme-exports.mjs walks the same members and then keeps only the markdown, so the same declaration would paste it into every prompt whose surface brushes any package source file with 97 of every 100 leads naming a gate that never opens the file — the shape check-examples-live-imports.mjs already refuses by name at 1.6%.

The instrument change: documented non-coverage

undetermined is the honest verdict and was an unreadable report — the bucket holds gates the derivation cannot place, gates it need not place, and gates it already places through a trigger, and nothing told them apart. #10542 was filed against a count of that pile.

A gate may now carry, in its own source, a whole-line

// dispatch-gates: no-path-population -- THE REASON, in prose

mirroring the workflow-level no-check-families marker one level down, and for the same reason: a roster in dispatch-gates.mjs would be a second copy of a fact that belongs on the thing it describes. The residue reports those apart from the unexamined remainder and prints each reason against its family under --residue. A reason is required; the marker is not an escape from having a population, and the self-test asserts against the live tree that no family both declares one and names paths anyway.

The shared enumerator refactor — deferred, with the reason pinned

The card called the twelve private pnpm-workspace.yaml parsers "the same refactor as the fix rather than a separate one". Under the current derivation it is the opposite, and this is now an assertion rather than a paragraph:

resolveCheckToFiles reads the script paths out of the npm script's command string, and discoverFamilies scans exactly those files. A module a gate imports is never opened.

So moving a population declaration into a shared enumerator would delete it from every gate that imports it — silently undoing #10540's declarations and the ones this PR adds. The consolidation is blocked on teaching the derivation to follow first-party imports, not on the gates, and is filed separately. The new self-test case pins the import opacity so a later author measures it instead of trusting this paragraph.

Gates run — all green at 5f8e2b4e67

Derived with node scripts/pm/dispatch-gates.mjs on the real change set (no hand-written path list), re-derived after the final commit — same 12 families, no additions. Exit codes captured before any pipe.

gateverdict line
pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 557 cases pass.
pnpm check:published-files✓ check:published-files — 69 publishable package(s) of 78 workspace member(s) … + ✓ … 21 pattern case(s), 12 classification case(s) and 4 population-declaration case(s) over 11 live workspace glob(s).
pnpm check:published-readme-exports✓ check:published-readme-exports --self-test — …, full scan green on a built tree (319 documented symbols checked)
pnpm check:pnpm-filter-targets✓ check-pnpm-filter-targets --self-test: 40 assertions … + ✓ check:pnpm-filter-targets: 121/149 --filter occurrence(s) … 0 dead
pnpm check:release-bodyexit 0
pnpm check:cross-package-test-inputs / node scripts/check-cross-package-test-inputs.mjsexit 0
pnpm check:entry-guard, pnpm check:parse-guard, pnpm check:nul-bytesexit 0
node scripts/check-ci-filter-parity.mjsexit 0
node scripts/check-dev-prereqs.mjs --self-test, node scripts/check-prerelease-pin-watch.mjs --self-testexit 0

check:published-readme-exports and check:published-files were re-run after turbo run build — the first refuses an unbuilt tree by design and every finding said so.

Lint, narrowed with its evidence.eslint --no-inline-config over the 7 changed files: 7 files linted, 0 errors, 0 warnings (count read from --format json, not from the console). The narrowing is a measurement rather than a skip because the population is read from the config itself, which states it enables "no parserOptions.project, no typed @typescript-eslint rules) for ANY file" — with type-aware linting off, this diff cannot move the verdict on any untouched file.

Reverse-verified. The new refusal assertion in check-published-readme-exports.mjs was ablated under a restoring trap: injecting a workspace glob into the declaration (mutation confirmed on disk by grepping both the removed and the injected spelling — old 1→0, new 0→1) turned the self-test red with the intended message, and the restore leg was confirmed by the same grep. Direction as predicted.

Landing class.node scripts/pm/check-governed-merges.mjs --test $(git diff --name-only) on the final file list:

governed-surface predicate: 0 of 7 path(s) hit the register (5 surfaces, repo-agnostic).
✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.

Changeset. Scripts-only, CI-internal — the textbook skip-changeset case by lint.yml's own prescription ("this PR edits a CI-internal script is the textbook skip-changeset case — such a PR releases nothing"). Label applied and read back; the changeset-family self-tests in lint.yml are unconditional and unaffected.

Files

  • scripts/pm/dispatch-gates.mjs — the no-path-population marker, its residue accounting, the CHANGE_KIND_GATES entry, and the pinning cases for all of it
  • scripts/check-published-files.mjs — population declaration, reconciled against the live parse
  • scripts/check-pnpm-filter-targets.mjs — the scripts-subtree declaration + the workspace-globs refusal, both pinned
  • scripts/check-published-readme-exports.mjs — the measured refusal, pinned
  • scripts/check-prerelease-pin-watch.mjs, scripts/release-github-releases.mjs — markers
  • scripts/check-dev-prereqs.mjs — header note recording why it needs neither

Generated by Claude Code


Generated by Claude Code


Generated by Claude Code

…es could never name (#10542)
Seven of the discovered gate families compute their population at RUNTIME by
parsing pnpm-workspace.yaml, so they spell no path literal and the dispatch
derivation named them for no card in the tree. Re-measured on current main the
class is 8 unrescued families across 137 discovered, not the 5 of 119 the card
recorded — the instrument moved when the tree did.
Read each one against its source rather than declaring a blanket population,
and the readings disagree with the card in one place that matters:
check:published-files DECLARES the workspace globs. It walks and
judges every non-build file of every
publishable member — 4803 of the 5263 files
the declaration names, 91.3%. Reconciled
against the live parse in its self-test, so
a workspace root that moves fails here.
check:published-readme-exports REFUSES the same declaration, against the
card's own triage. It narrows the same walk
to published markdown and manifests: 149 of
5263, 2.8% — the `filtered` shape
check-examples-live-imports refuses by name
at 1.6%. Recorded with the measurement and
pinned, so a later author meets an assertion.
check:pnpm-filter-targets DECLARES scripts/** (228 of 235, 97.0%); the
per-member manifest half has no truthful
spelling and stays a documented blind spot.
check:cross-package-test-inputs moves to CHANGE_KIND_GATES instead: its
judged population IS the test-file kind, and
a subtree declaration would name it at 49.6%.
check:release-body declares no path population, with a reason.
check:prerelease-pins likewise — its population is one repo-root
file its own workflow already names in
`paths:`, so the CI-trigger key reaches it.
check:dev-prereqs needs nothing: its one hint is honest.
The instrument grows the half that made the count unreadable: a gate may carry
`dispatch-gates: no-path-population -- <reason>` in its own source, mirroring
the workflow-level marker one level down, and the residue reports those apart
from the families nobody has examined. A declaration is not an escape from
having a population, and the self-test holds that line against the live tree.
The shared workspace-enumerator consolidation the card proposed is NOT done,
and the reason is now an assertion rather than a paragraph: resolveCheckToFiles
reads the script paths out of the npm script's COMMAND STRING, so a module a
gate imports is never opened. Moving a population declaration into a shared
enumerator would DELETE it from every gate that imports it — undoing #10540's
declarations and this card's. The consolidation is blocked on teaching the
derivation to follow first-party imports, not on the gates.
Co-Authored-By: Claude <noreply@anthropic.com>
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

ACCEPT — session 757f0dc0-5dae-51d7-9053-c15c57ebfe3b(派发席,卡 #10542)。

逐文件读完全量 diff(7 文件,+522/−22,head 5f8e2b4e67):

  1. 逐门判读兑现且纠正了卡:卡按当时树数出 5/119,本 PR 重测为 8/137 并逐一读源分类——published-filespnpm-filter-targets 取声明路线(91.3% / 97.0%,精度都写进 docblock 并由自测与活解析双向对账);published-readme-exports拒绝声明(2.8%,filtered 形状,与 check-examples-live-imports 的 1.6% 拒绝同类),拒绝本身以空数组 + 自测断言钉死;cross-package-test-inputs 迁入 CHANGE_KIND_GATES(类粒度 > 49.6% 的子树声明);prerelease-pin-watch(trigger 键已覆盖)与 release-body(self-test-only)按各自真实原因挂 no-path-population 标记;dev-prereqs 论证了既不声明也不挂标记(已有诚实 hint,自测钉住 hint⊕marker 互斥)。
  2. 仪器改良:undetermined 桶里「已读并解释」与「没人看过」从此分开报告,计数享受与其它 residue 计数同等的缺失即拒绝纪律;marker 要求 own-line + 强制 reason,活树断言防"声明了却还命名路径"的腐烂方向。
  3. 反向验证已做(变异钉住新拒绝断言,盘面 grep 双拼写确认、恢复亦确认);枚举器合并的 import 不透明约束钉为断言并另立 [finding] The shared workspace-enumerator consolidation is blocked on dispatch-gates following first-party imports — today it would DELETE the population declarations it is meant to centralise #11190,不越卡硬塞。
  4. 门禁:dispatch-gates 自测 557 例全绿,派生族 12 个终 commit 复跑全绿,lint 收窄三证齐,check-governed-merges --test = NOT governed,skip-changeset 已读回。

按普通队列落地:flip ready + auto-merge(SQUASH)。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-zhuang@claude