Skip to content

fix(pm): read the ownership markers through the undecorated line, and make a near miss audible - #18756

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-18680-decorated-claim-release-markers
Sep 17, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-18680-decorated-claim-release-markers

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18680

Clause-②: no

The defect

CLAIM_COMMENT_MARKER and RELEASE_COMMENT_MARKER anchor the bare word at line start and tolerate only a leading blockquote, so a line written in the decorated spelling a seat uses when it bolds the directive — **Release:** … — begins with an asterisk and the record reads as ABSENT. Nothing goes red; the ownership rows (H2, H47, H66, H67) simply read a different history than the thread carries, which is the silent direction. #10102 made exactly this judgement for the other directive family (Blocked-by: / Restart-when:) and never for these two markers; PR #18678 landed H67 declaring the loss on its own row rather than widening, because widening moves three landed rows' populations and is its own card. This is that card.

Before-readings (reproduced offline, on the fetched specimen)

The live specimen is objectstack#16529 comment 5691473966 (os-try-charles, 2026-09-16T03:11:44Z), fetched through the REST proxy and never retyped. ⚠️ One correction to the card's prose: the **Release:** line is the 45th line of that comment, not its first — both markers are m-flagged and scan every line, so line position was never what hid the record. The asterisks were.

probe RELEASE_COMMENT_MARKER (bare)
the specimen's whole comment body false
the specimen's release line alone false
the SAME line with **Release:** replaced by Release: (control) true
**Claim:** … / `Release:` … / __Release:__ … / ## Release: … / - Release: … all false

undecorateProseLine — the #10102 function — was measured rather than assumed: its body is a single replace over a character class holding exactly a backtick and an asterisk. It removes backticks and asterisks and nothing else — not underscores, not a heading hash. That measurement is what decides which spellings this repair reaches and which become near misses.

Consumers of the two exported constants, all found by grep across scripts/: nine call sites inside check-half-states.mjs itself (H2, governingClaim, latestClaimComment, H34's guard, h44ArtefactShape, h46ClaimNamesBranch, latestMarkedComment — which is H47's, H49's, H50's, H53's and H67's shared resolver — SEAT_SIGNATURE_FORMS, and h66ReleaseVerdict), each fed a raw comment body or one raw line; and one cross-file importer, scripts/pm/check-clause2-carriers.mjs, which feeds CLAIM_COMMENT_MARKER a raw comment body. That importer is outside this card's file surface and is deliberately unaffected: the constants keep their bare semantics.

The ONE place decoration is handled

markerMatches(marker, text), declared beside the two markers. It tries the bare reading FIRST and short-circuits, then re-tests against the same undecorateProseLine the Blocked-by: family uses. All nine in-file call sites now go through it; ⛔ no regex was widened, ⛔ no second stripper exists, and the two constants still describe the bare directive (the cases that pin them still assert on them directly, and they stay green).

Two properties fall out:

  • Strictly additive, by construction rather than by inspection. The bare test short-circuits, so ⛔ no body that read before can stop reading. Measured on 770 live comments below: 0 regressions.
  • It refuses to undecorate through a markdown LIST ITEM. The shared stripper takes every asterisk, so a * Claim: bullet would become a directive while H20's pinned - Claim: stays refused. A list marker is followed by whitespace and a decoration is not; that is the whole discriminator, and it is pinned both ways.

The near-miss vocabulary — a line that looks like a marker makes a sound

Widening alone leaves the same silence one decoration further out, which is the triage's second half (comment 5716952460). OWNERSHIP_MARKER_NEAR_MISS_FORMS is a frozen, named roster in the register of #18560's SCHEMA_PROPERTY_FORMS; each member carries its own example fixture, and the roster is asserted EQUAL to a frozen list of ids, so a form added without a fixture reds and a form silently dropped reds.

id what example
heading the directive written as a markdown heading ## Release: …
list-item the directive written as a markdown list item - Release: …
underscore-emphasis emphasised with underscores, which the shared stripper does not remove __Release:__ …
inflected-word a spelling the marker's vocabulary does not carry Released: …
separator the canonical word with a separator that is not the canonical colon Release — …

ownershipMarkerNearMisses(commentRows) is the reader; it buys nothing (the sweep hands it threads other rows already paid for), files no finding and proposes no state. It reports on an unconditional summary clause (Ownership-marker near misses: …) naming the card, the comment id and the offending prefix, capped at five named entries with the remainder counted. A line the reading DOES read is ⛔ never a near miss — the two are complements by construction, so a future widening shrinks this census automatically.

H67's declared loss is retired in the same edit, on its row and in its summary clause: both said the decorated line was invisible, and that is no longer true.

Pins

New battery H2/H47/H66 decorated ownership marker, 98 cases, pinned at 94. The roster floor rose 4 to 5.

  • the fetched A consuming repo cannot ask "was this dist built from the tree I pin?" — the content stamp already exists, covers only the AMPLIFIERS list, and is not readable across the repo boundary #16529 specimen: refused by the bare marker, READ through markerMatches; the derived bare spelling of the same line matches both ways (control); a release is still not a claim; the record reads from any line of the body
  • the rows: H2 goes clean on a decorated claim (with the no-claim control still firing); latestMarkedComment locates a decorated release; H66 reads it on the canonical leg with destination pm:queue, quoted undecorated
  • what the stripper measures: backticked and bold-italic directives read; __Release:__ does NOT, and is a near miss instead
  • the firing controls of a widening, inside the same battery: seven bare spellings still read, prose containing the word still does not, Released: is still MALFORMED, the fullwidth colon still does not match (the 2026-08-11 ruling is untouched), a dash-written claim is still H34's row
  • the bullet guard: an asterisk bullet, a hyphen bullet, an ordered 1. marker and a blockquoted bullet all refused; the whitespace discriminator pinned as a pair
  • the vocabulary: every member driven against its own fixture — not read by either marker, reports naming its own form, with the comment id and the offending prefix — plus the counterfactual roster-equality pin, frozen-ness, distinct ids, and ⛔ no g and ⛔ no m flag
  • the summary clause: counts, named entries, the cap clause, unconditional rendering, render order, the forwarding contract, and ⛔ no undefined
  • the PR pm(half-states): H67 reads the queue for cards a PR already landed on #18678 pin, EDITED and not deleted: H67 ⚠️ loss: a DECORATED **Release:** line does not stand the row down becomes H67 ⚠️ loss CLOSED: … now STANDS THE ROW DOWN, as the bare one always did, and its companion flips from "the row DECLARES that blind spot" to "the row no longer DECLARES a blind spot it no longer has". Its two control cases are untouched and still green, because they assert on the marker CONSTANT — which this PR does not change.

Self-test: 4782 cases / 4 batteries becomes 4881 cases / 5 batteries (98 battery cases, plus one case the per-anchor summary-clause coverage loop registers for the new clause automatically).

Ablation

