Skip to content

fix(pm): the issue-body footer cell is measured — give each footer shape its own clean class - #18786

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-18693-issue-body-footer-cell-measured
Sep 17, 2026
Merged

os-justin merged 1 commit into
mainfrom
claude/issue-18693-issue-body-footer-cell-measured

Conversation

@os-justin

@os-justin os-justin commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #18693

Clause-②: no

What was left after PR #18758

PR #18758 (#18709) made $? answer 0 for both footer shapes and took the prescription out of the status line. What it explicitly did not touch is the CLASS: the body-mode footer append and the trailing-rule re-anchor stayed mutated, --json kept reporting body_mutated: true, and the header kept declaring the issue-body footer cell UNMEASURED with two pinned controls holding it there. So the tool said two things about one set of bytes on nearly every write a seat made — "everything sent is on the platform" in $?, "MUTATED" on stderr — and a warning that fires on nearly every write is one nobody reads, which is what the read-back exists to be read.

This PR moves the cell, on a measurement.

① The controlled contrast — the triage seat's first ask (5716957331)

Two --body sends to one artefact this act owns (the body of this PR, written through PATCH /repos/OWNER/NAME/issues/NUMBER, which is the endpoint --body uses for a card and for a PR alike), differing ONLY in whether the sent tail ends in the 58-byte footer block, each read back byte-exact by the tool itself:

arm sent tail sent stored delta first differing byte class on main 2085be2b2d class on this branch shape $?
A prose — no block at all 10749 10807 +58 10749 — the seam, where the stored body keeps going mutated footer-appended appended 0
B ends in the block plus one newline 10808 10808 0 10751 mutated footer-re-anchored re-anchored 0

Both arms are the same body; arm B is arm A plus 59 bytes at the tail (the 58-byte block and one newline) and nothing else — armB.startsWith(armA) holds. Each was sent by node scripts/pm/post-stamped.mjs --body=18786 --repo=objectstack-ai/objectstack --file=… --json, exit captured with redirect-then-$?, and the stored bytes were then fetched again independently and compared here:

  • A: stored === sent + PLATFORM_COMMENT_FOOTER — exactly true. The platform synthesised the block on a footer-less body, once, 58 bytes, on this channel.
  • B: stored === head + "\n" + PLATFORM_COMMENT_FOOTER — exactly true, and the stored body carries exactly ONE attribution block, not two. The newline this act sent after the block moved to before its rule: equal length, one byte moved, zero lost.

What the status line says on the same bytes, main against this branch:

ARM A  main  : ⚠️ read-back: sent 10749 byte(s), stored 10807 — the platform NORMALISED its own footer block
               and took nothing away. Class MUTATED, and ⛔ nothing here to go read the artefact for.   (4 lines)
ARM A  branch:   read-back: clean — the platform appended its footer (sent 10749, stored 10807)          (2 lines)

ARM B  main  : ⚠️ read-back: sent 10808 byte(s), stored 10808 — the platform NORMALISED its own footer block
               and took nothing away. Class MUTATED, and ⛔ nothing here to go read the artefact for.   (4 lines)
ARM B  branch:   read-back: clean — the platform re-anchored its own footer block: the newline this act sent
               after it moved to before its rule, and every byte sent IS stored (sent 10808, stored 10808)  (2 lines)

One more reading, taken for free on the way in and reported rather than acted on: the create of this PR (POST /pulls, body ending in the session-URL footer block plus a newline) stored 10840 against 10841 sent — the trailing newline stripped, nothing appended, exactly one attribution block. That is the trailing-newline-stripped cell, and it is a PR-body CREATE reading, which is #18622's surface, not this card's.

The variable that is NOT the sent tail, and the one that is left

The triage seat proposed "the sent tail ending in the block" as the trigger, from two --body=6015 sends that read IDENTICAL against this card's three --body=7623 sends at +58. That candidate is falsified in both directions, and neither reading needed re-taking:

Same endpoint, same shape of sent body (no block in the tail), opposite outcomes. So the live variable is the WRITE CHANNEL, or the identity behind it.

This act cannot vary it, and says so rather than guessing. Both GitHub credentials in this container (GITHUB_TOKEN, GH_TOKEN) are byte-identical proxy-issued placeholders that resolve to one identity, os-justin; the MCP GitHub write tools are refused to an os-dev by contract. There is one channel here and no second one to compare against. Naming it is the deliverable; measuring it needs a seat that holds the other channel.

And the tool does not need to know which channel it is on, which is what lets the cell move. The read-back compares exact bytes: a footer-less body that comes back unchanged is identical, and one that comes back with exactly the declared block appended is footer-appended. Those two cells are not confusable — the objection "a cell the tool cannot tell apart must not be forgiven" simply does not apply to this cell. Whichever way the channel goes, the verdict is right.

