Skip to content

fix(pm): a retracted claim leaves the governing-claim pool, so a withdrawn claim never governs - #18770

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-18719-retracted-claim-leaves-the-pool
Sep 17, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-18719-retracted-claim-leaves-the-pool

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #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 #18373 counterfactual, re-derived offline

The six comment ids were fetched ONCE through the REST proxy and replayed offline through claimCarrierSelection / pairInputRecord. #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 #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 (#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 #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 #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 d753e2a8cf06d8f72c436e0a1917b2fecb8be813 == 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 d753e2a8cf06d8f72c436e0a1917b2fecb8be813 ; 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

#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 #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 #18756 lands and this file adopts markerMatches, both ownership markers gain decoration tolerance in one edit — that edit is #18764's.

#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

The pool was every comment matching CLAIM_COMMENT_MARKER, ranked by
recency. A retraction carries no `Claim:` line, so it was never in the
pool and could not remove the claim it retracts: on #18373 a claim its
own author withdrew 84 seconds later kept governing, naming a branch
origin does not have, while the seat actually working the card read
SUPERSEDED.

Membership is now resolved before recency. A claim leaves the pool when
a later comment by the same author retracts it, read through the two
channels the protocol already writes: the `Release:` directive, and a
prose line that opens with the retraction act and names the claim by
comment id. A retracted claim is listed RETRACTED with the retracting
comment id, never SUPERSEDED and never dropped.

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

The battery replays the #18373 thread offline from its six real comment
ids, with each body carrying the load-bearing lines of the real comment
rather than a retyped paraphrase. Its BEFORE leg is produced by removing
the retraction comment, which is the reading main takes.

The measured trap is pinned too: the seat's own report line carries a
retraction word and its own claim id, so an `includes` anchor would have
read the comment that filed this defect as a withdrawal of the claim it
defends. The anchor opens the line instead.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@os-justin
os-justin marked this pull request as ready for review September 17, 2026 20:29
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit d9ba33d Sep 17, 2026
37 checks passed
@os-justin
os-justin deleted the claude/issue-18719-retracted-claim-leaves-the-pool branch September 17, 2026 20:53
This was referenced Sep 17, 2026
os-tesla pushed a commit that referenced this pull request Sep 19, 2026
…read the sibling's way

