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-provenanceabsent 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
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