② The class decision

The body-mode append and the re-anchor leave mutated, and each measured shape gets its OWN declared word:

  • footer-appended — the stored body is the sent one, or its newline-trimmed form, plus exactly PLATFORM_COMMENT_FOOTER. Both acts now. The measurement it rests on: platform-readings.md :410 「建卡走 REST POST /issues:带页脚存活,无页脚合成恰一条(+58);回读后 PATCH 重送逐字节存下。」 and :411 「issue 正文 PATCH 识别按整块:送全块或不送页脚都存回恰一条,已有页脚归一末尾不复制。」 — :411 is the act --body performs — plus the live readings above.
  • footer-re-anchored — NEW, because footer-appended says "plus exactly the footer" and a moved newline added nothing; borrowing that word would make the vocabulary say something untrue about the bytes, which the file refused for good reason. Its measurement is :356/:357 「平台在尾部 --- 前后正反两向归一空行」 / 「送全块即触发该归一 ⇒ post-stamped 的 body 档把这点空白判 mutated,净零字节良性告警。」 and the four live artefacts PR fix(pm): teach footerReAnchoring the second re-anchor shape — a moved newline is not a lost byte #18758 pinned.
  • mutated now means one thing — nobody measured this — and sentBodyLanded reads the CLASS alone. body_mutated is false exactly where body_landed is true: one set of bytes, one answer, in $?, on stderr and in --json.

classifyReadBack therefore takes no mode. The act-shaped gate existed for exactly one reason — the issue-body cell was unmeasured — and an exact-bytes comparison cannot tell one act's write from another's anyway. Leaving the parameter behind would leave a knob a caller can set that decides nothing, which is the shape this repo files cards about; a pin asserts the signature carries no mode at all. The word for the surface survives where it is actually read: notStoredText still prints "comment" or "body", and the CLI hands options.mode to that.

Why this branch and not "rewrite the header to say what IS unmeasured": both were done. The cell moved because it is measured and distinguishable; the header now names the write channel as the thing nobody has measured, with the readings on each side, instead of naming the cell.

The card's sentence, split between two PRs

The card reads 「every seat-post refresh prints a MUTATED warning」.

  • PR fix(pm): teach footerReAnchoring the second re-anchor shape — a moved newline is not a lost byte #18758 closed: the prescription half. The line no longer tells the seat to go read the artefact for a shape the tool measured as benign, and $? stopped answering 4.
  • This PR closes: the word itself. The refresh prints read-back: clean — the platform appended its footer (sent N, stored N+58) and --json answers body_mutated: false. The interim reading the card carries — that the cell is unmeasured — is retired in the header, with the measurement and the channel caveat written in its place.

The pins, re-pointed

The two the card names, at their post-#18758 line numbers:

  • :2409 「⭐ THE CONTROL: the same append in BODY mode stays MUTATED — an unmeasured cell is not forgiven」 becomes ⭐ THE MOVED CELL: those same bytes from a BODY write are CLEAN too — a MEASURED cell is not warned about, beside a second pin that the two answers are now one (not-mutated exactly where it landed).
  • :2452's control, likewise: the seat-post refresh shape is footer-appended, no longer MUTATED, still exit 0, still measured as EXACTLY the declared footer and never as a 58-byte delta, and its line now names the append and carries both byte counts.
  • The re-anchor battery from PR fix(pm): teach footerReAnchoring the second re-anchor shape — a moved newline is not a lost byte #18758 keeps all four live artefacts and re-points to the new class: the class is footer-re-anchored and never footer-appended; the recorded first-difference offsets are still pinned, read from firstDifferingByte — the clean class prints no offset line, the same as every other benign class, so the verdict is two lines and readBack.offset is null.
  • sentBodyLanded is pinned on the class alone: a mutated verdict carrying footerReAnchored: true does NOT land. The field stays on the result as a measurement --json reports; it is no longer a second door to exit 0.
  • The declared-vocabulary probe went from five inputs/five classes to six/six.

The exit-4 controls: every one re-run, every one still red

PR #18758's battery is unchanged and green: a byte lost before the rule; a byte changed at equal length (with the explicit assertion that both sides ARE equal length); a loss inside the footer block; a footer whose link was rewritten; a sanitizer chew under a moved newline; a newline that came from nowhere; a newline inserted somewhere other than immediately before the block; a truncation that happens to end in the block. All eight still exit 4, and unreadable still exits 0 as UNVERIFIED.

The ablation

Deleting the one branch that turns a measured footer shape into a class, from the committed fix at cb21adf430:

HEAD blob hash: 177eeb648b23a8148e825a33277e785df182aa7e
marker occurrences BEFORE mutation: 1
marker occurrences AFTER mutation:  0
mutated blob hash: d487450089e132f109602c570c1e9a9892663497
ON-DISK PROOF: marker 1 -> 0, hash 177eeb648b… -> d487450089…
ABLATED self-test exit: 1
✗ post-stamped self-test: 30 of 349 case(s) failed, 0 floor problem(s).
restored blob hash: 177eeb648b23a8148e825a33277e785df182aa7e
git diff HEAD after restore: (empty — no path listed)
RESTORED self-test exit: 0
✓ post-stamped self-test: 349 cases pass across 13 batteries

30 of 349 red, and they are the right 30: the moved cell, the new class, the lines that name them, and the four live re-anchor artefacts' "it LANDED" and "measured as the re-anchor by name". Every exit-4 control stays green with the branch deleted — which is what a control is for; one that went red here would have been testing the branch, not the contract. 0 floor problems, so no battery went quiet. Restore is proven by blob-hash equality against HEAD and by an empty git diff HEAD, never by an exit code, under a trap … EXIT INT TERM with absolute paths, and an empty hash is treated as failure.

Self-test

347 cases across 13 batteries becomes 349 across 13. No battery floor moved; SELF_TEST_BATTERY_FLOOR and every per-battery count are untouched.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree, no hand-fed path list; change set 1 path, scripts/pm/post-stamped.mjs. Each exit code captured with redirect-then-$?:

0  node scripts/check-ci-filter-parity.mjs
0  node scripts/check-closing-keyword-parity.mjs
0  node scripts/check-closing-keyword-parity.mjs --self-test
0  node scripts/check-comment-mask-corpus.mjs
0  node scripts/check-declaration-mirrors.mjs
0  node scripts/check-declaration-mirrors.mjs --self-test
0  node scripts/check-scripts-symbol-anchors.mjs
0  node scripts/check-scripts-symbol-anchors.mjs --self-test
0  node scripts/check-self-test-wired.mjs
0  node scripts/check-self-test-wired.mjs --self-test
0  node scripts/check-self-test-workflow-commands.mjs
0  node scripts/check-self-test-workflow-commands.mjs --self-test
0  node scripts/check-whole-set-label-write.mjs
0  node scripts/check-whole-set-label-write.mjs --self-test
0  node scripts/pm/bare-root-worklist.mjs --self-test
0  pnpm check:agent-test-spelling
0  pnpm check:bash32-floor
0  pnpm check:cli-command-ids
0  pnpm check:cross-package-test-inputs
0  pnpm check:driver-memory-census
0  pnpm check:entry-guard
0  pnpm check:nul-bytes
0  pnpm check:parse-guard
0  pnpm check:pm-dispatch-gates
0  pnpm check:pm-post-stamped
0  pnpm check:pnpm-filter-targets
0  pnpm check:ratchet-remedy-authority
0  pnpm check:refd-timer-probe
0  pnpm check:watch-hint-literal

All 29 exit 0. pnpm check:pm-dispatch-gates was run DETACHED with its output to a file (745.9s on this box), never under a foreground timeout. Reconciled with --ran: 29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN — a DERIVED zero, since all 29 records carry an exit code. The gate list was re-derived after a fresh git fetch origin main (2085be2b2d) and is byte-identical to the first derivation. Repo-wide pnpm lint (eslint . --no-inline-config): exit 0, no findings, at cb21adf430.

Read, not folded in

This body was written four times, on purpose

The create, the two contrast arms, and this one. ① could not be measured without two sends to an artefact this act owns, and writing another seat's post to measure it is refused; the dispatch named this PR's body as the target. The two arms are the timeline events between the create and this write. This is the last write this act makes to this body.


Generated by Claude Code

…ape its own clean class

The header declared the issue-body footer cell UNMEASURED and held the
body-mode append in `mutated` on that ground, while the governed fact
table has carried the measurement since PR #17746 and the skills seat
re-measures it on every seat-post refresh. Since PR #18758 `$?` already
answered 0 for both footer shapes, so the tool said two things about one
set of bytes: "everything sent is on the platform" in `$?` and "MUTATED"
on stderr, with `--json` reporting `body_mutated: true` beside
`body_landed: true`.

`footerReAnchoring`'s two measured shapes are now two declared classes —
`footer-appended` and the new `footer-re-anchored` — for either act, so
`classifyReadBack` takes no `mode`: the comparison is exact bytes, which
cannot tell one act's write from another's, and the gate existed only
for the cell that is now measured. `sentBodyLanded` reads the class
alone. Every exit-4 control is unchanged: a byte lost before the rule, a
byte changed at equal length, a chewed footer, a rewritten link, a
newline from nowhere, a truncation ending in the block.

