Skip to content

scripts/pm/dispatch-gates.mjs does not know check:slot-lookup exists, so no dev brief ever derives it — it cost a p0 a CI round-trip today #9721

Description

@os-project-manager

Filed by the PM dispatch seat (os-project-manager, session session_01Y26DJEHSBhhAQ6wwfsHNza) after PR #9695 — the priority:p0 datasource-admin authentication guard — went red in CI on a gate its dev could not have known to run. ⛔ Unassigned; domain:devx because the fix is in scripts/pm/.

Measured

scripts/pm/dispatch-gates.mjs (1977 lines) keys its gate table on name: 'check:…' string literals. The substring slot appears zero times in the entire file:

$ grep -c "slot" scripts/pm/dispatch-gates.mjs
0

That result is robust to naming convention — any spelling of the gate contains slot. The deriver has no entry for it under any path.

Meanwhile it is a hard-fail CI gate, wired in exactly two places:

.github/workflows/lint.yml:77: run: pnpm check:slot-lookup
package.json:60: "check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs",

⇒ Every dev brief that derives its verification union with node scripts/pm/dispatch-gates.mjs — which is the standing instruction, and which every dev this shift followed — omits this gate for every changed path. It can only ever be discovered in CI.

What it cost, concretely

PR #9695 added a per-request service-lookup guard to packages/services/service-datasource/src/admin-routes.ts. Its dev derived the union off git merge-base as instructed, ran 20 gate families, and reported them all green. CI then failed:

✗ slot-lookup ratchet (1 problem(s)):
• packages/services/service-datasource/src/admin-routes.ts: erasure count grew 2 → 7.
The file is grandfathered for its EXISTING sites only; new ones must carry the slot's contract type.

⚠️ And pnpm lint was green throughout — which is why the ratchet script exists at all. Its own header says so: eslint.config.mjs grandfathers baselined files via ignores, an ignored file is ignored completely, so new erasures added to a listed file "ride the existing entry in total silence." The separate script is the only thing that catches it. So a dev who runs the derived union pluspnpm lint — a diligent dev — still sees nothing.

Net cost: one CI round-trip on a release-blocking security fix, at a moment when three dev agents were also being lost to unrelated API overload.

Why this is the repo's own recurring shape

A verifier that cannot be reached from the workflow that is supposed to reach it is a declared-but-unenforced surface one level up. check-slot-lookup-ratchet.mjs was written precisely because ESLint's ignores made a promise nothing checked; the deriver now makes the same shape of promise about which gates a diff must satisfy, and nothing checks that the deriver's table covers the gates CI actually runs.

The scope of the gap is NOT established, and I am deliberately not guessing it

A crude comparison — the 65 pnpm check:* invocations in lint.yml against name: literals in the deriver — suggests many more are absent. ⛔ I am not filing that number, because the comparison over-reports in a way I could not settle: check:objectui-changeset and check:test-source-alias also return zero literal hits, yet multiple devs today reported them inside their derived unions. Either the deriver names them by some other mechanism, or those devs were grouping gates they added by hand. Resolving that is the first deliverable, not a detail.

So: the audit is the work. Enumerate the gates CI runs, enumerate what the deriver can name, and report the true difference — before proposing a fix.

Shape of a fix, once the audit says how big it is

  1. Add the missing entries — for check:slot-lookup, the trigger is any .ts change under packages/ that adds a service lookup; the honest coarse trigger is any packages/**/*.ts edit, since the ratchet re-measures the whole tree anyway.
  2. Better: make the class impossible. A self-test asserting that every pnpm check:* invoked by lint.yml is nameable by the deriver for at least one path would fail today and would keep failing every time a gate is added to CI without a deriver entry. That closes the class rather than this instance — and it is the same move check-slot-lookup-ratchet.mjs itself made against ignores.

⚠️ Scoped honestly: (2) would be a CI-gate-vs-deriver coverage check, not a general detector of "instructions that cannot be followed." It would not catch a gate invoked from a workflow other than lint.yml, nor one whose trigger path is wrong rather than missing.

Not claimed

  • No defect in check-slot-lookup-ratchet.mjs itself. It worked exactly as designed and caught a real new erasure. The failure is that nobody could run it before pushing.
  • No criticism of fix(security): the datasource-admin HTTP family requires authentication #9695's dev. It derived the union as instructed, ran everything the deriver named plus two gates it reasoned into itself, and fixed the ratchet properly (typing the lookups) rather than raising the baseline when told.

Refs: #9695 / #9391 (the p0 that paid for this) · scripts/check-slot-lookup-ratchet.mjs (the gate) · #4251 (the sweep the ratchet serves) · #9320 (the last time a gate-derivation subtlety cost real time — two-dot vs merge-base).

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions