Filed by the devx lane PM (session session_01PfaSTikked61BkcsB5Rn69, round 13) as the only surviving actionable half of #9165, whose own headline premise was falsified by measurement. Full evidence, tables and reproduction live on #9165 — this card exists so the work rides a premise that is true today instead of a body that is not.
⚠️ PREMISE-FIRST — read this before doing anything. Every number below was measured on 2026-08-18 @ c07d6e8b9, and PR #9712 has since landed in the same gate family. They are the reason to look, ⛔ not the spec. The first deliverable is re-measuring them on current main. If the counts have moved materially, say so and re-price before writing a line of gate code — a card that turns out to be smaller than filed is a good outcome here, and #9165 closed exactly that way.
The re-measurement is not optional politeness: the PM tried it while filing this and got a refusal, not a reading —
node scripts/check-engine-double-contract.mjs
Nothing was measured: this gate exited before running a single check …
— which is PREREQUISITE NOT MET (unbuilt workspace), and ⛔ says nothing about the tree. Build the closure and re-run. ⚠️ Note that gate's own warning: piping it reports the pipe's status, so node … | tail reads green either way. Capture the exit code before any pipe.
What is being decided
2a — a criterion for scripts/check-durability-degradation-log-level.mjs: "a read seam whose catch binds no error parameter must discriminate or propagate."
A parameterless catch { provably cannot discriminate by error type, so the criterion is purely syntactic — no judgement, no new declared vocabulary, no new script, no new CI step. It drops into the existing selfTestReadSeams() harness and the existing baseline.
Measured pull, as of 08-18: every catch in both of #9165's instances was parameterless, and on the pre-#9067 tree the criterion's first-run set of 18 names all four of instance A's seams by file and line, publishPackageDrafts() guards findOne() among them — i.e. it would have replaced discovery-by-luck.
The actual deliverable: triage, not a gate
The criterion's first-run red set was 13 at c07d6e8b9. 3 of those (packages/metadata/src/utils/history-cleanup.ts, three seams) are in #8901's already-named correct cohort. The other 10 were never triaged, and that triage is the whole decision:
packages/metadata-protocol/src/protocol.ts:5605 getMetaItems() guards mergePackageAwareOverlay()
packages/metadata-protocol/src/protocol.ts:8437 findData() guards count()
packages/metadata-protocol/src/protocol.ts:18175 reportUnhydratableOrgScopedRows() guards find()
packages/metadata-protocol/src/seed-loader.ts:1126 resolveSoleOrganizationId() guards find()
packages/metadata-protocol/src/seed-loader.ts:1242 resolveFromDatabase() guards find()
packages/objectql/src/engine.ts:3040 probeInstallOrganizations() guards find()
packages/objectql/src/engine.ts:4963 referenceExists() guards findOne()
packages/objectql/src/engine.ts:6439 readMigrationFlagVerified() guards find()
packages/objectql/src/engine.ts:6825 announceOpenMigrationGates() guards readMigrationFlagVerified()
packages/objectql/src/lifecycle/lifecycle-service.ts:784 loadGovernance() guards find()
Read those 10 catch sites and answer, per site: is it genuinely wrong (swallowing a failure it should discriminate or propagate), or correct-but-undeclared? That answer decides the card, and the two outcomes are opposite:
⛔ Do not land the criterion before the triage. The precedent is explicit and this repo already paid it: #8845 proposed a 15-red criterion with 7 sites already correct and it was declined, with PR #8898 recording the refusal in the gate's header instead of baselining it. 13 reds against a shrink-only ledger holding one entry is the same order of magnitude, and the precision advantage (3 known-correct vs 7) is exactly what is unproven for the other 10. "Record the measurement in the header" remains a legitimate outcome of this card.
Ruled out — ⛔ do not re-open inside this card
Why it is worth doing at all, stated without inflating it
Nothing is on fire — every affected path is green today, and both #9165 instances are repaired. The pull is that this class has now been found by luck three times, and false-GREEN never self-announces. The strongest argument is authoring-time: a parameterless catch { is the cheapest thing an AI writes and is precisely the construct that cannot discriminate, and the cheapest way to satisfy the criterion is to bind the parameter and ask isMissingTableError — which is the correct fix, not a workaround. That is the rare gate whose cheapest satisfaction is the right behaviour.
Related
#9165 (source, closed — read its disposition comment first) · #8901 (blocker if the triage lands that way) · #8845 / PR #8898 (the declined precedent) · #9882 (shrink-only delta-ratchet for the same gate) · #9680 / PR #9712 (landed) · #12138 · #12358 · #12360 · #12576 (same gate's other open cards — check for overlap before touching its AST pass)
Filed by the devx lane PM (session
session_01PfaSTikked61BkcsB5Rn69, round 13) as the only surviving actionable half of #9165, whose own headline premise was falsified by measurement. Full evidence, tables and reproduction live on #9165 — this card exists so the work rides a premise that is true today instead of a body that is not.c07d6e8b9, and PR #9712 has since landed in the same gate family. They are the reason to look, ⛔ not the spec. The first deliverable is re-measuring them on currentmain. If the counts have moved materially, say so and re-price before writing a line of gate code — a card that turns out to be smaller than filed is a good outcome here, and #9165 closed exactly that way.The re-measurement is not optional politeness: the PM tried it while filing this and got a refusal, not a reading —
— which is⚠️ Note that gate's own warning: piping it reports the pipe's status, so
PREREQUISITE NOT MET(unbuilt workspace), and ⛔ says nothing about the tree. Build the closure and re-run.node … | tailreads green either way. Capture the exit code before any pipe.What is being decided
2a — a criterion for
scripts/check-durability-degradation-log-level.mjs: "a read seam whosecatchbinds no error parameter must discriminate or propagate."A parameterless
catch {provably cannot discriminate by error type, so the criterion is purely syntactic — no judgement, no new declared vocabulary, no new script, no new CI step. It drops into the existingselfTestReadSeams()harness and the existing baseline.Measured pull, as of 08-18: every catch in both of #9165's instances was parameterless, and on the pre-#9067 tree the criterion's first-run set of 18 names all four of instance A's seams by file and line,
publishPackageDrafts() guards findOne()among them — i.e. it would have replaced discovery-by-luck.The actual deliverable: triage, not a gate
The criterion's first-run red set was 13 at
c07d6e8b9. 3 of those (packages/metadata/src/utils/history-cleanup.ts, three seams) are in #8901's already-named correct cohort. The other 10 were never triaged, and that triage is the whole decision:Read those 10 catch sites and answer, per site: is it genuinely wrong (swallowing a failure it should discriminate or propagate), or correct-but-undeclared? That answer decides the card, and the two outcomes are opposite:
⛔ Do not land the criterion before the triage. The precedent is explicit and this repo already paid it: #8845 proposed a 15-red criterion with 7 sites already correct and it was declined, with PR #8898 recording the refusal in the gate's header instead of baselining it. 13 reds against a shrink-only ledger holding one entry is the same order of magnitude, and the precision advantage (3 known-correct vs 7) is exactly what is unproven for the other 10. "Record the measurement in the header" remains a legitimate outcome of this card.
Ruled out — ⛔ do not re-open inside this card
getAllObjects) — ruled out with the dev on A partial test double is only ever discovered by deleting thecatchthat hides it — twice this shift.check:engine-double-contractpins engine doubles; nothing pins registry doubles #9165. It is a scope change, not a vocabulary addition:DRIVER_READ_CALLEESis anchored toIDataDriver's read methods precisely so the generic names mean "a storage seam", and the gate's own header prices the cliff — drop that anchor and the shape matches 91 of the 314 catch clauses in these three roots.catchthat hides it — twice this shift.check:engine-double-contractpins engine doubles; nothing pins registry doubles #9165 framing 1) — ruled out by measurement: 0 of 157 registry doubles visible, no producer-side predicate can exist for a Map walk.delete()member takes 319 pinned to 318 and the gate stays green #9680 landed PR feat(gate): enumerate check:engine-double-contract's pinned population so a lost pin names itself #9712 for the engine gate).Why it is worth doing at all, stated without inflating it
Nothing is on fire — every affected path is green today, and both #9165 instances are repaired. The pull is that this class has now been found by luck three times, and false-GREEN never self-announces. The strongest argument is authoring-time: a parameterless
catch {is the cheapest thing an AI writes and is precisely the construct that cannot discriminate, and the cheapest way to satisfy the criterion is to bind the parameter and askisMissingTableError— which is the correct fix, not a workaround. That is the rare gate whose cheapest satisfaction is the right behaviour.Related
#9165 (source, closed — read its disposition comment first) · #8901 (blocker if the triage lands that way) · #8845 / PR #8898 (the declined precedent) · #9882 (shrink-only delta-ratchet for the same gate) · #9680 / PR #9712 (landed) · #12138 · #12358 · #12360 · #12576 (same gate's other open cards — check for overlap before touching its AST pass)