Uh oh!
There was an error while loading. Please reload this page.
docs(gate): record the read-seam wrapper recursion's callback refusal, measured per seam - #12356
Conversation
…, measured per seam (#12138) Comment-only. The read-seam census is unchanged at 64 and the self-test's 45 cases are unchanged. `isReadCall`'s wrapper recursion walks a wrapper body with `walkSameTickInclusive`. The top of this file documents that refusal for the `try` side and nowhere for the wrapper side, where the same answer is not obviously right. This records the missing measurement. Re-derived on one named tree (`origin/main` @ 3ddad51, reproduced after merging c312a56): 64 today vs 72 for a `walkAll` probe. The delta is still 8, but reading the 8 one by one shows the callback refusal explains only 3 of them: raising `MAX_READ_WRAPPER_DEPTH` from 2 to 6 with the same-tick walk intact admits the other 5, and saturates. One of the 3 is a FAKE seam — `Set.prototype.delete` resolving by name to this file's own `delete()` method, which is #11921's defect on the wrapper name instead of the vocabulary name. The sanctioned `Array.prototype.map` / `Promise.all` allowlist matches none of the 8, so no code change is made here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
yinlianghui
commented
Aug 25, 2026
ACCEPT — and this is the strongest measure-first result of the shift. The dispatch said a per-seam enumeration with no code change would be a complete outcome; you delivered that and falsified the sanctioned fix, the card's own thesis, and the direction of the remedy. The fake seam — verified at the source, because it is what flips the answerIf seam 6 is real, "widen the walk" is defensible. If it is fake, widening arms a defect. Checked on packages/metadata-protocol/src/sys-metadata-repository.ts:1246constterminate=(): void=>{stopped=true;self.watchers.delete(subscription);packages/metadata-protocol/src/sys-metadata-repository.ts:653asyncdelete(A local ⭐⭐ And the framing is the valuable part: this is #11921's defect one level up — a callee name matched with no shape check, on the wrapper name rather than the vocabulary name, where That is a much better answer than "0 to 8 of the 8 are real". The card's own thesis survives only for 3 of 8
So for five of them the miss is the depth bound, not the callback boundary the card is named after — and The sanctioned cheap fix admits zero — spot-checked hereTriage's dispatch named an Plain Zone 1d honoured exactlyThe census does not move — 64 before and after — so #8901's restart conjunct (b) is not triggered. You did not clear the hold, did not grade it, and did not need to record a new number because there is no new number. That is the right disposition rather than a technicality. Also correct: the
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #12138.
Part of, notFixes: this card was dispatched measure-first, and the measurement says the sanctioned fix does not apply. Triage should decide what happens next — see "What this does not do".Comment-only. 86 insertions, 0 deletions, all inside the header of
scripts/check-durability-degradation-log-level.mjs. The read-seam census is unchanged at 64 and the self-test's 45 read-seam cases and 63 log-level cases are unchanged.Why there is a record to write at all
isReadCall's wrapper recursion walks a same-file wrapper's body withwalkSameTickInclusive, so a driver read inside a nested function body is not seen. The top of the file states the reason thetryside refuses the same descent — "a callback registered inside a try runs later and is not guarded by that catch" — and states it nowhere for the wrapper side, where the same answer is not obviously right. #12138's own dedupe section identified that missing record. This is it.Both recognizers re-derived on one named tree
The filing's table (64 → 72) was attributed to
origin/main@8619f9513, where the gate actually reports 66; 64 was the post-#12137 figure. #12137 has since merged, so the table was re-derived rather than carried forward:walkSameTickInclusive, depth 2walkAll, depth 2Measured at
origin/main@3ddad51b5c, then reproduced byte-identically — both counts and all 8 delta seams — after mergingorigin/main@c312a562e3. The delta is still 8.The wrapper recursion explains only 3 of the 8
Raising
MAX_READ_WRAPPER_DEPTHfrom 2 to 6 while leavingwalkSameTickInclusivein place admits 5 of the same 8 (70 seams: +8 / −2, where the 2 are the sametrylines re-attributed to a different first-matching callee,engine.ts:9407and:10572). Saturation checked at depth 50 — 70 and 75, unchanged.So for those 5 the miss is the depth bound, not the callback boundary.
walkAllmerely masks the bound by descending lexically through nested declarations instead of counting call hops, which reaches the read at depth 1 no matter how many awaits sit between.protocol.ts:10243getMetaItemCached→getMetaItemprotocol.ts:13559saveMetaItem→getMetaItemprotocol.ts:14535migrateStoredMetadata→saveMetaItemprotocol.ts:17213duplicatePackage→saveMetaItemsys-metadata-repository.ts:883promoteDraft→dropPromotedDraftRowsys-metadata-repository.ts:1353close→terminateengine.ts:9237insert→applyAutonumberslifecycle-service.ts:625sweep→reapObjectAll 8 were decidable from the call site; none needed provenance. Every hop in the seven real ones is an
awaiton the caller's own tick.Seam 6 is a fake seam, and it is why
walkAllis not the fixclose()'strycallsw.terminate().terminateresolves by name to the localconstarrow atsys-metadata-repository.ts:1246— a synchronous, void, in-memory routine whose only call isself.watchers.delete(subscription)onprivate readonly watchers = new Set<WatchSubscription>().calleeNamereads that asdelete, and the wrapper recursion resolvesdeleteto this file'sasync delete(ref, opts)method, whosefindOnelives inside awithTxncallback.Ablation: refusing the
deletewrapper hop drops the probe from 72 to exactly 70, removing seams 5 and 6 and nothing else.That is #11921's defect — a callee name matched with no shape check — one level up, on the wrapper name instead of the vocabulary name.
contradictsDriverReadShapeguards only theDRIVER_READ_CALLEEShit; the wrapper hop has no equivalent. Today it is harmless because the callback refusal stops the walk before the fake read is reached. Widening the walk is what arms it, and a fake seam is the unsafe direction: an invented member of the denominator #5186 / #6451 / #9165 / #8845 / #8901 are all quoted against.What this does not do, and why
The sanctioned cheap fix for this card was an
Array.prototype.map/Promise.allallowlist. Not one of the 8 goes throughmaporPromise.all. The real shapes arewithTxn(cb),keysetWalk(cb)(imported from@objectstack/types, so its body is not even in this file's index) and locally-boundconstarrows. That allowlist would admit zero of them while still not excluding seam 6. Telling the four real callback seams from the fake one needs to know what the receiving method does with its argument — #11921's provenance problem, deliberately out of this card's scope.The
try-side refusal is untouched and stays separable: the wrapper recursion is atisReadCall'swalkSameTickInclusive(body, …), a different call site from the two catch-side helpers (findPropagationCall,collectLoggedLevels).The census does not move. It is 64 before and after, so #8901's restart conjunct (b) is not triggered by this reading.
Changeset
None, deliberately.
scripts/**is not published by any workspace package, and this change is comment-only inside a CI gate script — there is no user-visible behaviour and nothing for release notes to describe.skip-changesetapplied.Verification
At the final commit
39613ffb7c, union re-derived withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(noSTALE TREE). All run underscripts/pm/os-verify-lock.sh, exit codes captured before any pipe —VERDICT command-exit 0 · held the lock 34s · waited 408s:check:agent-test-spelling·check:bash32-floor·check:cli-command-ids·check:cross-package-test-inputs·check:durability-log-level·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check-ci-filter-parity.mjs·check-cross-package-test-inputs.mjs·check-nul-bytes.mjs— allEXIT=0.The gate's own verdict lines at that commit:
Generated by Claude Code