Filed unassigned by the dev seat working #9165, session session_01XqDQYVU5smx29ts9pAErja. Measured on origin/main @ c07d6e8b9, two-direction ablation, reproduction below. Not a claim about the gate's own slice logic, which is correct on the population it holds.
⛔ No domain:* and no labels set — triage's field.
The gap
scripts/check-engine-double-contract.mjs discovers its population by looking for an object literal / class / mock-constructor whose member named delete or update has an engine-shaped signature. Discovery therefore requires the member to exist. A double that stops declaring it simply leaves the population.
Nothing watches for that. The four invariants in the header are DISCOVERED, PINNED, RECONCILED, DECLARED:
DISCOVERED fires at zero — "Zero is not 'a clean repo', it is a broken scan". It does not fire at one fewer than yesterday.RECONCILED reconciles in both directions, but only over baseline entries. A file in the DEBT ledger whose count drops is an error. A pinned file whose double disappears is invisible: pinned files are not enumerated anywhere durable, so the pinned count is a printed number, not a checked one.
So the population is unratcheted in exactly the direction that loses coverage.
Measured — two-direction ablation on a PINNED double
Subject: packages/core/src/utils/migration-journal.test.ts, whose fake declares both write verbs and routes each through the producer predicate.
Baseline @ c07d6e8b9:
check-engine-double-contract: OK — 319 pinned, 133 in the DEBT ledger, 2 exempt.
Ablation A — delete the whole async delete(...) member (the shape a partial double actually has: the method is absent, not wrong):
pinned [update] packages/core/src/utils/migration-journal.test.ts
check-engine-double-contract: OK — 318 pinned, 133 in the DEBT ledger, 2 exempt.
exit 0
Green. The delete-slice pin for that file evaporated and the only trace is a printed integer nobody compares.
Control — keep the member, delete only the assertEngineDeleteDispatch(options); line:
x PINNED [delete]: packages/core/src/utils/migration-journal.test.ts declares 1 engine
double(s) whose delete() does not route through assertEngineDeleteDispatch (line 50). …
check-engine-double-contract: 1 problem(s).
exit 1
Red, named, actionable. So ablation A's green is a real blind spot, not a broken harness.
Reproduce:
git worktree add ../os-ablate origin/main && cd ../os-ablate && pnpm install
node scripts/check-engine-double-contract.mjs # 319 pinned
# remove the 4-line `async delete(...)` member from packages/core/src/utils/migration-journal.test.ts
node scripts/check-engine-double-contract.mjs; echo $? # 318 pinned, exit 0
git checkout -- packages/core/src/utils/migration-journal.test.ts
Why it matters, stated without inflating it
This is the #4868 family the script's own DISCOVERED note names — a check that runs, is green, and structurally cannot reach its subject — with the reach lost gradually rather than all at once. The gate's whole value is that a hand-rolled engine fake cannot silently diverge from the real one; a fake that silently stops being a fake is the same loss reached by a cheaper route.
⛔ What this card does NOT claim. No instance is demonstrated. I did not sweep for a double that lost a verb, and I am not asserting one exists — this is a property of the checker, measured by ablation, exactly as #8639 and #8553 were. The engine gate is also not the gate that would have caught either instance on #9165 (both were registry doubles; neither declares delete or update at all).
Not a duplicate of the neighbours, and why
#8553 (open, pm:on-hold) — a double restated as Object.assign(base, …) is not counted as new, so a new contract goes unpinned while the count stays put. Fixing it ("count by the value that reaches the subject") would not fix this: a double that drops the member leaves the population however you count values.#8639 (closed) — implOf dropped a call-expression initializer, so delete: vi.fn(async …) was never discovered. That was a double the scan never had. This is a double the scan had and lost.#8058 / #8194 (both closed, #8194 by PR #8423) — existence coherence of a double's members. Different property: theirs is a member that answers wrongly, this is a member that is absent.
Common root with #8553: the discovered population is a printed quantity rather than a checked one. They want different fixes.
Candidate shapes, none costed
- Ratchet the discovered count the way the DEBT ledger is ratcheted — a declared expected minimum per slice, shrink-fails. Cheapest, and reuses machinery the file already has, but it is a number to maintain and a legitimate test-file deletion reddens it.
- Enumerate the pinned set, not just its size, so a pin that disappears names itself. Bigger artifact, but the diff says which file lost which verb, which is the message an author can act on.
- Report the population delta as a distinct verdict rather than folding it into OK — the generalisation of
DISCOVERED from zero to delta, and the same idea #9657 and #9165 want in the sibling gates: "I could not recognise this shape" must not read the same as "clean".
Option 3 is a design act with a maintainer in it (same class as #8901), so it is deliberately not recommended here.
Related
#9165 (the card this was measured under — its H1 answer) · #8553 · #8639 · #8058 · #8194 · #9657 · #8845 · #8901
Filed unassigned by the dev seat working #9165, session
session_01XqDQYVU5smx29ts9pAErja. Measured onorigin/main@c07d6e8b9, two-direction ablation, reproduction below. Not a claim about the gate's own slice logic, which is correct on the population it holds.⛔ No
domain:*and no labels set — triage's field.The gap
scripts/check-engine-double-contract.mjsdiscovers its population by looking for an object literal / class / mock-constructor whose member nameddeleteorupdatehas an engine-shaped signature. Discovery therefore requires the member to exist. A double that stops declaring it simply leaves the population.Nothing watches for that. The four invariants in the header are
DISCOVERED,PINNED,RECONCILED,DECLARED:DISCOVEREDfires at zero — "Zero is not 'a clean repo', it is a broken scan". It does not fire at one fewer than yesterday.RECONCILEDreconciles in both directions, but only over baseline entries. A file in the DEBT ledger whose count drops is an error. A pinned file whose double disappears is invisible: pinned files are not enumerated anywhere durable, so the pinned count is a printed number, not a checked one.So the population is unratcheted in exactly the direction that loses coverage.
Measured — two-direction ablation on a PINNED double
Subject:
packages/core/src/utils/migration-journal.test.ts, whose fake declares both write verbs and routes each through the producer predicate.Baseline @
c07d6e8b9:Ablation A — delete the whole
async delete(...)member (the shape a partial double actually has: the method is absent, not wrong):Green. The delete-slice pin for that file evaporated and the only trace is a printed integer nobody compares.
Control — keep the member, delete only the
assertEngineDeleteDispatch(options);line:Red, named, actionable. So ablation A's green is a real blind spot, not a broken harness.
Reproduce:
Why it matters, stated without inflating it
This is the
#4868family the script's ownDISCOVEREDnote names — a check that runs, is green, and structurally cannot reach its subject — with the reach lost gradually rather than all at once. The gate's whole value is that a hand-rolled engine fake cannot silently diverge from the real one; a fake that silently stops being a fake is the same loss reached by a cheaper route.⛔ What this card does NOT claim. No instance is demonstrated. I did not sweep for a double that lost a verb, and I am not asserting one exists — this is a property of the checker, measured by ablation, exactly as
#8639and#8553were. The engine gate is also not the gate that would have caught either instance on#9165(both were registry doubles; neither declaresdeleteorupdateat all).Not a duplicate of the neighbours, and why
#8553(open,pm:on-hold) — a double restated asObject.assign(base, …)is not counted as new, so a new contract goes unpinned while the count stays put. Fixing it ("count by the value that reaches the subject") would not fix this: a double that drops the member leaves the population however you count values.#8639(closed) —implOfdropped a call-expression initializer, sodelete: vi.fn(async …)was never discovered. That was a double the scan never had. This is a double the scan had and lost.#8058/#8194(both closed,#8194by PR#8423) — existence coherence of a double's members. Different property: theirs is a member that answers wrongly, this is a member that is absent.Common root with
#8553: the discovered population is a printed quantity rather than a checked one. They want different fixes.Candidate shapes, none costed
DISCOVEREDfrom zero to delta, and the same idea#9657and#9165want in the sibling gates: "I could not recognise this shape" must not read the same as "clean".Option 3 is a design act with a maintainer in it (same class as
#8901), so it is deliberately not recommended here.Related
#9165(the card this was measured under — its H1 answer) ·#8553·#8639·#8058·#8194·#9657·#8845·#8901