Observed live: a dispatched card ran its full derived gate union plus the mandated pnpm lint, reported everything green, and CI's Lint & Repo Gates came back red on a gate the derivation never named.
The instance
PR #9951 (card #9832, packages/cli/src/commands/serve.ts + one new test file). The dev ran node scripts/pm/dispatch-gates.mjs with no paths (script-derived change set: 3 paths → 10 path-matched + 5 convention-triggered) and pnpm lint, reported ~15 gate readings all green. CI then failed:
check-test-source-alias FAILED
✗ @objectstack/cli: NEW unaliased artifact import(s) since this entry was measured: @objectstack/service-cache.
Alias them in the package's vitest.config.* — widening the registry entry is not the fix.
The finding is real and the gate is right — the new test imports CacheServicePlugin from @objectstack/service-cache with no alias, so it was a verdict about built dist rather than about source. No complaint about the gate. The complaint is that nothing told the dev to run it.
Why no path literal can describe this trigger
check:test-source-alias's population is not "files under some glob". It is:
for each package with a registry entry, the set of workspace specifiers its test files import that no vitest.config.* alias resolves to source
The trigger is therefore a new cross-package import appearing inside a test file — a property of the file's content, and specifically of an import graph that crosses package boundaries. dispatch-gates.mjs derives from paths. packages/cli/src/commands/*.test.ts is a perfectly ordinary path; the same path is harmless on Monday and gate-failing on Tuesday depending on what a single import line names.
Note this is not the same as the existing convention trigger "adds or edits a test file" — that one fires on the path and is already handled. The gate that actually fires here depends on which package the test reaches into, which the convention trigger does not model. check:cross-package-test-inputs (which the derivation did name, and which passed) is a different gate answering a different question, and its passing is easy to misread as covering this.
This is the fourth confirmed instance of the blind spot, and a new species
Prior recorded instances — #9721, #9700 — are of the form "dispatch-gates names neither pnpm lint nor check:slot-lookup", i.e. gates that exist but are not derived. Those are handled today by a standing instruction in every dispatch brief ("run pnpm lint regardless").
This one is different in kind and the standing workaround does not catch it: pnpm lint was run, and it is Lint & Repo Gates (the CI job) that contains check:test-source-alias, not the local pnpm lint script. So the brief's compensating instruction is not equivalent to the CI job it is meant to compensate for — a gap worth stating on its own, independent of this gate.
Compare #9873, which records a structurally similar case from the other direction: check:type-check-debt cannot be named for a scripts/** card because the root package's tsc scope is defined by exclusion, so no path literal will ever describe it. Same root shape: a gate whose population is defined by something other than a path set.
Not asserted
No claim about which fix is right. Candidates, none measured here:
- teach the convention-trigger machinery a content-derived kind (a test file that gains an import of another workspace package), which is a real change in what the script is allowed to read;
- have dispatch briefs name the CI job (
Lint & Repo Gates) rather than the local script pnpm lint, since the two are not the same population — cheap, and it fixes the general case rather than this gate; - have the script state, in its residue block, that the
undetermined bucket includes gates whose population is not a path set at all — today's residue text says their sources name no path, which is true but reads as "probably irrelevant".
The middle option looks cheapest and broadest, but the judgement is the devx seat's, not this one's.
Repo: objectstack. Related: #9721, #9700 (prior instances of the naming gap), #9873 (a gate undescribable by paths, for a different structural reason), #9914 (piped-exit-code reading, the other way a gate run is believed when it should not be). Seen on PR #9951 / card #9832.
Observed live: a dispatched card ran its full derived gate union plus the mandated
pnpm lint, reported everything green, and CI'sLint & Repo Gatescame back red on a gate the derivation never named.The instance
PR #9951 (card #9832,
packages/cli/src/commands/serve.ts+ one new test file). The dev rannode scripts/pm/dispatch-gates.mjswith no paths (script-derived change set: 3 paths → 10 path-matched + 5 convention-triggered) andpnpm lint, reported ~15 gate readings all green. CI then failed:The finding is real and the gate is right — the new test imports
CacheServicePluginfrom@objectstack/service-cachewith no alias, so it was a verdict about built dist rather than about source. No complaint about the gate. The complaint is that nothing told the dev to run it.Why no path literal can describe this trigger
check:test-source-alias's population is not "files under some glob". It is:The trigger is therefore a new cross-package import appearing inside a test file — a property of the file's content, and specifically of an import graph that crosses package boundaries.
dispatch-gates.mjsderives from paths.packages/cli/src/commands/*.test.tsis a perfectly ordinary path; the same path is harmless on Monday and gate-failing on Tuesday depending on what a singleimportline names.Note this is not the same as the existing convention trigger "adds or edits a test file" — that one fires on the path and is already handled. The gate that actually fires here depends on which package the test reaches into, which the convention trigger does not model.
check:cross-package-test-inputs(which the derivation did name, and which passed) is a different gate answering a different question, and its passing is easy to misread as covering this.This is the fourth confirmed instance of the blind spot, and a new species
Prior recorded instances — #9721, #9700 — are of the form "dispatch-gates names neither
pnpm lintnorcheck:slot-lookup", i.e. gates that exist but are not derived. Those are handled today by a standing instruction in every dispatch brief ("runpnpm lintregardless").This one is different in kind and the standing workaround does not catch it:
pnpm lintwas run, and it isLint & Repo Gates(the CI job) that containscheck:test-source-alias, not the localpnpm lintscript. So the brief's compensating instruction is not equivalent to the CI job it is meant to compensate for — a gap worth stating on its own, independent of this gate.Compare #9873, which records a structurally similar case from the other direction:
check:type-check-debtcannot be named for ascripts/**card because the root package's tsc scope is defined by exclusion, so no path literal will ever describe it. Same root shape: a gate whose population is defined by something other than a path set.Not asserted
No claim about which fix is right. Candidates, none measured here:
Lint & Repo Gates) rather than the local scriptpnpm lint, since the two are not the same population — cheap, and it fixes the general case rather than this gate;undeterminedbucket includes gates whose population is not a path set at all — today's residue text says their sources name no path, which is true but reads as "probably irrelevant".The middle option looks cheapest and broadest, but the judgement is the devx seat's, not this one's.
Repo:
objectstack. Related: #9721, #9700 (prior instances of the naming gap), #9873 (a gate undescribable by paths, for a different structural reason), #9914 (piped-exit-code reading, the other way a gate run is believed when it should not be). Seen on PR #9951 / card #9832.