Skip to content

fix(pm): a claim its own session later yielded is not the card's claim - #26016

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-1r3slx
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-1r3slx

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25791

Clause-②: no
Metadata-protocol: none

What this changes

A Claim: comment that its own session later yielded is no longer read as a claim of the card. This applies in both claim readers in scripts/pm/check-half-states.mjs, and through them in the closing-target guard (scripts/check-closing-target-claim.mjs, check-run "The card this PR closes must claim this branch").

  • New export yieldedClaimIndexes(commentRows), the only recogniser. It takes REST rows or bare bodies and returns the indexes of yielded claims. A claim counts as yielded only when all three of these hold:
    1. a comment's first non-blank line reads the protocol's already claimed — yielding (CLAIM_YIELD_MARKER, read through the shipped markerMatches decoration reading; no dash or wording dialect);
    2. that first line names exactly one distinct session id (SEAT_SESSION_ID);
    3. the claim's own session (claimSessionId: the first session id on its Session: line) is that session, and the claim precedes the yield (created_at when both parse and differ, thread order otherwise, the same rule claimGovernance uses).
  • claimGovernance skips yielded claims for both of its readings (governing and the newest-claim malformed reading).
  • h46ClaimNamesBranch skips yielded claims, so H46 leg (b) and the guard's positive predicate agree with governance.
  • The guard imports the same predicate. It does not fork one. Its "no claim at all" failure sentence now says that a yielded claim does not count.

What the card's "Wanted" section asked to keep, kept: a yield naming no session, a yield naming two sessions, a hyphen-written yield, a yield by another session, and a Claim: the yielding session posts after its yield all keep today's reading. Only the yield's first line is read. The measured yield names the winning claim and its session in its body, and reading the whole body would withdraw the winner's claim.

The yield spelling already in use was machine-readable enough, so execution-duties.md is not touched, and this PR changes no governed surface.

Mechanism, measured on PR #25780's thread (card #25729) at origin/main 05f7758f

Readers run over the live comment rows of #25729:

thread claimGovernance before after h46ClaimNamesBranch(loser branch) before after
up to the yield (claim A on a card-thread branch, claim B, B yields) governing = B's branch, malformed null, so the guard said UNCLAIMED (red, run 40183909709) governing null, malformed = A, so UNDETERMINED (pass, warned) true false
with the hand re-post of A malformed = re-post (UNDETERMINED) malformed = re-post (unchanged) true false

All three PM hypotheses held. (1) CLAIM_BRANCH_SHAPE gives zero branches for claude/project-thread-biervl. (2) The .some counted the yielded claim. (3) newest included the yielded claim. With the yield skipped in both readers, the measured red goes away, and a PR on the yielded branch no longer passes on a claim whose session yielded it.

