Uh oh!
There was an error while loading. Please reload this page.
fix(gate): read-seam recognizer matched a callee NAME with no shape check, so Array.prototype.find pulled non-read catches into the census - #12137
Merged
Conversation
…heck, so Array.prototype.find pulled non-read catches into the census (#11921) `isReadCall()` decided "is this a storage read?" by matching the callee name against `DRIVER_READ_CALLEES` and nothing else. `find` is `IDataDriver.find` and it is also `Array.prototype.find`, so a pure in-memory helper matched — and the same-file wrapper recursion then attributed that "read" to the helper's CALLER, pulling a try/catch that guards no storage read into the read-seam population. The fix is a declared fact about the contract already anchored above the vocabulary, not a new list of names: every `IDataDriver` read takes the object NAME first, and `Array.prototype.find`'s first argument is always a predicate. A call whose first argument is a function literal is therefore not a call to that contract. It can only decline to exclude, so it cannot drop a real driver read. Measured over the full census rather than tuned to the known instance: the receiver-based alternative drops 14 of 66 seams, 12 of them real driver reads. The shape test drops 2, and the second (`resolveDeferredUpdates`, a WRITE seam) was not known when the card was filed. Census restated, not silently invalidated: 66 -> 64, re-measured with the corrected recognizer over the #8845 anchor tree as well as today's. No baseline entry added. Also decides #8897 (option 1) as its `Restart-when:` requires, and names #8901 per its trigger-file clause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
…der ablation The wrapper-hop fixture spelled its array `find` inside a `records.map(...)` callback. `isReadCall`'s wrapper recursion walks the SAME TICK only, so the call was never reached in either direction: the case passed pre-fix and post-fix, asserting nothing. Caught by the reverse verification, not by review. Respelled as a same-tick `for` loop, which is also the shape of the live instance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
This was referenced Aug 25, 2026
yinlianghui
marked this pull request as ready for review
August 25, 2026 10:52
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 25, 2026
Merged
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#11921
isReadCall()decided "is this call a storage read?" by matching the callee name againstDRIVER_READ_CALLEESand nothing else.findisIDataDriver.findand it is alsoArray.prototype.find, so a pure in-memory helper matched — and the same-file wrapper recursion then attributed that "read" to the helper's caller, pulling atry/catchthat guards no storage read at all into the read-seam census.The fix, and why this shape
A declared fact about the contract the vocabulary is already anchored to, not a new list of names:
So a call whose first argument is a function literal is not a call to that contract, however it is spelled. The predicate can only decline to exclude: it fires on a shape the declared contract cannot produce, so it cannot drop a real driver read.
Chosen on measurement, not on taste — what each alternative would have excluded
The card offered three shapes and recommended none. All three were measured across the whole census (90 matched reads across 66 seams), not against the one known site.
thisor athis.chain", the closest syntactic form)Card shape (1) is the dangerous one, and only measurement says so. Of the 14 seams it removes, 12 are real driver reads —
history-cleanup(×4),build-probes(×3),seed-tenancy-backfill,diffMetaItem, the dangling-reference audit,loadGovernance,checkGovernance. The genuine driver reads arrive through receivers spelledthis,this.engine,this.ql,driver,engine,engineAny,ledger,port; the array reads throughlist,allResultsand a.map(...)chain. Nothing in the shape of a receiver separates those two lists — only what the name is bound to does, and that is a type-checker. Declaring the receiver names instead only postpones it: the next driver binding gets a name the list does not have, and its seam leaves the census silently. That is the unsafe direction — a population that shrinks without a word.The taken shape and card (2) are indistinguishable on today's data; the taken one is preferred because it is strictly narrower in the safe direction (a function in a query option cannot make a real driver read disappear).
⭐ Not tuned to the known instance. The card measured 1 of 66. The correct number is 2 of 66, and the second was not known when the card was filed:
getMetaItems(metadata-protocol/src/protocol.ts) — the filed instance. ItstryguardsmergePackageAwareOverlay, a pure helper whoselist.find((c) => …)runs on a plain local array. Thetryholds exactly oneawaitand nofind/findOne/countof its own.resolveDeferredUpdates(metadata-protocol/src/seed-loader.ts) — new, and the worse of the two: itstryguardswriteDeferredReference, a WRITE. It was in the read-seam population only becauseallResults.find(r => …)updates an in-memory stats row.Five spurious matches across four call sites, in two files. Two of the four sit at depth 0 (a direct
findin thetryblock), not at a wrapper hop — so "exclude a wrapper hop", as shape (2) was phrased, would have been too narrow by half.The census, restated rather than silently invalidated
Every figure on #9165, #8901 and #8845 is quoted against a 66-seam population. Two of those 66 were never read seams. Re-measured with the corrected recognizer over the same anchor tree, so the correction is a census and not a subtraction:
origin/main@8664a2c(#8845 anchor)origin/main@8619f9513(this branch's base)The old recognizer reproducing 66 at
8664a2cis the calibration: that is the number the script header already tells the reader to re-run the gate and check, so the instrument is verified against the record before it is trusted. The same two functions are the non-members on both trees.Directly measured and unaffected: the #9165 assignment ladder (3 / 1 / 1 / 0) and every parenthetical in the verdict line — 8 type-discriminated, 1 pass-through, 1 answer-by-assignment, 1 baselined — are byte-identical before and after. Only the seam count moves.
⛔ The #8845 and #9165 ladder rungs are deliberately not re-derived here. Re-running those narrowings is the census re-run #8901's restart conjunct (b) reserves, and #8901's own ⛔ forbids re-opening the #8845 decision; #11921 forbids this fix riding along with any other change. What is restated is the denominator every one of those figures is quoted against, plus enough of each departing seam's catch shape (recorded in the header) that any rung's owner can place it exactly.
⛔ No baseline entry was added. The baseline's own header says an entry means "a human read it, not a rule that guesses", and booking a recognizer imprecision there is the failure this card exists to prevent.
#8897 — decided here, as its
Restart-when:requires#8897's restart line is "any PR touches
scripts/check-durability-degradation-log-level.mjs(decide options 1/2/3 in that change)". This PR touches it, so the three-way is settled rather than passed by.Taken: option 1 — leave
LOGGER_RECEIVERSas it is, and record the narrowness in the header beside the other honest limitations. The decision, its reasoning and its cost are written at the constant itself; the top-of-file limitations list grew from two to three.The filer's weak preference was also (1), but the reason recorded is not the filer's — it is the measurement this PR ran on the mirror-image question, which none of the three options had:
loggerLevel. And the direction of their error is the unsafe one, which the filer's analysis did not price: for the read-seam rulecollectLoggedLevelspowers an exemption ("the catch said something, so this is the other rule's question"). Widening what counts as a logger widens an exemption — a seam that invents an answer gets excused because something that is not a logger happens to own a.warn.dangling-reference-audit.ts, receiverport) invents none. #8897's second restart condition is unchanged and is the promote-immediately one: a seam reporting through an injected receiver going red with a "silent" message. If that fires, this decision was wrong and the evidence will say so out loud.⛔ Not decided here, deliberately: the call-shape half of the same function's narrowness (#9657, closed) — a different defect, already answered by #9609.
pm:on-holdand its stated restart has now fired and been answered. Its label needs updating when this lands.#8901 — named, per its trigger-file clause
#8901's clause is real, and it is worth saying where, because the card's body attributed it to #8901's
Restart-when:line and it is not there. It is in the maintainer hold record of 2026-08-16 (comment 5306092540), verbatim:Reaffirmed on 2026-08-17 ("trigger files unchanged"). Named here, therefore, and #8901 remains open and on hold. ⛔ The #8845 decision is not re-opened. #8901 is not addressed here beyond this naming — out of scope: the declared failure-propagation vocabulary it exists for.
Restart-when:conjunct (b) is measured against moves from 66 to 64. Its ladder readings taken at945ffbea8and earlier were all taken with the pre-fix recognizer.Self-test, both directions, and one fixture that was not
Five fixtures, and
expectSeamsis the load-bearing assertion in the passing ones —expectViolation: falsewould hold vacuously if the seam were merely graded green, and what is asserted is that it is not in the population at all.Reverse-verified by ablation, in both directions, with the mutation confirmed on disk each time and restored through a trap:
return true) → the 3flagscases red (31 in total), i.e. the guards against subtracting a real read are not vacuous either.findinside arecords.map(...)callback, andisReadCall's wrapper recursion walks the same tick only, so the call was never reached in either direction and the case passed pre-fix and post-fix alike. Respelled as a same-tickforloop — which is also the shape of the live instance — in its own commit.Verification
Gate union re-run at the final commit
9154242e8, all exit 0:check:agent-test-spelling,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, pluscheck:ratchet-remedy-authority,bare-root-worklist.mjs --self-testandcheck-nul-bytes.mjs. Gate list derived withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackin this worktree.Repo-wide
eslint . --no-inline-config: 5099 files, 0 errors, 0 warnings (not narrowed).The two landing obligations that a gate edit can owe were checked rather than assumed, and neither fires:
bare-root-worklist.mjsreports 0 untriaged rows, andcheck-ratchet-remedy-authority --liststill observes this file as[anchor: shrink], with the sweep's control corpus set-equality audited both ways (theOR :offer text is untouched).skip-changeset: the diff is one file under rootscripts/; nothing published changes.Generated by Claude Code