Uh oh!
There was an error while loading. Please reload this page.
fix(devx): the read-seam wrapper hop gets the shape check the vocabulary hit already has - #13444
Merged
os-project-manager merged 2 commits intoAug 30, 2026
Merged
Conversation
…ary hit already has #11921 gave `isReadCall`'s vocabulary match a shape test (`contradictsDriverReadShape`) and applied it at exactly one place. The wrapper hop resolved a callee name against the flat, file-scoped `functionBodies` index with no equivalent test and no receiver check, so any call whose name collided with a same-file declaration was followed into that body. `contradictsWrapperResolution` asks the same question of the resolved declaration: a receiver that is itself a property access, index or call result names another object's member, and a call supplying fewer arguments than the declaration requires is not a call to it. Measured zero-delta on today's tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
…e check The receiver clause reads receiver DEPTH, not `this`-rootedness. Nothing asserted that admission, so tightening it to `this`/`super` only passed the whole suite. `const self = this;` is how the live file reaches its own members from a closure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager
marked this pull request as ready for review
August 30, 2026 12:35
os-project-manager
enabled auto-merge
August 30, 2026 12:35
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-12358-wrapper-hop-shape-check
branch
August 30, 2026 12:53
This was referenced Aug 30, 2026
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.
Fixes#12358
#11921 gave
isReadCall's vocabulary match a shape test —contradictsDriverReadShape— and applied it at exactly one place. The wrapper hop resolved a callee name against the flat, file-scopedfunctionBodiesindex with no equivalent test and no receiver check, so any call whosecalleeNamehappened to equal a function declared in the same file was followed into that body, whatever it was called on and whatever it was passed.That runs in the unsafe direction. Every other narrowness in this gate under-counts; this one invents a member of the read-seam denominator that #5186, #6451, #9165, #8845 and #8901 are all quoted against — and it is silent, because the fake seam prints in
--listlooking exactly like a real one.The live instance is still there
Located by content, not by the card's line numbers (which were treated as claims, and happen to be right): in
packages/metadata-protocol/src/sys-metadata-repository.ts,close()at 1343 callsw.terminate();terminateis the local const arrow at 1246 whose only call isself.watchers.delete(subscription)on the watcher registry Set declared at 303.calleeNamereads that asdelete, and the hop resolveddeleteto this file'sasync delete(ref, opts)at 653.Re-derived, not inherited
The card's table was measured five days ago on
3ddad51b5c. The denominator has since moved 64 to 66, so everything was re-run on this branch's base5f0a9c4a. The delta reproduced exactly — same +8, the same 8 seams by identity, ablation still removing exactly two:walkSameTickInclusive, depth 2walkAll, depth 2deletewrapper hop refusedThe ablation is the control, not the fix. Refusing the
deletehop outright drops two seams —closetoterminate(the fake) andpromoteDrafttodropPromotedDraftRow(a realawait this.delete(ref, ...)). In a summary that is indistinguishable from the correct outcome, which is why the pair is pinned in the self-test.The census, which the card required before any narrowing
Both candidate shapes measured over the whole scan root, on today's recognizer and under the probe that arms the defect:
this/selfonlycontradictsDriverReadShapecalled on the hopEvery row adds zero seams — measured, not argued: the added-seam set is empty in all six runs.
B1 is a vacuous fix, and the census is what says so. Read literally, "give the hop a
contradictsDriverReadShape-style argument test" means calling that predicate on the hop: refuse when the first argument is a function literal.self.watchers.delete(subscription)passes an identifier, so it refuses nothing — on this tree or in principle. B2 is that candidate read at the level the vocabulary-side predicate actually works at: not the literal predicate, but its method — refute the call against the contract already in hand.Why A + B2 rather than either. The measurement cannot separate them; all three cost nothing and remove exactly the fake seam. Their failure modes are independent and each is one edit away in live code: A alone returns the fake seam if
delete's second parameter becomes optional; B2 alone returns it if the registry is reached through a bare identifier after a destructure. The conjunction measures at the same zero cost, so both are asked.What ships
contradictsWrapperResolution(node, body)beside the predicate it mirrors, asked once at the hop. Two clauses, neither a new vocabulary, neither carrying a staleness obligation — both read off the declaration the index already holds:const self = this;is how the live file reaches its own members from a closure.It reads the declaration through
body.parent, which is available becausesetParentNodesis fixed true inscripts/ts-parse.mjs— sofunctionBodieskeeps its value shape and none of its other consumers move.This is not the receiver allowlist
contradictsDriverReadShaperejects. That objection is measured and it stands, for the vocabulary hit, where the receiver is the driver binding and requiring athis-rooted receiver drops 14 of 66 seams, 12 of them real. This clause is not a list of receiver names and does not requirethis: it reads receiver depth, admits every bare identifier, and measures at zero seams over the whole scan root.The deliverable is a measured zero-delta
The defect is latent — today's recognizer never reaches the fake read, because the wrapper recursion's
walkSameTickInclusivestops at thewithTxncallback. So this PR changes no behaviour on today's tree, and that is the point:--listline (diffof the complete run, not just the totals);closetoterminateand keepingpromoteDrafttodropPromotedDraftRow;Tests
Six new read-seam self-test cases (45 to 51). Every one is non-vacuous, proven by ablation rather than asserted — each was run with a specific mutation and predicted to redden, and all five predictions held:
contradictsWrapperResolutionreturns falseexpectSeams: 0cases (1 seam each)deletehop by namethis/superThe bare-identifier admission case was added because the second ablation found nothing to redden without it — tightening the receiver clause to
this/superpassed the entire suite.Gates run on final head
33643001, after the last commit: the 13 path-derived families fromnode scripts/pm/dispatch-gates.mjs(derived from my own diff, not from a hand-written list), plus the gate-script convention obligationspnpm check:pm-dispatch-gates,node scripts/pm/bare-root-worklist.mjs --self-testandnode scripts/check-self-test-wired.mjs, pluspnpm check:nul-bytes. All green.pnpm check:durability-log-levelprints66 read seam(s), none invents an unreported answer, and--self-testprints63 case(s) passedand51 case(s) passed. Full-repopnpm lintwas run under the shared verify lock: 5490 files, 0 errors, 0 warnings.Two families exited non-zero for reasons that are not findings and are recorded as NOT MEASURED:
node scripts/check-test-completeness.mjsexits 3 withPREREQUISITE NOT MET(it grades a savedturbo run testlog that CI tees and this run has none), and the dispatch brief'scheck-self-test-wiredis a script path, not a pnpm script —pnpm check:self-test-wireddoes not exist and exits 254; the real gatenode scripts/check-self-test-wired.mjsis green.No changeset: this is a CI-internal gate script that publishes nothing from any package, which
.github/workflows/lint.ymlcalls the textbookskip-changesetcase in as many words. All three prior merged PRs on this exact file — including #12137, this card's direct predecessor — were single-file with no changeset. Theskip-changesetlabel is applied.Out of scope, deliberately untouched and still open: #12360, the
MAX_READ_WRAPPER_DEPTH = 2bound, which the table above shows costs the census 6 further seams. It is a separate card on the same file.Generated by Claude Code