Skip to content

test(pm): run the dispatch-gates self-test in CI and anchor its convention-gate pins - #8477

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-8162-dispatch-gates-selftest-ci
Aug 13, 2026
Merged

test(pm): run the dispatch-gates self-test in CI and anchor its convention-gate pins#8477
hotlong merged 1 commit into
mainfrom
claude/issue-8162-dispatch-gates-selftest-ci

Conversation

@hotlong

@hotlonghotlong commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes#8162
Fixes#8364

Two halves of one file's problem: the dispatch-gates self-test ran nowhere, and where it did pin the convention-gate names it pinned them by bare substring.

Part 1 — the self-test now runs in CI

Direction 1 (a dedicated check:pm-dispatch-gates, self-test only), not direction 2 (one job folding every scripts/pm/** self-test). Why:

  • The two existing scripts/pm/ gates are each their own check:* script with their own named step in the ESLint job (check:pm-skill-ratchet, check:pm-skill-id-lint). Direction 2 would have had to restructure both to fold them in — other jobs' surface, for no gain today.
  • The decisive reason is measurable, and it is this card's own subject: the derivation discovers a gate family by reading the literal command in a workflow run: body. A folded loop over scripts/pm/*.mjs renders as one opaque step naming no family, so the very tool being wired could never discover its own gate. A named pnpm check:pm-dispatch-gates step is discovered — measured below.
  • The live derivation stays out of CI: it answers a question about a card's file surface, is print-only, and exits 0 on any completed run. There is no verdict in it for CI to hold, only a slow read of the whole workflow tree. The self-test is the half that has one — and it is not fixture-only either (it reads the real pr-automation.yml and walks the real packages tree), so the derivation's contact with reality is gated too.

The step is deliberately unconditional — no if:, no label read, no paths filter — matching the Changeset-family gate self-tests step it sits near, for the reason that step's comment gives: an exemption is what a self-test must not have, or the gap just moves.

Why the gate is its own file (measured, not stylistic)

The obvious spelling is "check:pm-dispatch-gates": "node scripts/pm/dispatch-gates.mjs --self-test". That is the one shape this tool cannot have. The derivation resolves a check family to its script file and scans that file for the path literals it operates on — and this tool's tests are made of path strings. Measured on the tree: 49 literals extracted from dispatch-gates.mjs, of which 2 name inputs it really reads and 43 are self-test fixtures naming other packages (packages/spec/src, packages/objectql, packages/plugins, packages/drivers, .claude/agents, .changeset, ...). Wired directly, the derivation printed, for a card touching spec's filter schema:

- pnpm check:pm-dispatch-gates [lint.yml] matched via packages/spec/src/data/filter.zod.ts ⇢ 'packages/spec/src'

A fabricated lead in the column the tool's contract reserves for high-signal answers, on most of the tree — exactly what its header rejects ("22 leads is the same as none"). So the gate is scripts/pm/check-dispatch-gates.mjs, the same shape as the other two pm gates, whose only watch hint is the tool it runs. After the change the same three probes are clean:

probe pathnaive wiringas shipped
packages/spec/src/data/filter.zod.tsMATCHED (false)not matched
packages/objectql/src/engine.tsMATCHED (false)not matched
.claude/agents/os-dev.mdMATCHED (false)not matched
scripts/pm/dispatch-gates.mjsMATCHEDMATCHED (true)

A note worth keeping, because the first draft of that file did it: watch-hint extraction reads any quoted span, backticks included, and does not skip comments — so a header written the ordinary way, with each path in backticks, reproduced ten of the false hints from the very file explaining the pollution. That file now names paths unquoted and says why.

Proof the wiring runs

  • pnpm check:pm-dispatch-gates — green, 61/61 cases, 0.12s.
  • It can go red: with one CHANGE_KIND_GATES name mutated, the gate exits 1 and prints 1 of 61 case(s) failed. Restored, exit 0. (A gate proven only green is a gate not proven at all.)
  • Discovery, which is the point of the card: the derivation reported 90 families before, 91 after, and running it on this PR's own file surface now names its own gate:
- pnpm check:pm-dispatch-gates [lint.yml] matched via scripts/pm/dispatch-gates.mjs ⇢ 'scripts/pm/dispatch-gates.mjs'

One honest deviation from the card's suggested probe: node scripts/pm/dispatch-gates.mjs .github/workflows/lint.yml shows the new family only in the discovered count (90 to 91), not in the matched list, because the gate does not read lint.yml — it reads the tool. Naming .github/workflows as a hint would have made that probe print prettier by claiming an input the gate does not have.

Part 2 — the convention-gate pins are anchored

Both halves of the test-file entry's name assertion now anchor on the rendered delimiters (- pnpm x —), the shape the i18n entry's pins in the same file already use. The two conventions in one file no longer disagree.

Reverse-verified in the direction predicted before running it — rename both gates in CHANGE_KIND_GATES to prefix-preserving -v2 names, from a committed fix so restoring came out of a real commit:

legpin formself-testlive run
Asubstring includes (origin/main)61/61 pass, exit 0prints both gates STALE, correctly
Brendered-delimiter anchors (this PR)1 of 61 failed, exit 1✗ the section names both convention gates, runnablysame
restoreanchors, names restored61/61 pass, exit 0no STALE

Leg A is the defect verbatim: the pin stays green through the one rot class the STALE branch exists to report.

Verification

Union of the PM's dispatch list and a re-derivation against the actual diff (node scripts/pm/dispatch-gates.mjs over the four changed paths). All green:

check:pm-dispatch-gates, check:changeset-gate-self-tests, check:filter-alias-parity, check:node-version, check:nul-bytes, check:required-contexts, check:shard-attestation, check:type-check-coverage, check:type-source-resolution, check:workflow-status-functions, node scripts/check-changeset-no-major.mjs, node scripts/check-empty-changeset.mjs, plus eslint on both scripts.

The re-derivation added node scripts/check-empty-changeset.mjs (its consumer block asserts this job's step shape, so a step insertion is its business) and node scripts/check-shard-attestation.mjs over the PM's list; both green.

check:type-check-debt is the one gate not runnable here: its --re-measure half refuses to run in a tree whose workspace closure is unbuilt (55 dependencies with no built type entry point) rather than measure a different world, and lint.yml builds that closure in the step immediately before it. Its --self-test and the check-type-check-coverage wiring half both ran green; this diff contains no TypeScript, so no ledger input moved.

No changeset: PM tooling plus CI config, nothing released — skip-changeset.

Out of scope, filed

#8478 — the general form of the watch-hint pollution measured above: fixture and comment literals become hints in other check scripts too (check-adr-0087-registration.mjs matches packages/runtime, check-empty-changeset.mjs matches skills/demo/SKILL.md). This PR sidesteps it for one gate; it does not fix it. #8478 remains open and is not addressed here.


Generated by Claude Code

…ntion-gate pins
The dispatch-gates tool carried a 61-case --self-test that no CI job ran, and
its test-file convention-gate names were pinned by bare substring, so a
prefix-preserving rename passed the pin that exists to catch it.
- new gate check:pm-dispatch-gates, wired as an unconditional step in lint.yml
beside the other two scripts/pm gates; runs the self-test only
- the gate lives in its own file so the tool's fixture path literals do not
become watch hints and fabricate MATCHED leads across the tree
- both halves of the test-file entry's name pin now anchor on the rendered
delimiters, matching the i18n entry's pins in the same file
Fixes#8162Fixes#8364
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 3:11pm

Request Review

@hotlonghotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 13, 2026 — with Claude
@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file labels Aug 13, 2026
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 15:38
@hotlong
hotlong added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 4552b82Aug 13, 2026
30 checks passed
@hotlong
hotlong deleted the claude/issue-8162-dispatch-gates-selftest-ci branch August 13, 2026 16:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude