From 62a4eb6ff6d0a2ac25685dad7245d0d157ddd817 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:21:50 +0000 Subject: [PATCH 1/3] fix(tooling): derive check-engine-double-contract's own corpus figures instead of writing them down (#9943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every figure this gate's prose carried about its own corpus was a constant that nothing regenerated: `310` CallExpression members with a callee table summing to it, `93` admitted / `217` rejected, `250 doubles this gate discovers`, `82 PINNED`, and two spellings "measured at ZERO". By 2026-08-19 the DISCOVERED and PINNED figures had drifted roughly 2x and 4x, and the comment carrying them ends `re-run that count before adding a name here` — it is an instruction to a future editor calibrating against numbers that are wrong. Refreshing the constants would reproduce the defect with newer values (#9803 / PR #9909), so this removes them and adds `--census`: the gate's own `implOf` / `unwrapCallImpl` / `isEngineVerbShape` walk, printed. #9943 had to hand-copy the criterion to re-scan and got 341 against the recorded 310 with no way to say which was wrong; that question no longer exists. Two results worth recording, both re-derived rather than assumed: - The REPOSITORY_ONLY_MEMBERS claim SURVIVED the corpus growth. `--census` runs the veto's own ablation (discovery with the veto off, diffed) and on 2026-08-20 the pair still moves EXACTLY 2 constructs, still both of #5945's `IScopedContext` witnesses under packages/spec, and still 0 pinned doubles. The conclusion held even though every number describing the corpus did not. - The two "measured at ZERO" spellings are STILL zero. That claim held up too. The census population itself is the finding. It was scoped to "every member whose initializer is a CallExpression" — the shape the recogniser already reads — so it could only ever find more of what it already understood, and both of its ZERO readings are CallExpression spellings for that reason. `--census` buckets by initializer KIND first, so a spelling this gate cannot read shows up as its own row rather than as silence. On this commit that row reads `defaulted ?? — read 0/2`, which is #9877's blind spot, visible for the first time. `--census` reaches no verdict and changes no exit code; `report()` does not call it. Self-tested on both sides of every bucket it splits. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- scripts/check-engine-double-contract.mjs | 385 +++++++++++++++++++++-- 1 file changed, 357 insertions(+), 28 deletions(-) diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index f1f247aec4..bef7c032c0 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -474,24 +474,13 @@ function testFiles() { * * ## How wide to unwrap, measured rather than assumed * - * Full census of the scanned corpus — every `delete`/`update` member whose - * initializer is a CallExpression, 310 of them, no truncation: - * - * 163 vi.fn() no argument at all - * 89 vi.fn(fn) ← the implementation - * 39 vi.fn().mockResolvedValue(value) a VALUE, not an implementation - * 9 rec('DELETE') local recorder factory, string arg - * 4 vi.fn().mockImplementation(fn) ← the implementation - * 3 record('DELETE') ditto - * 2 on('DELETE') ditto - * 1 vi.fn().mockRejectedValue(ERR()) a value, from a call - * - * So the criterion is STRUCTURAL and callee-agnostic: a call carrying EXACTLY - * ONE argument which is a function expression / arrow function. That admits the - * 93 that hold an implementation (`vi.fn(fn)` and, for free and correctly, the - * chained `.mockImplementation(fn)` — the arrow there IS what the double runs) - * and rejects all 217 that do not, without an allowlist of callee names that - * would go silently blind the day someone writes `vitest.fn` or a local wrapper. + * The criterion is STRUCTURAL and callee-agnostic: a call carrying EXACTLY ONE + * argument which is a function expression / arrow function. That admits every + * spelling holding an implementation (`vi.fn(fn)` and, for free and correctly, + * the chained `.mockImplementation(fn)` — the arrow there IS what the double + * runs) and rejects the ones that hold a VALUE or nothing at all, without an + * allowlist of callee names that would go silently blind the day someone writes + * `vitest.fn` or a local wrapper. * * Deliberately NOT widened, both measured at ZERO occurrences on this corpus: * @@ -501,7 +490,32 @@ function testFiles() { * - a function in the chained receiver (`vi.fn(fn).mockResolvedValue(v)`), * which would need this to recurse into `init.expression`. * - * Both are measurements, not opinions — re-run that census before widening, + * ## ⛔ The per-spelling counts are NOT written here any more (#9943) + * + * This header used to carry the census as CONSTANTS — `310` CallExpression + * members, a callee table summing to it, `93` admitted and `217` rejected. + * Nothing regenerated them, so nothing could catch them drifting, and by + * 2026-08-19 they had. Writing fresher constants in their place would reproduce + * the defect with newer values (#9803 / PR #9909), so they are gone and + * `--census` prints the same table from THIS FILE'S OWN `implOf` walk instead. + * `#9943` had to hand-copy the criterion to re-scan and got 341 against the + * recorded 310 with no way to say which was wrong; that question no longer + * exists. The two ZERO readings above are printed by `--census` too, and were + * still ZERO on 2026-08-20 — re-run it rather than trusting this sentence. + * + * ## What the old census could not have told you, and #9877 is the proof + * + * Its population was "every member whose initializer is a CallExpression" — + * the shape this function already reads. A census of what the matcher already + * matches cannot report the matcher's blind spots however carefully it is + * re-run, and both spellings it listed as ZERO are CallExpression spellings for + * that reason. #9877's defaulted initializer (`overrides.delete ?? vi.fn(fn)`) + * is a BinaryExpression and was never in the population at all, while hiding a + * live unguarded engine `delete` double. `--census` buckets by initializer KIND + * first for exactly this reason: a spelling the gate cannot read shows up as a + * row with a low read-rate, not as silence. + * + * Both remain measurements, not opinions — run `--census` before widening, * exactly as the REPOSITORY_ONLY_MEMBERS note above asks. * * Note which way the remaining error leans. A `vi.fn()` with no argument stays @@ -608,7 +622,7 @@ function takesObjectNameFirst(fn) { return /^(string|string \| number)$/.test(t); } -function isEngineVerbShape(fn, memberNames = new Set()) { +function isEngineVerbShape(fn, memberNames = new Set(), opts = {}) { const params = fn.parameters ?? []; // The DRIVER veto outranks everything, at every arity (#5480). // @@ -643,11 +657,22 @@ function isEngineVerbShape(fn, memberNames = new Set()) { // facade, so a member-free reading would be guessing on a corpus that // genuinely disagrees with itself. // - // Measured before it was written, over the 250 doubles this gate discovers: - // the pair moves exactly 2, both of them #5945's witnesses, and 0 of the 82 - // PINNED doubles. Widening it is a measurement, not an opinion — re-run that - // count before adding a name here. - if (!takesObjectNameFirst(fn)) { + // Measured, not assumed — and the measurement is the gate's own now. This + // comment used to state the corpus as constants (`250 doubles this gate + // discovers`, `82 PINNED`), in the present tense, so they read as standing + // properties rather than as the dated reading they were; nothing regenerated + // them and by 2026-08-19 they had drifted roughly 2x and 4x (#9943). + // `--census` prints this veto's effect directly — which constructs the pair + // moves, and how many of them are pinned — so the claim below is re-derivable + // instead of restated. + // + // Re-derived 2026-08-20 on the grown corpus: the pair still moves EXACTLY 2 + // constructs, still both of #5945's `IScopedContext` witnesses under + // packages/spec, and still 0 pinned doubles. The conclusion survived the + // growth even though every number describing the corpus did not — which is + // the argument for deriving them rather than writing them down. Widening this + // is a measurement, not an opinion: run `--census` before adding a name here. + if (!opts.skipRepositoryVeto && !takesObjectNameFirst(fn)) { for (const n of memberNames) if (REPOSITORY_ONLY_MEMBERS.has(n)) return false; } if (params.length < 2) { @@ -740,7 +765,7 @@ function localFunctions(sourceFile) { * producer on `delete` and hand-waving on `update` is exactly the asymmetry * #5393 hit and #5480 removed the excuse for. */ -function scanSource(fileName, text, slice = SLICES[0]) { +function scanSource(fileName, text, slice = SLICES[0], opts = {}) { const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); const pinnedNames = pinnedImportsOf(sf, slice); const locals = localFunctions(sf); @@ -772,7 +797,7 @@ function scanSource(fileName, text, slice = SLICES[0]) { // pieces of engine evidence for the update slice, `{ update }` is none. const siblings = [...names].filter((n) => ENGINE_SIBLINGS.has(n) && n !== slice.verb); if (siblings.length < 2) return; - if (!isEngineVerbShape(target, names)) return; + if (!isEngineVerbShape(target, names, opts)) return; const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1; doubles.push({ line, siblings: siblings.sort(), pinned: bodyIsPinned(target) }); }; @@ -963,6 +988,277 @@ function censusUnrecognised() { return { rows, scopedOut }; } +// ════════════════════════════════════════════════════════════════════════════ +// THE RECOGNIZER CENSUS (#9943) -- `--census`, the gate's own walk, printed +// ════════════════════════════════════════════════════════════════════════════ +// +// Every figure this file's prose used to carry about its own corpus was a +// CONSTANT: `310` CallExpression members, `250 doubles this gate discovers`, +// `82 PINNED`, and two spellings "measured at ZERO". Nothing regenerated any of +// them, so nothing could catch them drifting, and by 2026-08-19 (#9943) they +// had -- `250` had roughly doubled and `82` had roughly quadrupled. Refreshing +// the constants would reproduce the defect with newer values (#9803 / PR +// #9909); the answer is to stop writing them down. +// +// So this mode exists, and the prose above points at it instead of quoting +// itself. It is the GATE'S OWN walk -- `implOf`, `unwrapCallImpl`, +// `fnInitializer`, `isEngineVerbShape`, the same functions the verdict uses -- +// so its numbers cannot disagree with the criterion the way a hand-written +// re-scan can. #9943 recorded that a hand copy of the criterion read 341 where +// the comment said 310 and could not say which of the two was wrong. This mode +// removes the question. +// +// ⛔ It reaches no verdict and changes no exit code. `--census` is a separate +// invocation; `report()` does not call it. +// +// ## Why the POPULATION is by initializer kind, and this is the #9877 lesson +// +// The doc census was scoped to "every delete/update member whose initializer is +// a CallExpression" -- i.e. to the shape `unwrapCallImpl` already reads. A +// census of what the matcher already matches cannot report the matcher's blind +// spots however carefully it is re-run: #9877's defaulted initializer +// (`overrides.delete ?? vi.fn(fn)`) is a BinaryExpression, so it was never in +// that population at all, and neither was the shorthand or the bare local +// binding. Both spellings the old note listed as "measured at ZERO" are +// CallExpression spellings -- the census could only ever have found more of +// what it already understood. +// +// This one buckets EVERY member first, by initializer kind, and prints how many +// of each kind the recognizer can read. A spelling the gate is blind to shows +// up as a row with a low read-rate rather than as silence. + +/** The initializer bucket a member falls in, for the census rows. */ +function initializerKind(member) { + if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) return 'method body'; + if (ts.isShorthandPropertyAssignment(member)) return 'shorthand member'; + const init = (ts.isPropertyAssignment(member) || ts.isPropertyDeclaration(member)) + ? member.initializer : null; + if (!init) return 'no initializer (signature or abstract)'; + if (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) return 'function literal'; + if (ts.isCallExpression(init)) return 'CallExpression'; + if (ts.isBinaryExpression(init)) { + const op = init.operatorToken.kind; + if (op === ts.SyntaxKind.QuestionQuestionToken) return 'defaulted `??`'; + if (op === ts.SyntaxKind.BarBarToken) return 'defaulted `||`'; + return 'BinaryExpression (other operator)'; + } + if (ts.isConditionalExpression(init)) return 'conditional `? :`'; + if (ts.isIdentifier(init)) return 'identifier (a binding elsewhere)'; + if (ts.isPropertyAccessExpression(init)) return 'property access'; + if (ts.isAsExpression(init) || ts.isParenthesizedExpression(init)) return 'parenthesised / `as`'; + return `other (${ts.SyntaxKind[init.kind]})`; +} + +/** `vi.fn`, `vi.fn().mockResolvedValue`, `rec` -- the callee, whitespace-collapsed. */ +function calleeSpelling(init, sf) { + return init.expression.getText(sf).replace(/\s+/g, ''); +} + +/** How a CallExpression's argument list reads, for the callee buckets. */ +function argShape(init) { + const args = init.arguments ?? []; + if (args.length === 0) return '()'; + const fns = args.filter((a) => ts.isFunctionExpression(a) || ts.isArrowFunction(a)); + if (args.length === 1) return fns.length === 1 ? '(fn)' : '(value)'; + return fns.length > 0 ? `(${args.length} args, ${fns.length} fn)` : `(${args.length} args)`; +} + +/** Does a function literal sit in the RECEIVER chain, `vi.fn(fn).mockResolvedValue(v)`? */ +function fnInChainedReceiver(init) { + let recv = init.expression; + while (recv) { + if (ts.isCallExpression(recv) && unwrapCallImpl(recv)) return true; + if (ts.isPropertyAccessExpression(recv) || ts.isCallExpression(recv)) recv = recv.expression; + else return false; + } + return false; +} + +/** + * `Object.assign(base, { … })` sites that carry a scanned verb (#8553). + * + * The override literal is walked by `scanSource` like any other object literal, + * so what decides whether it is COUNTED is the two-engine-sibling test: an + * override literal typically carries only the one or two members it varies, and + * inherits `find`/`insert`/`registry` from the base it is spread over. Fewer + * than two siblings in the literal ITSELF, so `consider()` returns and the + * override is accounted for by whatever the base was pinned or baselined as -- + * which is precisely #8553's question, and this row is the measurement of how + * often the situation actually arises. + */ +function objectAssignSites(sf, verbs) { + const sites = []; + const visit = (n) => { + if ( + ts.isCallExpression(n) + && ts.isPropertyAccessExpression(n.expression) + && ts.isIdentifier(n.expression.expression) + && n.expression.expression.text === 'Object' + && n.expression.name.text === 'assign' + ) { + for (const arg of n.arguments.slice(1)) { + if (!ts.isObjectLiteralExpression(arg)) continue; + const names = new Set(); + for (const m of arg.properties) { + const nm = memberName(m); + if (nm) names.add(nm); + } + const declaresVerb = [...verbs].filter((v) => names.has(v)); + const siblings = [...names].filter((x) => ENGINE_SIBLINGS.has(x) && !declaresVerb.includes(x)); + // Two different shapes, and only the first can hide a double this gate + // would otherwise have judged: + // VERB the override literal restates `delete`/`update` itself. If it + // carries fewer than two engine siblings of its own it is not + // counted, and its implementation is neither pinned nor + // baselined in its own right. This is #8553's hole proper. + // BASE the override varies OTHER engine members (`find`, `insert`) + // and inherits the verb from the double it is spread over. + // Nothing about the verb changed, so the base's accounting is + // the right accounting -- the shape #8553 actually observed. + if (declaresVerb.length === 0 && siblings.length === 0) continue; + sites.push({ + line: sf.getLineAndCharacterOfPosition(n.getStart(sf)).line + 1, + shape: declaresVerb.length ? 'VERB' : 'BASE', + verbs: declaresVerb.sort(), + members: [...names].filter((x) => ENGINE_SIBLINGS.has(x)).sort(), + siblings: siblings.length, + counted: declaresVerb.length > 0 && siblings.length >= 2, + }); + } + } + ts.forEachChild(n, visit); + }; + visit(sf); + return sites; +} + +/** The whole census, computed from the recognizer's own functions. */ +function censusRecognizer() { + const files = testFiles(); + const byKind = new Map(); + const byCallee = new Map(); + const notWidened = { severalArgs: 0, chainedReceiver: 0, conditionalArms: 0 }; + const assignSites = []; + let members = 0; + + const bump = (map, key, read) => { + const row = map.get(key) ?? { total: 0, read: 0 }; + row.total += 1; + if (read) row.read += 1; + map.set(key, row); + }; + + for (const abs of files) { + const rel = relative(ROOT, abs).split(sep).join('/'); + const text = readFileSync(abs, 'utf8'); + if (!/\b(delete|update)\s*[(:,}]/.test(text)) continue; + const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + + const consider = (props) => { + for (const m of props) { + const nm = memberName(m); + if (!SCANNED_VERBS.has(nm)) continue; + members += 1; + const read = implOf(m) !== null; + bump(byKind, initializerKind(m), read); + const init = (ts.isPropertyAssignment(m) || ts.isPropertyDeclaration(m)) ? m.initializer : null; + if (init && ts.isCallExpression(init)) { + bump(byCallee, `${calleeSpelling(init, sf)}${argShape(init)}`, read); + const args = init.arguments ?? []; + if (args.length > 1 && args.some((a) => ts.isFunctionExpression(a) || ts.isArrowFunction(a))) { + notWidened.severalArgs += 1; + } + if (fnInChainedReceiver(init)) notWidened.chainedReceiver += 1; + } + if (init && ts.isConditionalExpression(init)) { + const arm = (x) => ts.isFunctionExpression(x) || ts.isArrowFunction(x) || unwrapCallImpl(x); + if (arm(init.whenTrue) || arm(init.whenFalse)) notWidened.conditionalArms += 1; + } + } + }; + + const visit = (n) => { + if (ts.isObjectLiteralExpression(n)) consider(n.properties); + else if (ts.isClassDeclaration(n) || ts.isClassExpression(n)) consider(n.members); + ts.forEachChild(n, visit); + }; + visit(sf); + + for (const site of objectAssignSites(sf, SCANNED_VERBS)) assignSites.push({ file: rel, ...site }); + } + + // The REPOSITORY_ONLY_MEMBERS ablation: run discovery again with that veto + // off and diff. The prose used to assert "the pair moves exactly 2, and 0 of + // the PINNED doubles" as a constant; this recomputes it on today's corpus, + // which is the only form of that claim worth having. + const vetoed = []; + for (const slice of SLICES) { + for (const abs of files) { + const rel = relative(ROOT, abs).split(sep).join('/'); + const text = readFileSync(abs, 'utf8'); + if (!new RegExp(`\\b${slice.verb}\\s*[(:]`).test(text)) continue; + const withVeto = scanSource(abs, text, slice); + const without = scanSource(abs, text, slice, { skipRepositoryVeto: true }); + if (without.length === withVeto.length) continue; + const kept = new Set(withVeto.map((d) => d.line)); + for (const d of without) { + if (!kept.has(d.line)) vetoed.push({ verb: slice.verb, file: rel, line: d.line, pinned: d.pinned }); + } + } + } + + return { files: files.length, members, byKind, byCallee, notWidened, assignSites, vetoed }; +} + +function censusReport() { + const c = censusRecognizer(); + const rows = (map) => [...map].sort((a, b) => b[1].total - a[1].total); + const pad = (n) => String(n).padStart(5); + + console.log(''); + console.log( + `CENSUS [engine-double-contract]: ${c.members} member(s) named ${[...SCANNED_VERBS].join('/')} on an ` + + `object literal or class body, across ${c.files} scanned test file(s) in ${SCAN_ROOTS.join(', ')}. ` + + 'Computed by this gate\'s own `implOf`, so it cannot disagree with the verdict. ' + + 'This mode reaches no verdict and never fails a run.', + ); + + console.log('\n BY INITIALIZER KIND -- `read` is how many of them `implOf` can reach:'); + for (const [kind, r] of rows(c.byKind)) { + console.log(` ${pad(r.total)} ${kind.padEnd(38)} read ${r.read}/${r.total}`); + } + + console.log('\n CallExpression initializers, by callee spelling:'); + for (const [callee, r] of rows(c.byCallee)) { + console.log(` ${pad(r.total)} ${callee.padEnd(38)} read ${r.read}/${r.total}`); + } + + console.log('\n Spellings this criterion deliberately does NOT read:'); + console.log(` ${pad(c.notWidened.severalArgs)} a function among SEVERAL arguments traced('delete', fn)`); + console.log(` ${pad(c.notWidened.chainedReceiver)} a function in the chained receiver vi.fn(fn).mockResolvedValue(v)`); + console.log(` ${pad(c.notWidened.conditionalArms)} a conditional's arms cond ? a : b`); + + const verbSites = c.assignSites.filter((x) => x.shape === 'VERB'); + const baseSites = c.assignSites.filter((x) => x.shape === 'BASE'); + console.log( + `\n Object.assign over an engine double (#8553): ${verbSites.length} override literal(s) restate a ` + + `scanned verb, ${baseSites.length} vary other engine members and inherit the verb:`, + ); + for (const s of c.assignSites) { + console.log( + ` ${pad(s.siblings)} [${s.shape}] ${s.file}:${s.line} {${s.members.join(', ')}} -- ` + + `${s.counted ? 'COUNTED in its own right' : 'accounted for by its base'}`, + ); + } + + console.log( + `\n REPOSITORY_ONLY_MEMBERS veto moves ${c.vetoed.length} construct(s); ` + + `${c.vetoed.filter((v) => v.pinned).length} of them pinned:`, + ); + for (const v of c.vetoed) console.log(` [${v.verb}] ${v.file}:${v.line}${v.pinned ? ' (pinned)' : ''}`); + console.log(''); +} + // ════════════════════════════════════════════════════════════════════════════ // THE CONSUMER SEAMS (#8194, from #8058's audited sweep) // ════════════════════════════════════════════════════════════════════════════ @@ -3024,6 +3320,36 @@ const engine: any = { registry: {}, insert: async (o: string, d: any) => d, find censusSource('c.test.ts', visSrc, D).unrecognised.length === 1 && scanSource('c.test.ts', visSrc, D).length === 0); + // ── The RECOGNIZER CENSUS (#9943) ───────────────────────────────────────── + // + // `--census` exists so this file stops writing its own corpus down as + // constants that nothing regenerates. It only earns that if its buckets are + // proved on both sides -- a census whose kinds silently collapsed into one + // another would print a confident table and hide the same blind spot the + // constants did. + const kindsOf = (src) => { + const sf = ts.createSourceFile('k.test.ts', src, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + const out = []; + const visit = (n) => { + if (ts.isObjectLiteralExpression(n) || ts.isClassDeclaration(n)) { + for (const m of (ts.isObjectLiteralExpression(n) ? n.properties : n.members)) { + if (SCANNED_VERBS.has(memberName(m))) out.push(initializerKind(m)); + } + } + ts.forEachChild(n, visit); + }; + visit(sf); + return out.join(); + }; + expect('#9943 — the census gives a DEFAULTED initializer a bucket of its own, which is the row ' + + 'the CallExpression-scoped census could not have had', + kindsOf('const e = { delete: o.delete ?? vi.fn(f) };') === 'defaulted `??`'); + expect('#9943 — …and a plain call still reads as CallExpression (the split discriminates)', + kindsOf('const e = { delete: vi.fn(f) };') === 'CallExpression'); + expect('#9943 — a shorthand member is its own kind, so it cannot hide inside another bucket', + kindsOf('const e = { update };') === 'shorthand member'); + expect('#9943 — a method body is its own kind too', kindsOf('const e = { async update(o, d) {} };') === 'method body'); + if (failures.length) { for (const f of failures) console.error(` x self-test: ${f}`); console.error(`\ncheck-engine-double-contract --self-test: ${failures.length} failure(s).\n`); @@ -3049,10 +3375,13 @@ const engine: any = { registry: {}, insert: async (o: string, d: any) => d, find + 'be reached (a defaulted mock, a local binding, a shorthand), SCOPES OUT the two ' + 'spellings that carry no implementation at all rather than reporting them as noise, ' + 'ignores constructs with too few engine siblings to be in scope, and cannot move one ' - + 'double into or out of the population it counts.', + + 'double into or out of the population it counts; and buckets ' + + 'the RECOGNIZER CENSUS (#9943) by initializer kind so a spelling this gate cannot read ' + + 'shows up as its own row rather than as silence.', ); } if (process.argv.includes('--self-test')) selfTest(); else if (process.argv.includes('--write')) writeLedger(); +else if (process.argv.includes('--census')) censusReport(); else report(); From 36c2fcd0eec41d3b5848cb6de1a909dc0133c5d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:22:53 +0000 Subject: [PATCH 2/3] fix(tooling): read a DEFAULTED mock initializer, which was hiding a live unguarded engine delete double (#9877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `delete: overrides.delete ?? vi.fn(async (o, arg) => …)` is a BinaryExpression. `fnInitializer` knew a function literal and a mock constructor and nothing else, so `implOf` answered null, `consider()` returned before `isEngineVerbShape` was ever asked, and the construct left the population with no verdict recorded — not pinned, not baselined, not exempt. Absent, which reads to every consumer of this script's output as clean. `packages/rest/src/rest-batch-endpoint.test.ts` was that case, and it is not cosmetic: its fake ObjectQL declares an engine `delete` this suite never drives, which is exactly the shape #4434 shipped a dead REST route with. Measured on this branch, both directions: widening only, no pin PINNED [delete] and PINNED [update] on that file, exit 1 widening + the pin OK, exit 0; UNRECOGNISED 23 -> 21 ⛔ The shrink-only baseline is NOT touched: it stays at 66 delete / 100 update entries across all three commits. Both newly-visible doubles are PINNED at the source — the fake now opens with `assertEngineUpdateDispatch(data, options)` and `assertEngineDeleteDispatch(options)` — so the ratchet tightens (329 -> 331 pinned, ledger 318 -> 320 rows) rather than being raised. `@objectstack/rest` passes 2134/2134 with the predicates in place. The widening stops at `??` / `||`, and that is a narrowing, not an oversight. `a ?? b` reaches the subject as one arm or the other and BOTH are read; a conditional's arms are selected by a test this gate cannot evaluate, so it is deliberately left out (`--census` reports it, ZERO on 2026-08-20). The remaining 21 UNRECOGNISED constructs are the local-binding and shorthand spellings, which need `localFunctions`-style resolution and are reported, not silently widened. The census self-test that pinned this spelling as UNRECOGNISED is retargeted rather than deleted: the same source is now asserted absent from the census AND present in the population, so the two walks are proved to agree. Its old limb keeps a witness (`traced('DELETE', fn)`) or the branch would rot unreachable. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- packages/rest/src/rest-batch-endpoint.test.ts | 17 ++- scripts/check-engine-double-contract.mjs | 117 +++++++++++++++++- scripts/engine-double-contract.pinned.json | 10 ++ 3 files changed, 139 insertions(+), 5 deletions(-) diff --git a/packages/rest/src/rest-batch-endpoint.test.ts b/packages/rest/src/rest-batch-endpoint.test.ts index 5d4e650b32..560ab6bd06 100644 --- a/packages/rest/src/rest-batch-endpoint.test.ts +++ b/packages/rest/src/rest-batch-endpoint.test.ts @@ -7,6 +7,7 @@ // per-op API-exposure enforcement, $ref resolution, and error surfacing. import { describe, it, expect, vi } from 'vitest'; +import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core'; import { RestServer } from './rest-server'; // ── helpers ────────────────────────────────────────────────────────────────── @@ -36,8 +37,20 @@ function makeQl(overrides: Partial> const ql: 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 })), + // Both defaults open with the producer's own dispatch predicate rather than + // a hand-mirrored `if` (#4434 / #5480). Until #9877 the gate could not read + // either of them: a `??` default is a BinaryExpression, so `implOf` answered + // null and these two doubles sat in NEITHER the pinned population nor the + // baseline — absent, which reads as clean. The delete double in particular + // is never driven by this suite, which is exactly the shape #4434 shipped. + update: overrides.update ?? vi.fn(async (_object: string, data: any, options?: any) => { + assertEngineUpdateDispatch(data, options); + return { ...data }; + }), + delete: overrides.delete ?? vi.fn(async (_object: string, options: any) => { + assertEngineDeleteDispatch(options); + return { success: true, id: options?.where?.id }; + }), }; return ql; } diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index bef7c032c0..4e7f9990b4 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -533,6 +533,53 @@ function unwrapCallImpl(init) { return null; } +/** + * The implementation behind a DEFAULTED initializer, or null (#9877). + * + * `delete: overrides.delete ?? vi.fn(async (o, arg) => …)` is a + * BinaryExpression, which is neither of the two shapes `fnInitializer` knew, so + * `implOf` answered null, `consider()` returned before `isEngineVerbShape` was + * ever asked, and the double left the population with no verdict recorded -- + * not baselined, not exempt, not pinned. Absent. `rest-batch-endpoint.test.ts` + * held a live unguarded engine `delete` double behind exactly this spelling and + * the gate reddens on it the moment it can read it, so this is not cosmetic. + * + * ## Why the census above could not have found this, which is the real lesson + * + * That census enumerates "every delete/update member whose initializer is a + * CallExpression". A defaulted initializer is a BinaryExpression, so it was + * never in the census POPULATION -- the census is scoped to the shape the + * recognizer already reads, and a census of what the matcher already matches + * cannot report the matcher's blind spots however carefully it is re-run. The + * two spellings it lists as "measured at ZERO" are both CallExpression + * spellings for the same reason. `--census` fixes the population, not the + * arithmetic: it now buckets EVERY initializer by kind first, so a spelling + * outside the recognizer shows up as its own row rather than as silence. + * + * ## Which side, and why both are read + * + * `a ?? b` and `a || b` reach the subject as `a` when `a` is present and as `b` + * otherwise, so BOTH sides are implementations the double may run. The default + * is read first because it is the one this file itself authored and the only + * one that is statically knowable -- the left side of a `??` default is by + * construction the maybe-absent, caller-supplied one, and where it IS statically + * a function (`f ?? g`, both literals) reading the left as a fallback keeps the + * spelling from going dark again. + * + * Deliberately NOT widened, and this one is a judgement rather than a count: + * `cond ? a : b`. A conditional's arms are selected by a test this gate cannot + * evaluate, so "the implementation" is not a property of the initializer at all + * -- unlike `??`/`||`, where the fallback is unconditionally reachable. Measured + * at ZERO occurrences on this corpus (`--census`, ConditionalExpression row); + * re-run that before widening it, not the number in this sentence. + */ +function unwrapDefaultedImpl(init) { + if (!ts.isBinaryExpression(init)) return null; + const op = init.operatorToken.kind; + if (op !== ts.SyntaxKind.QuestionQuestionToken && op !== ts.SyntaxKind.BarBarToken) return null; + return fnInitializer(init.right) ?? fnInitializer(init.left); +} + /** * One initializer reading, shared by BOTH initializer spellings below. * @@ -545,6 +592,8 @@ function unwrapCallImpl(init) { function fnInitializer(init) { if (!init) return null; if (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) return init; + const defaulted = unwrapDefaultedImpl(init); + if (defaulted) return defaulted; return unwrapCallImpl(init); } @@ -2518,6 +2567,49 @@ const engine = { expect('a mock resolving to a VALUE is not an implementation', scanSource('v.test.ts', viFake('vi.fn().mockResolvedValue(true)')).length === 0); + // ── The DEFAULTED spelling (#9877), driven arm by arm. + // + // `delete: overrides.delete ?? vi.fn(fn)` is a BinaryExpression, so `implOf` + // answered null and the double was in NEITHER half of the ledger — absent, + // which reads as clean, while `rest-batch-endpoint.test.ts` held a live + // unguarded engine delete behind it. Same discipline as the #8639 block + // above: one fixture per arm, because an unfixtured arm has already been + // measured in this file to be worth nothing. + d = scanSource('v.test.ts', viFake('overrides.delete ?? vi.fn(async (o: string, opts?: any) => ({ ok: true }))')); + expect('#9877 — a `??`-defaulted engine delete is in scope', d.length === 1 && d[0].pinned === false); + + d = scanSource('v.test.ts', viFake('overrides.delete || vi.fn(async (o: string, opts?: any) => ({ ok: true }))')); + expect('#9877 — the `||` spelling of the same default is in scope too', d.length === 1); + + d = scanSource('v.test.ts', IMPORT + viFake( + 'overrides.delete ?? vi.fn(async (o: string, opts?: any) => { assertEngineDeleteDispatch(opts); return 1; })')); + expect('#9877 — a defaulted delete whose default calls the predicate is PINNED', + d.length === 1 && d[0].pinned === true); + + // The LEFT arm. `a ?? b` runs `a` whenever `a` is present, so a function on + // the left is an implementation this double may run and reading only the + // default would go blind to it. Without this fixture that fallback is + // untested and could be deleted with the self-test still green. + d = scanSource('v.test.ts', viFake('vi.fn(async (o: string, opts?: any) => ({ ok: true })) ?? overrides.delete')); + expect('#9877 — a function on the LEFT of the default is read as well', d.length === 1); + + // Both arms unreadable: still absent, and correctly so — there is no function + // anywhere in the initializer for `isEngineVerbShape` to judge. + expect('#9877 — a default with no function on either side is still not an implementation', + scanSource('v.test.ts', viFake('overrides.delete ?? base.delete')).length === 0); + + // The DRIVER veto must still decide at this spelling, or the widening admits + // driver doubles the shape test exists to keep out (#5480's measured harm was + // 19 in one file). + expect('#9877 — a DRIVER double at the defaulted spelling is still vetoed', + scanSource('v.test.ts', viFake('overrides.delete ?? vi.fn(async (o: string, id: string) => true)')).length === 0); + + // Deliberately NOT widened, and this is the negative that says so: a + // conditional's arms are selected by a test this gate cannot evaluate, unlike + // a `??` fallback which is unconditionally reachable. + expect('#9877 — a conditional `? :` initializer is deliberately NOT read', + scanSource('v.test.ts', viFake('flag ? vi.fn(async (o: string, opts?: any) => 1) : vi.fn(async (o: string, opts?: any) => 2)')).length === 0); + // The CLASS-FIELD spelling of the same thing — `implOf`'s PropertyDeclaration // branch. Measured at ZERO occurrences in the corpus the fix landed against, // so this fixture is the only evidence that branch works at all; without it @@ -3272,7 +3364,23 @@ function makeEngine() { expect('#9747 — a construct the gate CAN read is not in the census (it is in the population)', c.unrecognised.length === 0 && c.scopedOut.length === 0); - c = censusSource('c.test.ts', censusFake('delete: overrides.delete ?? vi.fn(async (o: string, opts?: any) => true),'), D); + // ⛔ This fixture used to be the census's "carries a function the unwrap + // declined" witness. #9877 taught `fnInitializer` to descend a `??`/`||` + // default, so the SAME source is now READ -- and the census must follow the + // recognizer rather than keep a row the population already owns. Asserted + // from both sides: absent from the census, present in the population. + const defaulted = censusFake('delete: overrides.delete ?? vi.fn(async (o: string, opts?: any) => true),'); + c = censusSource('c.test.ts', defaulted, D); + expect('#9877 — a DEFAULTED initializer left the census when the recognizer learned to read it', + c.unrecognised.length === 0 && c.scopedOut.length === 0); + expect('#9877 — …and landed in the population instead (the two walks agree)', + scanSource('c.test.ts', defaulted, D).length === 1); + + // The limb the fixture above used to drive still needs a witness, or the + // "declined to unwrap" branch becomes unreachable and rots silently. A + // function among SEVERAL arguments is the spelling `unwrapCallImpl` + // deliberately does not read (`--census` reports it, ZERO on 2026-08-20). + c = censusSource('c.test.ts', censusFake("delete: traced('DELETE', async (o: string, opts?: any) => true),"), D); expect('#9747 — an initializer carrying a function the unwrap declined is UNRECOGNISED', c.unrecognised.length === 1 && c.unrecognised[0].why.includes('declined to unwrap')); @@ -3372,10 +3480,13 @@ const engine: any = { registry: {}, insert: async (o: string, d: any) => d, find + 'separates the shared envelope from a local mint, discounts a refusal that lands after ' + 'the receipt, and REPORTS the seam that answers without refusing at all; and, on the ' + 'UNRECOGNISED CENSUS (#9747), counts a construct whose implementation exists but cannot ' - + 'be reached (a defaulted mock, a local binding, a shorthand), SCOPES OUT the two ' + + 'be reached (a function among several arguments, a local binding, a shorthand), SCOPES ' + + 'OUT the two ' + 'spellings that carry no implementation at all rather than reporting them as noise, ' + 'ignores constructs with too few engine siblings to be in scope, and cannot move one ' - + 'double into or out of the population it counts; and buckets ' + + 'double into or out of the population it counts; and reads a DEFAULTED initializer ' + + '(#9877) on both arms of `??` and `||`, pinned and unpinned alike, while still vetoing a ' + + 'driver there and still refusing a conditional; and buckets ' + 'the RECOGNIZER CENSUS (#9943) by initializer kind so a spelling this gate cannot read ' + 'shows up as its own row rather than as silence.', ); diff --git a/scripts/engine-double-contract.pinned.json b/scripts/engine-double-contract.pinned.json index 0232e8a037..30f010c403 100644 --- a/scripts/engine-double-contract.pinned.json +++ b/scripts/engine-double-contract.pinned.json @@ -1421,6 +1421,16 @@ "verb": "update", "pinned": 1 }, + { + "file": "packages/rest/src/rest-batch-endpoint.test.ts", + "verb": "delete", + "pinned": 1 + }, + { + "file": "packages/rest/src/rest-batch-endpoint.test.ts", + "verb": "update", + "pinned": 1 + }, { "file": "packages/rest/src/rest-server-meta-read-org-scope.test.ts", "verb": "delete", From ebb487b6026491d485b342ddf8fbcd807045abbc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:23:26 +0000 Subject: [PATCH 3/3] fix(tooling): name the inheritance rule in both ratchet remedies, so Object.assign is not an invisible third option (#8553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #8537's CI history is an unusually clean natural experiment: the same test file went red -> green on this gate by changing only the SPELLING of a double. A fresh literal declaring the engine verbs was counted; `Object.assign(makeQl(), { … })` over the file's existing double was not, and the count returned to its baseline with the double still there. The mechanism is the two-engine-sibling test. This gate counts DECLARATION SITES, so an override literal — which normally carries only the members it varies — has too few siblings of its own, `consider()` returns, and the construct is accounted for by whatever the base was pinned or baselined as. That is CORRECT while the override leaves the scanned verb alone, and #8537's use was legitimate and documented in the test. What made it a card is that the gate's own message offers exactly two ways out ("pin the new one" / "raise it, do not") while a third, cheaper, invisible route exists. A ratchet does not erode by someone defeating it; it erodes when the cheapest route out is the unseen one. Settled by measurement rather than by preference. `--census` (#9943, previous commit) now reports both `Object.assign` shapes, and on 2026-08-20 the scanned corpus holds: 0 override literals that RESTATE `delete`/`update` <- the hole proper 6 that vary other engine members and inherit the verb <- inheritance, correct So counting by VALUE instead of by site would move zero constructs today, at the price of a resolution step over an empty population — and every construct it might later admit would arrive UNPINNED against a shrink-only, maintainer-only baseline. The defensible act is the other one the card offers: say the rule. Both ratchet remedies now carry it, because an author meets whichever one their file's baseline state produces and a clause on only one of them is a coin flip. Guarded the same way #8435 guarded its authority marker: a detector rather than a string compare, self-tested on both remedies plus a negative fixture that REJECTS the two-option framing this card filed against — so a rewording that dropped the clause fails here instead of passing vacuously. And the day a VERB-shaped override does land, `--census` names it and this becomes a priced act rather than a hypothesis. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- scripts/check-engine-double-contract.mjs | 101 ++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index 4e7f9990b4..398a7444d9 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -2104,6 +2104,62 @@ function ratchetRemedyCarriesAuthority(message) { return message.includes(RATCHET_AUTHORITY_MARKER); } +/** + * The INHERITANCE rule, spelled out in both ratchet remedies (#8553). + * + * The gate counts DECLARATION SITES: an object literal (or class body) that + * declares the verb alongside at least two engine siblings. Restating a double + * as `Object.assign(base, { … })` or `{ ...base, … }` therefore hands this gate + * the OVERRIDE literal, which normally carries only the members it varies — + * fewer than two engine siblings — so `consider()` returns and the construct is + * accounted for by the base it was spread over. + * + * That is CORRECT wherever the override leaves this verb alone, and #8553's own + * observed instance is that shape: the base is counted, the override varies + * `find`/`insert`, nothing about the verb changed. What made it worth a card is + * that the two-option framing below ("pin it" / "raise the baseline, do not") + * reads as exhaustive, so an author who reaches for the override spelling to + * get out of a red gate cannot tell from the message whether they satisfied the + * rule or side-stepped it — and it is cheaper than either sanctioned path. A + * ratchet does not erode by someone defeating it; it erodes when the cheapest + * route out happens to be the invisible one. + * + * ⛔ Named rather than closed by counting VALUES instead of sites, and the + * reason is a measurement rather than a preference: `--census` reports both + * `Object.assign` shapes, and on 2026-08-20 the corpus held ZERO override + * literals restating `delete`/`update` and six that vary other engine members + * and inherit the verb. Counting by value would move no construct today, at the + * price of a resolution step over an empty population — while every construct + * it might later admit arrives UNPINNED against a shrink-only, maintainer-only + * baseline. So the hole is documented where an author meets it and MEASURED + * where a maintainer can watch it: the day a VERB-shaped override lands, + * `--census` names it and this becomes a priced act rather than a hypothesis. + */ +const INHERITANCE_MARKER = 'A THIRD spelling exists and is NOT a third option'; + +/** + * Same convention as `ratchetRemedyCarriesAuthority` above and for the same + * reason: a detector, so the self-test can prove the assertion still reaches + * its subject instead of passing vacuously after a rewording. + * + * @param {string} message + * @returns {boolean} + */ +function remedyNamesInheritance(message) { + return message.includes(INHERITANCE_MARKER) && message.includes('Object.assign'); +} + +const INHERITANCE_NOTE = (verb) => ( + ` ${INHERITANCE_MARKER}: restating this double as ` + + '`Object.assign(base, { … })` or `{ ...base, … }` over an already-counted double leaves this ' + + `gate reading the OVERRIDE literal, which usually declares too few engine siblings to be ` + + `counted in its own right — so it inherits the base's accounting. That is correct while the ` + + `override leaves ${verb}() alone. If your override RESTATES ${verb}() with a different ` + + `contract, inheriting is a hole and not a pass: pin it there, or keep it in the literal this ` + + 'ledger already names. `node scripts/check-engine-double-contract.mjs --census` counts both ' + + 'shapes.' +); + /** * PINNED's text, named and pure so the self-test can assert on the exact string * the author reads. Extracted from the audit loop by #8435 for that reason -- @@ -2130,6 +2186,7 @@ function pinnedMessage(slice, file, unguarded) { + `${BASELINE_REL} saying why not — with ` + `"verb": ${JSON.stringify(slice.verb)}. That baseline is shrink-only, so an entry weakens a ` + 'ratchet and needs a maintainer to agree first — do not take this path to get CI green.' + + INHERITANCE_NOTE(slice.verb) ); } @@ -2209,7 +2266,8 @@ function audit() { errors.push( `PINNED [${slice.verb}]: ${file} now has ${unguarded.length} unguarded engine double(s), ` + `baseline records ${entry.unguarded}. The baseline is shrink-only — pin the new one ` - + 'rather than raising it.', + + 'rather than raising it.' + + INHERITANCE_NOTE(slice.verb), ); } else if (unguarded.length < entry.unguarded) { errors.push( @@ -3215,6 +3273,27 @@ class Svc { expect(`#8435 — PINNED marks the baseline path ${RATCHET_AUTHORITY_MARKER} (it is shrink-only, so ` + 'adding an entry is a maintainer action, not the author\'s second option)', ratchetRemedyCarriesAuthority(pinned)); + // ── The INHERITANCE clause (#8553) ───────────────────────────────────────── + // + // The card's finding was that the two-option framing ("pin it" / "raise the + // baseline, do not") reads as exhaustive while a third, cheaper, INVISIBLE + // route exists — restate the double as an override of a counted one. Three + // assertions on the same non-overlapping plan as #8435 above: the clause is + // on BOTH ratchet remedies (an author meets whichever one their file's + // baseline state produces, so a clause on only one of them is a coin flip), + // and the detector discriminates. + expect('#8553 — the PINNED remedy names the inheritance rule, so an author who reaches for ' + + '`Object.assign` can tell whether they satisfied the rule or side-stepped it', + remedyNamesInheritance(pinned)); + const shrinkOnlyRemedy = `PINNED [update]: f.test.ts now has 2 unguarded engine double(s), ` + + 'baseline records 1. The baseline is shrink-only — pin the new one rather than raising it.' + + INHERITANCE_NOTE('update'); + expect('#8553 — …and so does the SHRINK-ONLY remedy, which is the one #8537 actually hit', + remedyNamesInheritance(shrinkOnlyRemedy)); + expect('#8553 — remedyNamesInheritance() REJECTS the two-option framing this card filed against ' + + '(proves the detector discriminates rather than approving everything)', + !remedyNamesInheritance('The baseline is shrink-only — pin the new one rather than raising it.')); + const unmarkedOffer = `PINNED: add a MEASURED entry to ${BASELINE_REL} saying why not.`; expect('#8435 — the synthetic unmarked-offer fixture is still recognised as an offer', RATCHET_EXPANSION_OFFER.test(unmarkedOffer)); @@ -3458,6 +3537,23 @@ const engine: any = { registry: {}, insert: async (o: string, d: any) => d, find kindsOf('const e = { update };') === 'shorthand member'); expect('#9943 — a method body is its own kind too', kindsOf('const e = { async update(o, d) {} };') === 'method body'); + const assignSites = (src) => objectAssignSites( + ts.createSourceFile('a.test.ts', src, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX), SCANNED_VERBS, + ); + let sites = assignSites('const ql = Object.assign(makeQl(), { async find() { return []; }, async insert() { return null; } });'); + expect('#8553 — an Object.assign override varying OTHER engine members reads as BASE-accounted ' + + '(the shape #8537 actually used, and inheriting is correct there)', + sites.length === 1 && sites[0].shape === 'BASE' && sites[0].counted === false); + sites = assignSites('const ql = Object.assign(makeQl(), { async delete(o: string, opts?: any) { return true; } });'); + expect('#8553 — an override that RESTATES the verb with too few siblings of its own is the hole, ' + + 'and the census names it rather than leaving it silent', + sites.length === 1 && sites[0].shape === 'VERB' && sites[0].counted === false); + sites = assignSites('const ql = Object.assign(makeQl(), { async delete(o: string, opts?: any) { return true; }, ' + + 'async find() { return []; }, async insert() { return null; } });'); + expect('#8553 — …and an override carrying two engine siblings of its own IS counted in its own ' + + 'right, so the census does not report a double the population already holds', + sites.length === 1 && sites[0].shape === 'VERB' && sites[0].counted === true); + if (failures.length) { for (const f of failures) console.error(` x self-test: ${f}`); console.error(`\ncheck-engine-double-contract --self-test: ${failures.length} failure(s).\n`); @@ -3486,7 +3582,8 @@ const engine: any = { registry: {}, insert: async (o: string, d: any) => d, find + 'ignores constructs with too few engine siblings to be in scope, and cannot move one ' + 'double into or out of the population it counts; and reads a DEFAULTED initializer ' + '(#9877) on both arms of `??` and `||`, pinned and unpinned alike, while still vetoing a ' - + 'driver there and still refusing a conditional; and buckets ' + + 'driver there and still refusing a conditional; carries the INHERITANCE rule (#8553) on ' + + 'BOTH ratchet remedies with a detector that rejects the two-option framing; and buckets ' + 'the RECOGNIZER CENSUS (#9943) by initializer kind so a spelling this gate cannot read ' + 'shows up as its own row rather than as silence.', );