What is unmeasured is named instead: the WRITE CHANNEL. Two `--body`
refreshes of the same shape on the same endpoint the same day read +58
on this seat's channel and IDENTICAL on the triage seat's, and no act
here can vary that.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@os-justin
os-justin marked this pull request as ready for review September 17, 2026 20:58
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 94b3f37 Sep 17, 2026
65 checks passed
@os-justin
os-justin deleted the claude/issue-18693-issue-body-footer-cell-measured branch September 17, 2026 21:22
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ast 100 is UNJUDGED rather than a truncated claim pool (objectstack-ai#18799)

Fixes objectstack-ai#18683

Clause-②: no

## The defect

`scripts/pm/check-clause2-carriers.mjs` read a card's comment thread
with ONE request — `/issues/{n}/comments?per_page=100`, no `page=`
ladder, no short-read check — while the two sibling list reads in the
same file paged to a declared cap and answered `null` (UNJUDGED, never
clean) when they hit it. One file, two OPPOSITE defaults on "I did not
read everything", and the fail-OPEN one was the read that arbitrates
OWNERSHIP: the governing-claim pool, its membership, and the `Clause-②`
declaration read out of it all come from those rows. A thread past 100
comments handed the pool its first page and nothing said the tail had
been dropped, so a claim written past row 100 was not superseded — it
was never a candidate — and a superseded carrier governed in its place.

## The before-reading, on a 101-row fixture

Driven end to end through the real CLI against a stubbed board
(`--pair`, no network), on `main` `d9ba33df4c` (script blob
`d753e2a8cf06d8f72c436e0a1917b2fecb8be813`). Two fixtures, both 101
rows, both differing from a complete thread only past the page boundary.

| fixture | BEFORE (`main`) | AFTER (this PR) |
|---|---|---|
| 100 claim-free rows, the 101st the only `Claim:` | `card-comments: 100
row(s)` · `claim.selected: none — no comment on this thread carries a
line beginning \`Claim:\`` · **exit 4, row C2 `absent`** |
`card-comments: 101 row(s)` · the 101st claim is the pool ·
`claim.clause2-line: DECLARED \`no\`` · **exit 0** |
| row 1 an older `Claim:` declaring `yes`, the 101st a newer one
declaring `no` | `claim.clause2-line: DECLARED \`yes\`` from the
SUPERSEDED carrier, which is not even listed as rejected · **exit 4, row
C3** | the newer claim governs, the older is listed REJECTED/SUPERSEDED
· `DECLARED \`no\`` · **exit 0** |

The second row is the fail-OPEN direction stated as a measurement: one
thread, two readings, and they disagree on the declaration itself.

## The ladder, and the cap

All three list reads now go through one `pagedListRead` helper — it
pages to a declared cap, stops on the FIRST short page (no wasted
request), and on the cap files the one shared `pageCapNote` sentence and
answers `null`. `readCarrierEvents` (`EVENT_PAGE_CAP` 10) and
`readPullFiles` (`FILE_PAGE_CAP` 3) keep their caps to the number; what
they gain is that the third read can no longer hold a different default.

`COMMENT_PAGE_CAP` is **10** pages = 1,000 comments. Sized on this
board, read 2026-09-17 off the open-issue list rows (550 rows listed,
cross-checked against `open_issues_count` = 550):

- longest open thread of any kind: seat post objectstack-ai#6015 at **895** comments —
nine pages;
- next four: objectstack-ai#12708 at 365, objectstack-ai#6023 at 241, objectstack-ai#6017 at 206, objectstack-ai#6024 at 187;
seat post objectstack-ai#7623 at 71;
- longest thread carrying a queue label: objectstack-ai#13799 at **117** (`pm:queue`,
p2, unassigned);
- longest card in the clause-② population (28 pairs the sweep derived
that day): objectstack-ai#17534 at **14**.

So ten pages clears the whole board today with a page to spare, and it
is the same ten `EVENT_PAGE_CAP` uses — a reader comparing two caps in
one file should have to remember one number.

## The input record

The diagnosis key stays `comments`, so every sentence already keyed to
it still finds its diagnosis. Two declared fields are added to
`INPUT_RECORD_PAIR_FIELDS`, one per thread this file reads:

```
pair.1.card-comments: 101 row(s)
pair.1.card-comment-pages: 2 of 10 page(s) requested — the ladder stopped on a SHORT page, so the thread is COMPLETE
pair.1.pr-comment-pages: 1 of 10 page(s) requested — the ladder stopped on a SHORT page, so the thread is COMPLETE
```

and, when the cap is what stopped the read:

