Skip to content

Commit d9ba33d

Browse files
os-justinclaude
andauthored
fix(pm): a retracted claim leaves the governing-claim pool, so a withdrawn claim never governs (#18770)
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 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 **#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](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5100c42 commit d9ba33d

1 file changed

Lines changed: 478 additions & 19 deletions

File tree

0 commit comments

Comments
 (0)