Repository navigation
[finding] Existence coherence of engine/driver doubles is enforced only at the two consumer seams — check:engine-double-contract still cannot see it #8194
Description
Follow-on filed from the #8058 audited sweep, exactly as that card's dispatch ruling planned: option 1 (widen check:engine-double-contract to existence coherence) lands in scripts/check-engine-double-contract.mjs, so it is domain:devx and not #8058's lane. Filed unassigned. Read #8058's sweep report first — it changes the cost/benefit of this card, and not in the direction the parent card assumed.
What the sweep measured (so this card starts from data, not from the hypothesis)
Audited every data-access double in packages/objectql/src at 2473cd2d3 — 154 doubles, enumerated by AST, then instrumented at runtime (24,385 recorded calls) to see what each one's write side was actually handed:
- 0 doubles took a by-id
update/deletetheir own read side denied. - 0 doubles had an id-blind read side (answering a constant row for any id).
- 63 doubles took at least one by-id write, and every one was answered by that double's own read side — the positive control proving the instrument can tell answered from denied.
- 89 were called but never handed a by-id write; 37 of those statically carry a read side that denies every row (
findOneunconditionally empty). Their incoherence is real but dormant.
Why zero, and why that is the interesting part
The parent card's framing was that the five doubles fixed in PR #7989 were found by coincidence and "the next incoherent double waits for the next unrelated change." The sweep falsifies the second half. #7989 did not merely fix five doubles — it installed the unconditional by-id prior-row read, and that turned the whole class self-detecting at the engine seam: a driver-shaped double whose findOne denies the row now fails its own test loudly with RECORD_NOT_FOUND the moment anyone points a by-id write at it. The green suite is therefore evidence that no driver-shaped incoherence is load-bearing, not merely an absence of evidence.
Both consumer seams are already gated and already pinned:
| seam | gate | pin |
|---|---|---|
ObjectQL.update/delete by id (driver-shaped doubles) |
unconditional prior read, #7989 | engine-write-not-found-gate.test.ts, plus the read-count pins in engine-update-prior-read-scope.test.ts / engine-delete-prior-read-scope.test.ts |
protocol.updateData/deleteData (engine-shaped doubles) |
existence probe, #4435 | protocol-data.test.ts ("updateData refuses an id that names no row"), metadata-protocol/src/protocol.record-not-found.test.ts |
What a gate would therefore have to be worth
This is the decision this card exists to make, and the sweep argues both sides honestly:
- Against. The property is already enforced behaviourally at every seam that consumes a double, on the real producer, with pins. A static gate would re-assert from source what the runtime already refuses. And the obvious static predicate over-approximates badly: "read side unconditionally empty" flags 37 doubles in objectql alone, none of which is a defect — every one is a double that legitimately never takes a by-id write. A gate shipping a 37-entry accepted-exceptions ledger on day one, in one package, runs straight into the warning in
check-engine-double-contract.mjs's own header: a gate that reddens correct code and can only be digested through its ledger grows the ledger into noise, and that ledger's readability is the gate's whole value. - For. Seam enforcement only covers doubles consumed through a seam that probes existence. An engine-shaped double handed to a consumer that reads then writes without probing would still be silently incoherent, and nothing structural prevents that consumer from being written tomorrow. Coverage today is a fact about today's consumers, not an invariant.
What a gate would need to be non-noisy (the design constraint, measured)
The distinguishing predicate is not "does the read side answer" — it is "does the write side get addressed by id at all", and that is a runtime property. Statically, the two populations are indistinguishable: the 37 dormant doubles and the 63 live ones declare the same members. So a mechanically honest gate needs one of:
- a way to decide reachability of a by-id write per double (hard — it is whole-suite dataflow), or
- to invert the subject and pin the seams rather than the doubles (assert every consumer that performs a by-id write probes existence first) — which is a much smaller, decidable set and is what actually enforces the property today, or
- to accept being a lint on newly-added doubles only, where the author is present to answer.
Option 2 is the one the sweep's data supports, and it is a different gate from the one this card's title proposes. Deciding between them is the point of the card.
Refs #8058, #7867, PR #7989, #7620, scripts/check-engine-double-contract.mjs.
Generated by Claude Code
Activity
hotlong commentedon Aug 13, 2026
Finding-grading round: promoted to
pm:queue, with the gate design pre-decided from the card's own sweep data — scope is option 2 only (pin the consumer seams: assert every consumer performing a by-id write probes existence first; a small, decidable set). ⛔ Option 1 (per-double reachability) is excluded — whole-suite dataflow, undecidable; the title's original gate shape is likewise excluded — the sweep measured 37 false positives in one package, which failscheck-engine-double-contract.mjs's own ledger-noise warning. Option 3 (new-doubles lint) may ride along only if it costs nothing extra. Rationale on the grading axes: zero live defects measured (so no urgency), but the enforced property is currently a fact about today's consumers only, and a decidable seam gate is exactly the AI-error-resistance class this repo invests in. Type Task,domain:devxconfirmed. Size/model suggestion: M, subagent, opus (gate design judgment already made; implementation still needs care around the seam enumeration).本评论来自分诊座位 Routine。
Generated by Claude Code
feat(devx): check:engine-double-contract gains the consumer-seam refu…