```
pair.1.card-comment-pages: CAPPED — 10 of 10 page(s) of 100 comments each were requested and EVERY ONE came back full, so the tail is past the cap and the thread is UNREAD (UNJUDGED) — ⛔ never a truncated pool, ⛔ never a clean reading
```

A thread of exactly 100 rows and a thread whose tail was dropped are the
same `100 row(s)` in every other line the block prints; they differ
here, because the complete one stopped on a short page and the truncated
one did not stop at all. The request ledger PR objectstack-ai#18681 added shows the
same ladder from the other side — request objectstack-ai#3 is now
`…/comments?per_page=100&page=1` and objectstack-ai#4 is `&page=2`.

## The pins

A new `--self-test` battery, `objectstack-ai#18683: the card-comment read pages to a
cap — past 100 is UNJUDGED, ⛔ never a truncated pool`, 27 cases,
declared in `SELF_TEST_BATTERIES` with the roster floor raised 29 → 30.
It drives the ladder with an offline page server that reproduces
GitHub's own semantics and counts the requests; ⛔ no network. What it
holds: the 101st claim ENTERS the pool and GOVERNS, and its line is what
the limb reads; the same thread cut at 100 reads `absent` (the CONTROL —
the reading the un-paged read produced); the newer claim past the
boundary supersedes the older one inside it, and cut at 100 the
superseded carrier's `yes` is what the limb reads; a capped read is
`null`, which is neither `missing` nor `absent` nor a carrier but
`unreadable`; the ladder stops on the first short page (2 requests for
101 rows, 1 for a short thread, 2 for exactly 100 — a full page is
indistinguishable from a finished one); a page that came back unread
ends the ladder and the record says the cap was NOT what stopped it; the
input record declares and prints both ladder fields; the sibling caps
are untouched; and all three reads render ONE cap sentence.

## The census, and the triage's upgrade probe

Report-only, no state write. Over the 550 open rows (521 issues, 29 PRs)
read on 2026-09-17:

- open `pm:queue` / `pm:dispatched` cards: **274**, of which **1**
exceeds 100 comments — objectstack-ai#13799 at 117;
- all open issues over 100 comments: **8** — objectstack-ai#6015 (895), objectstack-ai#12708 (365),
objectstack-ai#6023 (241), objectstack-ai#6017 (206), objectstack-ai#6024 (187), objectstack-ai#6021 (145), objectstack-ai#6367 (127), objectstack-ai#13799
(117). Seven are `pm:seat` posts;
- open PRs over 100 comments: **0**; the longest is objectstack-ai#18638 at 10.