Revert the one call that routes the markers through the undecorated line (delete the undecorated leg of markerMatches, leaving the bare test alone), from the committed state, with an EXIT/INT/TERM trap restoring by absolute path.

Live-board delta — report-only, ⛔ no state write of any kind

Two full sweeps, node scripts/pm/check-half-states.mjs against objectstack-ai/objectstack: BEFORE on a detached worktree at 62bce5c29 (17:53Z to 18:01Z), AFTER on this branch (18:01Z to 18:09Z).

H2 / H47 / H66 verdicts: identical. H2 fired on #13597 and #15638 in both; H47 and H66 listed nothing in either. Eight rows differ between the two runs (H14 #18617, H38 #7623, H52 #18617 dropped; H1 #18709, H19 #18734, H36 #18414/#18720/#18741 appeared) and every one is board churn in the eight minutes between them — none reads an ownership marker.

#16529 specifically still lists on H67 in both, and the reason has nothing to do with the marker: its newest merge is now PR #18678 (merged 2026-09-17), which is NEWER than the 2026-09-16 release record, so "nobody has looked since the delivery landed" is a correct reading. Its row text did change — the declared loss is gone.

Because a sweep only judges threads it bought, the zero above understates the reading. So the same question was asked directly, over the 286 open pm:queue / pm:dispatched cards and their 770 comments:

And the counterfactual the rows themselves cannot show, offering the SAME live thread to both readings — 3 of 6 cards change:

card H47 H66
#16529 FIRES becomes clean (the release now answers the claim) none becomes pm:queue
#17852 FIRES becomes clean none becomes the maintainer
#15468 clean becomes FIRES (a bolded claim nobody has answered) unchanged
objectstack-ai/objectui#10102, #16233, #18143 unchanged unchanged

⛔ Nothing was written to any card, PR or label from either sweep, and ⛔ no verdict here is a proposal about any of those cards.

Gates

Derived from this worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (⛔ no hand-fed path list; change set: scripts/pm/check-half-states.mjs, one path). 38 families, every one run, exit code captured by redirect-then-$? before any pipe, reconciled with --ran.

37 of 38 exited 0, with the two qualifications named below. Notably: check:pm-half-states 0 · check:nul-bytes 0 · check:closing-target-claim 0 · check:commit-card-trailers 0 · check:self-test-wired 0 · check:scripts-symbol-anchors 0 · check:declaration-mirrors 0 · check:whole-set-label-write 0 · check:changeset-no-major 0 · check:pm-governed-queue-guard 0 · check:cross-package-test-inputs 0 · check:parse-guard 0.

⚠️ pnpm check:nul-bytes exited 1 on the first pass and was right to: an editing tool had materialised a backslash-u-0001 escape (written out in words here for the same reason) into a real 0x01 byte in the near-miss dedupe key — the exact slip that gate exists for. Fixed by writing the escape text (byte-identical at runtime), re-run green, and the rule's own grep -naP control-byte self-scan over the file returns nothing.

⚠️ pnpm check:pm-dispatch-gates is the one family whose self-test runs longer than this container's foreground ceiling: a first attempt reached 1768 green cases and was killed by the timeout wrapper at 560s (exit 124 = no verdict reached, which is NOT MEASURED and ⛔ not a red). It was re-run detached; its verdict is reported in this card's os-dev-report comment rather than guessed here. ⛔ Its diff-relevant half is unaffected either way — this PR touches neither dispatch-gates.mjs nor its fixtures.

Repo-wide pnpm lint (eslint . --no-inline-config): exit 0, as PR #18654 did.

Not in scope, read and left alone

Acceptance notes

  • H66's summary clause still carries the dated reading 「Measured 2026-09-16 over 29 threads on two boards, the canonical Release: line appeared ZERO times」. It names its date and its boards, so it stays true as written, but it was taken with the bare reader and this PR changes what a re-measure would find. Noted, not filed — the sentence is a dated measurement, not a live claim. Who would meet it: the next author of H66's buy-order or clause.
  • check-clause2-carriers.mjs reads CLAIM_COMMENT_MARKER against a raw comment body and therefore still cannot see a decorated claim. That is correct for this card's file surface (the constant is unchanged) and is a reading about that file, not a defect in this one. Noted, not filed; the seat decides whether that gate wants the same reading. Who would meet it: whoever next touches that gate's claim leg.

Generated by Claude Code

… make a near miss audible

`CLAIM_COMMENT_MARKER` and `RELEASE_COMMENT_MARKER` anchor the bare word at
line start and tolerate only a leading blockquote, so a seat that BOLDS the
directive — `**Release:** …` — writes a record no reader in this file could
see. The failure is the silent one: nothing goes red, the ownership rows just
read a different history than the thread carries.

Every site that asks whether a comment IS a `Claim:` or a `Release:` now asks
`markerMatches`, which tries the bare reading first and then the same
`undecorateProseLine` the `Blocked-by:` / `Restart-when:` family has used since
#10102. The two constants keep their bare semantics — they are the protocol's
spelling, a sibling gate imports one of them, and the cases that pin them still
assert on them directly. Decoration is handled in ONE place, and the reading is
a strict superset by construction: the bare test short-circuits, so no body that
read before can stop reading.

Undecorating is refused through a markdown LIST ITEM: the shared stripper takes
every `*`, so a `* Claim:` bullet would otherwise become a directive while H20's
pinned `- Claim:` stays refused. A list marker is followed by whitespace and a
decoration is not, which is the whole discriminator.

Widening alone would leave the same silence one decoration further out, so
`OWNERSHIP_MARKER_NEAR_MISS_FORMS` names five refused spellings, the sweep
censuses them off the threads other rows already bought, and an unconditional
summary clause names the card, the comment id and the offending prefix. A future
decoration is ADDED to that list rather than rediscovered from a silent row.

H67's declared loss is retired in the same edit — its row and its summary clause
said the decorated line was invisible, and that is no longer true.

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

An editing tool materialised the escape into a real 0x01 while the key was
written — the slip `check-nul-bytes` exists for. Byte-identical at runtime.

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 19:22
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 95e1745 Sep 17, 2026
39 checks passed
@os-justin
os-justin deleted the claude/issue-18680-decorated-claim-release-markers branch September 17, 2026 19:45
This was referenced Sep 17, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…drawn claim never governs (objectstack-ai#18770)

Fixes objectstack-ai#18719

Clause-②: no

## The defect

`scripts/pm/check-clause2-carriers.mjs` built the governing-claim pool
from `CLAIM_COMMENT_MARKER` alone and then ranked it by recency, so the
pool was closed under addition: a claim entered it and nothing ever took
one out. A RETRACTION carries no `Claim:` line of its own, so it was
never in the pool and could not remove the claim it retracts — which
means the one act the protocol calls explicit (「释放是显式动作」) was the one
act the ownership arbiter could not see. The repair is not a re-sort:
every ordering of a pool that still holds the withdrawn record picks the
withdrawn record, and the next retraction is exactly as invisible. What
changes here is MEMBERSHIP — the selector READS the retraction, and
membership is resolved before recency.

## The objectstack-ai#18373 counterfactual, re-derived offline

The six comment ids were fetched ONCE through the REST proxy and
replayed offline through `claimCarrierSelection` / `pairInputRecord`.
objectstack-ai#18373 itself is `needs-user-decision` and another seat's; nothing here
re-grades it.

| | BEFORE (`main`) | AFTER (this PR) |
|---|---|---|
| governing claim | `5717315121` (`os-bill`) — **withdrawn by its own
author 84 seconds later** | `5717143021` (`os-litant`) |
| branch it names |
`claude/issue-18373-include-bare-directory-provenance` — **absent from
origin** | `claude/issue-18373-type-source-resolution-bare-dir-include`
— the one ref `git ls-remote origin 'refs/heads/claude/issue-18373*'`
returns |
| the live claimant | listed **SUPERSEDED** | in the pool, governing |
| the withdrawn claim | not mentioned as withdrawn at all | listed
**RETRACTED**, naming `5717333576` |
| `claim.selected` author | `os-bill` | `os-litant` — the card's only
assignee |

The BEFORE row is produced by REMOVING the retraction comment from the
same six rows, so it is a reading rather than a description. The AFTER
`claim.selected` line now names the author, which is the point of the
last row: the LABEL face (assignee `os-litant`) and the SELECTOR face
(governing claim's author) are printed where a reader compares them
instead of reconstructing the contradiction by hand. The assignee itself
is still not read on this path, and the record says so.

## The routes, on the four axes

Triage ruled **A** out as the deliverable (「修法是让选择器读到撤回」), so the choice
is B, C, or both read as ONE predicate. **Recommended: B and C as one
predicate**, which is what this PR implements.

**A — leave it; the selection is an input record.**
*实际业务需求*: fails on measurement. The printed block is a record, but the
POOL is not: `cardDeclaration` reads the pool for the `Clause-②`
declaration, the governing branch is what the liveness rows probe, and
the record is what a seat reads to decide who owns a card. *项目长远合理性*: a
monotone set standing in for a mutable fact (ownership) cannot express a
withdrawal at any ordering. *防 AI 写错*: the worst axis — an arbiter that
answers confidently with the wrong owner is worse than one that
declines, which is the triage's 「一个只会做加法的池子,和一个没有池子,危险方向相反但都不安全」.
*创业阶段不扩散*: cheapest, and that is its only argument.

**B — the `Release:` directive only.**
*需求*: real, and already declared: `AGENTS.md` :408 and `SKILL.md` :468 /
:470 / :496 make `Release:` the act that takes a card out of a seat's
hands, and `check-half-states.mjs` already exports
`RELEASE_COMMENT_MARKER` for H47. *长远*: the best-shaped half — one
declared spelling, contract-first, nothing inferred. *防 AI 写错*:
strongest, because it is 声明即强制. *不扩散*: adds nothing new at all. **⛔ But
measured, B alone does not close the case it was filed for**: the objectstack-ai#18373
retraction carries NO `Release:` line — it is a bold opening line 「🚨
**撤回上一条认领(`5717315121`)…**」 posted with the assignee cleared. Under B
the counterfactual above stays red. B is necessary and not sufficient.

**C — skip a claim retracted by a later comment of the same author.**
*需求*: closes the measured case. *长远*: on its own C has no declared shape
— "retracts" still needs a predicate, and an unanchored one is a
natural-language matcher inside an ownership arbiter. *防 AI 写错*: C
without a declared act is the lenient-consumer shape the framework
rejects; its measured cost is in the next section. *不扩散*: C alone
invites the treadmill (objectstack-ai#16170's lesson — close one spelling, leave the
next exactly as silent).

**B ∧ C, as ONE predicate — the recommendation.**
*需求*: closes the measured case AND reads the act the protocol already
declares. *长远*: one sentence — a claim leaves the pool when a LATER
comment BY THE SAME AUTHOR retracts it — read through two channels,
which is the shape `check-half-states.mjs` already uses for a block
(`Blocked-by:` directive plus `PROSE_BLOCKER_ANCHORS`). An in-family
reading, ⛔ not a new mechanism. *防 AI 写错*: every loosening is refused in
the direction that would let one seat void another's ownership — same
author, strictly later, the act OPENS the line, the line NAMES the claim
by comment id, fail-closed on an unreadable author on either side;
under-reading leaves the claim standing, which is today's behaviour.
*不扩散*: no governed text moves, no new label, no new request, no new
field in the record roster, one file.

⛔ No protocol text is changed: `Release:` is already the act and its
marker is IMPORTED rather than restated. Whether the protocol should
REQUIRE the `Release:` spelling of a retraction — which would let the
prose channel be retired — is a governed-text question and is raised in
the report's `open_questions`, ⛔ not answered here.

## The three shapes, each with its control

| shape | reads | control |
|---|---|---|
| (1) a retraction NAMING the claim by comment id | leaves the pool |
the SAME words from a DIFFERENT author retract nothing |
| (2) a `Release:` line from the claim's own author, no id | leaves the
pool | the same `Release:` posted BEFORE the claim retracts nothing |
| (3) a `Release:` / retraction from a DIFFERENT author | retracts
nothing | the live claimant's claim still GOVERNS, and is not merely
un-rejected |

Also pinned: a retraction naming some OTHER comment id; an unreadable
author on the retractor, and on the claim; the act buried mid-sentence;
decoration in front of the act (「- ⚠️ **撤回** …」) which is not a
difference; bare `release` which is not on the anchor roster, because in
this repository a "release" is overwhelmingly a VERSION release.

**The measured trap that shaped the predicate.** An `includes`-style
anchor (the shape `PROSE_BLOCKER_ANCHORS` correctly uses, because that
reader only NOMINATES candidates it then resolves) fails here, and the
proof is on this very thread. `os-litant` — the seat whose claim the fix
has to PROTECT — wrote, inside the dev report that REPORTED this defect
(`5717738051`), a line carrying a retraction word and its own claim id:
「"question": "The governing-claim instrument now names a retracted
claim. … marks my dispatch's `5717143021` as SUPERSEDED …"」. Under
`includes` that line retracts the claim it is defending, same author,
later timestamp — the comment that filed the card would have silently
voided the ownership the card exists to restore. The first run of this
branch did exactly that, on the real fixture. ⇒ the act must be what the
line is ABOUT, so it OPENS the line after leading decoration is
stripped, and the line must NAME the claim by id. `os-litant`'s
5717775707 「⚠️ 但它暴露了一个**工具缺陷**:… 那条已撤回的 `5717315121` …」 is the same
shape from the other direction and is excluded twice over — by the
author test and by this one.

## The RETRACTED listing

A retracted claim is ⛔ never dropped from the listing and ⛔ never called
SUPERSEDED. It is rejected with its own sentence naming the retracting
comment, its author, its timestamp and the channel — `RETRACTED —
comment 5717333576 at 2026-09-17T15:54:48Z, by the same author
(os-bill), takes it back via a retraction line OPENING with the act and
naming the claim by comment id (5717315121). ⛔ NOT superseded: a
withdrawn claim is not a candidate for governance at all, whatever its
date`. When EVERY claim is retracted the result is a state this file
already has and ⛔ never a fabricated carrier: `misplaced` when the
withdrawn record left a declaration line on the thread (a C2 row, its
value ⛔ not accepted), `absent` when it did not — both pinned, plus the
control that the same thread WITHOUT the retraction reads `missing`. The
`claim.selected` sentence tells "nobody claimed" apart from "every claim
was withdrawn"; the printed `CLAIM_SELECTION_RULE` now carries the
membership half, so two runs stay comparable on the rule as well as on
the selection.

## The pin the triage asked for, and the ablation

New battery `objectstack-ai#18719: a RETRACTED claim leaves the pool — a withdrawn
claim never governs`, 36 cases, floor pinned at 36;
`SELF_TEST_BATTERY_FLOOR` 28 → 29. The objectstack-ai#18373 replay carries the REAL
ids, stamps and logins, and each body carries the load-bearing LINES of
the real comment, extracted from the REST rows rather than retyped.

Self-test: **802 cases pass** on the base file (read from the shared
checkout, unchanged since `a84f61a7c4`) → **838 cases pass** here, exit
0.

Ablation from the committed fix — `claimRetractions` made to return an
empty Map on disk, which is exactly `main`'s membership rule:

```
BASELINE blob d753e2a == HEAD:scripts/pm/check-clause2-carriers.mjs
PRE-COUNTS anchor=1 marker=0
POST-COUNTS marker=1   blob 35125502841ab3e5e34667619f90c63d46bc81f7
ABLATED self-test EXIT=1
ABLATED failing cases: 15
RESTORED blob d753e2a ; git diff HEAD on the path: (empty)
marker left on disk after restore: 0
RESTORED self-test EXIT=0
```

The mutation was proven on disk by marker count and by a CHANGED blob
hash before the suite ran, not by the editor's exit code; the restore is
`git checkout HEAD -- path` under a `trap … EXIT INT TERM` with an
absolute repo root, and is proven by the blob hash returning to the HEAD
blob AND by an empty `git diff HEAD` on the path. **All 15 reds are
cases of the new battery and nothing pre-existing went red** (checked by
matching each failing case name against the battery's own roster). The
`⛔ CONTROL` cases — the ones that assert `main`'s reading — stay GREEN
under the ablation, which is the asymmetry that makes them controls.

## Gates

Derived from the worktree with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (no hand-fed paths) at
`ee9bf03bd4`: 34 commands over a change set of 1 path. Every one was run
and its exit code captured with redirect-then-`$?`, then reconciled with
`--ran`.

```
0 :: node scripts/check-adr-0087-registration.mjs --base origin/main
0 :: node scripts/check-adr-0087-registration.mjs --self-test
0 :: node scripts/check-changeset-no-major.mjs --base origin/main
0 :: node scripts/check-changeset-no-major.mjs --self-test
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:changeset-gate-self-tests
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-clause2-carriers
0 :: pnpm check:pnpm-filter-targets
0 :: pnpm check:ratchet-remedy-authority
0 :: pnpm check:refd-timer-probe
0 :: pnpm check:watch-hint-literal
0 :: pnpm check:pm-dispatch-gates   (run detached, never under a foreground timeout: 1788 cases, 756.2s on this box)
```

Repo-wide `pnpm lint` (`eslint . --no-inline-config`): **exit 0**, run
whole rather than narrowed.

The derivation also names eight artifact-roster families whose roster
sits under a directory one of these paths is in, where silence is
evidence in neither direction. Run: `check-published-list-mirrors` 0 ·
`check:console-injection` 0 · `check:engine-double-contract` 0 ·
`check:i18n-stale-fill` 0 · `check:pm-label-desc-cap` 0 ·
`check:single-claim-paths` 0 · `check:dts-closure` **exit 3** and
`check:published-readme-exports` **exit 3** — both PREREQUISITE NOT MET
(no build in this worktree), recorded as NOT MEASURED and ⛔ not as a
pass: both read built `dist/*.d.ts`, which a repo-root `scripts/pm/**`
diff cannot move, and CI builds.

## The two queued cards on this file — read, ⛔ neither folded in

**objectstack-ai#18764** (a decorated `**Claim:**` never enters this same pool) is ⛔
NOT folded. It moves the ENTRY side of membership (what makes a comment
a claim); this card moves the EXIT side (what takes one out). They are
two predicates, and folding it would mean importing `markerMatches` from
PR objectstack-ai#18756, which is in the merge queue and ⛔ not on this base — a second
predicate plus a dependency on an unlanded PR. **The boundary, named
rather than crossed:** shape (2) reads the protocol `Release:` through
the IMPORTED `RELEASE_COMMENT_MARKER`, which on this base is
undecorated-only, so a decorated `**Release:**` is NOT read by the
directive channel. The PROSE channel is decoration-tolerant by its own
leading-decoration stripper, which is this file's reader and not the
sibling's constant. When objectstack-ai#18756 lands and this file adopts
`markerMatches`, both ownership markers gain decoration tolerance in one
edit — that edit is objectstack-ai#18764's.

**objectstack-ai#18683** (the un-paged comment read, `per_page=100` with no page
ladder) is ⛔ NOT folded: it is about WHICH rows reach the reader, not
about what the reader does with them — a different limb, and a truncated
thread would truncate this reading exactly as it truncates today's.

---
_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
…status endpoint as an empty-set default (objectstack-ai#18775)

Fixes objectstack-ai#18733

`Clause-②: no`

One file: `.claude/skills/pm-dispatch/references/platform-readings.md`,
held at **466 / 466**
(ceiling 466, headroom 0; widest line in the file still 120 B at `:464`,
untouched). No ceiling
raised, net line change **0**: one row added, one row retired in the
same file.
`skip-changeset` — `.claude/**` is shipped by no package's `files[]`, so
nothing published moves.

## The trap

`:28` is the row that sends a reader to `GET /commits/{sha}/status`. On
a commit that carries **no
legacy commit statuses at all**, that endpoint answers `state:
"pending"` with `total_count: 0` —
the API's default for an empty set, not a verdict about anything. In a
repo whose gates are all
check-runs the field is `pending` forever and means nothing, while
reading exactly like "gates still
running": a seat that waits on it waits forever, and a seat that reads
`pending` as not-yet-red can
read a red PR as merely unfinished. The gate reading is
`/commits/{sha}/check-runs`.

## Measurements

**Three `objectstack-ai/cloud` heads — the card's table, verbatim** (all
fully green):

| PR | head | check-runs | combined status |
|:--|:--|:--|:--|
| `objectstack-ai/cloud#2319` | `03c170ec` | 2/2 `completed/success`
(2026-09-16T16:13:44Z) | `state: "pending"`, `total_count: 0` |
| `objectstack-ai/cloud#2320` | `b4b1c5c4` | 2/2 `completed/success`
(2026-09-17T15:47:03Z) | `state: "pending"`, `total_count: 0` |
| `objectstack-ai/cloud#2321` | `23e928b7` | 2/2 `completed/success`
(2026-09-17T17:14:33Z) | `state: "pending"`, `total_count: 0` |

**This repo, fetched through the REST proxy at
2026-09-17T19:57Z–19:59Z** — measured here, ⛔ not
retyped from the card. Raw `state` / `total_count` on both sides:

| commit | `/commits/{sha}/status` | its `statuses[]` |
`/commits/{sha}/check-runs` |
|:--|:--|:--|:--|
| `d852dae1` (PR objectstack-ai#18750 head) | `state: "success"`, `total_count: 1` |
`Vercel` / `success` | `total_count: 40`, 29 `completed/success` + 11
`completed/skipped` |
| `702b4241` (PR objectstack-ai#18756 head) | `state: "success"`, `total_count: 1` |
`Vercel` / `success` | `total_count: 39`, 28 `completed/success` + 11
`completed/skipped` |
| `2f38ee0d` (PR objectstack-ai#18758 head) | `state: "success"`, `total_count: 1` |
`Vercel` / `success` | `total_count: 32`, 24 `completed/success` + 8
`completed/skipped` |
| `95e17452` (`main` tip at branch point) | `state: "success"`,
`total_count: 1` | `Vercel` / `success` | `total_count: 59` |
| `e2050cef` (`main`) | `state: "success"`, `total_count: 1` | `Vercel`
/ `success` | `total_count: 71` |
| `bc2ec806` (`main`) | `state: "success"`, `total_count: 1` | `Vercel`
/ `success` | `total_count: 64` |

**The reading that scopes the row**: `objectstack-ai/objectstack` posts
a legacy `Vercel` status on
every commit, so the empty-set default never arises here. One further
control makes that exact,
taken on this branch's own first commit `55c8e844` at 19:59:48Z, seconds
after the push and before
any workflow had started:

| commit | `/commits/{sha}/status` | its `statuses[]` |
`/commits/{sha}/check-runs` |
|:--|:--|:--|:--|
| `55c8e844` (this branch, pre-CI) | `state: "pending"`, `total_count:
1` | `Vercel` / `pending` | `total_count: 0` |

This repo answers `pending` too — for the opposite reason. There,
`total_count: 1` and a real
`Vercel` status genuinely in flight; in the card's three cloud heads,
`total_count: 0` and no status
at all. **`total_count` is the discriminant, `state` is not**, which is
why the row names both
fields rather than the word `pending` alone.

⇒ the row is scoped 「零 legacy status 的仓」, ⛔ not 「所有仓」.

## The row added — `:29`, 118 B

```text
- 零 legacy status 的仓恒答空集默认值 `pending`+`total_count: 0`,⛔ 非门禁读数,门禁读 check-runs。
```

It sits directly under `:28` 「`unstable` 可源自 check-runs 看不见的 commit
STATUS(如 `Vercel`)⇒
③ 另读 `/commits/{sha}/status`」, which names this repo's legacy status and
points at the endpoint.
The new row inherits that subject — the idiom the block already uses at
`:30`, `:63`, `:65`, `:67` —
and states the condition under which the endpoint stops answering the
question.

Against the card's proposed line, at the file's ≤ 120 B cap: 「那是空 legacy
集合的默认值」 is kept as
「空集默认值」 (「legacy」 already stands in the row's first clause), 「⛔ 不是门禁读数」
as
「⛔ 非门禁读数」, and 「门禁只认 `/commits/{sha}/check-runs` 的 `completed/success`」
as
「门禁读 check-runs」. What changed on purpose is the scope: the card wrote
「在只有 check-runs 的
commit 上」, and the measurement above makes it a property of the
**repo**, which is where the legacy
status is configured.

## The payment — `:343` retired, 85 B

**before** (`:343`, pre-edit numbering):

```text
- 署名页脚的写侧变异按通道与输入双重定域,⛔ 不是一条定律。
```

Why the tree no longer needs it as a separate line: it is a **preamble
that states the block's own
conclusion**, which the same block states again at its end, more
precisely. It carried two clauses,
and both are held:

| the retired clause | where it is held (post-edit numbering) |
|:--|:--|
| 「按通道与输入双重定域」 | `:355` 「⇒ 形态随动作与送出体尾部变,改侧还随通道变」 — three loci named
where the retired row named two, and `:342` 「⇒ 失效既依拼写又依载体:评论里验过页脚对 PR
正文什么都没证明」 states the same localisation at the point of use |
| 「⛔ 不是一条定律」 | `:355` 「⛔ 不由任一条推其余,写后必回读」, and `:351` 「⛔
无受控对照(同通道只差该块两送)⇒ 是拟合不是定论,⛔ 不外推到别的动作」 |

No fact leaves the corpus: every reading the block holds (`:340`–`:343`,
`:344`–`:354`) is untouched,
and the row that generalises them — the one a reader reaches **after**
the evidence rather than
before it — keeps the generalisation with the read-back prescription
attached. The retired row is not
one of the nine PR objectstack-ai#18689 adopted, not one of the three `配额` rows objectstack-ai#18744
names (`:137`, `:145`,
`:146`), and not one of the four PR objectstack-ai#18741 landed today (`:47`–`:49`,
`:163`).

The ratchet's standing one-file exception 「唯一例外:`platform-readings.md`
增量抬上限到落地行数,免
决策卡,记 `ruledRaises` 引常设裁决」 (`scripts/pm/check-skill-line-ratchet.mjs`)
was **not** taken:
a payment with zero fact loss was available, so the ceiling stays at 466
and no ruling is spent.

## ② re-read — CARRIED, no row, and a premise correction to the card

The card's ② says: "The existing entry says `/search/*` is refused by
the egress proxy. What it does
not say is the **shape** of the refusal." On `main` that sentence is
false. Three consecutive rows
say exactly the shape (pre-edit numbering, the seat's `:209`–`:211`):

```text
- 会话代理只服务 repo-scoped 路径,`/search/*` 的 403 体解析成净零。
- 代理回 403 加体 sessions are bound to their configured repositories,而那是合法 JSON。
- 读 `total_count` 得 None、打印成 total: None,与真空集只差一个字符,而请求根本没跑。
```

Mapped against the card's proposed addition, clause by clause:
「它的拒绝是成功形状的」 is
`:209`'s 「403 体解析成净零」; 「回 JSON」 is `:210`'s 「而那是合法 JSON」; 「`total_count`
键缺失 ⇒
`.get()` 读成 `None`、打印出来像 0」 is `:211`, which adds the measurement the
card does not
have — 「与真空集只差一个字符,而请求根本没跑」.

The only residual is the card's **prescription** 「断言键在,⛔ 不断言值」, and that
is precisely what
the open PR objectstack-ai#18666 (for objectstack-ai#18454, governed, awaiting the maintainer) lands
on that same line. Its hunk
on this file is one line, `@@ -209,7 +209,7 @@`:

```text
-- 读 `total_count` 得 None、打印成 total: None,与真空集只差一个字符,而请求根本没跑。
+- 读 `total_count` 得 None、打印成 total: None,与空集只差一字符;缺键即拒绝,判别式是状态码。
```

「缺键即拒绝」 is the assert-the-key prescription in the file's own voice, and
「判别式是状态码」 is
one notch sharper than the card asked for: it names the discriminant
rather than the symptom. So the
residual is zero once objectstack-ai#18666 lands, and the line a residual row would
have to touch is objectstack-ai#18666's own
hunk — a reserved line. ⇒ **no row for ②**, and the `search_issues`
block `:192`–`:201` is untouched.

The card's own attribution note observes that the `domain:spec @
objectui` seat recorded the same
shape independently and argues that is the reason to put it in the
shared table. It is in the shared
table; what the card measured is that a reader did not find it there.

## Reserved rows — verified against the open PRs' hunks, by content

- `:10`–`:12` (objectstack-ai#18469 PR-A) — byte-identical before and after; the
diff's two hunks are at `:26`–`:32`
  and `:341`–`:347`, so these lines are in neither.
- `:208`–`:212` (PR objectstack-ai#18666) — the only open PR touching this file,
confirmed by reading all 26 open
PRs' file lists at 2026-09-17T19:50Z; its hunk's changed line is the
`total_count` row. **The band
moves by the insertion**: on `main` the changed line is `:211` and the
hunk's full context window
is `:208`–`:214`; on this head they are `:212` and `:209`–`:215`. Every
byte in the band is
  unchanged, so objectstack-ai#18666 still applies.
- `:431` (objectstack-ai#18469 PR-A) — **this number was already stale before this
diff**. The row the card names,
「harness 按内容拒写:同会话派发 PR 上 PASS 拒为 `[Self-Approval]`」, reads `:432` on
`main`, and
read `:432` at `7636cd9b81^` too, so PR objectstack-ai#18741's body carried the
off-by-one rather than the row
having moved. `:431` is a different row (「分支删除被拒有第二形态:代理回 403」). After
this diff the
`[Self-Approval]` row is still at `:432`: the insertion at `:29` and the
retirement at `:344`
  cancel across it.

## Ratchet before / after

`node scripts/pm/check-skill-line-ratchet.mjs`, both pins, exit 0 on
both sides:

```text
main  447e2e8  ✓ .claude/skills/pm-dispatch/references/platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0).
main  447e2e8  ✓ .claude/skills/pm-dispatch/references/platform-readings.md is 466 lines (ceiling 466; headroom 0).
head  d6bc477  ✓ .claude/skills/pm-dispatch/references/platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0).
head  d6bc477  ✓ .claude/skills/pm-dispatch/references/platform-readings.md is 466 lines (ceiling 466; headroom 0).
```

Widest line in the file, measured per row in bytes: **120 B at `:464` on
both sides**, unchanged; no
row exceeds 120 B on either side; the added row is 118 B.

```text
 .claude/skills/pm-dispatch/references/platform-readings.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
```

## Derived gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree,
no hand-fed path list — 18 families, all re-run at the final head
`d6bc4775e5` with `$?` captured
before any pipe:

```text
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/pm/check-governed-queue-guard.mjs --self-test :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:required-contexts :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0
```

Reconciled: `dispatch-gates.mjs --ran` reads 「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)」.

On the first pass `check:doc-formula-expressions` exited **3 —
PREREQUISITE NOT MET**, its own text
「Nothing was measured: this gate exited before running a single check」;
its two declared
prerequisites were built under `scripts/pm/os-verify-lock.sh` (`VERDICT
command-exit 0`, held 158 s,
waited 0 s) and it then exited 0, which is the reading recorded above.

Also run, outside the derived 18: `pnpm check:pm-settings-deny-roster`
exit 0 — the derivation marks
it 「roster under .claude, which one of your paths is in」, where silence
is evidence in neither
direction. Repo-wide `pnpm lint` (`eslint . --no-inline-config`): **exit
0** at `d6bc4775e5`.

---
_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
… platform refuses in (objectstack-ai#18796)

Fixes objectstack-ai#18664

Clause-②: no

## The defect

`scripts/pm/check-half-states.mjs` declared `export const
ISSUE_BODY_LIMIT = 65536;` with a docblock asserting "GitHub's hard cap
on an issue body ... A body that exceeds the cap is REJECTED by the
API", and no provenance at all: no unit, no reading, no date. It was
false in both directions at once. Bodies four times larger store fine,
so the number is too strict where it is used to bound a report; and a
real refusal boundary sits above it that nothing was watching, and
crossing it is SILENT — the platform keeps the OLD body, answers 200 and
reports nothing (which is precisely why `post-stamped.mjs` grew
`EXIT_NOT_STORED`). One constant, over-strict where it cuts and blind
where the refusal lives.

## Premise re-derived on `main` before any edit

- The filing card's first artefact has MOVED since it was measured.
objectstack#6015 was read by the triage seat at 150,507 chars / 257,945
bytes; fetched again for this PR at 2026-09-17T21:03Z it had been
compacted to **72,754 chars / 125,561 bytes** (`updated_at`
2026-09-17T20:04:42Z). The card's own numbers are therefore quoted here
as the card's, not re-taken. The falsification survives the compaction
anyway and is re-taken here: 72,754 chars / 125,561 bytes is past 65,536
in EITHER unit, so no reading of the old constant survives.
- The constant and its docblock were confirmed on the tip this branch
was cut from (`6de7a2d6e6`), at `:17148` / `:17149`, with the renderer's
cut at `:19420` and the pin at `:28367`. `SEAT_BODY_SOFT_LIMIT` is at
`:2037` (the card's `:1802` is stale); it and H6 were READ and are
untouched, as the card directs.
- One nuance the card's causal sentence does not carry, stated so the
next reader is not surprised: what trims the report today is
`MARKDOWN_BODY_BUDGET` at `:19420`, not `ISSUE_BODY_LIMIT` — the cap is
what the budget was chosen to sit under, and it appears in the self-test
pins. The defect is the same; the mechanism has one step in it.

## The measurement

A throwaway issue was opened for it — objectstack#18793, never a seat
post and never another card — and its body rewritten 17 times with
filler of chosen byte sizes through `scripts/pm/post-stamped.mjs
--body=18793`, each write read back byte-exact. Budget set for this: 18
writes (a dozen was the guide; the exact bracket cost 15, the unit
question 2, the record 1). Exit 0 with class `identical` = every byte
sent is stored; exit 4 (`EXIT_NOT_STORED`) = the platform kept the old
body.

| # | size sent (bytes) | chars | exit | class | verdict |
|---:|---:|---:|---:|---|---|
| 1 | 257,945 | 257,945 | 0 | identical | landed |
| 2 | 263,533 | 263,533 | 4 | not stored | refused |
| 3 | 260,739 | 260,739 | 0 | identical | landed |
| 4 | 262,136 | 262,136 | 0 | identical | landed |
| 5 | 262,834 | 262,834 | 4 | not stored | refused |
| 6 | 262,485 | 262,485 | 4 | not stored | refused |
| 7 | 262,310 | 262,310 | 4 | not stored | refused |
| 8 | 262,223 | 262,223 | 4 | not stored | refused |
| 9 | 262,179 | 262,179 | 4 | not stored | refused |
| 10 | 262,157 | 262,157 | 4 | not stored | refused |
| 11 | 262,146 | 262,146 | 4 | not stored | refused |
| 12 | 262,141 | 262,141 | 0 | identical | landed |
| 13 | 262,143 | 262,143 | 0 | identical | landed |
| 14 | **262,144** | 262,144 | 0 | identical | **landed** |
| 15 | **262,145** | 262,145 | 4 | not stored | **refused** |
| 16 | 262,145 | 222,145 | 4 | not stored | refused (multi-byte) |
| 17 | 262,144 | 222,144 | 0 | identical | landed (multi-byte) |

Rows 1 and 2 re-take the card's two endpoints as this run's OWN bracket
rather than inheriting them. Rows 3 to 15 bisect it.

**Interval reached: a single value.** 262,144 bytes stored; 262,145
bytes refused. The card forbade writing 262,144 as "known" unless
exactly that size landed and one byte more was refused — row 14 and row
15 are that pair. 262,144 is 256 KiB, which is why the guess was
tempting; it is reported here because the bisection landed on it, not
because it is round.

## The unit answer

Rows 16 and 17 fill the body with a 20,000-character run of U+4E2D
(three UTF-8 bytes each) plus ASCII padding, so byte length and
character length differ by 40,000.

- Row 16 sends **262,145 bytes but only 222,145 characters**. A cap
counted in characters — or in UTF-16 code units, the same number here —
would have taken it with 40,000 to spare. It was REFUSED.
- Row 17 is the positive control: the same multi-byte shape at **262,144
bytes / 222,144 characters** LANDED. So what refused row 16 is the one
extra byte, not the multi-byte content.

**The platform counts UTF-8 BYTES.**

Incidental, recorded because the tool's own header calls the cell
unmeasured: every landed write above read back `identical` — no
trailing-newline strip (these bodies carry none) and no synthesised
footer on an ISSUE body. The cell for a body that already ends in the
footer block stays unmeasured; none of these did.

The probe issue is closed `completed`, with the bisection table as its
final landed body.

## The constant, before and after

Before: `export const ISSUE_BODY_LIMIT = 65536;`, docblock "GitHub's
hard cap on an issue body ... A body that exceeds the cap is REJECTED by
the API".

After: `export const ISSUE_BODY_LIMIT = 262144;`, and the docblock now
carries the provenance the old one lacked — the UNIT (UTF-8 bytes), BOTH
sides of the bracket (`262,144 bytes STORED` / `262,145 bytes REFUSED`),
the DATE (2026-09-17) and the PROBE ISSUE (objectstack#18793) — plus the
correction that the refusal is silent rather than an API rejection, and
the reason the round number is not the reason.

## The guard and the budget, in one unit

The renderer's cut at `:19420` counted `line.length` against
`MARKDOWN_BODY_BUDGET`, and seeded its accumulator with `body.length +
indexText.length`. **That unit is UTF-16 code units** — JS `.length` —
while the platform refuses in bytes, over rows that are largely CJK
prose, where the two differ by 3x.

- The accumulator and the guard now count `bodyBytes()` (a new tiny
export, `Buffer.byteLength(..., 'utf8')`). The `+ 1` stays: the joining
newline is one byte in UTF-8.
- `familyLedgerReservation` is reserved out of the same budget, so it
returns bytes too — which is what its own header has said since it was
written ("in bytes"); it was measuring `.length`. Its two digit-slack
terms are ASCII digits, already byte-correct.
- H6 next door (`h6SeatBodyOversized`) has counted `Buffer.byteLength`
since it was written. The renderer now agrees with it and with the
platform.
- **`MARKDOWN_BODY_BUDGET` does NOT move — it stays 60,000, now BYTES.**
A cap that was mis-measured is a correction to a reading, not a licence
to print more; how much the anchor issue prints is a fold decision
nobody has taken, and this PR takes none. Because bytes are never fewer
than characters, the new guard cuts at or before where the old one did:
strictly narrowing, never widening, which is what the card asks for.
- The margin is stated rather than left to arithmetic, in the docblock
and in a pin: the budget sits **202,144 bytes below the measured cap**,
22.9% of it. The `FAMILY_LEDGER_WORST_CASE_BYTES` docblock's stale
sentence about "the 5,536-byte headroom ... (65,536)" is corrected in
the same place, with a note that its two measured figures were taken
with `.length` under a name that said BYTES.

## The pins

The pin at `:28367` is re-pointed to the measured cap and re-measured in
bytes, and twelve other size pins across the suite move to bytes with it
(they asserted `.length` against a budget the guard no longer counts
that way). A new floored battery is added in the shape PR objectstack-ai#18756 landed
— `SELF_TEST_BATTERIES` gains `'ISSUE_BODY_LIMIT measured cap': 37` and
`SELF_TEST_BATTERY_FLOOR` rises 5 to 6, with the two sibling floor pins
that name the roster size updated. 40 cases register against it:

- **provenance** (7): the docblock attached to the constant names the
unit, both sides of the bracket, the date and the probe issue — anchored
on the docblock slice, not on the file text, where these assertion
strings would satisfy themselves; plus the two controls that the
extractor found a real docblock and not the whole file.
- **the cap** (4): the value, that it is 256 KiB exactly, that the
retired 65,536 is gone, and that the measured cap is four times it.
- **the unit** (4): `bodyBytes('U+4E2D')` is 3 where `.length` is 1;
ASCII agrees; an absent body is 0 and not a throw.
- **the guard** (7), with its firing controls inside: a 4,000-row CJK
report is trimmed to the BYTE budget and announces the trim; the row
text really is multi-byte (3x), so the bound is not vacuous; the block
it lays out fills the budget in bytes while its character count is under
half that; and the COUNTERFACTUAL, computed from that same run — how
many rows a character-counting guard would have laid out from the same
fixture, and what that body would have weighed.
- **the over-cap report** (6), the objectstack-ai#4690 shape against the measured cap:
a fixture whose rows alone exceed 262,144 bytes untrimmed (asserted, so
it is not vacuous) renders under the cap AND under the budget, SAYS it
trimmed, points at the run log, and still never reaches the loud row.
- **the margin** (4) and **the floor** (6).

Read the counterfactual honestly, and the battery comment says so: it
overruns the renderer's BUDGET, not the platform's measured cap, which
no character count can reach at today's budget. The budget is the thing
that stops meaning anything when it is kept in the wrong unit — and the
thing that would become dangerous the day anybody raises it toward the
cap. That is an argument for landing the unit fix before, not instead
of, any budget decision.

## Ablation

From the COMMITTED fix (`5fc8e0ce57`), two legs, each mutating on disk,
proving the mutation by grep counts on both the removed and the injected
text AND by `git hash-object` against the HEAD blob, running the suite,
and restoring with `git checkout HEAD -- path` verified by hash equality
and an empty `git diff HEAD`, under a `trap ... EXIT INT TERM` with
absolute paths.

HEAD blob `153d10a0158ba27906a5400df8a9c486d77ae061`.

**Leg A — put `65536` back.** Mutated blob
`7d34ca5db0377d33d7e5062792dd54a51edbe763`; removed-text count 1 to 0,
injected-text count 0 to 1. Self-test exit 1, **5 cases red, every one
of them in the new battery**, nothing pre-existing red:

```
objectstack-ai#18664 cap: the cap is the bisected value (got 65536, want 262144)
objectstack-ai#18664 cap: ...which is 256 KiB exactly, checkable by hand (got false, want true)
objectstack-ai#18664 cap: the retired 65,536 is gone (got true, want false)
objectstack-ai#18664 cap: ...and the measured cap is four times it (got false, want true)
objectstack-ai#18664 margin: ...by the stated margin, to the byte (got 5536, want 202144)
```

**Leg B — put the character-counting accumulator back** (`let used =
body.length + indexText.length + ledgerReservation;`). Mutated blob
`d1ec008854fb628f8822232cbfa4344ace4d2342`. Self-test exit 1, **9 cases
red**: two in the new battery (the CJK trim and the over-cap body's
budget bound) and **seven of the file's PRE-EXISTING budget pins** —
`markdown: ...and under the renderer's own budget`, `markdown: ...and
the body is still under budget`, `H17 budget`, the `④ budget` box,
`objectstack-ai#13947 order`, `objectstack-ai#13947 reserved` and `objectstack-ai#13947`'s flood pin. That is the
intended direction and worth reading twice: those seven pins are red
under the old guard **because this PR re-measured them in bytes**. Under
`.length` they could not fail — and the reason they fail now is that
this file's own fixtures already carry enough multi-byte text (em dashes
in the row messages) that a character-bounded body overruns the byte
budget it was supposed to keep. The old guard was not bounding the thing
that matters, on this suite's own inputs.

Both legs restored: hash matches the HEAD blob and `git diff HEAD` is
empty. Working tree clean afterwards.

## Self-test

```
check-half-states self-test: 4921 cases pass. Batteries: H66 released queue card 183/172,
H19 judged-set founding 37/34, H65 tier declaration spelling 46/42,
H67 queued merged-delivery reading 152/142, H2/H47/H66 decorated ownership marker 98/94,
ISSUE_BODY_LIMIT measured cap 40/37.
```

4,881 cases before, 4,921 after.

## Derived gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree, no hand-fed path list —
38 commands derived against the change set it took from the merge base
itself (1 path: `scripts/pm/check-half-states.mjs`). Every one run, exit
code captured by redirect-then-`$?`, never through a pipe. Reconciled
with `--ran`: 38 derived, 38 run, 0 unrun.

```
0  node scripts/check-changeset-no-major.mjs --base origin/main
0  node scripts/check-changeset-no-major.mjs --self-test
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  node scripts/pm/board-snapshot.mjs --self-test
0  node scripts/pm/check-governed-queue-guard.mjs --self-test
0  node scripts/pm/sweep-closed-cards.mjs --self-test
0  pnpm check:agent-test-spelling
0  pnpm check:bash32-floor
0  pnpm check:changeset-gate-self-tests
0  pnpm check:cli-command-ids
0  pnpm check:closing-target-claim
0  pnpm check:commit-card-trailers
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:partof-closing-keyword
0  pnpm check:pm-half-states
0  pnpm check:pnpm-filter-targets
0  pnpm check:ratchet-remedy-authority
0  pnpm check:refd-timer-probe
0  pnpm check:watch-hint-literal
?  pnpm check:pm-dispatch-gates   -- see below
```

`pnpm check:pm-dispatch-gates` is the 38th. It runs past this
container's foreground ceiling, so it was started DETACHED with its
output redirected to a file, never under a foreground timeout. Its
verdict is reported in the delivery comment on objectstack-ai#18664, read from that
log at report time; if it had not reached a verdict by then it is
recorded there as NOT MEASURED by name, not as a green.

Repo-wide `pnpm lint` (`eslint . --no-inline-config`): **exit 0**, whole
repo, no narrowing, run at `5fc8e0ce57`. A control-character scan over
the edited file (`grep -naP` over the C0 set plus DEL) matched nothing,
exit 1.

## Acceptance notes

Observations from this work, filed nowhere and changed nowhere, per the
scope rule:

- **Five workflow files carry the same unmeasured 65,536, about COMMENTS
rather than issue bodies** —
`.github/workflows/cross-repo-issue-closer.yml:166`,
`docs-drift-check.yml:557`, `merge-queue-triage.yml:178`,
`scheduled-full-run-card.yml:125` and `test-nightly-tiers.yml:436`, each
bounding a log tail or a report against "GitHub's 65536-character
comment limit". This PR measured the ISSUE BODY cap only; the COMMENT
cap is **not measured**, so nothing here shows those five are wrong —
what it shows is that they carry a number of the same provenance as the
one just falsified, in a unit ("character") that the body cap has now
been measured NOT to use. Worth a card and its own bisection; out of
this card's one-file surface. Dedupe words: `65536 comment limit
workflow log tail truncation`.
- **`FAMILY_LEDGER_WORST_CASE_BYTES`'s two measured figures (2,285 B /
3,754 B) were taken with `.length` under a name that says BYTES.** Their
pins are now measured in bytes and still fit the 6,000 ceiling, so
nothing is wrong; the numbers in the prose are simply readings in the
other unit. Noted in the docblock rather than re-measured, because
re-measuring them is a diff about that constant and not this one.
- **Raising `MARKDOWN_BODY_BUDGET` toward the measured cap is now
possible and is deliberately NOT done here.** It changes what the
standing patrol's anchor issue prints, which is a fold decision for a
seat or the maintainer, not a consequence of correcting a measurement.
Successor: none queued — it wants a card of its own if anybody wants the
longer report.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

Projects

None yet

2 participants