Uh oh!
There was an error while loading. Please reload this page.
fix(tooling): read a defaulted mock initializer, name the inheritance rule, and derive check-engine-double-contract's own corpus figures - #10176
Merged
Conversation
…s instead of writing them down (#9943) 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
…ive unguarded engine delete double (#9877) `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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
…Object.assign is not an invisible third option (#8553) 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-zhuang
marked this pull request as ready for review
August 20, 2026 13:05
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #9877
Fixes#8553
Fixes#9943
Folded family dispatch against
scripts/check-engine-double-contract.mjs, one commit permember card. All numbers below are from this branch's own runs at
ebb487b, the finalcommit; the gate union was re-derived from the real diff with
node scripts/pm/dispatch-gates.mjs(no path arguments) and run at that same head.⛔ The guardrail first: the shrink-only baseline is NOT touched
It stands at 66 delete / 100 update unguarded doubles on every one of the three
commits, exactly as on
origin/main. The two constructs this widening pulls into thepopulation are pinned at source, so the ratchet tightens rather than being raised:
origin/mainebb487bUNRECOGNISED#9877— a defaulted initializer was hiding a live unguarded enginedeletedelete: overrides.delete ?? vi.fn(async (o, arg) => …)is a BinaryExpression, soimplOfanswerednull,consider()returned beforeisEngineVerbShapewas asked, andthe double was in neither half of the ledger. Absent, which reads as clean.
fnInitializernow descends??/||, reading the default first and the left arm as afallback (both are reachable implementations).
Ablation, both directions, on the committed tree. "
UNRECOGNISEDdropped by 2" isnot the evidence — the gate reddening on the double it was blind to is:
x PINNED [delete]: packages/rest/src/rest-batch-endpoint.test.ts declares 1 engine double(s) whose delete() does not route through assertEngineDeleteDispatch (line 36)(+ the[update]twin; 4 problems)UNRECOGNISEDclimbs back to 23 and the two ledger rows becomex RETAINED … declares NO engine double with a delete any more--self-test#9877failures — the new limbs are not vacuousEach mutation was confirmed on disk by grepping for the injected text and the removed
text before the run, then confirmed reverted the same way.
packages/rest/src/rest-batch-endpoint.test.tsis pinned at source withassertEngineUpdateDispatch(data, options)andassertEngineDeleteDispatch(options);@objectstack/restpasses 2134/2134. That file'sdeletedouble is never driven byits own suite — the #4434 shape precisely.
shorthand — 21 constructs) need
localFunctions-style resolution across two morepackages. Priced with a throwaway probe: +10 constructs, 7 files needing a pin, 0
needing a baseline raise, enumerated in the new sub-issue #10175. That is the ruling's
"narrow the widening to what you can pin", so this PR says
Part ofrather than closingthe parent — the PM can close it by hand if the
??half is judged to be the whole card.#8553— the inheritance rule is named in both ratchet remediesSettled by measurement, not preference.
--census(below) reports bothObject.assignshapes, and the corpus holds 0 override literals that restate
delete/updateand6 that vary other engine members and inherit the verb — every one of the six correct.
Counting by VALUE instead of by site would therefore move zero constructs today, at
the price of a resolution step over an empty population, while every construct it might
later admit would arrive unpinned against a maintainer-only ratchet.
So the other option the card offers is the one taken: both remedies now state that
restating a double as
Object.assign(base, { … })is not a third way out, thatinheriting is correct while the override leaves the verb alone, and that it is a hole when
the override restates the verb with a different contract. On both messages, because an
author meets whichever one their file's baseline state produces. Guarded the way #8435
guarded its authority marker — a detector, self-tested on both remedies plus a negative
fixture that rejects the old two-option framing.
#9943— the figures are derived now, not written downAdded
--census: the gate's ownimplOf/unwrapCallImpl/isEngineVerbShapewalk, printed.
#9943had to hand-copy the criterion to re-scan and got 341 against therecorded 310 with no way to say which was wrong; that question is gone. Per the ruling, no
constant was swapped for a fresher one — the constants are removed, and what remains is
either derivable or anchored past-tense with a date.
Two results from re-running rather than extending the census:
REPOSITORY_ONLY_MEMBERSclaim survived the corpus growth.--censusrunsthat veto's own ablation (discovery with the veto off, diffed). On 2026-08-20 the pair
still moves exactly 2 constructs, still both of [spec]
HookContext.api声明为z.unknown()—— 按(ctx: HookContext)标类型的 hook 无法调用ctx.api.object(…),而文档/技能全在这么教 #5945'sIScopedContextwitnessesunder
packages/spec, and still 0 pinned doubles — on a corpus roughly twice thesize the original reading was taken on. The conclusion held; every number describing the
corpus did not.
spelling this PR deliberately declines to widen.
⭐ The census's population was the real defect. It was scoped to "every member whose
initializer is a CallExpression" — the shape the recogniser 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 of its ZERO readings are CallExpression spellings for that reason.
#9877's defaulted initializer was never in that population at all.--censusbuckets byinitializer kind first, and on the
#9943commit alone that table readsdefaulted ?? — read 0/2: the blind spot, visible before the code that closes it.310and the pinnedledger was
310rows. Different populations that coincided — and they have alreadydiverged (the ledger was 318 rows on
origin/main, 320 here, while the CallExpressioncount is 335). Nothing in this PR merges them.
Verification at
ebb487b--censusand the union were run after the final commit. Every family the derivedlist named, all green:
check:engine-double-contract·check:cross-package-test-inputs·check:dispatcher-error-vocabulary·check:slot-lookup·check:query-options-erasure·check:where-matcher·check:type-check-coverage·check:nul-bytes·scripts/docs-audit/check-affected-docs.mjs·pnpm --filter @objectstack/rest typecheck·
@objectstack/rest2134/2134.Declared narrowing:
check:type-check-debtwas not run — its--re-measureneeds thewhole workspace closure built. The structural half (
check:type-check-coverage) is greenand
@objectstack/rest typecheckis green; CI runs the ratchet.skip-changeset: judged from the diff — rootscripts/plus one test file, nothing landsin a published package.
Generated by Claude Code