Filed by the domain:services PM seat (#6021) after PR #16730 shipped a CI red that the card's own gate union honestly reported as clean. Category (a), a reproducible tooling defect. The domain label is where it was found — relabel if a lane owns scripts/pm/** outright.
What happened
PR #16730 added packages/plugins/plugin-auth/src/list-user-invitations-verification.ts, a new module that writes a Hono response. check:route-envelope treats a discovered-but-undeclared response-writing module as an ERROR, so CI went red on Lint & Repo Gates.
The delivering dev ran the card's gate union and reported 66 derived / 66 run / 64 exit 0 / 2 exit 3 / 0 exit 1, plus a clean --ran reconcile ("66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED"). That report was honest. check:route-envelope was never in the union to run.
The measurement
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue \
packages/plugins/plugin-auth/src/list-user-invitations-verification.ts \
packages/plugins/plugin-auth/src/auth-manager.ts \
packages/runtime/src/dispatcher-error-vocabulary.ts
places the family here:
Silent (source names paths, none of which cover yours — the weakest verdict): 184 famil(ies).
…
- pnpm check:route-envelope [lint.yml] names: packages/services/service-storage/src/storage-routes.ts,
packages/services/service-settings/src/settings-routes.ts,
packages/services/service-datasource/src/admin-routes.ts, …
Negative control, so the zero is measured rather than assumed: grep -c for route-envelope — and for runner-env-posture, cli-test-child-env, vendor-version-stamps, authz-resolver, service-providers — is 0 in scripts/pm/dispatch-gates.mjs. No family is named literally there; derivation is dynamic over .github/workflows/*.yml (274 families across 33 workflow files). So the grep zero proves nothing on its own, and the --residue placement above is the actual evidence.
Why the placement is wrong
check:route-envelope's real population is a whole-tree walk: it scans the repo for modules that write c.json(…) / res.json(…) and errors on any it finds that the table does not declare. Its own header states this as the design — "lifting it to a repo-wide scan buys the thing per-package copies structurally cannot: a module nobody thought to convert still gets audited", and "A module discovered by the scan but absent from the table is an ERROR, not a default."
But the paths its workflow step names: are its already-declared members. So the derived population is the set of files that are already fine, and a brand-new response-writing module — the one case the gate exists to catch — matches none of them and falls to Silent.
The failure mode is self-concealing: the gate fires precisely when a file is not in the list that the derivation reads to decide whether the gate applies. A card can only trip this gate by touching a path that guarantees the gate is not derived for it.
To be fair to the tool, Silent is explicitly labelled "the weakest verdict" and the output warns ⛔ never to read silence as a clearance. The tool did not lie. But a verdict that is correct-and-useless for a whole class of gates is worth fixing rather than worth remembering.
Suggested direction (not a ruling — whoever picks this up should re-derive)
dispatch-gates already has buckets that fit this shape and would surface the family instead of hiding it:
Always runs (declared population) — 7 families "DECLARE that their population is the WHOLE TREE, so every card implicates them".
Declared WIDE population — 10 families whose CI population is "too wide to place, so no path of yours can narrow them and their absence from the matched block above is NOT a clearance".
- The residue output also names the idiom for the subtree case: "a gate that walks a subtree declares it (the
ROOT_DIR_WATCH_HINTS idiom)".
So the likely fix is on the gate's side, not the deriver's: check-route-envelope.mjs should declare its walk population the way its whole-tree siblings do, so the deriver places it in a bucket that a dev cannot read as "not mine".
Scope note — this is a class, not one gate
The shape to sweep is: a gate whose population is a whole-tree (or subtree) walk, but whose workflow names: enumerates only its current members. Every such gate is invisible to path derivation for exactly the cards that add a member. check:route-envelope is one confirmed instance; the other 183 Silent families for this card have not been examined and should not be assumed clean. A sweep wants a positive control — take a gate, add a file that should trip it, and confirm the deriver names the family before CI does.
Cost of leaving it
Every card introducing a new response-writing module ships red and burns a CI cycle plus a repair dispatch, and the dev is blamelessly surprised each time — the union is the artifact devs are told to trust.
Filed by the
domain:servicesPM seat (#6021) after PR #16730 shipped a CI red that the card's own gate union honestly reported as clean. Category (a), a reproducible tooling defect. The domain label is where it was found — relabel if a lane ownsscripts/pm/**outright.What happened
PR #16730 added
packages/plugins/plugin-auth/src/list-user-invitations-verification.ts, a new module that writes a Hono response.check:route-envelopetreats a discovered-but-undeclared response-writing module as an ERROR, so CI went red onLint & Repo Gates.The delivering dev ran the card's gate union and reported 66 derived / 66 run / 64 exit 0 / 2 exit 3 / 0 exit 1, plus a clean
--ranreconcile ("66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED"). That report was honest.check:route-envelopewas never in the union to run.The measurement
places the family here:
Negative control, so the zero is measured rather than assumed:
grep -cforroute-envelope— and forrunner-env-posture,cli-test-child-env,vendor-version-stamps,authz-resolver,service-providers— is 0 inscripts/pm/dispatch-gates.mjs. No family is named literally there; derivation is dynamic over.github/workflows/*.yml(274 families across 33 workflow files). So the grep zero proves nothing on its own, and the--residueplacement above is the actual evidence.Why the placement is wrong
check:route-envelope's real population is a whole-tree walk: it scans the repo for modules that writec.json(…)/res.json(…)and errors on any it finds that the table does not declare. Its own header states this as the design — "lifting it to a repo-wide scan buys the thing per-package copies structurally cannot: a module nobody thought to convert still gets audited", and "A module discovered by the scan but absent from the table is an ERROR, not a default."But the paths its workflow step
names:are its already-declared members. So the derived population is the set of files that are already fine, and a brand-new response-writing module — the one case the gate exists to catch — matches none of them and falls to Silent.The failure mode is self-concealing: the gate fires precisely when a file is not in the list that the derivation reads to decide whether the gate applies. A card can only trip this gate by touching a path that guarantees the gate is not derived for it.
To be fair to the tool,
Silentis explicitly labelled "the weakest verdict" and the output warns ⛔ never to read silence as a clearance. The tool did not lie. But a verdict that is correct-and-useless for a whole class of gates is worth fixing rather than worth remembering.Suggested direction (not a ruling — whoever picks this up should re-derive)
dispatch-gatesalready has buckets that fit this shape and would surface the family instead of hiding it:Always runs (declared population)— 7 families "DECLARE that their population is the WHOLE TREE, so every card implicates them".Declared WIDE population— 10 families whose CI population is "too wide to place, so no path of yours can narrow them and their absence from the matched block above is NOT a clearance".ROOT_DIR_WATCH_HINTSidiom)".So the likely fix is on the gate's side, not the deriver's:
check-route-envelope.mjsshould declare its walk population the way its whole-tree siblings do, so the deriver places it in a bucket that a dev cannot read as "not mine".Scope note — this is a class, not one gate
The shape to sweep is: a gate whose population is a whole-tree (or subtree) walk, but whose workflow
names:enumerates only its current members. Every such gate is invisible to path derivation for exactly the cards that add a member.check:route-envelopeis one confirmed instance; the other 183 Silent families for this card have not been examined and should not be assumed clean. A sweep wants a positive control — take a gate, add a file that should trip it, and confirm the deriver names the family before CI does.Cost of leaving it
Every card introducing a new response-writing module ships red and burns a CI cycle plus a repair dispatch, and the dev is blamelessly surprised each time — the union is the artifact devs are told to trust.