Skip to content

feat(devx): check:engine-double-contract gains the consumer-seam refusal invariant (#8194) - #8423

Merged
qq9340100 merged 4 commits into
mainfrom
claude/issue-8194-consumer-seam-existence-probe
Aug 13, 2026
Merged

feat(devx): check:engine-double-contract gains the consumer-seam refusal invariant (#8194)#8423
qq9340100 merged 4 commits into
mainfrom
claude/issue-8194-consumer-seam-existence-probe

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes#8194

check:engine-double-contract gains a second, walled-off subject: the CONSUMER SEAMS. Scope is the finding-grading ruling's option 2 only — pin the consumers, not the doubles. Option 1 and the card's own title shape stay excluded, and no ledger entry was added in either direction.

The ruling's premise did not survive measurement, and the gate is narrower for it

The ruling scoped this to "every consumer performing a by-id write probes existence first — a small, decidable set". Measured on origin/main, that predicate is neither small nor true:

  • 45 by-id writes in production sources (update/delete carrying a scalar where.id), across 23 files
  • ~40 of them correctly perform no existence probe of their own

Because #7867/#7989 put the gate at the funnel, not at each consumer. ObjectQL.update/.delete read the prior row unconditionally on their by-id branch and throw recordNotFoundError when it is missing — engine.ts says so in as many words ("placed at the one point all of them funnel through, so it is not a fourth site"). A sql-http-outbox.ack(id) or a db-queue-adapter.purgeFailed(messageId) is already refused. Shipping the literal predicate would have reddened ~40 correct call sites — the identical 37-false-positive shape the ruling used to exclude the title's own gate, one layer out.

So the scan carries three conjuncts, each measured for what it removes:

conjunctsites remainingwhat it removes
1. performs a by-id write45
2. on a caller-supplied id1629 sites writing by an id they just read off a row
3. and answers a receipt412 sites that return nothing, so cannot misreport

Result: 6 seams (4 functions, callData holding two verbs), 0 false positives, 0 exemptions, no ledger.

The assertion is REFUSAL, not "probes"

The four seams do not share a mechanism, and demanding the commonest one would redden correct code: protocol.updateData probes and shares the read with its OCC gate; protocol.deleteData reads the driver's Promise< boolean > (=== false is the contract's own positive not-found signal); callData probes with a find; the MCP bridge probes with findById. So the gate asks only that the function refuses before it answers — same contract-first shape as the slices above it: pin the decision that must be reached, not the road to it.

Discovery reads three options spellings, and the second is load-bearing rather than a nicety: protocol.updateData and deleteData both build const opts = { where: { id: request.id } } and pass the variable, so a scan reading only inline literals discovers neither — it would report a green tree it never looked at. That is pinned by a named self-test assertion and by a real-tree wiring assertion.

What it deliberately does not claim

Verification

  • node scripts/check-engine-double-contract.mjs — OK; 193 pinned, 133 DEBT, 2 exempt (all unchanged), plus consumer seams: 6 in 3 source file(s) — 4 refusing through recordNotFoundError, 2 through a locally minted error, 0 not refusing at all.
  • node scripts/check-engine-double-contract.mjs --self-test — OK.
  • Mutation-tested, direction predicted in writing before each run. Eight mutations, each neutering one decision; every one caught by a named assertion. Two of them found real holes rather than confirming the gate, and both are fixed here:
    • the $in assertion passed for the wrong reason (rootIdentifier already rejects object literals, so the scalar test was a phantom check) — now pinned on scalarWhereIdOf directly, where a mutation can reach it;
    • the mint-vs-shared claim was proved only for new Error, leaving the call arm untested — the local-factory spelling (the MCP shape) and the unresolvable-imported-factory spelling are now both fixtures.
  • pnpm check:nul-bytes — OK (7602 files, 0 control bytes); plus a direct grep -naP self-scan of the changed file.
  • npx eslint scripts/check-engine-double-contract.mjs — clean.
  • node scripts/pm/dispatch-gates.mjs scripts/check-engine-double-contract.mjs re-derived against the actual changed path: no path-scoped family beyond the repo-wide set. No test file added — the coverage is embedded in --self-test, this file's own convention — so check:type-check-coverage / check:type-check-debt are not pulled in.

Scripts-only, no runtime behaviour changed ⇒ skip-changeset. The consumer seams in packages/objectql and packages/metadata-protocol were read as the gate's subject and never edited; the diff is one file.


Generated by Claude Code

@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 12:28pm

Request Review

@qq9340100qq9340100 added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 13, 2026 — with Claude
@qq9340100
qq9340100 marked this pull request as ready for review August 13, 2026 12:54
@qq9340100
qq9340100 added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 122a847Aug 13, 2026
24 checks passed
@qq9340100
qq9340100 deleted the claude/issue-8194-consumer-seam-existence-probe branch August 13, 2026 13:14
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] Existence coherence of engine/driver doubles is enforced only at the two consumer seams — check:engine-double-contract still cannot see it

2 participants

@qq9340100@claude