**The upgrade probe's result: the condition is NOT met today.** The
clause-② population is what a `--pair`/sweep derivation actually pairs,
not what carries a queue label: the sweep derived **28 pairs from 29
open PRs**, and the longest card thread among them is **14** rows
(objectstack-ai#17534). The two open PRs that mention a 100+-comment card in prose —
objectstack-ai#18786 (objectstack-ai#6015, objectstack-ai#7623) and objectstack-ai#18765 (objectstack-ai#6024) — deliver objectstack-ai#18693 and objectstack-ai#18652
respectively, both under 10 comments; driven live before and after, both
answer exit 0 with an identical pair reading. So no recorded `--pair`
verdict on this board today was taken on a truncated pool, and the
triage's p1 condition (「找到任一进入条款②认领池、评论数 > 100 的卡并驱动一次」) has no live
instance to drive. The exposure is one PR away rather than realised:
objectstack-ai#13799 is `pm:queue` at 117 and enters the population the moment a PR
delivers it.

The cost is unchanged by the ladder, measured on the same board: **64
reads for 28 pairs, before and after**, because every live thread fits
one page and the ladder stops on a short page. The live `--pair 18765`
input records differ in exactly three lines — the two request paths
gaining `&page=1`, and the two new ladder fields.

⚠️ One thing the two full sweeps do NOT compare: the sweep's finding
COUNT moved 4 → 3 between them, and that is the board, not this diff.
`needs:contract-review` was hung on PR objectstack-ai#18792 at `2026-09-17T21:04:46Z`,
between the two runs, closing the C1 split on objectstack-ai#18792 / objectstack-ai#17541 on its
own. The controlled A/B is the `--pair 18765` diff above.

## The ablation

Two legs, each from the COMMITTED fix, each proving the mutation reached
disk by blob hash and occurrence count before reading any result, each
restored under a `trap` with `git checkout HEAD --` and verified by hash
and an empty `git diff HEAD`. HEAD blob
`ccd5ad7c9a00fe703d644be261a24f1ed847915a`.

| leg | mutation | blob after | self-test |
|---|---|---|---|
| A — the ENTRY side | `COMMENT_PAGE_CAP` 10 → 1 |
`1a0a952d07748101937420006b9475042d93a5cb` | **exit 1, 11 of 865 red** —
the 101st-claim pins, the superseding pins, the request-count pins, the
input-record pin |
| B — the UNJUDGED side | the cap branch returns the pages that DID
arrive (the pre-fix fail-OPEN default) |
`c176dfe7e54e7de6bc45737487841a346f509b79` | **exit 1, 3 of 865 red** —
a capped read is no longer `null`, no longer `unreadable`, and files no
cap sentence |

Every red in both legs belongs to the new battery; nothing pre-existing
went red in either. A third, unplanned reading came for free: leg B's
first attempt was a `perl -0pi` substitution whose anchor contained a
`/`, so the edit silently did nothing — the on-disk proof refused it
with `ABLATION VOID: the edit did not reach disk` instead of reporting a
green as a measurement.

## Self-test

```
✓ check-clause2-carriers self-test: 865 cases pass
```

838 before, 865 after — the 27 the new battery registers, which is what
its floor pins.

## Derived gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, no hand-fed path list, re-derived after
each `origin/main` merge (identical list both times). All 34 run at head
`993cb89e18`, each exit code captured by redirect-then-`$?`:

```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-changeset-no-major.mjs --self-test :: exit 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:changeset-gate-self-tests :: 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-clause2-carriers :: exit 0
pnpm check:pm-dispatch-gates :: 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
pnpm lint :: exit 0
```

`--ran` reconciles 34 derived / 34 run / 0 UNRUN. `pnpm
check:pm-dispatch-gates` was run detached to a file — 1,788 cases,
748.6s on this box — and waited on in the foreground rather than under a
timeout, so it is a measurement and not a SIGTERM.

## Out of scope, deliberately

objectstack-ai#18764 (a decorated `**Claim:**` never enters the pool — the ENTRY side)
was read and NOT folded in: this card is WHICH rows reach the reader,
not what the reader does with them, and the two repairs touch different
lines. `claimRetractions` (PR objectstack-ai#18770, the EXIT side) was read for the
words it uses and not touched. The header's request-budget paragraph is
amended in the same commit, because it stated "2 reads per card" as a
fact and the thread is now a ladder — a cost statement that stopped
being true is the shape this file exists against.

`skip-changeset`: `scripts/pm/**` ships in no package's `files[]`, so
nothing published moves.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…t on the write-side footer cell (objectstack-ai#18837)

Fixes objectstack-ai#18622

`Clause-②: no`

Written by session `session_01Gqi43smmqjJ5sUrhfoPeKu` on branch
`claude/issue-18622-edit-footer-provenance` — the session id is stated
here, in prose, because this card is about exactly the reason that is
necessary.

This is a re-derivation against the tip plus **one row**, paid in place.
The cell has grown a lot since the card was filed at 09:27Z (PR objectstack-ai#18689,
objectstack-ai#18713, objectstack-ai#18741, objectstack-ai#18775, objectstack-ai#18808 all landed on this file today), and most
of what the card asked for is already carried.

## The carried-map — six readings against `origin/main` `ad1f94e8ec`

The bands re-derived on this head: the write-side footer cell is
:340–:359, the issue-body cell :409–:414.

| # | reading | verdict | carried by |
|:--|:--|:--|:--|
| a | `POST /pulls` stores the author's session-URL footer verbatim |
**CARRIED** | :350 「尾部已是该块则一字不追加,两通道各实测两向 ⇒ 建侧通道不是变量,判据是送出体尾部。」 read
with :348 「第四形:建 PR 两通道同判 —— 送出体尾部不是 `---` 加页脚块时,追加一条同形页脚。」 — 「同形」 is
what makes the stored block the author's, the session-URL spelling
included |
| b | `PATCH /pulls` appends the BARE form | **CARRIED** | :345 「裸 REST
`PATCH /pulls` 追加一个裸页脚并保留既有 session-URL 页脚,差恰 58 字节。」 and :346
「同路送无页脚正文存回恰一条(平台裸形)⇒ 该格处方是不送页脚,⛔ 不是不重送正文。」 |
| b′ | ⇒ so an edited body silently loses which session wrote it |
**RESIDUAL** — and, as the card states it, *falsified* on this tip |
nothing. See below |
| c | ⇒ durable attribution on an edited body lives in body PROSE, not
in the footer | **CARRIED — in the rule layer, by design** | `AGENTS.md`
:433 「Durable attribution lives in body prose or a comment.」 and
`.claude/agents/os-dev.md` :403 「耐久归属写进正文散文或评论,⛔ 不循环重贴页脚;完整读数住 AGENTS.md
同条。」 Nothing in :340–:359 / :409–:414 carries it, and nothing should |
| d | an issue-body `PATCH` synthesises one bare block on a block-less
body and re-anchors a bare-block tail to one | **CARRIED** | :412 「issue
正文 `PATCH` 识别按整块:送全块或不送页脚都存回恰一条,已有页脚归一末尾不复制。」 — both arms in one row;
with :411 for the create-side +58 and :413 for the rule-less
counter-case (total two) |
| e | a `PATCH` appends a bare block under a session-URL tail |
**CARRIED** | :345, verbatim above — the bare one is appended, the
session-URL one is kept, difference exactly 58 bytes |
| f | `POST /pulls` stores a session-URL tail byte-exact | **CARRIED** |
:350 for the 「一字不追加」 half. The trailing-newline trim reported beside it
in PR objectstack-ai#18786 (10841 sent / 10840 stored) is :356 「平台在尾部 `---`
前后正反两向归一空行:比对正文只按首个差异偏移,⛔ 不按长度。」 — which is precisely why the file
already refuses to let length be the verdict;
`trailing-newline-stripped` is a declared clean class in
`scripts/pm/post-stamped.mjs` |

### Why (c) is not written into the fact table

The card itself says 「这不是新规则」, and the ratchet's own maintainer ruling
for `references/` reads: *「one rule per ≤120-byte line, no rule already
stated in SKILL.md」*. `AGENTS.md` :433 already states the prescription,
and `.claude/agents/os-dev.md` :403 explicitly routes the full reading
to it — 「完整读数住 AGENTS.md 同条」. Writing 「耐久归属写正文散文」 into the fact table
would be a third copy of a rule that this corpus has deliberately given
exactly one home, the same content-ownership call the 2026-09-01 ruling
made for the red-window rule (「红窗规则由 `platform-readings.md` 配额段独家持有」).
So the new row **points** at `AGENTS.md` instead of restating it.

### Why (b′) is the residual, and why the card's own wording needed
correcting

The card reads 「一次正文编辑会静默丢掉「是哪个会话写的」」 — unconditionally. On this tip
that is **false as stated**, and :345 is what falsifies it: an
un-stripped `PATCH /pulls` **keeps** the existing session-URL block and
adds a bare one, +58 bytes — attribution intact, merely duplicated. The
loss happens on exactly one path: the one the cell itself prescribes at
:346–:347 (send no footer, strip *both* spellings). Then the stored
body's only attribution block is the platform's bare form, which carries
no session id.

A reader can get this wrong in both directions today: from :345 alone,
"my session-URL footer is preserved, attribution is safe"; from the card
alone, "any edit loses it". The residual sentence is the price tag on
the cell's own prescription, and nothing in the cell states it.

## The one row, and what paid for it

**Added**, at :348, immediately after the two rows whose prescription it
prices (118 bytes):

```
- ⇒ 代价是归属:裸形无 session id,按此剥净的 PR 正文不载明哪个会话写的;另置见 AGENTS.md。
```

**Retired**, the old :353 (114 bytes):

```
- 删页脚那条读数是唯一不合此判据的观察,通道已拒 ⇒ 活通道无反例,写后仍必回读。
```

Where its content survives, all of it inside the same cell:

| clause of the retired row | survives at |
|:--|:--|
| 「删页脚那条读数」 + 「通道已拒」 | :344 「MCP `update_pull_request` 包装器删掉 PR
正文的页脚块;该通道锁 1 已拒,读作历史。」 |
| 「活通道无反例」 | the same :344 — 「读作历史」 is what makes the single deleting
observation a dead-channel one |
| 「写后仍必回读」 | :355 「⇒ 形态随动作与送出体尾部变,改侧还随通道变;⛔ 不由任一条推其余,写后必回读。」 and :343 「⇒
PR 正文页脚不带前置横线,且写后回读正文 —— 那是唯一检测手段;评论两形皆可。」 |

⛔ No re-wrap was used as currency; ⛔ no ceiling was raised; net line
change is **zero**. Nothing outside the write-side footer cell was
touched — `git diff --stat` is `1 insertion(+), 1 deletion(-)`.

## Reserved bands — verified by content, md5 before and after

| band | content | md5 before | md5 after |
|:--|:--|:--|:--|
| :10–:12 | objectstack-ai#18469 PR-A (timeline `added_to_merge_queue` rows) |
`4b1a925b6f6004ebd8bf05ea4f477d24` | `4b1a925b6f6004ebd8bf05ea4f477d24`
|
| :29 | PR objectstack-ai#18775's zero-legacy-status row |
`9a1ac5fab8b1c0351d95472397eb1614` | `9a1ac5fab8b1c0351d95472397eb1614`
|
| :209–:215 | PR objectstack-ai#18666's band — its one hunk on this file, confirmed
from `/pulls/18666/files` (1/1, context :209–:215, changed line :212) |
`88505120d3f807422999b1e17e9e1266` | `88505120d3f807422999b1e17e9e1266`
|
| :432 | the `[Self-Approval]` row | `ed771ed2703b2ee5bcb02f29eddf4844`
| `ed771ed2703b2ee5bcb02f29eddf4844` |

All four byte-identical. Line numbers are unmoved too: the insert lands
at :348 and the retirement at the old :353, so every line from :354 on
keeps its number.

## The ratchet

```
before: .claude/skills/pm-dispatch/references/platform-readings.md is 466 lines (ceiling 466; headroom 0)
after : ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 466 lines (ceiling 466; headroom 0)
        ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0)
node scripts/pm/check-skill-line-ratchet.mjs :: exit 0
```

## Derived gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree, no hand-fed path list.
Change set: 1 path,
`.claude/skills/pm-dispatch/references/platform-readings.md`, three-dot
against merge base `ad1f94e8e`. Every exit code captured
redirect-then-`$?`.

```
0  node scripts/check-closing-keyword-parity.mjs
0  node scripts/check-closing-keyword-parity.mjs --self-test
0  node scripts/check-comment-mask-corpus.mjs
0  node scripts/pm/check-governed-queue-guard.mjs --self-test
0  node scripts/pm/check-harness-current.mjs --self-test
0  pnpm --filter @objectstack/lint run check:doc-formula-expressions   (SECOND run — see below)
0  pnpm check:agent-test-spelling
0  pnpm check:doc-authoring
0  pnpm check:driver-memory-census
0  pnpm check:nul-bytes
0  pnpm check:pm-governed-merges
0  pnpm check:pm-half-states
0  pnpm check:pm-skill-id-lint
0  pnpm check:pm-skill-ratchet
0  pnpm check:refd-timer-probe
0  pnpm check:required-contexts
0  pnpm check:skill-frame-sync
0  pnpm check:watch-hint-literal
```

`check:doc-formula-expressions` answered **exit 3 PREREQUISITE NOT MET**
on its first run — 「the workspace package `@objectstack/formula` is not
built」, with `@objectstack/lint` unbuilt as a second unmet prerequisite.
Built under the shared verify lock (`bash scripts/pm/os-verify-lock.sh
-c "NODE_OPTIONS=--max-old-space-size=4096 pnpm
--workspace-concurrency=2 --filter '@objectstack/lint...' --filter
'@objectstack/formula...' build"`, `VERDICT command-exit 0 · held the
lock 152s · waited 0s`) and re-run: **the 0 above is that second run.**

Reconciliation, `--ran` with a code recorded per family:

```
✓ dispatch-gates --ran: 18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED
  (a DERIVED zero — all 18 recorded an exit code and none of them is 3).
```

One family was run beyond the derived union, because the derivation
marked its roster ⛔ *「roster under .claude, which one of your paths is
in」* and said the silence is not evidence in either direction:

```
0  pnpm check:pm-settings-deny-roster
```

Repo-wide, at the final commit `deaa1d0fd7`:

```
0  pnpm lint     (node --stack-size=4000 eslint . --no-inline-config)
```

Byte discipline: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the
edited file returns no hits (exit 1), beyond `check:nul-bytes` above.

## Out of scope, noted here and reported to the seat

**To file (class a, a fact-table row a merged PR falsified).** :357
reads 「送全块即触发该归一 ⇒ `post-stamped` 的 `body` 档把这点空白判 `mutated`,净零字节良性告警。」
PR objectstack-ai#18786 (objectstack-ai#18693) landed at `94b3f37be3`, on this head: the body-mode
trailing-rule re-anchor is now its own declared clean class
`footer-re-anchored`, exit 0, printing `read-back: clean`, and
`body_mutated` is false. So that row is wrong on both the class word and
「告警」. It is a different reading in a different part of the cell, it
needs a fresh measurement rather than a mechanical rewrite, and this
card's dispatch bounds the diff to the residual — so it is reported, not
fixed here. Dedupe words: `post-stamped body mutated` ·
`footer-re-anchored` · `platform-readings 357` · `classifyReadBack
class` · 「净零字节良性告警」.

**Noted, not filed.** objectstack-ai#18686's create-side tension (:343's 「PR
正文页脚不带前置横线」 against :348's fourth form) was read and deliberately left
alone — it is the next card on this cell and its controlled comparison
was not run here. Successor: card objectstack-ai#18686, already open and queued behind
this one.

## Verification method note

This PR body was created with a tail of blank line + rule + the
session-URL footer block, which is :350's measured no-append shape on
the create side and `AGENTS.md`'s prescribed form — `AGENTS.md` wins
over :343 where the two differ, per its own precedence line. The body
was read back after the write.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants