You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 39abe1d
Browse filesBrowse the repository at this point in the historyBrowse files
fix(pm): a size refusal is a refusal — post-stamped stops reporting a real 422 as a missing route (#18934)
Fixes#18843
Clause-②: no
`post-stamped.mjs` reported a real HTTP 422 from the platform as exit 3
`EXIT_PREREQUISITE_NOT_MET` — 「no route, no act at all」 — and prescribed
a
transport remedy. This gives the size refusal an exit class of its own,
whose
text carries the bytes SENT, the measured cap for THAT surface, and the
remedy
that works; and it homes the comment cap beside `ISSUE_BODY_LIMIT`,
which the
card says is the same card.
## The fixture, verbatim, and where it came from
Built from the record on probe objectstack#18826 — comment id
**5722505167**,
written 2026-09-17T23:19:18Z. ⛔ No new oversized write was made anywhere
to
reproduce this. Captured there from `POST /issues/{n}/comments` at
262,145 bytes:
```json
{"resource":"IssueComment","code":"unprocessable","field":"data",
"message":"Body is too long (maximum is 65536 characters)"}
```
## The two readings the dispatch carried as unmeasured, now measured
1. **The 422 message is NOT the same string on the two endpoints.** The
record
is explicit: the create side answers `Body is too long (maximum is 65536
characters)`, and `PATCH /issues/comments/{id}` answers the same
sentence with
a lower-case `body`; `POST /issues` says it twice. So the trigger is
case-insensitive rather than an equality — an equality pinned to the
create-side capital would have left the update side at exit 3. Pinned in
both
spellings, and the ablation below proves the flag is load-bearing.
⚠️ One correction to the dispatch's framing: `PATCH
/issues/comments/{id}` is
not a surface this tool ever writes. Its two write surfaces are
`POST /issues/{n}/comments` (`--comment`) and `PATCH /issues/{n}`
(`--body`).
Both caps are declared; the update-side spelling still matters, because
it is
the second measured spelling of the sentence the trigger keys on.
2. **The two caps live as two constants, beside each other in
`check-half-states.mjs`.** `COMMENT_BODY_LIMIT` is declared after
`MARKDOWN_BODY_BUDGET`, with its own bisection provenance. It is homed
there
rather than in `post-stamped.mjs` on a measured ground: five workflow
comments
(`cross-repo-issue-closer.yml`, `merge-queue-triage.yml`,
`scheduled-full-run-card.yml`, `test-nightly-tiers.yml`) already send a
reader
to `ISSUE_BODY_LIMIT` in that file for the number and warn against
re-deriving 65,536 — the folklore grows back wherever one of two caps is
kept
somewhere else. ⛔ Two constants and not one shared number: two surfaces
measured independently that agree is not one measurement, and the day
the
platform moves one a shared number would lie about the other.
## The exit register, before and after
| code | before | after |
|---:|---|---|
| 0 | written, and everything sent is on the platform | unchanged |
| 1 | usage | unchanged |
| 2 | the body broke the stamp contract | unchanged |
| 3 | PREREQUISITE NOT MET — no route, no token, no act at all |
unchanged, and it now prints what the platform said |
| 4 | written, and the platform did NOT store it | unchanged |
| 5 | — | **TOO LARGE — the platform ANSWERED and refused the body for
its size. Nothing written; shorten or split it. ⛔ Not a route problem.**
|
`EXIT_TOO_LARGE = 5` is declared in `post-stamped.mjs` beside
`EXIT_NOT_STORED`.
The usage text, the not-stored text and the prerequisite text each name
it, so a
reader of a 3 or a 4 learns the fifth code exists.
## The reader test
A seat that sends a 300 KB comment body now reads: **exit 5, 307,200
byte(s)
sent, cap 262,144 bytes for a comment, 45,056 byte(s) over it, shorten
or split
the body** — and ⛔ not a word about routes or tokens. Verbatim from the
`--self-test` fixture:
```text
post-stamped: TOO LARGE — the platform ANSWERED this write and REFUSED it: the body is over the cap
for this surface. NOTHING WAS WRITTEN.
Sent to #18843: 307,200 byte(s) as a comment (`POST /issues/{n}/comments`).
The measured cap for a comment is 262,144 bytes — UTF-8 BYTES, bisected on
objectstack#18826, one byte either side.
This body is 45,056 byte(s) over it.
The platform said: Validation Failed · Body is too long (maximum is 65536 characters)
⛔ Its number is FALSE in unit and value — the same fleet measured a 262,144-BYTE comment stored,
four times what that sentence claims as a maximum.
```
## What the class deliberately does NOT take
- **Not every 422.** The issues listing refuses deep pagination — page
99 stores,
page 100 answers 422 (measured 2026-08-31, recorded in
`check-half-states.mjs`) — and that refusal is about a cursor. It keeps
its 3,
pinned as a control, as do the same sentence on a 403, a 500 and a 200.
- **Not the platform's number.** The trigger stops before `maximum is`,
so
keying on a false clause cannot un-classify the refusal the day GitHub
corrects its own text; a text carrying only that clause is pinned NOT to
classify.
- **Not the read path.** A read sends no body, so the `--body` pre-read
keeps its
PREREQUISITE reading unchanged.
- **Not a widened `--body` story.** Over the cap, `PATCH /issues/{n}`
refuses
SILENTLY (200, old body kept, #18793), which is exit 4's population;
only a
422 the platform actually answers reaches 5.
## A refusal the cap does not explain
If the platform ever refuses a body at or under the measured cap, the
text says
the refusal CONTRADICTS the reading and asks for a re-measurement rather
than
printing an overage of zero — `over` is `null`, never 0. The ordinary
over-cap
text is pinned NOT to carry that sentence.
## Self-test counts
| battery ledger | before | after |
|---|---:|---:|
| `post-stamped --self-test` cases | 349 | **402** |
| `post-stamped` batteries (floor) | 13 | **14** |
| new battery `the size refusal: …` (registered/floor) | — | **53 / 53**
|
| `check-half-states --self-test` cases | 4,963 | **4,978** |
| battery `ISSUE_BODY_LIMIT measured cap` (registered/floor) | 40 / 37 |
**55 / 52** |
The check-half-states "before" is the merge base `d5ccfb4e3`: 4,978
minus the 15
cases this PR registers. Two independent readings agree on it — the
dispatch
recorded 4,963 at PR #18911's head, and this branch measures 4,921 at
its own
branch point `625db0e85`, which is before #18911 landed. Both floors
RISE by
exactly the number of cases added; neither is held flat.
## Ablations — each mutation proven on disk, each restore proven against
HEAD
All four through `scripts/ablation-replace.mjs`, which asserts the
anchor count
fell, the blob hash changed, and after restore that the blob equals
HEAD's and
`git diff HEAD` is empty.
| mutation | expected | observed |
|---|---|---|
| drop the `i` flag from `BODY_TOO_LONG_RE` | red | **19 of 402 cases
fail by name**; the lower-case pin stays green, so the ablation
discriminates |
| put `ROUTE_REMEDY` in the size text's `Fix:` line | red | **exactly
the 2 held-apart pins fail** |
| `COMMENT_BODY_LIMIT` 262144 → 65536 | red | **5 of 4,978 cases fail by
name** |
| delete `UTF-8 BYTES` from the comment cap's docblock | red | **exactly
1 pin fails** — and the sibling docblock also contains that phrase, so a
mis-pointed extractor would have stayed green |
⭐ **The first ablation found a defect in this PR's own first draft.** It
came
back GREEN: the classifier was rebuilding the regex from `.source` with
the
flags retyped at the call site, so the exported constant documented a
rule it
did not carry and every case-insensitivity pin was vacuous. Fixed in
`36ba41a64`, after which the same ablation turns 19 cases red. A second
pass
(`290acfad0`) made those pins fail by NAME rather than crash on a
TypeError
before the verdict machinery ran.
## Gates
Derived in the worktree with
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`
at HEAD `0e5e054d3`: **39 families, all 39 run, all exit 0**, exit codes
captured
by redirect-then-`$?`. `--ran` reconciliation: 39 derived, 39 run, 0
UNRUN. The
first derivation (before the merge, one path) gave 29; adding
`check-half-states.mjs` grew it by 10 and those were run too. Each
edited
script's own `--self-test` is in that set
(`check:pm-post-stamped`, `check:pm-half-states`). `git grep` of both
filenames
under tests finds no separate suite — the `--self-test` IS the suite.
**ESLint was not narrowed at all.** The repo-wide run is the one that
was taken:
`pnpm lint` (= `eslint . --no-inline-config`, this repo's only style
authority)
**exit 0** at HEAD `0e5e054d3`. The targeted run over the two changed
files is
there too — 2 files, 0 errors, 0 warnings — but it is not what this PR
rests on.
## Acceptance notes
- **Exit 3 still hosts other refusals the platform ANSWERED.** The deep
pagination 422 is the measured one, and its remedy text is a route
remedy just
as wrongly. ⛔ Not filed: it is unreachable through this tool (a comment
list
would need 10,000 comments to reach page 100), so it is an observation,
not a
reproducible defect.
- **`rest()` now surfaces the platform's sentence on every non-2xx**,
which is
the same defect family (the tool discarding the answer it was given) and
is
mechanically part of this change; it is not a separate fix.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
// docblock — unit, both sides of the bracket, the date and the probe issue —
24376
24416
// because a number with no provenance is what got re-derived from memory the
24377
24417
// first time.
24378
-
'ISSUE_BODY_LIMIT measured cap': 37,
24418
+
'ISSUE_BODY_LIMIT measured cap': 52,
24379
24419
});
24380
24420
24381
24421
/** The floor on the ROSTER itself — how many batteries must be declared at all. */
@@ -28559,6 +28599,32 @@ async function selfTest() {
28559
28599
b(BATTERY18664, '#18664 cap: ⛔ the retired 65,536 is gone', ISSUE_BODY_LIMIT === 65536, false);
28560
28600
b(BATTERY18664, '#18664 cap: …and the measured cap is four times it', ISSUE_BODY_LIMIT === 65536 * 4, true);
28561
28601
28602
+
// #18843: the COMMENT surface's cap, declared beside its sibling. Pinned in
28603
+
// THIS battery because what the battery floors is a measurement and its unit,
28604
+
// and the sibling is the same measurement on the other surface — ⛔ not in a
28605
+
// battery of its own, which would leave the name-roster pin below describing
28606
+
// a ledger it no longer covers.
28607
+
const commentCapDocblock = (() => {
28608
+
const at = selfSource.indexOf('\nexport const COMMENT_BODY_LIMIT =');
28609
+
const open = at < 0 ? -1 : selfSource.lastIndexOf('/**', at);
28610
+
return at < 0 || open < 0 ? '' : selfSource.slice(open, at);
28611
+
})();
28612
+
b(BATTERY18664, '#18843 provenance: ⭐ the extractor really found the comment cap\'s own docblock', commentCapDocblock.startsWith('/**') && commentCapDocblock.endsWith('*/'), true);
28613
+
b(BATTERY18664, '#18843 provenance: ⛔ …and not the whole file, which would satisfy every pin below with its own text', commentCapDocblock.includes('export const'), false);
28614
+
b(BATTERY18664, '#18843 provenance: the docblock names the UNIT', commentCapDocblock.includes('UTF-8 BYTES'), true);
28615
+
b(BATTERY18664, '#18843 provenance: …the landed side of the bracket', commentCapDocblock.includes('262,144 bytes STORED'), true);
b(BATTERY18664, '#18843 provenance: …the date it was taken', commentCapDocblock.includes('2026-09-17'), true);
28618
+
b(BATTERY18664, '#18843 provenance: …the issue it was measured on', commentCapDocblock.includes('objectstack#18826'), true);
28619
+
b(BATTERY18664, '#18843 provenance: …and that THIS surface refuses LOUDLY, which the issue body does not', commentCapDocblock.includes('HTTP 422'), true);
28620
+
b(BATTERY18664, '#18843 cap: the comment cap is the bisected value', COMMENT_BODY_LIMIT, 262144);
28621
+
b(BATTERY18664, '#18843 cap: …which is 256 KiB exactly, checkable by hand', COMMENT_BODY_LIMIT === 256 * 1024, true);
28622
+
b(BATTERY18664, '#18843 cap: ⛔ the retired 65,536 is gone here too', COMMENT_BODY_LIMIT === 65536, false);
28623
+
b(BATTERY18664, '#18843 cap: …and the measured cap is four times it', COMMENT_BODY_LIMIT === 65536 * 4, true);
28624
+
b(BATTERY18664, '#18843 pair: ⭐ the two surfaces agree — two bisections, one number', COMMENT_BODY_LIMIT === ISSUE_BODY_LIMIT, true);
28625
+
b(BATTERY18664, '#18843 pair: ⛔ …and each stands on its OWN probe, so neither provenance borrows the other\'s', capDocblock.includes('objectstack#18793') && capDocblock.includes('objectstack#18826') === false, true);
28626
+
b(BATTERY18664, '#18843 pair: ⛔ …the comment one likewise cites only the probe that measured it', commentCapDocblock.includes('objectstack#18793') === false, true);
28627
+
28562
28628
// The unit, at the primitive the guard is built on.
0 commit comments