Two rulings on the claim arbiter, one region. A retraction is a `Release:`
line (batch #156 item 3, letter A): the prose channel PR #18770 taught —
a closed verb roster, a comment-id scan and a stripper of this file's own —
is deleted, not disabled. The protocol's definition of a decorated ownership
line is the shared claim reading (batch #156 item 4, letter A): the
`Release:` channel now reads through markerMatches instead of the raw
constant, so a bolded or backticked release retracts exactly as a bare one.

Measured consequence, replayed in the self-test: two of the twelve
cross-author pairs #18862 counted — objectui#7848 (a `**Release:**`) and
#7924 (a backticked one) — stood only because the raw constant could not
see the holder's release; os-warren's backticked release on #17852 is read
the same way. The #18373 specimen is replayed both ways: the prose line
retracts nothing (the withdrawn claim stands again, the cost ruling A
accepted), the same line in the declared act takes it out, and the
sigil-led spelling is named by the sibling's `leading-sigil` near-miss
row rather than stripped. The twelve spellings the card compared read the
same through the one path on both sides of this file (5 disagreements → 0).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ast 100 is UNJUDGED rather than a truncated claim pool (objectstack-ai#18799)

Fixes objectstack-ai#18683

Clause-②: no

## The defect

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

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

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

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

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

## The ladder, and the cap

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

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

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

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

## The input record

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

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

and, when the cap is what stopped the read:

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

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

## The pins

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

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

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

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

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

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

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

## The ablation

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

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

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

## Self-test

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

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

## Derived gates

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

```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-changeset-no-major.mjs --self-test :: exit 0
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-clause2-carriers :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm lint :: exit 0
```

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

## Out of scope, deliberately

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

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

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

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…h the sibling's one reading (objectstack-ai#18824)

Fixes objectstack-ai#18764

Clause-②: no

## The defect

`scripts/pm/check-clause2-carriers.mjs` built the governing-claim POOL,
and indexed retractions, by testing the imported `CLAIM_COMMENT_MARKER`
against the RAW comment body. That constant anchors the bare word at
line start and tolerates leading whitespace and one blockquote marker —
nothing else — so a claim a seat wrote as a bolded or backticked
`Claim:` was not SUPERSEDED here, it was never a candidate: not listed,
not rejected, not named anywhere in the record. Meanwhile
`claimGovernance`, imported from the same sibling, has read both
ownership markers through `markerMatches` since objectstack-ai#18680, so ONE FUNCTION
held both answers at once — governance saw the bolded claim and the pool
beside it did not.

## The before-reading, re-derived here and not taken from the card

Taken through this file's own offline reader (`--pair-json`), on
`origin/main` `e7eb4e9184` — the before leg was run in the shared
checkout, whose copy of both `scripts/pm/check-clause2-carriers.mjs`
(blob `ccd5ad7c9a`) and `scripts/pm/check-half-states.mjs` (blob
`153d10a015`) is byte-identical at `e7eb4e9184` and at `a7bafc29af`, so
the reading is a reading of this branch's base.

| fixture | BEFORE (`e7eb4e9184`) | AFTER (this branch) |
|---|---|---|
| a bolded `Claim:` carrying its own `Branch:` and `Clause-②: no` | exit
**4**, row **C2 — MISPLACED**; `claim.selected: none — no comment on
this thread carries a line beginning \`Claim:\``; `claim.clause2-line:
(no carrier, so no line was read from one)` | exit **0**;
`claim.selected: 1 comment(s) in the pool`; `claim.clause2-line:
DECLARED \`no\`` |
| the backticked spelling, same lines | exit **4**, row **C2 —
MISPLACED**, identical record | exit **0**, `DECLARED \`no\`` |
| a BARE older claim declaring `yes` beneath a DECORATED newer one
declaring `no` | exit **4**, row **C3**; the OLD claim governs;
`claim.clause2-line: DECLARED \`yes\`` | exit **0**; the decorated newer
claim governs; the older is listed `rejected: 1 … a SUPERSEDED claim`;
`claim.clause2-line: DECLARED \`no\`` |

The third row is the expensive direction and it is why this is not
merely a missing reading: the value the declaration limb hands every
downstream reader FLIPS, and the wrong value produced an adverse C3 row
about a contract-review gate that was never owed.

### One premise of the dispatch is falsified, and it is stated rather
than smoothed over

The escalation's two live bodies (objectui#9660 comment `5721120402`,
objectui#9717 comment `5720184809`, read 2026-09-17T22:02Z) carry **no
`Branch:` directive line at all**. Driven through the pool verbatim,
both read `claim-branch-unparsed` and exit **2** on BOTH sides of this
change — `claim.selected: NONE — the newest claim comment (5721120402 at
2026-09-17T20:57:09Z) parses ZERO branches`. That state is already
correct on `main`, because it is resolved by the sibling's governance
leg, which already reads decorated claims. So the fixtures in the table
above ADD the `Branch:` / `Clause-②` lines: the defect this PR repairs
is the one that only becomes visible once a claim is otherwise complete.
Neither shape ever read `absent`.

## The repair: one path, and it is the sibling's

Both former raw tests now read through
`markerMatches(CLAIM_COMMENT_MARKER, body)`, imported from
`check-half-states.mjs` — the pool (`claimCarrierSelection`) and the
retraction indexer (`claimRetractions`). The bare marker is tried first
inside `markerMatches`, so the change is provably additive: no body that
matched yesterday stops matching.

- No `\*\*` was added to `CLAIM_COMMENT_MARKER`. Decoration is an open
set, so admitting one spelling buys exactly that spelling; the constant
is the protocol's spelling and every reader imports it rather than
restating it.
- No second undecorator was written in this file. That would give this
file a private definition of "decorated" for the sibling to drift from,
which is the failure the card names.
- The near-miss vocabulary (`OWNERSHIP_MARKER_NEAR_MISS_FORMS`) is the
sibling's and is not re-declared here.
- `scripts/pm/check-half-states.mjs` is untouched.

## The retraction stripper, measured against that path — it STAYS, and
that is a reading

`undecorateRetractionLine` (this file) and the sibling's
`undecorateProseLine`-through-`markerMatches` were run over one fixture
set, with the retraction stripper extracted from the shipped source text
rather than retyped. **5 of 12 fixtures read differently:**

| fixture | `undecorateRetractionLine` | `markerMatches` (shared) |
same? |
|---|---|---|---|
| bare `Claim:` | true | true | yes |
| bold | true | true | yes |
| backticks | true | true | yes |
| **underscore emphasis** `__Claim:__` | true | **false** | **NO** |
| leading blockquote | true | true | yes |
| blockquote + bold | true | true | yes |
| **list item (hyphen)** `- Claim:` | true | **false** | **NO** |
| **list item (asterisk)** `* Claim:` | true | **false** | **NO** |
| **sigil prefix** `🚨 Claim:` | true | **false** | **NO** |
| a line that is NOT a marker but starts with the word | false | false |
yes |
| inflected word `Claiming:` | false | false | yes |
| **heading** `## Claim:` | true | **false** | **NO** |

Per the dispatch, the retraction stripper is **kept as is** and is ⛔ NOT
unified by hand. The divergence is load-bearing in both directions,
which is why: the objectstack-ai#18373 retraction this file's own battery replays
opens with a leading sigil, and narrowing the retraction path to the
shared reading would stop reading the specimen objectstack-ai#18719 landed for;
widening the shared path the other way would make a list item a claim,
which H20 pins as not one. Two strippers, two jobs — now stated in the
file, and pinned, rather than discovered again. The unification question
is for the seat to route; it is not folded in here.

## The pins

A new self-test battery, `objectstack-ai#18764: a DECORATED claim ENTERS the pool —
ONE reading, and it is the sibling's`, 24 cases, with
`SELF_TEST_BATTERY_FLOOR` raised by exactly the one battery it adds (30
→ 31). It pins: a bold and a backticked claim ENTER the pool and GOVERN,
with their `Branch:` and `Clause-②` lines read from them; a decorated
NEWER claim supersedes a BARE older one and the value the limb reads
moves with it; the older record is listed SUPERSEDED rather than
dropped; the retraction index sees a decorated claim as retractable by
its own author; the input record names the decorated row it selected, by
id and by date.

The controls are the other half: the constant itself still refuses those
same bodies; a list item, a heading and an underscore-emphasis line are
still not claims; the `Clause-②-correction:` comment does not enter the
pool through the new door either; a bare claim reads exactly as before;
and the two undecoration paths are pinned as the two jobs they are. The
bare-constant pins that assert on the CONSTANT
(`CLAIM_COMMENT_MARKER.test(...)` in the objectstack-ai#17366 and objectstack-ai#17149 batteries)
are byte-untouched. No network in the self-test.

## Census — report-only, over THIS gate's own population

The population is the one this gate derives: the open PRs of one board
and the cards they deliver, by this file's own `derivePairs`. ⛔ This is
NOT the half-states population the triage scanned (46 cards / 253
comments over two boards).

- **`objectstack-ai/objectstack`, read 2026-09-17T22:43:06Z** — 26 open
PRs, 24 derived pairs, 24 distinct cards, 147 comments read, 0 unread.
[LIT CTRL] bare-marker claim comments in today's pool: **29**. Decorated
claims the shared reading sees and today's pool does not: **0**.
- **`objectstack-ai/objectui`, read 2026-09-17T22:44:02Z** (the board a
seat reaches with `PM_SWEEP_REPO`, named because the escalation's
instances live there) — 14 open PRs, 12 derived pairs, 12 distinct
cards, 69 comments read. [LIT CTRL]: **13**. Decorated claims invisible
to today's pool: **2**.
- card objectui#9629, comment `5721993574` (`os-sales`,
2026-09-17T22:19:38Z), bolded — the card's ONLY claim. Before: pool
empty, declaration `misplaced no` (an adverse C2). After: pool = that
comment, declaration `declared no`.
- card objectui#9317, comment `5653084747` (`claude[bot]`,
2026-09-13T11:51:39Z), backticked — the card's ONLY claim. Before: pool
empty, `claim-branch-unparsed`. After: in the pool, still
`claim-branch-unparsed` — the gate now names the carrier by id instead
of reading the thread as unclaimed.

⚠️ Every instance above carries its reading time because it may have
left this state since. ⛔ No state was written by this census.

## Ablation, from the committed fix

Two legs, each mutating on disk from the committed state, each proven to
have reached disk by blob hash, each restored under a `trap` and
verified by blob hash plus an empty `git diff HEAD`.

| leg | mutation | blob before → after | self-test |
|---|---|---|---|
| the POOL predicate back to the raw `.test` | `claimCarrierSelection` |
`f7ee45d4ef` → `ae548569fd` | exit **1** — **8 of 889** cases red, all 8
from the new battery, nothing pre-existing red |
| the RETRACTION indexer predicate back to the raw `.test` |
`claimRetractions` | `f7ee45d4ef` → `708893dc5c` | exit **1** — **2 of
889** cases red, both from the new battery, nothing pre-existing red |

Both restorations: `RESTORED: blob
f7ee45d, git diff HEAD empty`.

⚠️ Worth recording: with the fix in place and the new battery removed
from the picture, the pre-existing 865 cases pass **unchanged** on both
sides of the repair. The existing suite could not see this defect, which
is exactly why the battery is the deliverable and not an extra.

## Self-test

`node scripts/pm/check-clause2-carriers.mjs --self-test` — 865 cases
before, **889 cases pass** after (865 + 24), exit 0; also green as `pnpm
check:pm-clause2-carriers` on the final head.

## Derived gates, with exit codes

Derived from this worktree with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (no hand-fed path list) at
`984a93a173`: change set 1 path,
`scripts/pm/check-clause2-carriers.mjs`, three-dot against merge base
`a7bafc29a`. **34 derived commands, 34 run, all exit 0**, reconciled
with `--ran` recording each exit code: `34 derived famil(ies) accounted
for — 34 run, 0 NOT-MEASURED (a DERIVED zero)`.

`node scripts/check-adr-0087-registration.mjs --base origin/main` ·
`--self-test` · `node scripts/check-changeset-no-major.mjs --base
origin/main` · `--self-test` · `node scripts/check-ci-filter-parity.mjs`
· `node scripts/check-closing-keyword-parity.mjs` · `--self-test` ·
`node scripts/check-comment-mask-corpus.mjs` · `node
scripts/check-declaration-mirrors.mjs` · `--self-test` · `node
scripts/check-scripts-symbol-anchors.mjs` · `--self-test` · `node
scripts/check-self-test-wired.mjs` · `--self-test` · `node
scripts/check-self-test-workflow-commands.mjs` · `--self-test` · `node
scripts/check-whole-set-label-write.mjs` · `--self-test` · `node
scripts/pm/bare-root-worklist.mjs --self-test` · `pnpm
check:agent-test-spelling` · `pnpm check:bash32-floor` · `pnpm
check:changeset-gate-self-tests` · `pnpm check:cli-command-ids` · `pnpm
check:cross-package-test-inputs` · `pnpm check:driver-memory-census` ·
`pnpm check:entry-guard` · `pnpm check:nul-bytes` · `pnpm
check:parse-guard` · `pnpm check:pm-clause2-carriers` · `pnpm
check:pm-dispatch-gates` · `pnpm check:pnpm-filter-targets` · `pnpm
check:ratchet-remedy-authority` · `pnpm check:refd-timer-probe` · `pnpm
check:watch-hint-literal` — each exit 0.

`pnpm check:pm-dispatch-gates` was run DETACHED with output to a file
and read from that file: 1809 cases pass, 747.5s on this box, exit 0. ⛔
It was never run under a foreground timeout, so no reading here is a
SIGTERMed run.

Repo-wide `pnpm lint` (`eslint . --no-inline-config`): exit 0, run on
the final head `984a93a173`.

## Scope

`skip-changeset`: the only changed file is
`scripts/pm/check-clause2-carriers.mjs`, which no package ships —
nothing published moves.

objectstack-ai#18807 (`listOpenPulls`'s bare page cap, behind this card on the same
file) was read and is not addressed here: its lines are untouched by
this diff. PR objectstack-ai#18799's `pagedListRead` ladder and PR objectstack-ai#18770's
`claimRetractions` were read for the words the record and the selector
use, and neither was touched.

## Acceptance notes

- The two undecoration paths in this file diverge on 5 of 12 fixtures
(table above). Whether the protocol should converge them — and in which
direction — is a real question and is deliberately left to the seat
rather than answered by a grep. Noted, not filed by the dev.
- On a thread whose newest claim is decorated and parses zero branches,
`claim.clause2-line` now reads from that comment instead of printing
`(no carrier, so no line was read from one)`, because the pool is no
longer empty on it. This makes the decorated case render exactly as the
bare case already did; `claim.selected` still reports `NONE … parses
ZERO branches` and the pair is still UNJUDGED. Stated as a measured
rendering delta, not a verdict change.

---
_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
…he shared reading, the prose channel retired, cross-author live claims named (row C9) (objectstack-ai#19169)

Fixes objectstack-ai#18862
Fixes objectstack-ai#18829
Part of objectstack-ai#18773 — the READER half only. The governed-text half of ruling
A (AGENTS.md's release clause, SKILL.md's yielding line at its line
ceiling, the core-rules.md twin) remains open on that card: Tier H, held
by the seat, no governed file is touched here.

Clause-②: no

## What this PR does

One skills round, one PR on `scripts/pm/check-clause2-carriers.mjs`, as
the three director records of the maintainer's 「同意」 order (batches objectstack-ai#154
/ objectstack-ai#156). The claim arbiter used to infer three ownership acts the
protocol never declared — a supersession between seats, a prose
retraction, a stripped prefix — and this PR makes it read the declared
act only.

1. **Cross-author live claims with no `Release:` between — row C9**
(objectstack-ai#18862, ruling b, 5725370464). `claimHandovers` reads the live,
attributable claims (membership decided by `claimRetractions`, exactly
as C8 and governance read it) and names every point where the author
changes. A hand-over whose taking claim is dated strictly after
`CROSS_AUTHOR_CLAIM_ROW_EFFECTIVE_AT = '2026-09-19T03:45Z'` (the frozen
UTC minute the row was authored — it names the rule's landing window;
the seat reconciles pairs between it and the merge by hand) is a **C9
row, exit 4**, with the ruling's remedy sentence: the holder posts
`Release:`; the taker posts nothing until then; a taker that yields
posts its own `Release:` with 去向 「让先到者」. A hand-over dated at or before
the instant is a **`C9-BEFORE-EFFECTIVE` note** and a `claim.handover`
record field — listed, informational, never the exit. C8 is unchanged in
behaviour; its direction-(b) pin (silent on cross-author) is replaced by
the pin that C9 is the reader that speaks — never both for two authors
with one claim each.
2. **One undecoration definition** (objectstack-ai#18829, ruling A, 5725677876). The
retraction channel reads `Release:` through the sibling's
`markerMatches`, never through a stripper of its own; the second
stripper is deleted from the tree. The sigil-led objectstack-ai#18373 shape is a
**named near-miss row, `leading-sigil`**, in
`OWNERSHIP_MARKER_NEAR_MISS_FORMS` (`scripts/pm/check-half-states.mjs`,
one roster member and its BATTERY68 pins — no predicate, no H-row, no
other export, per the claim amendment 5738916776). The twelve spellings
the card compared now read the same on every side of the reader (5
disagreements → 0, pinned on the pool and on the retraction index); `-
Claim:` / `* Claim:` stay not a claim (H20).
3. **The prose retraction channel retires** (objectstack-ai#18773, ruling A,
5725677697 — reader half). The verb roster, the comment-id scan and the
act-verb reading PR objectstack-ai#18770 taught are deleted, not disabled. A
retraction is read only as a `Release:` line by the claim's own author,
later than the claim; it needs no comment id (it names its target by
authorship) — that requirement did not exist before and is not added.
The objectstack-ai#18373 specimen is replayed both ways: the prose line no longer
retracts (the withdrawn claim stands again and governs — the cost ruling
A priced and accepted, ⛔ C), the same line in the declared act takes it
out, and the thread is **listed by C9's note** rather than read
silently.

## Measured, not assumed

- **A live consequence of item 2, replayed from the REST rows (read
2026-09-19T03:30Z through the proxy):** four of the twelve pairs carried
the earlier holder's `Release:` all along, bolded or backticked — objectstack-ai#17852
(os-warren, 5700605769), objectui#7804 (os-justin, 5707789209 and
5710740327), objectui#7848 (claude[bot], 5617804323), objectui#7924
(os-warren, 5613413938) — and the raw `RELEASE_COMMENT_MARKER.test` the
retraction channel used could not see any of them. With the shared
reading, **objectui#7848 and objectstack-ai#7924 clear by this change alone** (one
author left, nothing named, no seat posts anything); on objectstack-ai#17852 the pair
named today is os-litant → os-elon-musk (2026-09-18T22:04Z), not the
os-warren / os-litant pair the sweep counted; on objectui#7804 what
stands is os-tesla ×4 (C8) and os-sam.
- **The ten remaining measured pairs** are all listed today (none judged
— every taking claim predates the instant), and every one re-dated sixty
days forward is judged: the row is reachable on the real shapes, not
only on synthetic ones. objectui#9370's 「⛔ NOT a re-claim」 line is named
anyway — the reader reads order, not intent.
- **objectstack-ai#18373** (the objectstack-ai#18719 specimen) becomes a listed cross-author pair
once the prose channel retires (os-litant → os-bill, 2026-09-17),
cleared by os-bill's own `Release:` — added to the seat's reconciliation
list below.
- **Offline acceptance runs** (`--pair-json`, head `4cb58bc`): a
synthetic pair with the taker dated after the instant → `exit 4`, `✗ C9
—` with the remedy sentence; the real objectstack-ai#15811 rows → `exit 0`, `ℹ️
C9-BEFORE-EFFECTIVE —` and `claim.handover: … LISTED`; two claims by one
author → `exit 4`, `✗ C8 —` only, `claim.handover: none`.

## Verification

- `node scripts/pm/check-clause2-carriers.mjs --self-test`: **978 → 1046
cases pass**, exit 0 (head `4cb58bc`). Battery roster **33 → 34**
(`objectstack-ai#18862: cross-author LIVE claims…`, floor 52); `objectstack-ai#18719` battery
rewritten around the one channel (floor 36 → 46 by count); `objectstack-ai#18764`
two-paths pins replaced by the twelve-spelling replay; `objectstack-ai#18828`
direction (b) replaced.
- `node scripts/pm/check-half-states.mjs --self-test`: **5043 → 5063
cases pass**, exit 0; `H2/H47/H66 decorated ownership marker` 193/184 →
213/184; roster pin `…separator,leading-sigil,heading-bare,bare-word`.
- **Reverse verification, both files, through
`scripts/ablation-replace.mjs` (mutation proven on disk, restored to the
HEAD blob, `git diff HEAD` empty after each):** `judged: dated ===
'after',` → `judged: false,` in clause2 reds **21 of 1046** cases (every
judged-direction pin); `id: 'leading-sigil',` → `id:
'leading-sigil-ablated',` in the sibling reds **6 of 5063** (the
exact-roster pin and the form's own pins).
- **Absence, tree-wide at `4cb58bc`:** `git grep -n
undecorateRetractionLine` → 0 (control `undecorateProseLine` → 20); `git
grep -n RETRACTION_PROSE_ANCHORS` → 0 (control `RELEASE_COMMENT_MARKER`
→ 64). No control bytes in either file (`grep -naP` over the C0/DEL
range → 0).
- **Lint, narrowed and measured** (head `4cb58bc`): `npx eslint
--no-inline-config --format json` over the two changed files → exit 0,
**2 files, 0 errors, 0 warnings**. Population: `eslint.config.mjs`'
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` object minus `NEVER_LINTED`, so
both `.mjs` files are in it; the count is the JSON output's length (2).
Invariance: the config states it 「never enables type-aware linting (no
`parserOptions.project`, no typed `@typescript-eslint` rules) for ANY
file」, so this diff moves no untouched file's verdict — the narrowing
excludes nothing.
- **Derived gate families** (`node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` from the merge-base: 42 commands
— eight more than the dispatch's list, pulled in by the
`check-half-states.mjs` touch — plus `check:pm-governed-merges`, named
by the dispatch): the sweep was in progress when this PR was opened;
every exit code, with the `--ran` reconciliation, is in the
`os-dev-report` comment on objectstack-ai#18862.

## Acceptance notes

- **For the seat's reconciliation sweep (ruling item 2, the seat's
act):** objectui#7848 and objectstack-ai#7924 need no `Release:` — they clear on
landing; objectstack-ai#17852's live pair is now os-litant / os-elon-musk;
objectui#7804 is os-tesla / os-sam beside os-tesla's C8; objectstack-ai#18373 (closed)
joins the informational population as os-litant / os-bill.
- **Deviation from the dispatch's mechanism assumption, reported rather
than forced:** the dispatch expected the objectstack-ai#18373 prose line itself (「🚨
**撤回上一条认领…」) to be reported by the near-miss row. It cannot be without
reading the verb 撤回 — the channel ruling A on objectstack-ai#18773 retires (⛔ B). So
the named row is the sigil-led spelling of the declared act (the same
line with `Release:` in place of the verb, sigil and bold intact —
pinned as `leading-sigil` in both files), the prose specimen is pinned
as named by no form with the reason, and its thread is loud through C9's
note instead. The ruling's intent — the shape is named, not stripped;
the thread is not silent — holds on both counts.
- The live `--pair N` path: node's built-in fetch does not read
`HTTPS_PROXY`; the script already prints the `--use-env-proxy` hint.
Noted, not filed — not a defect in this reader. 承接者:无.
- `--pair-json` documents that omit the PR's own comment thread read
UNJUDGED (exit 2) on the record read — by design; the acceptance runs
above supply it.

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

---------

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

Labels

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

Projects

None yet

2 participants