Skip to content

dispatch-gates never derives a gate for a card that edits the gate's OWN check script — the resolved script paths are already in hand and unused #8509

Description

@hotlong

Found while implementing #8478 (module-body-only watch hints), measured rather than reasoned.

The observation

derive() in scripts/pm/dispatch-gates.mjs resolves each discovered check:* family to the script FILES that implement it, and stores them on entry.files. It then throws that away as a matching key: the only thing compared against the card's input paths is entry.hints, the path literals scanned out of those files' contents.

So the most direct relationship the tool has access to is the one it never uses — this gate IS this file. A card that edits scripts/check-empty-changeset.mjs should derive pnpm check:changeset-gate-self-tests, the gate whose entire job is running that script's self-test. It does not, because the gate's invocation names the script in package.json, not in the script's own source.

Measured, on main at 3208222 and on the #8478 branch

"Does editing a gate script derive at least one gate family?", over all 66 discoverable gate scripts:

treescripts that derive a gate when edited
main (before #8478)14 of 66
#8478 branch (module-body hints)7 of 66

The 14 were never a feature — they are the scripts that happen to quote their own filename somewhere in their source (a usage line, a subprocess spawn in the self-test). #8478 narrows the scan to the module body, so the accidental half mostly goes away and the structural gap is what is left. Concretely, on the branch:

node scripts/pm/dispatch-gates.mjs scripts/check-empty-changeset.mjs
-> no check family names the given paths in its own source

scripts/pm/dispatch-gates.mjs itself is one of the seven survivors, and only because a separate file — scripts/pm/check-dispatch-gates.mjs — names it as its one module-body constant. That is the shape all 66 would need, one thin gate file each, which is not a proposal.

Why it is worth a card

This is the one derivation that needs no heuristic at all. Every other match is a judgment about whether a path literal covers an input; this one is an identity the tool computed itself, three lines earlier, and then discarded. The blind spot is also self-shaped: it is exactly the class of card that edits gate tooling, which is the work most likely to break a gate.

Direction (not a recommendation)

In derive(), treat each of entry.files as a watch hint for its own family, alongside the scanned ones — the matched via column would then read scripts/check-empty-changeset.mjs => 'scripts/check-empty-changeset.mjs' with the file path as provenance. Nothing needs listing: entry.files is already resolved at runtime from package.json, the same derived-never-listed contract the rest of the script keeps. The self-test would pin both directions (a gate script derives its own family; an unrelated script does not).

Deliberately NOT attempted in #8478: that card's scope is extractWatchHints and its self-test, and this fix lives in derive.

Related


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions