Repository navigation
fix(pm): read-back judge collapses the read path's own-repo reference expansion before calling a body mutated - #24804
Merged
objectstack-fleet[bot] merged 2 commits intoOct 20, 2026
Conversation
… expansion before calling a body mutated A proxied REST read in a cloud seat container intermittently spells a bare `#N` as `owner/repo#N`. The stored bytes are unchanged, but classifyReadBack compared them byte-exact and answered `mutated` (exit 4 / relay `not-stored`), whose remedy sends a seat to re-send the expanded body. Before `mutated`, the judge now walks sent and stored in step and, only where the sent body opens a bare reference, allows one of the fleet roster's slugs in front of that `#` (one slug per body). If the collapsed body is one of the declared classes, the verdict is the new landed class `reference-expanded-on-read` with `afterCollapse`, `referenceExpanded` and `referencePrefix`; otherwise it stays `mutated`, with the offset taken after the collapse. post-stamped prints one stderr line naming the count on both output shapes, `--json` carries the fields, and the NOT STORED text warns against re-sending an expanded read. classifyReadBack's signature is unchanged, so fleet-write/dispatch.mjs's imported judge and its content-based comment lookup take the collapse with no edit there. Claude-Session: https://claude.ai/code/session_01HEdP5tPhqQfzwRmKDxwn85 Co-authored-by: Claude <noreply@anthropic.com>
… as named red cases Claude-Session: https://claude.ai/code/session_01HEdP5tPhqQfzwRmKDxwn85 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
维护者速读(终稿)· PR #24804 ·
|
objectstack-fleet
Bot
deleted the
claude/issue-24747-readback-judge-reference-expansion
branch
October 20, 2026 07:55
This was referenced Oct 20, 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 free
to 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 #24747
Clause-②: no
One file,
scripts/pm/post-stamped.mjs. The read-back judge (classifyReadBack) now undoes the read path's own-repository reference expansion before it calls a stored bodymutated. This is not a governed surface (scripts/pm/**). There is no changeset: nothing underscripts/pm/ships in any package'sfiles[], andskip-changesetis sent with this create. Opened as a draft; making it ready and queueing it are the owning seat's steps.The defect
In a cloud seat container, the proxy rewrites REST reads. A body read there intermittently comes back with a bare
#Nspelledobjectstack-ai/objectstack#N. Card #24729 recorded this, and PR #24744 added it toplatform-readings.md(配额段). Contract review 6283172819 (flag ③-2) escalated it. The stored bytes are unchanged, but the judge compares bytes exactly. So a body that was stored whole read back asmutated, which meant exit 4 onpost-stampedandnot-storedon the fleet-write relay row. That verdict's remedy then sends the seat to read the expanded body and send it back, which stores the expansion for real.What changed
referenceExpansion(sent, stored)(new, exported, pure) walks both bodies in step, starting from the SENT side. It allows the stored body to carry anowner/repoprefix before a#only where the sent body opens a bare reference. A bare reference is a#followed by a digit, at the start of the body or after a character that is neither an ASCII letter or digit nor/. The first prefix it meets fixes the prefix for the whole body. Every other character is compared literally, and the walk stops at the first other difference. It never does a replace over the stored bytes: a body may carry an author's own fullobjectstack-ai/objectstack#N, and a global replace would collapse that too, then disagree with a sent body that still spells it in full.OWN_REFERENCE_PREFIXES(new, exported) is the fleet roster's slugs, imported fromGOVERNED_REPOSrather than written out again.classifyReadBack({ sent, stored } = {})keeps its signature, so the existing self-test pin on it is unchanged. The existing arms run first. When none matches, the judge undoes the expansion and asks the same arms again:reference-expanded-on-read, carryingafterCollapse(the class the collapsed body reads as),referenceExpanded(the count) andreferencePrefix.mutated. Its offset is taken after the collapse, in sent bytes, so it points at the real difference instead of the first expanded reference. The count is kept beside it.READ_BACK_CLASSESgains the class.sentBodyLandedis untouched: it readsclassother thanmutated, so the new class givesbody_landedtrue andbody_mutatedfalse.readBackVerdictprints one clean line for the new class: the count, the prefix, the collapsed class and the length identity (bytes read − 26 × count = bytes collapsed). The identity is printed as a reading, never tested. When amutatedverdict carried a collapse, a fifth line says those references are not the difference.referenceExpansionNoteadds one stderr line naming the count.mainprints it on both output shapes, text and--json, whatever the verdict.--jsonread_backgainsafter_collapse,reference_expandedandreference_prefix.notStoredTextadds that a body read on this path must not be sent back as it reads.What is not widened: no footer arm changed, there is no tolerance band and no length test, and no new gate or label. A body that still differs after the collapse is
mutated.Dispatch hypotheses, as measured
repoparameter. The relay's judge call infleet-write/dispatch.mjs(judgeStoredBody) passes{ sent, stored }only, and that relay path is the one the expansion lives on. A judge that needed the repository would have left that path uncovered untildispatch.mjschanges. So the prefix is read off the stored body, limited to the roster, and must be one per body. The residual risk: a body whose every bare reference came back naming a sibling roster repository would pass. Nothing has been measured producing that, because the proxy spells the repository it read from. The exact cure is on the caller's side:readBackStrokealready holdspayload.repoand could hand it over.afterCollapse. A separate word matters because the relay row (readBackLine) prints the class. Reusingidenticalwould describe bytes that were not read. With its own word, the reading shows on the relay row with no edit todispatch.mjs. The combinations are pinned: footer appended (with and without the newline strip), re-anchored, blank collapsed (with the strip), and trailing newlines stripped.The locator half of the card
The card says 「The same collapse applies to the locator's bytes-based lookup」. Measured on this branch: fleet-write's content-based comment lookup in
readBackStrokepicks a comment byjudgeStoredBody(...).verdict === 'landed', which is this file'sclassifyReadBack. So that lookup gets the collapse without any edit todispatch.mjs. A probe ofjudgeStoredBodyon an expanded body answeredlanded reference-expanded-on-readfor bothissue_patchandcomment, and stillnot-storedfor the one-byte control.post-stamped's own relay lookup,pickRelayComment, is pinned the same way here.dispatch.mjsandexecute.mjsbelong to the sibling card #24755 and are untouched. As dispatched, the locator half of the card (anydispatch.mjs-side change) waits until #24755 lands and is taken up then; the items are listed under Acceptance notes.Verification (local, at
f0ecb34f62)node scripts/pm/post-stamped.mjs --self-test: exit 0, "724 cases pass across 25 batteries" (base46bb5e3e54: 679 across 24). The new battery has 45 cases and its floor is pinned at 45.pnpm check:pm-fleet-write-dispatch: exit 0, "222 cases pass across 20 batteries".node scripts/ablation-replace.mjsin WRAP mode. For every one the tool reported the mutation landed (anchor 1 → 0, blob changed), and afterwards that the restore held (blob equal to HEADdbdd62185e89,git diff HEADempty). Each self-test went red:const expansion = null;): exit 1, 20 of 724 red, including the filed shape, the director-post reading, all four combinations and the relay lookup.owner/repoinstead of the roster: exit 1, 2 red, including the prefix outside the roster./.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 34 commands from merge base46bb5e3e5. All 34 exited 0.--ranreconciled: "34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN". They are:--self-testand scan ofcheck-ci-filter-parity,check-closing-keyword-parity,check-comment-mask-corpus,check-declaration-mirrors,check-position-name-fold-loaders,check-scripts-symbol-anchors,check-self-test-wired,check-self-test-workflow-commands,check-whole-set-label-writeandbare-root-worklist;check:gatesagent-test-spelling,bash32-floor,cli-command-ids,cross-package-test-inputs,driver-memory-census,entry-guard,gitlink-declared,nul-bytes,parse-guard,pm-dispatch-gates(fast tier, 1913 cases),pm-post-stamped,pm-write-pace,pnpm-filter-targets,pnpm-flag-spellings,ratchet-remedy-authority,refd-timer-probeandwatch-hint-literal.pnpm exec eslint --no-inline-config scripts/pm/post-stamped.mjsexited 0. The repo-wide lint, the artifact-roster families and the type-check lanes are CI's and were not run here.origin/mainis ata43405d596, one commit ahead of the base. That commit touches one line of.claude/skills/pm-dispatch/references/filing-gate.md, which nothing here reads, andgit merge-treeis clean, so the branch was not merged.Acceptance notes
mutated, unless the raw size was counted in another unit; the raw body is needed to decide. Noted, not filed. Carrier: none.dispatch.mjs, the sibling card's file surface:judgeStoredBody's landed result dropsreferenceExpanded.notStoredTextremedy (「Go READ what is stored and send a body that can land」) carries no warning about undoing an expansion.payload.repoto the judge would make the prefix exact.Noted, not filed. Carrier: the
domain:skillsseat, to sequence after fleet-write: a relay stroke of up to 20 content-creating writes runs at the 3 s gap — write-pace rule ⑤ (batch gap 30 s) never applies on the relay path, so one stroke is a burst #24755 lands.维护者速读(草稿)
#N间歇展开成objectstack-ai/objectstack#N;判定器从发出的字节出发,把这种展开折回,再用原有规则判一次。对得上的读数归入新类别reference-expanded-on-read,算写入成功,退出码 0,并在 stderr 用一行报出展开个数。作者自己手写的全拼引用不会被误折。Generated by Claude Code