Skip to content

check-engine-double-contract cannot read three mock-initializer spellings, and one of them hides a live unguarded engine delete double #9877

Description

@os-steve

Found while implementing #9747 (the UNRECOGNISED verdict pilot, PR #9875). Not fixed there — widening this matcher is the separately-declined act #9747's own scoping excludes, and it would redden CI on constructs nobody has triaged. Recording with the measurement.

What the new census found

PR #9875 adds a printed, non-failing UNRECOGNISED count to check-engine-double-contract: constructs that declare a scanned verb alongside at least two engine siblings and whose implementation the gate could not read, so they are in neither the pinned population nor the ledger. On origin/main that count is 23, in three spellings:

spellingcountexample
the initializer carries a function the unwrap declined2delete: overrides.delete ?? vi.fn(async (_object: string, arg: any) =' ({ success: true, id: arg?.where?.id }))
the initializer roots at a binding the file declares7delete: del, with const del = vi.fn(async () =' ({ deleted: 1 })) above it
shorthand member14const engine: any = { registry, insert, findOne, update, delete: del }

117 further constructs are correctly scoped out (vi.fn() and mocks returning a VALUE carry no implementation at all, so nothing about them could be looser than the producer) and are not part of this report.

The measured harm — one of the 23 is a live unguarded double

packages/rest/src/rest-batch-endpoint.test.ts builds its fake ObjectQL with ?? defaults:

constql: any={transaction: vi.fn(async(cb: any,ctx: any)=' cb({ __trx: true, ctx })),
insert: overrides.insert??vi.fn(async(_object: string,data: any)=' ({ id: `id_${++seq}`, ...data })),
update: overrides.update??vi.fn(async(_object: string,data: any)=' ({ ...data })),
delete: overrides.delete??vi.fn(async(_object: string,arg: any)=' ({ success: true, id: arg?.where?.id })),
};

implOf reads a PropertyAssignment initializer through fnInitializer / unwrapCallImpl, which accept a function expression or a CallExpression with exactly one function argument. A BinaryExpression is neither, so implOf answers null, consider() returns before isEngineVerbShape is asked, and the double is absent — not baselined, not exempt, not pinned.

Ablation on 372d93284, both directions:

treeUNRECOGNISEDgate
as shipped23OK — 321 pinned, 133 in the DEBT ledger, 2 exempt. exit 0
remove only the ?? default on line 40 (same implementation, readable spelling)22exit 1PINNED [delete]: packages/rest/src/rest-batch-endpoint.test.ts declares 1 engine double(s) whose delete() does not route through assertEngineDeleteDispatch (line 36)

So this is not a cosmetic gap. The gate has a real finding about that double the moment it can read it, and the ?? is the only reason it does not.

Why the file's own census missed this

unwrapCallImpl's header carries a full census of the corpus and lists the spellings deliberately NOT widened, both measured at ZERO occurrences: a function among several arguments, and a function in the chained receiver. A ?? default is a third spelling that census never considered — a recognizer narrower than the code it was judging, in the note explaining why the recognizer is wide enough. Worth re-running that census, not just extending it.

Shape of a fix, and its price

Teaching fnInitializer to descend a BinaryExpression with ?? / || is one line. The other two spellings (a local binding, a shorthand) need localFunctions-style resolution, which the file already has for bodyIsPinned.

⚠️The price is the part that needs a maintainer. Every construct this widens into the population arrives unpinned, so each one is a PINNED error until it is either pinned or given a MEASURED baseline entry — and that baseline is shrink-only and marked maintainer-only. 23 constructs is the upper bound; how many are genuinely engine doubles (rather than driver or repository doubles the shape test would veto) is unknown until the widening runs, because the shape test is exactly what never got asked. That measurement is the first step of any fix.

Related: #8639 (the same blind half, at the vi.fn(fn) spelling) · #9747 (the meta-shape) · PR #9875 (the census that surfaced this) · #8845 / #9165 (widening priced and declined elsewhere in this family)

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions