feat(gate): judge a read-seam catch that ANSWERS BY ASSIGNMENT (#9165) - #11751
Conversation
The read-seam invention rule judges the expression a `catch` RETURNS, so
`catch { return []; }` is red and `catch { ids = []; }` is green — one
invention delivered a statement apart, and only the first is seen. #9261
is where that cost something: `probeInstallOrganizations` answered a
failed `sys_organization` read with an assigned-and-memoised empty list,
system inserts landed unstamped, the mandated multi-org refusal was
skipped, and the gate was green throughout. It was found by the #8901
census re-run, not by this gate.
Third invention criterion, on the same three preconditions (a read in the
`try`, no log at all, no type discrimination): an `inventedEmptyValue`
written into a variable the function declared BEFORE the `try`. It reuses
the value table unchanged — the criterion is about how the invention is
DELIVERED, never about which values count as invented.
⛔ This is NOT #8845's declined fall-through criterion, and the difference
is why it is affordable. #8845 judges a catch that writes NOTHING, which
needs the checker to guess which variable is the answer (15 sites, 7
already correct, and the obvious exemption clears `publishPackageDrafts`'
fabricated push). Here the catch NAMES the variable. `push`/`++`/`+=`
accumulation stays unjudged and #8845's census is unchanged.
Measured before it was added (收窄先行, the #6451 discipline) at 945ffbe
over the three scan roots, 66 read seams: 3 catches carry any plain
assignment, 1 assigns an invented empty value to a pre-`try` name, and the
first-run RED set is 0 — that one site is #9261's own seam, passing
because PR #9817 gave it `isMissingTableError` discrimination. Zero
baseline entries, no new vocabulary, no new declared name.
A criterion whose discoveries are invisible on a green run cannot be told
from a selector that stopped selecting — this card's own subject — so the
green verdict now counts them: 7 type-discriminated seams becomes 8, plus
a named answer-by-assignment count.
Part of #9165
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
|
ACCEPT. Verified by content against Both corrections to the record holdYou corrected the card on two points and both check out on 1. PR #9284 landed propagate, not discrimination. 2. The precedent that actually transfers is #9817 / #9261. Discrimination, on a catch that answers by assignment, in the read-seam rule's own scan root. That is the shape, verbatim. And the conclusion you drew from it is the part I want to name. The easy report here was "the named precedent is dead, so step 1's premise is dead" — literally true of the antecedent, and wrong. You found the precedent that post-dates the re-pricing and proceeded on it. Reporting a card dead on a technicality when a better precedent exists is a failure mode I would not have caught from the outside. The population claim — and a correction of my own
I say that having first measured it wrong. My extractor terminated the range on Criterion and fixtures, by content
Controls that must NOT move, and don't:
The judgement call — you were right to flag it, and it holdsI checked the quote the whole thing turns on. #8901 says verbatim: "⛔ Do not re-open the #8845 decision. Recording the measurement and adding no criterion was the ruled outcome." And its scope warning carries "drop it and this shape matches 91 of the 314 catch clauses" — which is why my grep for that string in the gate file read 0 in both trees; it lives in #8901's text and you attributed it there correctly. The disjointness argument survives the check because the two chains branch at their first narrowing, not at their last:
A catch cannot be in both sets: the first is defined by writing nothing, the second by writing something. That is a structural exclusion, not a coincidence of today's numbers, and it is what makes "not one site moves between them" load-bearing rather than incidental. The one seam you discover is type-discriminated, so it is excluded from #8845's chain at the last narrowing too — belt and braces. And the decisive practical point: #8901 exists because #8845's criterion reddens 7 already-correct seams that nothing declared can excuse. Yours reddens 0. It asks nothing of the missing vocabulary, which is the entire content of #8901's blocker. ⇒ Not a re-opening. Ruling made at this seat — prior triage twice recorded #9165 as lane-PM territory, so this is mine to make, and I am not routing it to the decision box. What I am carrying forwardTwo of your measurements are decision-relevant beyond this PR and I am recording them on #8901 rather than letting them sit in a merged PR body: the #8845 chain now reads 18 (was 19 on 08-18, 25 at The two-dot/three-dot trap you hit is real and I have hit the same class today: #9165 stays OPEN — this is Flipping to ready. Arming once every check is green — Generated by Claude Code |
Part of #9165 — this lands step 1's criterion. #9165 stays open: the registry-double
gate (its question 1) is untouched here and this PR makes no claim on it.
What this is
check:durability-log-level's read-seam invention rule judges the expression acatchreturns. So
catch { return []; }is red andcatch { ids = []; }is green — oneinvention delivered a statement apart, and only the first is seen. This adds a third
invention criterion for the second spelling, reusing
inventedEmptyValueunchanged.#9261 is where that spelling cost something.
probeInstallOrganizationsanswered afailed
sys_organizationread with an assigned — and then memoised — empty list.resolveSystemWriteOrganizationmaps 0 organizations to "no organization yet", so systeminserts landed unstamped on a
singleinstall and the mandated multi-organizationrefusal was silently skipped. It was found by the #8901 census re-run, not by this
gate; #9261's own filing names the reason: "this catch returns nothing and lets the
accumulator above the try answer."
⛔ This is NOT #8845's declined criterion
The distinction is the whole reason this one is affordable, and it is stated at length in
the gate header next to the #8845 block it narrows.
inventedEmptyValue, unchangedpublishPackageDraftspush, not an assignmentpush/++/+=accumulation stays unjudged, #8845's census is unchanged, and#8901 is untouched. This closes one spelling of #8845, not #8845.
Measured before it was added — 收窄先行, the #6451 discipline
Census over the three scan roots at
origin/main@945ffbea8, 66 read seams, narrowingone criterion at a time (instrumented copy of the checker,
seams.pushsite):x = …assignmenttryThe one site the criterion discovers is #9261's own seam, and it passes — PR #9817
gave it
if (!isMissingTableError(error)) throw error;and the existing type-discriminationexemption reads that correctly. Zero baseline entries, no new vocabulary, no new declared
name, no new script, no new CI step. Same answer #6451 measured, same reason it was affordable.
The other two assignments are the falsification control, and the criterion has to
decline both — it does:
counted = pageOffset + records.length(findData) is a computedvalue, and
storeUnavailable = true(loadMetaFromDb) is a flag the catch raises sothe caller is told, the opposite of an invention. Both are pinned as passing fixtures.
A widened criterion that reports nothing is indistinguishable from a broken one — this
card's own subject. Three things make this one falsifiable rather than silently green.
1. The green verdict counts what the criterion found, so a run that finds none is
visibly different from today's. Before / after on the same clean tree:
Note the seam census is 66 in both: this is a criterion extension, not a population
extension. No seam was added to the scan;
DRIVER_READ_CALLEESis untouched.2. Two-direction ablation on the real tree, planting #9261's pre-#9817 shape in
packages/objectql/src/engine.ts— direction predicted in writing before running, andobserved as predicted.
Mutation proven on disk by anchor counts in both directions plus sha256, never by an
editor exit code: removed-text count
1 → 0(measured inside the function), injectedmarker
0 → 1,sha256 fdcb0ecb… → 09e6df3e…,wc -l 12941 → 12940. The script assertedthe removed-text count was exactly 1 and would have aborted the reading as void otherwise,
and it carried
trap … EXIT INT TERMso a foreground-cap SIGTERM could not leave the treemutated.
The control is what proves the new criterion is what fires, not something else. Its
message, quoted from the gate:
Restore proven byte-identical:
sha256back tofdcb0ecb…, injected-marker count backto 0,
git status --porcelainempty for the file. No rebuild leg applies and this isverified rather than assumed — the gate is a pure AST scan reading
packages/objectql/src/**from disk, which the control run demonstrates empirically.3. Five self-test fixtures, both directions, so deleting the criterion reddens
--self-testrather than passing vacuously.expectInventspins the reported set, notjust the verdict — the #6451 lesson that
expectViolation: falseis a vacuous assertionfor a case that passes because an exemption fired.
[]into a pre-tryvariableisMissingTableError(pins the label
assigns ids = [] (type-discriminated))return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845pushaccumulation, deliberately still unjudgedSelf-test: 35 → 40 read-seam cases.
#8901 says "⛔ Do not re-open the #8845 decision." This PR does not, and here is the
check rather than the assertion:
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845 cohort is untouched. Its chain today is 66 / 47 / 39 / 28 / 18 at the"silent, valueless exit, not type-discriminated" level. This criterion's set is 1,
and that one seam is type-discriminated — so it is not a member of the 18. Not one
site moves between the two.
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845's criterionreddens 7 already-correct seams that deliver their failure by
push/++, andnothing declared can say so. This criterion reddens 0, so it needs no such
exemption and asks nothing of the missing vocabulary.
READ_SEAM_SCAN_ROOTSandDRIVER_READ_CALLEESare byte-unchanged; the seam census is 66 before and after. The read-seam invention rule has no declared failure-propagation vocabulary, so "the catch reported the failure" is uncheckable — the blocker measured under #8845 #8901's "drop the READ
vocabulary and this matches 91 of 314 catch clauses" is a warning about widening the
population, and this PR widens no population.
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845 header block is preserved verbatim, numbers and conclusion intact,including "the real answer is a declared propagation vocabulary rather than a looser
invention criterion." The new section sits next to it and says explicitly that it
closes one spelling of check-durability-degradation-log-level: the read-seam invention rule keys on
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845 and not check-durability-degradation-log-level: the read-seam invention rule keys onreturn, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845.If the reviewer reads it the other way, the honest description of what to revert is one
commit — no ledger, no baseline and no vocabulary entry was created, so nothing outlives it.
Verification at
1d2f19a77Gate union derived at this head with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(from the tree's own working-tree changeset, not a hand-written path list), all green,
each quoted from the gate's own verdict line, exit codes captured before any pipe:
check:durability-log-level—✓ read-seam invention (#5186 + #6451 + #9165, 3 package roots, vocabulary find/findOne/count): 66 read seam(s), none invents an unreported answer …and✓ self-test (read-seam invention rule): 40 case(s) passedcheck:nul-bytes—check-nul-bytes: OK (scanned 6557 text file(s) … no raw ASCII control bytes).check:agent-test-spelling—✓ … 0 violationscheck:cross-package-test-inputs—OK: 16 package(s) read outside themselves, all declaredcheck:entry-guard—✓ check:entry-guard: 143 scripts/ file(s) …check:parse-guard— exit 0check:pnpm-filter-targets—✓ … 135/168 --filter occurrence(s) … resolveHeavy commands ran through
bash scripts/pm/os-verify-lock.sh -c; verdicts read from itsVERDICTlines.No changeset —
skip-changesetChecked against the actual rule rather than assumed:
changeset-checkin.github/workflows/pr-automation.ymlexempts on theskip-changesetlabel (or thechangeset-release/mainPR) and has no path-based carve-out, so a label is the onlyroute. This PR touches one CI gate script under
scripts/, which sits in no publishedpackage (
scripts/has nopackage.json; the root manifest isprivate: true) — nothingpublished changes. Direct precedent: PR #9712, a gate-only change to this same family,
merged with
skip-changesetand the same justification.Corrections to the record this run measured
isMissingTableErrordiscrimination. The 2026-08-18re-pricing named finding(objectql): the roll-up summary index's registry read swallows into an empty index — and CACHES the invented emptiness, so every parent roll-up silently stops recomputing #9154's landed shape as the swallow-framing precedent; fix(objectql): the roll-up summary index's registry read propagates, and a failed read is never cached as an empty index (#9154) #9284's own body
shows it deleted both the
catchand the?.()outright. The precedent that actuallytransfers to an accumulator-answered catch is PR fix(objectql): discriminate a failed sys_organization probe from a genuinely empty one #9817 / probeInstallOrganizations answers a failed sys_organization read with a memoised empty list, so system-insert organization resolution fails open #9261, which landed exactly
if (!isMissingTableError(error)) throw error; ids = [];on one — and it post-dates there-pricing. Step 1's premise survives, on a better precedent than the one the card names.
B 13figure is stale. Re-measured today: the parameterless-catch red set is10, not 13 (
13was measured atc07d6e8b9on 08-18). Option B is untouched hereand stays parked behind its 10-site triage.
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845's census chain keeps shrinking: 66 / 47 / 39 / 28 / 18 today, against 19 on08-18 and check-durability-degradation-log-level: the read-seam invention rule keys on
return, so acatchthat degrades by FALLING THROUGH into an empty accumulator is structurally invisible #8845's recorded 25 at8664a2c. Still no second cohort.publishPackageDraftscarriesif (!isMissingTableError(error)) throw error;before itspush, and finding(objectql): the delete-cascade path's two registry-read swallows are the #8895 shape one layer up —
catch → returndisables every referential guard at once, silently #9002'sgetAllObjectscatches are gone (const objects: ServiceObject[] = this._registry.getAllObjects();).Generated by Claude Code
Generated by Claude Code