Skip to content

fix(pm): a size refusal is a refusal — post-stamped stops reporting a real 422 as a missing route - #18934

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-18843-post-stamped-size-refusal
Sep 18, 2026
Merged

os-zhuang merged 5 commits into
mainfrom
claude/issue-18843-post-stamped-size-refusal

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

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:

{"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:

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 objectstack-ai/objectstack#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, [probe] #18664 — bisecting GitHub's real issue-body refusal boundary, in BYTES #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

…and the measured cap

post-stamped reported a real HTTP 422 「Body is too long」 as exit 3
PREREQUISITE NOT MET — 「no route, no act at all」 — and prescribed a transport
remedy: run this somewhere node's fetch reaches api.github.com with a writing
token. The write had already reached the platform, been read and been refused,
so that remedy sends the caller to another route to be refused identically.
The file's own header names the shape: a refusal text prescribing a refused
remedy is a tool arguing with itself.

The fifth code, EXIT_TOO_LARGE, is that class. It triggers on HTTP 422 plus a
refusal text naming the body's LENGTH, case-insensitively — the two measured
spellings differ by one letter — and never on the platform's own number, which
is false in unit and value. Its text carries the bytes SENT, the measured cap
for the surface they were sent to, the overage, and the remedy that can work:
shorten the body or split it. The route remedy and the size remedy are each one
constant, pinned held apart in both directions.

rest() no longer discards what the platform said, so a refusal this class does
not recognise still prints the sentence it was answered with instead of a bare
status code.

Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
An ablation dropping the case-insensitive flag from the declaration left every
case-insensitivity pin green: the classifier rebuilt the regex from `.source`
and retyped the flags at the call site, so the constant documented a rule it did
not carry. Tested in place instead, which the non-global pin already makes safe.

Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
… classifying

Under the case-flag ablation the battery died on a TypeError before the verdict
machinery ran: a crash is red, but it prints no named case and never reaches the
floor. The assertions read through the null the classifier is allowed to return.

Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
…s own bisection

The comment surface was bisected on objectstack#18826 to the same 262,144 bytes
the issue-body surface was bisected to on #18793 — two surfaces, two brackets,
one number — and post-stamped needs the comment one to name the cap in a size
refusal.

It is declared here rather than in post-stamped because this is already where
this fleet's measured body caps live: five workflow comments send a reader to
`ISSUE_BODY_LIMIT` in this file for the number, and the 65,536 folklore this
pair replaces grows back wherever one of two caps is kept somewhere else.

⛔ Two constants and not one shared number: two measurements that agree is not
one measurement, and the day the platform moves one surface a shared number
would lie about the other. The docblock-anchored provenance pins of the
`ISSUE_BODY_LIMIT measured cap` battery are extended to the sibling, including
the control that each provenance cites only the probe that measured it.

Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
@os-elon-musk os-elon-musk added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0e5e054d36b0547ccfd5d35d68b7b9739fbd8c8d

① Derived judgments

  • Surface: scripts/pm/post-stamped.mjs (+447 / −9) and scripts/pm/check-half-states.mjs (+67 / −1); five commits (the class, the regex fix the ablation caught, the fail-by-name pass, one origin/main merge, the constant). Off the governed register (check-governed-merges.mjs --test: NOT governed); no published package moves; no contract's accept/reject set moves. The PR feat(pm): name the two measured claim near-miss forms so the sweep sounds on the unreadable claims #18911 commit (9364e1b01) is an ancestor of the head — the serial on check-half-states.mjs held.
  • The class: EXIT_TOO_LARGE = 5, triggered by SIZE_REFUSAL_STATUS (422) AND BODY_TOO_LONG_RE = /body is too long/i on the platform's text — both halves required, so the deep-pagination 422 keeps its 3 (the header names it). The text carries the bytes SENT (Buffer.byteLength(rendered.body, 'utf8')), the cap of the surface written to (WRITE_SURFACES: comment → COMMENT_BODY_LIMIT, body → ISSUE_BODY_LIMIT), the overage and SIZE_REMEDY; ROUTE_REMEDY is its own constant and stays in exit 3's text only, pinned apart in both directions. Only the write path classifies (the pre-read keeps its PREREQUISITE reading). A refusal the cap does not explain (sent ≤ cap) says so and asks for a re-measurement instead of printing a zero overage — the reading that must move is the cap. rest() now attaches the platform's sentence (platformRefusalText: message + errors[].message) to every non-2xx error and exit 3's text prints it when present — the same defect family, mechanically part of the change, ⛔ no widening of exit 3's trigger. The regex is tested in place (.test on the declared non-global constant), which is exactly what the dev's first ablation caught: a copy rebuilt from .source with the flags retyped had made the declared i vacuous (fixed in 36ba41a64; the pins made to fail by name in 290acfad0).
  • The constant: COMMENT_BODY_LIMIT = 262144 declared beside ISSUE_BODY_LIMIT with its own provenance docblock (objectstack#18826: 262,144 stored / 262,145 refused; UTF-8 bytes measured with a multi-byte body); two constants on purpose — two bisections that agree are not one measurement — the seat agrees, and the docblock says why. The dispatch's unmeasured item ② is answered yes; item ① is measured: the two spellings differ in case (Body / body), hence the i flag; and the dispatch's naming of PATCH /issues/comments/{id} as a post-stamped surface was the seat's error — the tool writes POST /issues/{n}/comments and PATCH /issues/{n} only. The seat records that correction as its own.
  • Seat spot-check on a scratch worktree at this head: post-stamped.mjs --self-test exit 0, 402 cases / 14 batteries (the dev's before: 349 / 13; the new battery floors at 53); check-half-states.mjs --self-test exit 0, 4,978 cases, 'ISSUE_BODY_LIMIT measured cap' 55 / 52 (was 40 / 37: +15 pins, the floor raised by exactly the cases added). The dev's four ablations are on the PR body with on-disk proof; the seat did not re-run them (the pins they exercise ran in the self-tests above).

② Semver level

  • skip-changeset is correct: scripts/pm/** ships in no package's files[]. Clause-②: no on the claim (5725684699) and in the body; PM_SWEEP_REPO=objectstack-ai/objectstack node scripts/pm/check-clause2-carriers.mjs --pair 18934 at 2026-09-18T06:42Z: exit 0, one live claim, pr-body.clause2-line DECLARED no, no widening tell.

③ Boundary flags

  • Dev flags: no open questions; two noted, not filed items — (a) other answered 422s (deep pagination) still sit in exit 3 with a route remedy: an observation, unreachable through this tool — agreed, not a card; (b) the dispatch's framing correction — recorded above. Deviations accepted: skip-changeset written through label-write.mjs (same endpoint, with the four-step read-back); five commits instead of two (the ablation's catch is readable commit by commit); repo-wide pnpm lint run, exit 0. Gates: 39 derived / 39 run / 0 NOT-MEASURED at this head (check:pm-dispatch-gates detached, 1848 cases, 737 s). 0 MCP calls; 3 REST writes.
  • CI on this head at 2026-09-18T06:44Z: 22 success · 11 skipped · 3 in_progress — nothing red; the landing act reads it again.

Implemented-by: claude/issue-18843-post-stamped-size-refusal
Reviewed-by: session_01BTeBejoPUvRHN8WdAJC6oF

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Landing record — blocked at the channel, not at a pre-check (skills seat, session_01BTeBejoPUvRHN8WdAJC6oF) · 2026-09-18T07:05Z

Every check on 0e5e054d3 completed with none red (37 runs: success or rostered skips, read at 2026-09-18T07:02Z); the review of record is 5726259616 (PASS); --pair 18934 exit 0. The landing act — POST …/pulls/18934/ccr/ready_for_review then PUT …/pulls/18934/ccr/auto_merge {"merge_method":"SQUASH"} — was refused twice by this session's auto-mode classifier at 2026-09-18T07:03Z (once as the pair, once the ready flip alone; reason given 「Merge Without Review」), the same route that landed PR #18911 from this seat at 2026-09-18T05:38Z. Per references/rest-channel.md a classifier refusal is 「no channel」: the PR stays draft, ACCEPTED, and lands the moment a channel exists — this seat's, once the maintainer allows the two CCR calls, or the maintainer's own hand. ⛔ Nothing here is a finding against the PR.


Generated by Claude Code

This was referenced Sep 18, 2026
os-elon-musk pushed a commit that referenced this pull request Sep 18, 2026
…xy plan

`post-stamped`, `label-write` and `board-snapshot` each declare their own guard
variable and set it on the child they spawn, but they handed `process.env`
straight to `proxyRearmPlan`, which read the patrol's shared name. So an
inherited `OS_HALF_STATES_PROXY_REARMED=1` answered "already re-armed" for all
three — silently — and the run then bypassed the proxy and 401'd.

Each now passes `guard: PROXY_REARM_GUARD`, and the own-guard `if` below the
plan call goes: it sat after `if (!plan.rearm) return null;` and was never
reached for that case, so it was dead for the defect and redundant otherwise.
The child env is unchanged — each still sets its own name.

Measured A/B (`--dry-run`, no write), same box, same minute:

  OS_HALF_STATES_PROXY_REARMED=1 label-write …   before: 401, exit 3, no banner
                                                  after:  banner, exit 0
  OS_LABEL_WRITE_PROXY_REARMED=1 label-write …   after:  exit 3, and the FIRST
    line names the variable, the proxy and the 401 it would be mistaken for

Each file gains a self-test battery pinning both directions, the loud line, the
unchanged runner leg, and two structural properties. Floors rise with the
rosters (label-write 8→9, board-snapshot 10→12); post-stamped's floor line sits
inside PR #18934's open range and is left alone — its roster rises 13→14 while
the pinned floor holds at 13.

Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review September 18, 2026 11:42
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 39abe1d Sep 18, 2026
39 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-18843-post-stamped-size-refusal branch September 18, 2026 12:09
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…when it suppresses (objectstack-ai#18980)

Fixes objectstack-ai#18939

Clause-②: no

`skip-changeset`: the whole diff is `scripts/pm/**` (PM loop tooling).
Nothing in any
package's `files[]` moves, so nothing publishes.

## What was wrong

`proxyRearmPlan` (`scripts/pm/check-half-states.mjs`) read **one
hard-coded** guard
variable — `OS_HALF_STATES_PROXY_REARMED`, this file's own — while every
consumer sets
its **own** name on the child it re-execs. So any sibling instrument's
guard, once
inherited, answered "already re-armed" for a tool that had **never**
re-armed. The
suppressed run then took the bypassed route and answered **401 Bad
credentials** on
every endpoint, `/rate_limit` included — a self-consistent story about a
dead
credential, and the recorded cost of reading it as one is about an hour
spent treating
a live channel as dead.

Two separate halves, and the second one is the expensive one:

1. the guard **name** was shared where it should have been per-caller;
2. the suppression was **silent** — `rearm: false, hint: false` prints
nothing at any
   consumer, so the run log carried no line pointing at the cause.

## The fix

```js
proxyRearmPlan({ env, execArgv, flagSupported, guard = PROXY_REARM_GUARD })
//   -> { rearm, hint, flag?, guarded?, reason }
```

- `guard` is a parameter defaulting to this file's own name. A caller
passes the
variable it actually sets, so only its own guard can stop its own
re-exec.
- The guard branch returns `hint: true` and `guarded: guard`, and its
`reason` names
the variable, the proxy and the 401 it would otherwise be mistaken for.
`hint` is the
one branch **every** consumer already prints, so the silence is gone
repo-wide —
  including for the nine consumers this PR does not edit.
- That branch can only fire on the anomaly: a real re-exec's child
carries
`--use-env-proxy` in its `execArgv` and is answered by the `routed`
branch above it,
  never by this one.
- `post-stamped.mjs`, `label-write.mjs` and `board-snapshot.mjs` each
pass
`guard: PROXY_REARM_GUARD` and drop the own-guard `if` that sat
**after**
`if (!plan.rearm) return null;` — it was never reached for this defect
and is now the
plan's own branch. Their child env is unchanged: each still sets its own
name.

⛔ No behaviour change where no proxy is configured. On a GitHub Actions
runner the
`no HTTPS_PROXY` branch returns first, so no child is spawned and no
extra word is
printed; re-pinned in both directions.

## A/B — measured, same box, same minute, `--dry-run`, no write

Exit codes captured redirect-then-`$?`, never across a pipe. No token is
printed by any
of these.

**Before** (`26c73fb4e`), the card's own repro, reproduced here:

```
$ node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 18932 --add finding --dry-run
ℹ️  re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) and node's fetch does not read it.
[…] ① 取现集 — objectstack-ai#18932 (open) carries 3 label(s) …
CONTROL_EXIT=0

$ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/label-write.mjs   (same args)
[…] ① read — COULD NOT READ: GET /repos/objectstack-ai/issues/18932 -> HTTP 401 (Bad credentials)
PREREQUISITE NOT MET …  ⛔ NOT MEASURED
PROBE_EXIT=3        ← and not one line said why
```

**After** (`e424b1cf9`), the same two commands plus the own-guard leg:

```
$ node scripts/pm/label-write.mjs   (same args)
ℹ️  re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) …
AFTER_CONTROL_EXIT=0

$ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/label-write.mjs   (same args)
ℹ️  re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) …
[…] ① 取现集 — objectstack-ai#18932 (open) carries 3 label(s) …
AFTER_SHARED_EXIT=0     ← the sibling's guard no longer suppresses this tool

$ OS_LABEL_WRITE_PROXY_REARMED=1 node scripts/pm/label-write.mjs   (same args)
ℹ️  OS_LABEL_WRITE_PROXY_REARMED=1 is set and this process is NOT routed through
    http://127.0.0.1:37101, so the one re-exec this run allows was already spent and
    every request below bypasses the proxy — if they answer 401 Bad credentials, that
    inherited guard is why, and unsetting OS_LABEL_WRITE_PROXY_REARMED is the fix
    rather than a new token. A refusal below may be about the route, not this container.
[…] ① read — COULD NOT READ: … -> HTTP 401 (Bad credentials)
AFTER_OWN_EXIT=3        ← still exit 3, but the cause is now the FIRST line on stderr
```

And the half that needed no edit — an **unedited** consumer,
`sweep-stale-finding.mjs`:

```
$ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/sweep-stale-finding.mjs --dry-run
ℹ️  OS_HALF_STATES_PROXY_REARMED=1 is set and this process is NOT routed through … 401 …
sweep-stale-finding: PREREQUISITE NOT MET — GET /repos/…/issues?… -> HTTP 401
EXIT=3
```

Its re-exec is still suppressed (it passes no `guard` — see the table),
but it is no
longer suppressed **in silence**, which is what the triage asked the
deliverable to
contain.

⛔ Not measured, and not claimed: whether a real seat's process actually
inherits one of
these variables from a sibling. The card flagged that as unmeasured and
it stays
unmeasured — every leg above sets the variable by hand.

## The 13 consumers, read at `26c73fb4e`

`git grep -l proxyRearmPlan scripts` lists 14 files; one of them defines
the plan, so 13
consume it. Only the three in the first row are edited.

| consumer | shape | after this PR |
|---|---|---|
| `pm/post-stamped.mjs`, `pm/label-write.mjs`, `pm/board-snapshot.mjs` |
own guard name, `process.env` handed to the plan raw | **edited** — each
passes its own `guard`; cross-suppression gone, and a suppressing guard
speaks |
| `check-single-claim-paths.mjs` (`proxyPlanEnv()`),
`pm/check-expected-skips.mjs`, `pm/check-prior-rulings.mjs` | map their
own name **onto** the shared default, then call | unchanged and still
correct — the default is untouched, pinned in this PR from the plan's
side; they gain the loud line for free |
| `pm/changeset-deadline-census.mjs`, `pm/check-clause2-carriers.mjs`,
`pm/sweep-closed-cards.mjs`, `pm/sweep-stale-finding.mjs` | own guard
name, `process.env` raw — **the same live defect** | **not edited** (out
of this card's ruled surface): still cross-suppressible, but no longer
silently. Filed as a finding |
| `check-issue-citations.mjs` | calls the plan for refusal **text**
only; never re-execs | unaffected |
| `pm/check-governed-merges.mjs` (declares a second plan of the same
name), `pm/ci-failure.mjs` (imports it) | a separate implementation
reading `OS_GOVERNED_MERGES_PROXY_REARMED`, shared across those two
files | untouched by this PR; the same one-name-two-scripts shape one
layer over. Filed as a finding |

## Self-test counts, before / after

| file | cases | batteries | floor |
|---|---|---|---|
| `pm/check-half-states.mjs` | 4963 → **4975** | 6 → 6 (new cases are
unbatteried `t()`) | 6 → 6, every battery pin holds |
| `pm/post-stamped.mjs` | 349 → **360** | 13 → **14** | 13 → 13 (held,
see below) |
| `pm/label-write.mjs` | 64 → **75** | 8 → **9** | 8 → **9** |
| `pm/board-snapshot.mjs` | 145 → **156** | 11 → **12** | 10 → **12**
(it had drifted below its own roster) |

`post-stamped`'s `SELF_TEST_BATTERY_FLOOR` line sits inside PR objectstack-ai#18934's
open hunk range,
so it is deliberately **not** touched: the roster rises to 14 while the
pinned floor
holds at 13. Both sides stay green whichever lands first, and objectstack-ai#18934's
own bump to 14
still applies. Every new battery is declared, registered and floored.

Each of the three callers gains the same battery — *the re-exec guard:
the name this
tool sets, and the patrol name that must not silence it* — pinning: this
tool's guard is
not the patrol's; the patrol literal pinned there **is** the plan's
default (so renaming
the shared constant reds the pin instead of leaving a phantom); a
proxied run with no
guard re-execs; its own guard suppresses; the patrol's does not; the
suppression prints
a line naming the variable and the 401; the runner leg is unchanged; and
two structural
properties — the dispatch really hands the plan this file's guard, and
the plan is
imported rather than restated.

## Reader test

A seat whose parent process leaked `OS_HALF_STATES_PROXY_REARMED=1` runs
`label-write`
and reads its **banner**, not a 401 story. If the leaked variable is
instead that tool's
*own* guard, the seat still gets exit 3 — correctly, the re-exec really
was spent — but
the first line on stderr names the variable and says unsetting it is the
fix, so nobody
goes looking for a new token.

## Acceptance notes

Noted while reading the surface, ⛔ not fixed here (outside this card's
ruled surface),
reported to the seat for filing:

- Four more consumers carry the **identical** defect and were not in the
card's count:
  `pm/changeset-deadline-census.mjs`, `pm/check-clause2-carriers.mjs`,
`pm/sweep-closed-cards.mjs`, `pm/sweep-stale-finding.mjs`. Each declares
its own guard
and hands `process.env` to the plan raw. One line each would fix them.
The card
explicitly read **0** own-guard names in `check-clause2-carriers.mjs`;
the name is
  there at `:816`.
- `pm/check-governed-merges.mjs` declares a **second** `proxyRearmPlan`
rather than
importing the patrol's, and shares its single guard name with
`pm/ci-failure.mjs` —
the same cross-suppression shape between those two files, in a plan this
PR does not
  touch.
- `noted, not filed:` the card's 「装饰性」 reading of the three own-guard
constants is
half right — each **is** read, by its own file and by nothing else. No
carrier: this
  is a correction to a card body, not a defect in the tree.

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

---------

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/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants