Skip to content

Commit 2fd23d0

Browse files
os-justinclaude
andauthored
fix(pm): post-stamped exits non-zero when the platform did not store the body (#18690)
Fixes #18663 Clause-②: no ## The defect `scripts/pm/post-stamped.mjs` already DETECTED the write the platform never stored — it read the artefact back, classified the difference as `mutated`, printed the byte counts and the first differing offset — and then returned `0`. On the filed hit (a seat-post body refresh, 2026-09-17) it sent 263,533 bytes, the platform stored 257,945 — the byte count of the version BEFORE that write — with the first difference at byte 6792, where the new block began: the old body had been kept whole and not one byte of the new one was there. A caller obeying every discipline this file's own header prescribes — no pipe, `$?` captured before any pipe — was still told the body had landed, and went on to post the comments that say the conclusion had already been written into it. That is the opposite direction from the pipeline trap the header already warns about: that one is about a caller throwing the code away, this was the tool handing out a zero. ## The discriminating predicate, and the verdict fields it reads One question decides the exit code: **did every byte this act sent reach the platform?** All three benign classes answer yes by construction and are untouched — `identical` by definition, `trailing-newline-stripped` gives up only newlines the platform does not keep, `footer-appended` adds without removing. `mutated` is the only class the rule reads, and it splits in two: ```js export function sentBodyLanded(readBack) { if (!readBack || readBack.class !== 'mutated') return true; return readBack.footerReAnchored === true; } ``` Two fields of `readBackVerdict`'s own verdict object, and nothing else: | field | what it is | |:---|:---| | `readBack.class` | the existing declared vocabulary — unchanged, and `classifyReadBack` is not touched | | `readBack.footerReAnchored` | new, and a MEASUREMENT: the stored body is the sent body — or its newline-trimmed form — followed by EXACTLY `PLATFORM_COMMENT_FOOTER`. `footerReAnchoring()` is the one spelling of that comparison, shared with the classifier's comment-only `footer-appended` class rather than written a second time | ⛔ Not a byte-count heuristic. The platform normalises blank lines around a trailing rule in both directions, so a length comparison answers a different question: "stored is shorter" is neither necessary (a re-anchored footer is LONGER) nor sufficient (an equal-length substitution loses just as much). ⛔ And it does not need the pre-write body, which only `--body` ever holds: "the platform kept the old one" is one INSTANCE of the class, not its definition — whatever is stored, a byte that differs INSIDE the body this act sent is a byte this act did not get onto the platform. The same predicate therefore covers the filed hit, a truncation, a sanitizer substitution, and a `--comment` write. `--comment` **does** share the verdict: `main` builds one `readBackVerdict` for both acts and passes `mode`, so the same predicate judges both. A comment the sanitizer chewed now exits non-zero; the comment footer append it is built to forgive is already clean one class earlier. ⛔ What did NOT change: `classifyReadBack`'s classes and exemptions, the warning line, the offset line, `body_mutated` in `--json`, what is SENT, and the stamp contract. There is no retry logic. **The one shape that must stay at 0, and does.** A `--body` refresh where the platform appends its 58-byte footer classifies as `mutated` — the issue-body footer cell is unmeasured, and this PR does not forgive it in the classifier. It exits 0 because nothing was lost. Measured on the fixture, byte-for-byte the shape the seat hits on every seat-post refresh: ``` --- FOOTER RE-ANCHORED (control) --- exit=0 landed=true mutated=true ⚠️ read-back: sent 53 byte(s), stored 111 — the platform MUTATED the body. Read the artefact … first difference at byte 53: sent (end of body) | stored …\n\n---\n_Generated by [Claude Code](https://claude.ai/code)_ …and that difference is EXACTLY the platform's footer, appended: every byte sent IS stored. Exit 0. --- FILED SHAPE --- exit=4 landed=false mutated=true ⚠️ read-back: sent 53 byte(s), stored 48 — the platform MUTATED the body. Read the artefact … first difference at byte 20: sent …2 · the board as it stands now.\n | stored …1 · the board as it stood.\n ⛔ a byte this act sent is NOT the byte stored at that offset — the write did NOT land. Exit 4. ``` ## The exit register, before and after | code | before | after | |:---:|:---|:---| | 0 | written — including every read-back the tool could not vouch for | written, and everything sent is on the platform | | 1 | usage. Nothing written | unchanged | | 2 | the body broke the stamp contract, or a refresh would have voided an unread knock. Nothing written | unchanged | | 3 | `PREREQUISITE NOT MET` — no route, no token. No act at all | unchanged | | 4 | — | **`EXIT_NOT_STORED`** — written, and the platform did NOT store it | What a caller does with a 4 is in the header and in the tool's own stderr report: **re-read the artefact**; ⛔ do not retry blindly — the measured hit was a size refusal the platform never reported, so an identical second write reproduces it exactly and a retry loop writes that failure into the card over and over. ⛔ `unreadable` is deliberately NOT widened into the new code. "The platform returned no readable body" is a failure to VERIFY, not a measured failure to store; it keeps its `UNVERIFIED` line and its 0 until somebody measures what that cell means — the same reason the body-footer cell is not forgiven in the classifier. The boundary is recorded in the header, not quietly crossed. ## Pins New battery in the file's `--self-test`, registered the way the ten existing ones are (`battery()` opens it, `t()` attributes to the one most recently opened, the floor is evaluated last): - battery name, verbatim: 「the exit code: the read-back reaches `$?`, or it reaches nobody」 - registered: **24** cases · pinned floor: **24** in `SELF_TEST_BATTERIES` - `SELF_TEST_BATTERY_FLOOR`: 10 → **11** What they pin: the filed shape (stored = the pre-write body, first difference inside the sent body) ⇒ 4 · a body-mode footer re-anchoring ⇒ 0, and still `mutated` with its offset · a re-anchoring over a stripped trailing newline ⇒ 0 · a trailing-newline strip ⇒ 0 · an identical read-back ⇒ 0 · a real mutation underneath an appended footer ⇒ 4 (the footer masks no loss) · a chewed `--comment` read-back ⇒ 4, the `--comment` footer append ⇒ 0 · a truncation ⇒ 4 · `unreadable` ⇒ 0, not widened · the five register values distinct · the footer measured as exactly the declared bytes and not as a 58-byte delta (58 bytes of `x` answers `null`). ## Ablation From the committed fix, on disk, under a `trap`, with absolute paths: the predicate's last line `return readBack.footerReAnchored === true;` replaced by `return true;` — which is exactly what the tool did before this PR. ``` PRE blob=d0dc60f58e4ca3b2155b5c05e312649fd9ed5d3e (== HEAD:scripts/pm/post-stamped.mjs) anchor occurrences BEFORE: 1 anchor AFTER: 0 inject AFTER: 1 POST blob=428cfb46b3fd3d40037bc370a605ea9a2f3c5cef # the edit provably reached the file ABLATED EXIT=1 — ✗ post-stamped self-test: 6 of 257 case(s) failed, 0 floor problem(s). ✗ ⭐ THE FILED SHAPE: the platform kept the PREVIOUS body, so the sent bytes did NOT land ✗ ⭐ …and the exit code is the not-stored one, never OK ✗ ⭐ a real mutation UNDERNEATH an appended footer still exits non-zero — a footer masks no loss ✗ ⭐ the `--comment` read-back SHARES this verdict and is judged by it: a chewed comment does not exit 0 ✗ ⭐ the rule is a predicate over the verdict's own fields, never over the byte counts ✗ ⛔ a TRUNCATION is a loss too: a stored body that stops short of the sent one does not exit 0 RESTORED blob=d0dc60f58e4ca3b2155b5c05e312649fd9ed5d3e RESTORE PROVEN: blob == HEAD:scripts/pm/post-stamped.mjs and 'git diff HEAD' is empty RESTORED EXIT=0 — ✓ post-stamped self-test: 257 cases pass across 11 batteries ``` Direction predicted before the run and observed: **turns red**, and only in the new battery — the ten existing batteries stay green, so the ablation moves exactly what it claims to move. The six that fall are the six that read `exit`/`landed` on a lost body; the footer-re-anchoring controls stay green under the ablation *by construction* (a predicate that always answers "landed" still exits 0 there), which is why the filed-shape pins and not the controls are the ones that prove the fix. Restore is proven by blob hash and by an empty `git diff HEAD`, not by an exit code; `git checkout HEAD -- PATH` names HEAD explicitly so a polluted index cannot serve the mutation back. ## Self-test ``` $ node scripts/pm/post-stamped.mjs --self-test # = pnpm check:pm-post-stamped ✓ post-stamped self-test: 257 cases pass across 11 batteries — offline, no network, no token. EXIT=0 ``` 233 → 257 cases, 10 → 11 batteries. ## Derived gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree at `2f7df2abc` (no hand-fed path list; change set = `scripts/pm/post-stamped.mjs`, 1 path). **29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN**; every exit code captured with redirect-then-`$?`, never across a pipe. Reconciled with `--ran`: ``` ✓ dispatch-gates --ran: 29 derived famil(ies) accounted for — 29 run, 0 NOT-MEASURED (a DERIVED zero — all 29 recorded an exit code and none of them is 3). ``` All 29 exited 0: ``` node scripts/check-ci-filter-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 node scripts/check-scripts-symbol-anchors.mjs :: exit 0 node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0 node scripts/check-self-test-wired.mjs :: exit 0 node scripts/check-self-test-wired.mjs --self-test :: exit 0 node scripts/check-self-test-workflow-commands.mjs :: exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 node scripts/check-whole-set-label-write.mjs :: exit 0 node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:bash32-floor :: exit 0 pnpm check:cli-command-ids :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:entry-guard :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:parse-guard :: exit 0 pnpm check:pm-dispatch-gates :: exit 0 pnpm check:pm-post-stamped :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:ratchet-remedy-authority :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` Plus the repo-wide scan the dispatch asked for, run whole and not narrowed: ``` $ pnpm lint # = eslint . --no-inline-config, repo-wide EXIT=0 ``` And, beyond `pnpm check:nul-bytes` (exit 0 above), the author-side control-byte scan on the one changed file: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' scripts/pm/post-stamped.mjs` → no hits (exit 1). ⛔ Not a complete account of what CI runs here: the derivation also names 53 artifact-roster families, 11 declared-wide-population families, 14 pending-changeset families and 1 path-scheduled CI job, each outside the 29 and each printed under its own heading by a run without `--commands`. `skip-changeset`: `scripts/pm/**` is repo tooling and is in no package's `files[]` — nothing published moves. ## Acceptance notes - **To file.** The header declares the issue-body footer cell UNMEASURED — 「whether the platform synthesises a footer for a footer-less ISSUE BODY is unmeasured … a footer on a body read-back stays MUTATED until somebody measures it」 — and somebody now has: the skills seat reports hitting exactly that shape (sent N, stored N+58, the difference being the declared footer) on *every* seat-post refresh. The file's own stated condition for moving the cell is met and the cell has not moved, so `--body` keeps printing a MUTATED warning on nearly every write, which is the noise #18296 removed for the other classes. This PR deliberately does not move it (the card pins the exemptions as they are) and closes only the exit-code half. Dedupe words: `post-stamped`, `classifyReadBack`, `footer-appended`, issue body, unmeasured cell, `platform-readings`. - **Noted, not filed.** An `unreadable` read-back still exits 0, so a caller checking `$?` walks on with a write the tool could not verify. No repro is in hand — the platform would have to return a body-less object — so it is an observation, not a defect card; the boundary is now written into the header rather than left implicit. Successor: whoever lands the card above, who edits this same function. - out of scope: #18664 remains open — same live hit, different file. Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu --- _Generated by [Claude Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent f8eaf67 commit 2fd23d0

1 file changed

Lines changed: 240 additions & 21 deletions

File tree

0 commit comments

Comments
 (0)