From 2a4cf2fcc34d9bf0016134c360689908135e2eb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 05:55:39 +0000 Subject: [PATCH 1/2] feat(gate): give three pilot gates an UNRECOGNISED verdict distinct from clean Fixes #9747 Visibility only, per the maintainer ruling of 2026-08-18: printed and counted, never a failure, no new merge-blocking context. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- ...check-durability-degradation-log-level.mjs | 136 ++++++++- scripts/check-engine-double-contract.mjs | 272 +++++++++++++++++- 2 files changed, 405 insertions(+), 3 deletions(-) diff --git a/scripts/check-durability-degradation-log-level.mjs b/scripts/check-durability-degradation-log-level.mjs index cf760604c2..1762287287 100644 --- a/scripts/check-durability-degradation-log-level.mjs +++ b/scripts/check-durability-degradation-log-level.mjs @@ -112,6 +112,59 @@ * file) but share no vocabulary, no baseline and no verdict: a seam red under * one is untouched by the other. * + * ## THE THIRD VERDICT (#9747, maintainer ruling of 2026-08-18) + * + * Both rules above end in one of two states: a finding, or `clean`. #9747 + * measured nine instances in this repo where a gate printed the second while + * meaning something else entirely — "I saw nothing I understood" — and two of + * them (#8897, #9657) are this file's own. The ruling accepted a THIRD state: + * + * > N constructs in the scan roots matched no rule in this gate's vocabulary + * > — printed and counted, visible in round reports; NOT a failure, and NO + * > new merge-blocking context. + * + * This file is one of the three pilot gates. What it now prints, on every run: + * + * UNRECOGNISED [durability-degradation-log-level]: N of M discovered seam(s) + * answer their catch through a call this checker could not read as a log + * + * The log-level rule can answer this honestly because its LOG-CALLEE + * vocabulary is a closed, structural thing — `resolveLogCallee` already + * returns `unreadable` for a call it cannot read, and #9657 is precisely the + * damage that fact does when it stays private. Before this, `unreadable` was + * collected only to choose a FINDING's verdict, so a seam that was correctly + * green and also carried something unreadable reported nothing at all. + * + * ## Why this is not `exit 2`, when the in-tree prior art is + * + * Four places already spell a third state and every one of them exits + * non-zero: `check-where-matcher-conformance` (missing baseline => `exit 2`, + * explicitly distinct from a finding's `exit 1`), `check-published-readme- + * exports` (hard refusal — "cannot tell debt from a new defect"), + * `check-governed-merges`' header ("non-zero exits classify the ENVIRONMENT, + * not the tree"), and #9700's drift guard. All four are the gate REFUSING TO + * RUN: the environment is broken and no verdict about the tree exists. + * + * This verdict is the opposite — the run completed and the count is an + * observation about it. `exit 2` would make it a failing CI job, which the + * ruling forbids in as many words. So the convention is matched where the + * convention is about SEMANTICS (a named third state, printed rather than + * inferred, distinct from both `clean` and a finding) and deliberately not + * where it is about the exit code. The line carries a stable, greppable + * prefix — `UNRECOGNISED []:` — so a round report can pick it up + * without any new merge-blocking context existing anywhere. + * + * ## Why the READ-SEAM rule prints NOT APPLICABLE instead of a number + * + * Measured, not assumed — see the note at that rule's own verdict. Its + * vocabulary is callee NAMES, and "a storage read I do not know about" cannot + * be counted without the spelling heuristic the vocabulary note refuses. A + * census over its three scan roots returns 25 candidates of which the + * majority (`JSON.parse`, `Array.isArray`, `getService`) are correctly out of + * scope, so a count there would be noise on day one. Printing NOT APPLICABLE + * rather than nothing is deliberate: an absent row cannot be told apart from + * "nobody looked", which is this card's own subject. + * * ## Usage * * node scripts/check-durability-degradation-log-level.mjs # audit (both rules) @@ -2150,6 +2203,42 @@ function runReadSeamRule({ list = false } = {}) { console.log(''); } + // ── The UNRECOGNISED verdict for THIS rule (#9747): stated, not invented ── + // + // The ruling asked for "N constructs in the scan roots matched no rule in + // this gate's vocabulary" as a third verdict. For the log-level rule above + // that is answerable and answered. For THIS rule it is not, and the honest + // act is to say so in the output rather than print a number. + // + // Measured, not asserted. This rule's vocabulary is DRIVER_READ_CALLEES + // (`find`/`findOne`/`count`, anchored to `IDataDriver`). A census over the + // three scan roots asking "which catches carry this rule's HARM shape — + // invent an unreported answer — while guarding a call the vocabulary does + // not name?" returns 25 sites, and the callee histogram is + // `Array.isArray` (5), `.raw` (3), a callback `fn` (3), `JSON.parse` (3), + // `getService` (2), `getDriver` (2), `toJSONSchema`, `stringify` … Most of + // them are not storage reads at all. Reporting those as "unrecognised" + // would put ~20 correct rows in the count on day one, which is the #8662 + // failure the pilot must avoid: a correct out-of-scope verdict rendered as + // noise discredits the direction for every gate after it. + // + // Narrowing that 25 to the genuine storage reads requires exactly the + // name-heuristic ("a callee matching /find|query|fetch|getAll/") this + // rule's own vocabulary note refuses, and refuses for a measured reason. + // So: proposal 2 does not apply per-construct here. The line below is + // printed anyway, because a pilot gate that emitted NO row would leave a + // round report unable to tell "measured at zero" from "nobody looked" — + // this card's own subject. + console.log( + 'UNRECOGNISED [durability-read-invention]: NOT APPLICABLE — this rule cannot enumerate what it ' + + 'failed to recognise. Its vocabulary is callee NAMES anchored to IDataDriver ' + + `(${[...DRIVER_READ_CALLEES.keys()].join(', ')}), and the only way to count "a read I do not ` + + 'know about" is the spelling heuristic the vocabulary note deliberately refuses. Measured ' + + 'on this tree: 25 catches carry the harm shape while guarding a non-vocabulary call, and ' + + 'the majority are JSON.parse / Array.isArray / getService — correctly out of scope. A count ' + + 'here would be noise, so none is invented (#9747, H3).', + ); + const baseline = loadReadInventionBaseline(); const allowed = new Map((baseline.entries ?? []).map((e) => [`${e.file}::${e.fn}`, e])); const violations = []; @@ -2248,7 +2337,7 @@ function runReadSeamRule({ list = false } = {}) { ).length; const passThrough = seams.filter((s) => s.invents.some((i) => i.startsWith('pass-through'))).length; console.log( - `✓ read-seam invention (#5186 + #6451, ${READ_SEAM_SCAN_ROOTS.length} package roots): ${seams.length} read seam(s), none invents an unreported answer` + + `✓ read-seam invention (#5186 + #6451, ${READ_SEAM_SCAN_ROOTS.length} package roots, vocabulary ${[...DRIVER_READ_CALLEES.keys()].join('/')}): ${seams.length} read seam(s), none invents an unreported answer` + (discriminated > 0 ? ` (${discriminated} answer on a type-discriminated benign branch)` : '') + (passThrough > 0 ? ` (${passThrough} pass an input through, reported)` : '') + (allowed.size > 0 ? ` (${allowed.size} baselined)` : '') + @@ -2374,6 +2463,37 @@ function run({ list = false } = {}) { console.log(''); } + // ── The UNRECOGNISED verdict (#9747) ───────────────────────────────────── + // + // Printed on EVERY run, before the failure branches: a count that appeared + // only on a clean run would be invisible exactly when a reader is looking + // hardest. It is a verdict, never a finding — nothing here changes the + // exit code. See "THE THIRD VERDICT" in the header for why this is not + // spelled `exit 2` even though the in-tree prior art is. + const unreadableSeams = seams.filter((s) => s.unreadable.length > 0); + const verdictRestsOnIt = unreadableSeams.filter( + (s) => + !s.rethrows && + !s.propagates && + s.loud.length === 0 && + s.quiet.length === 0 && + s.conditional.length === 0, + ); + console.log( + `UNRECOGNISED [durability-degradation-log-level]: ${unreadableSeams.length} of ${seams.length} ` + + 'discovered seam(s) answer their catch through a call this checker could not read as a log ' + + `(${verdictRestsOnIt.length} where nothing else in the catch could be read either, so the ` + + 'verdict rests entirely on a construct outside this vocabulary). Not a failure and not a ' + + 'clean bill: it is the count of what the recognizer did not understand (#9747, ruling of ' + + '2026-08-18).', + ); + for (const s of unreadableSeams) { + console.log( + ` unrecognised ${s.file}:${s.catchLine} guards ${s.callee}() ` + + `could not read: ${s.unreadable.join(', ')}`, + ); + } + const baseline = loadBaseline(); const allowed = new Map((baseline.entries ?? []).map((e) => [`${e.file}::${e.callee}`, e])); const violations = []; @@ -3179,7 +3299,17 @@ function selfTest() { const kindsMismatch = c.expectKinds !== undefined && JSON.stringify(kinds) !== JSON.stringify([...c.expectKinds].sort()); - if (got !== c.expectViolation || countMismatch || seamMismatch || sitesMismatch || kindsMismatch) { + // `expectUnrecognised` pins the #9747 census — how many discovered seams + // carry a call the log resolver could not read at all. It is deliberately + // independent of `expectViolation`: the whole point of the third verdict + // is that a seam can be correctly GREEN and still contain something this + // checker did not understand, and before #9747 that combination printed + // nothing anywhere. + const unrecognised = seams.filter((x) => x.unreadable.length > 0).length; + const unrecognisedMismatch = + c.expectUnrecognised !== undefined && unrecognised !== c.expectUnrecognised; + if (got !== c.expectViolation || countMismatch || seamMismatch || sitesMismatch || kindsMismatch + || unrecognisedMismatch) { failures++; console.error( ` ✗ ${c.name}: expected violation=${c.expectViolation}` + @@ -3187,7 +3317,9 @@ function selfTest() { (c.expectSeams !== undefined ? ` seams=${c.expectSeams}` : '') + (c.expectSitesUsed !== undefined ? ` sitesUsed=${JSON.stringify(c.expectSitesUsed)}` : '') + (c.expectKinds !== undefined ? ` kinds=${JSON.stringify(c.expectKinds)}` : '') + + (c.expectUnrecognised !== undefined ? ` unrecognised=${c.expectUnrecognised}` : '') + `, got violation=${got} count=${findings.length} seams=${seams.length}` + + (c.expectUnrecognised !== undefined ? ` unrecognised=${unrecognised}` : '') + (c.expectSitesUsed !== undefined ? ` sitesUsed=${JSON.stringify(usedList)}` : '') + (c.expectKinds !== undefined ? ` kinds=${JSON.stringify(kinds)}` : ''), ); diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index 04e121026c..48c55034c7 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -677,6 +677,183 @@ function scanSource(fileName, text, slice = SLICES[0]) { return doubles; } +// ════════════════════════════════════════════════════════════════════════════ +// THE UNRECOGNISED CENSUS (#9747) -- the third verdict, printed and never fatal +// ════════════════════════════════════════════════════════════════════════════ +// +// The four invariants above answer "is the discovered population guarded". +// DISCOVERED answers "is the population non-empty". Neither can answer the +// question #9747 measured across nine instances in this repo: +// +// > how many constructs did this gate SEE and fail to understand? +// +// A construct the recognizer cannot read is in NEITHER half of the ledger. It +// is not pinned, it is not baselined, it is not exempt -- it is absent, and +// absence reads to every consumer of this script's output as "clean". That is +// #8639's shape exactly (a `vi.fn(fn)` initializer the unwrap did not read), +// and #8639 was found by luck rather than by this gate saying anything. +// +// ## What this section is NOT +// +// ⛔ It does not widen discovery by one construct. `scanSource` is untouched +// and this census cannot reach it: the two walks share no state, so no count +// here can move a double into or out of the pinned population. Widening the +// matcher has been separately priced and separately DECLINED (#8845, #9165's +// 2b), and this card's own scoping says so. Counting is the whole act. +// +// ⛔ It never fails a run. Per the 2026-08-18 ruling on #9747 the third state +// is VISIBILITY ONLY: no new required context, no new merge-blocking failure. +// +// ## Why not `exit 2`, when the in-tree prior art uses `exit 2` +// +// Four places in this repo already spell a third state -- `check-where-matcher- +// conformance` (missing baseline => `exit 2`, explicitly distinct from a +// finding's `exit 1`), `check-published-readme-exports` (hard refusal), +// `check-governed-merges`' header ("non-zero exits classify the ENVIRONMENT, +// not the tree"), and the drift guard added by #9700. Every one of them exits +// non-zero because the gate is REFUSING TO RUN: the environment is broken and +// no verdict about the tree is available. +// +// This verdict is the opposite. The run completed, every invariant was +// evaluated, and the count is an observation ABOUT the run. Spelling it +// `exit 2` would make it a failing CI job, which is precisely what the ruling +// forbids. So it matches the convention where the convention is about +// SEMANTICS -- a named third state, distinct from both "clean" and "finding", +// printed rather than inferred -- and deliberately not where the convention is +// about the exit code. The line carries a stable, greppable prefix +// (`UNRECOGNISED []:`) so a round report can pick it up without a new +// merge-blocking context existing anywhere. +// +// ## SCOPED-OUT is not UNRECOGNISED, and the difference is the whole point +// +// #8662 is the instance that sharpens this: `check-where-matcher-conformance` +// drops inverted survivor filters as OUT_OF_SCOPE **correctly, by its own +// definition**, and that correct verdict still reads as "nothing to see". A +// census that folded those into "unrecognised" would report noise on day one +// and discredit the direction. So the two are counted apart: +// +// SCOPED OUT the gate has a STATED criterion that excludes this construct +// and the criterion is right. `delete: vi.fn()` carries no +// implementation at all, so there is no behaviour that could be +// looser than `ObjectQL.delete` -- `unwrapCallImpl`'s own +// census argues exactly this. Reported as a number only. +// +// UNRECOGNISED an implementation demonstrably EXISTS and this gate cannot +// reach it: the initializer carries a function the unwrap +// declined, or it roots at a binding this file declares. Each +// one is a double that could be looser than the producer and +// is in no ledger. Reported by file, line and spelling. +// +// The discriminator is structural, never an allowlist of callee names -- the +// same choice `unwrapCallImpl` documents, and for the same reason: an +// allowlist goes silently blind the day someone writes a new wrapper. + +/** Short, single-line echo of a construct, for the census rows. */ +function censusSnippet(node, sf) { + return node.getText(sf).replace(/\s+/g, ' ').slice(0, 96); +} + +/** Every identifier this file itself declares (functions, consts, lets). */ +function declaredBindings(sf) { + const names = new Set(); + const visit = (n) => { + if (ts.isFunctionDeclaration(n) && n.name) names.add(n.name.text); + if (ts.isVariableDeclaration(n) && ts.isIdentifier(n.name)) names.add(n.name.text); + ts.forEachChild(n, visit); + }; + visit(sf); + return names; +} + +/** + * One file's unrecognised / scoped-out constructs for one slice. + * + * The population it walks is the same structural evidence discovery uses -- a + * construct declaring the slice's verb alongside at least two engine siblings + * -- so a row here is never something the gate had no business reading. What + * separates it from `scanSource` is the single step where discovery stops: + * `implOf` answered null, so `isEngineVerbShape` was never asked and the + * construct left the population without any verdict being recorded. + */ +function censusSource(fileName, text, slice) { + const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + const declared = declaredBindings(sf); + const unrecognised = []; + const scopedOut = []; + + const consider = (members, node) => { + const names = new Set(); + let member = null; + for (const m of members) { + const n = memberName(m); + if (n) names.add(n); + // `memberName` reads a shorthand's identifier too, so `{ update }` lands + // here as a declaration of the verb -- which is what we want. + if (n === slice.verb) member = m; + } + if (!member) return; + const siblings = [...names].filter((n) => ENGINE_SIBLINGS.has(n) && n !== slice.verb); + if (siblings.length < 2) return; + if (implOf(member)) return; // discovery read it -- in the population + const line = sf.getLineAndCharacterOfPosition(member.getStart(sf)).line + 1; + const row = { line, text: censusSnippet(member, sf) }; + + if (ts.isShorthandPropertyAssignment(member)) { + unrecognised.push({ ...row, why: 'shorthand -- the implementation is a binding elsewhere in scope' }); + return; + } + const init = (ts.isPropertyAssignment(member) || ts.isPropertyDeclaration(member)) + ? member.initializer : null; + if (!init) { + scopedOut.push({ ...row, why: 'declaration only (a signature or an abstract member): no body exists' }); + return; + } + let carriesFn = false; + const scan = (n) => { + if (ts.isFunctionExpression(n) || ts.isArrowFunction(n)) carriesFn = true; + ts.forEachChild(n, scan); + }; + scan(init); + if (carriesFn) { + unrecognised.push({ ...row, why: 'the initializer carries a function this gate declined to unwrap' }); + return; + } + let root = init; + while (ts.isCallExpression(root) || ts.isPropertyAccessExpression(root)) root = root.expression; + if (ts.isIdentifier(root) && declared.has(root.text)) { + unrecognised.push({ ...row, why: `the initializer roots at \`${root.text}\`, which this file declares` }); + return; + } + scopedOut.push({ ...row, why: 'no implementation anywhere (a bare mock or a value), so nothing can be looser than the producer' }); + }; + + const visit = (n) => { + if (ts.isObjectLiteralExpression(n)) consider(n.properties, n); + else if (ts.isClassDeclaration(n) || ts.isClassExpression(n)) consider(n.members, n); + ts.forEachChild(n, visit); + }; + visit(sf); + return { unrecognised, scopedOut }; +} + +/** The census across every scanned file and every slice. */ +function censusUnrecognised() { + const rows = []; + let scopedOut = 0; + for (const abs of testFiles()) { + const rel = relative(ROOT, abs).split(sep).join('/'); + const text = readFileSync(abs, 'utf8'); + for (const slice of SLICES) { + if (!new RegExp(`\\b${slice.verb}\\s*[(:,}]`).test(text)) continue; + const c = censusSource(abs, text, slice); + scopedOut += c.scopedOut.length; + for (const u of c.unrecognised) rows.push({ verb: slice.verb, file: rel, ...u }); + } + } + rows.sort((a, b) => (a.file === b.file ? a.line - b.line : a.file < b.file ? -1 : 1)); + return { rows, scopedOut }; +} + // ════════════════════════════════════════════════════════════════════════════ // THE CONSUMER SEAMS (#8194, from #8058's audited sweep) // ════════════════════════════════════════════════════════════════════════════ @@ -1441,6 +1618,25 @@ function report() { ); console.log(''); + // ── The UNRECOGNISED verdict (#9747) ────────────────────────────────────── + // Printed on EVERY run, green or red, and before the failure branch below: + // a count that only appears on a clean run would be invisible exactly when a + // reader is looking hardest. It is a verdict, never a finding -- nothing + // here can change this script's exit code. + const census = censusUnrecognised(); + console.log( + `UNRECOGNISED [engine-double-contract]: ${census.rows.length} construct(s) in ${SCAN_ROOTS.join(", ")} ` + + `declare a scanned verb (${[...SCANNED_VERBS].join(', ')}) alongside engine siblings, and this gate ` + + 'could not read the implementation -- so they are in NEITHER the pinned population nor the ledger. ' + + `${census.scopedOut} further construct(s) are SCOPED OUT by a stated criterion and are not counted here. ` + + 'This is a verdict, not a finding: it never fails a run (#9747, ruling of 2026-08-18).', + ); + for (const r of census.rows) { + console.log(` unrecognised [${r.verb}] ${r.file}:${r.line} ${r.why}`); + console.log(` ${r.text}`); + } + console.log(''); + if (errors.length) { for (const e of errors) console.error(` x ${e}`); console.error(`\ncheck-engine-double-contract: ${errors.length} problem(s).\n`); @@ -2224,6 +2420,75 @@ class Svc { + 'predicate discriminates rather than approving everything)', !ratchetRemedyCarriesAuthority(unmarkedOffer)); + // ── The UNRECOGNISED census (#9747) ──────────────────────────────────────── + // + // Both directions on every limb, and the third direction this card exists for: + // a construct that is CORRECTLY out of scope must count as SCOPED OUT, never + // as unrecognised. #8662 is why -- a correct OUT_OF_SCOPE verdict that reads + // as noise discredits the whole direction on day one. + const D = SLICES.find((s) => s.verb === 'delete'); + const censusFake = (deleteMember, header = '') => `${header} +function makeEngine() { + return { + async find(o: string, opts?: any) { return []; }, + async insert(o: string, data: any) { return data; }, + ${deleteMember} + }; +} +`; + + let c = censusSource('c.test.ts', censusFake('async delete(o: string, opts?: any) { return true; },'), D); + 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); + expect('#9747 — an initializer carrying a function the unwrap declined is UNRECOGNISED', + c.unrecognised.length === 1 && c.unrecognised[0].why.includes('declined to unwrap')); + + c = censusSource('c.test.ts', censusFake('delete: del,', + 'const del = async (o: string, opts?: any) => true;\n'), D); + expect('#9747 — an initializer rooting at a binding this file declares is UNRECOGNISED', + c.unrecognised.length === 1 && c.unrecognised[0].why.includes('`del`')); + + // The shorthand limb is driven on the UPDATE slice, not delete: `{ delete }` + // is not valid shorthand (a reserved word), so the delete spelling could + // never occur in the tree and a fixture using it would assert nothing. Every + // shorthand row the real corpus carries is an `update`. + c = censusSource('c.test.ts', ` +const update = async (o: string, data: any, opts?: any) => data; +const engine: any = { registry: {}, insert: async (o: string, d: any) => d, findOne: async (o: string) => null, update }; +`, SLICES.find((s) => s.verb === 'update')); + expect('#9747 — a shorthand member is UNRECOGNISED', + c.unrecognised.length === 1 && c.unrecognised[0].why.includes('shorthand')); + + // ⛔ The H4 trap, pinned in both spellings: a bare mock and a mock returning a + // VALUE carry no implementation at all, so nothing about them could be looser + // than the producer. `unwrapCallImpl`'s own census argues this. They must be + // SCOPED OUT -- counting them would put 117 correct rows in the report today. + c = censusSource('c.test.ts', censusFake('delete: vi.fn(),'), D); + expect('#9747 — `vi.fn()` is SCOPED OUT, not unrecognised', + c.unrecognised.length === 0 && c.scopedOut.length === 1); + + c = censusSource('c.test.ts', censusFake('delete: vi.fn().mockResolvedValue(true),'), D); + expect('#9747 — a mock returning a VALUE is SCOPED OUT, not unrecognised', + c.unrecognised.length === 0 && c.scopedOut.length === 1); + + // The census reads the SAME structural evidence discovery does: a construct + // with fewer than two engine siblings was never this gate's business, and + // reporting it would be the noise the ruling's pilot is meant to avoid. + c = censusSource('c.test.ts', `const notAnEngine = { delete: del };\nconst del = async (o: string) => true;\n`, D); + expect('#9747 — a construct with fewer than two engine siblings is in NEITHER census bucket', + c.unrecognised.length === 0 && c.scopedOut.length === 0); + + // ⛔ Visibility only: the census must not be able to move discovery. Same + // source, both walks -- the double count is what it was before this section + // existed. Without this limb "it only counts" is a claim, not a property. + const visSrc = censusFake('delete: del,', 'const del = async (o: string, opts?: any) => true;\n'); + expect('#9747 — a construct in the UNRECOGNISED census is still absent from the population ' + + '(the census cannot widen discovery)', + censusSource('c.test.ts', visSrc, D).unrecognised.length === 1 + && scanSource('c.test.ts', visSrc, D).length === 0); + 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`); @@ -2244,7 +2509,12 @@ class Svc { + 'inline / const-bound / one-wrapper options forms, refuses to read a $in predicate as ' + 'by-id, accepts a probe, a driver boolean and a one-method-hop helper as refusals alike, ' + '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.', + + '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 ' + + '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.', ); } From b54c64417d5795d75e6040e36fb9e39f6956de92 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 05:59:15 +0000 Subject: [PATCH 2/2] test(gate): pin the UNRECOGNISED census in both directions on the durability rule Four fixtures, each proven to discriminate by flipping its expectation, and the production `unreadable` sink mutated to prove they fail when the census goes blind. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- ...check-durability-degradation-log-level.mjs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/scripts/check-durability-degradation-log-level.mjs b/scripts/check-durability-degradation-log-level.mjs index 1762287287..831a15033a 100644 --- a/scripts/check-durability-degradation-log-level.mjs +++ b/scripts/check-durability-degradation-log-level.mjs @@ -3248,6 +3248,71 @@ function selfTest() { expectCount: 1, expectKinds: ['silent-swallow'], }, + // ── The UNRECOGNISED census (#9747) ────────────────────────────── + { + // ⭐ The limb the card exists for. This seam is correctly GREEN — + // `logger.error` is right there and readable — and it ALSO contains + // a call the resolver cannot read. Before #9747 that fact reached + // no output at all: `unreadable` was collected only to pick a + // finding's verdict, so a seam that never became a finding threw it + // away, and `clean` was printed where the honest answer is "clean, + // and there is one call in here I did not understand". + name: 'census: an UNREADABLE call inside an otherwise LOUD catch is counted, and stays green', + code: ` + class P { async f(driver: any, obj: any, level: string) { + try { await driver.syncSchema('t', obj); } + catch (e) { this.logger.error('CONSEQUENCE + FIX', { e }); this.logger[level]('x'); } + } }`, + expectViolation: false, + expectSeams: 1, + expectUnrecognised: 1, + }, + { + // The other direction on the same axis: a catch whose ONLY answer is + // unreadable is BOTH a finding (`unreadable-report`, #9657) and a + // census row. Two independent verdicts about one seam; wiring either + // to the other would lose one of them. + name: 'census: a catch whose only answer is unreadable is BOTH a finding and a census row', + code: ` + class P { async f(driver: any, obj: any, level: string) { + try { await driver.syncSchema('t', obj); } + catch (e) { this.logger[level]('something happened', { e }); } + } }`, + expectViolation: true, + expectSeams: 1, + expectCount: 1, + expectKinds: ['unreadable-report'], + expectUnrecognised: 1, + }, + { + // ⛔ SILENT is not UNRECOGNISED. An empty catch is fully understood — + // the checker read it and it said nothing. Counting it here would + // conflate the two states this card exists to separate, in the + // direction that turns the count into noise. + name: 'census: a truly SILENT catch is NOT counted as unrecognised', + code: ` + class P { async f(driver: any, obj: any) { + try { await driver.syncSchema('t', obj); } catch { /* ignore */ } + } }`, + expectViolation: true, + expectSeams: 1, + expectCount: 1, + expectKinds: ['silent-swallow'], + expectUnrecognised: 0, + }, + { + // And the clean direction. Without this limb a census that counted + // every discovered seam would satisfy every limb above. + name: 'census: an ordinary loud catch contributes nothing to the census', + code: ` + class P { async f(driver: any, obj: any) { + try { await driver.syncSchema('t', obj); } + catch (e) { this.logger.error('CONSEQUENCE + FIX', { e }); } + } }`, + expectViolation: false, + expectSeams: 1, + expectUnrecognised: 0, + }, { // A propagating catch is still a SEAM — it is reported by `--list` // and it must not vanish from the census. #4754's whole precision