Tests

  • node scripts/check-closing-target-claim.mjs --self-test: 121 cases pass. New battery "A claim its own session later YIELDED is not the card's claim" (16 cases, pinned at 16; the roster floor goes 12 to 13). It covers the acceptance pair: claim A on a protocol branch, claim B, B yields. The guard is CLEAN for A (one comment page) and a FINDING for B, and the failure names A's branch. It also covers the card-thread winner (no red; UNDETERMINED, warned), the five today's-reading controls, and the no-fork pin: CLAIMED equals the shipped h46ClaimNamesBranch over every yield fixture.
  • node scripts/pm/check-half-states.mjs --self-test: 4962 cases pass. New battery "Yielded claim reading" with 21 cases (pinned at 19). It holds the measured pm-dispatch model tiering: a card whose surface touches docs/adr/** is dispatched at CONTRACT_REVIEW_TIER with effort xhigh, no one-line exit #25729 race verbatim in the lines the readers read, a protocol-branch control, the decoration, precedence and bare-body cases, and the controls that must withdraw nothing. Every pre-existing case stays green.
  • No ablation: the diff is scripts only (os-dev: scripts are not ablated).

Gates (local, at f32ecea4)

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 43 families from the two changed paths. Each was run with its exit code captured before any pipe, and all 43 exited 0. That includes pnpm check:closing-target-claim, pnpm check:pm-half-states and pnpm check:pm-dispatch-gates, the last under os-verify-lock: "VERDICT command-exit 0", fast tier 1917 cases.
  • --ran reconciliation: "✓ dispatch-gates --ran: 43 derived famil(ies) accounted for — 43 run, 0 NOT-MEASURED (a DERIVED zero — all 43 recorded an exit code and none of them is 3)."
  • Changeset: none. scripts/pm/** and scripts/check-* publish nothing, so the PR carries skip-changeset.

Acceptance notes

  • Out of scope and deliberately not changed: CLAIM_BRANCH_SHAPE parses only the claude/issue-N-slug shape. A claim naming a card-thread branch (claude/project-thread-…) therefore gives no branch, and every such PR reads UNDETERMINED in this guard. That includes this PR, whose card claim names claude/project-thread-1r3slx. On a race whose winner is on such a branch, the loser's PR also reads UNDETERMINED rather than red. The self-test pins that it is no longer CLAIMED, and nothing further. The guard header records why the branch reader is not widened here (the 2026-08-11 ruling; the repair direction is the write side). The report carries it as a finding for the seat.
  • A yield comment whose body happens to contain a line starting Claim: would still be read as a claim, as today. No such yield was measured. This is noted only.

Generated by Claude Code

The race re-read rule makes the loser yield in a comment, never by deleting
its claim, so the loser's claim is always the newer one. claimGovernance read
it as the governing claim and h46ClaimNamesBranch still counted it, so the
closing-target guard went red on the winner's PR.

yieldedClaimIndexes (exported once from check-half-states) recognises the
protocol's `already claimed — yielding` first line naming exactly one
session, and withdraws only the earlier claims whose Session: line names that
session. Both readers skip them; the guard imports the same predicate.

Claude-Session: https://claude.ai/code/session_01LqKsGyMNFwwnbF5FTAqyPn
Co-authored-by: Claude <noreply@anthropic.com>
…parator is strict equality

Claude-Session: https://claude.ai/code/session_01LqKsGyMNFwwnbF5FTAqyPn
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-26T09:28Z

Seat review of record for #25791 on head f32ecea4, against GitHub, not the report's own account. Skills-lane own code PR (scripts/** only), self-reviewed in seat at the default tier with the checklist unreduced.

  1. Shape. Draft, base main, body line 1 Fixes #25791; no closing keyword sits next to any other card number. Files (GET …/pulls/26016/files): scripts/pm/check-half-states.mjs (+169/−2) and scripts/check-closing-target-claim.mjs (+74/−4), inside the claim's file surface. check-governed-merges.mjs --pr 26016: NOT governed, 249 changed lines, under 3000. skip-changeset is correct: neither path ships in any package's files[].
  2. Premise, measured. The PR's mechanism table matches the seat's own reading in claim 6411570151: both readers skip a claim its own session later yielded, through one exported recogniser (yieldedClaimIndexes) that the guard imports rather than forks. Diff read in full: the yield is recognised only on the comment's first non-blank line, with exactly one session id, and only for claims that precede it. That keeps the winner's claim standing, because the measured yield names the winner only in its body. SEAT_SESSION_ID carries no g flag, so claimSessionId's exec holds no lastIndex state.
  3. Gates, pinned to head f32ecea4. 53 check-runs, 36 success and 17 skipped, none red or pending. That includes Lint & Repo Gates, TypeScript Type Check, Test Core (8/8 shards), Governed Surface Queue Guard, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, and The card this PR closes must claim this branch (success: an UNDETERMINED reading here, by construction, see 5).
  4. Accepted deviations. (a) No empty-branch probe push: os-dev.md rule 1 exempts a platform-assigned thread branch, and it wins over the order. (b) A first gate sweep ran before pnpm install finished; it was re-run whole at f32ecea4 with 43 of 43 families exiting 0, and --ran reconciled. (c) A first half-states self-test had 10 red cases from an array comparison; fixed in f32ecea4.
  5. Out-of-scope dispositions. (i) The claim reader parses only claude/issue-N-… branches, so every claim naming a card-thread branch reads UNDETERMINED in this guard: dropped as a card here. The repair is either a claim-spelling (pm-rule) change, which the filing gate files only on the maintainer's naming, or a reader widening, which the 2026-08-11 ruling closes. The seat raises it to the maintainer in the round report. (ii) A yield body containing a Claim:-led line: noted, not filed (Acceptance notes).
  6. Landing. Not governed and not over the size threshold ⇒ the seat readies it and arms auto-merge through the relay (pr_ready, then automerge_enable), follows it to MERGED, and confirms on both the queue branch and origin/main.

Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

Closing-target claim guard reads a yielded claim as the newest claim and reds the winning branch

2 participants