Skip to content

fix(pm): report a claim that parses to ZERO branches instead of discarding it - #17766

Merged
os-sales merged 3 commits into
mainfrom
claude/issue-17149-zero-branch-claim-loud
Sep 12, 2026
Merged

fix(pm): report a claim that parses to ZERO branches instead of discarding it#17766
os-sales merged 3 commits into
mainfrom
claude/issue-17149-zero-branch-claim-loud

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #17149

A Claim: comment that parses to ZERO branches is now reported as a MALFORMED claim instead of being discarded. The deliverable is triage 5620657752's, verbatim: 「⛔ do not fix the third spelling. A parser that silently yields zero must say so — an unparsed claim is an unclassified result, ⛔ never a no.」 Option 1 on the card (widening claimedBranches for the inline spelling) is not done here, and the branch reader's accept set is byte-identical — pinned as a case, because that is what keeps the NEXT unrecognised spelling loud on its first occurrence.

The mechanism

governingClaim picked the newest claim comment from which at least one branch parses, and if (branches.length === 0) return; threw the rest away. A discard is indistinguishable from an absence, so governance fell back to an older claim and said nothing.

claimGovernance(commentRows) is now the two-part reading:

  • governing — what governingClaim has always returned. governingClaim is a one-line wrapper over it, so the ~18 readers that legitimately need only branches are untouched (proved below: the self-test's 3616 pre-existing cases pass unchanged on the refactor alone).
  • malformed — the NEWEST claim-shaped comment when it parses to zero branches, carrying its comment id, its timestamp, and the created_at of whatever governance fell back to.

Recency is written once and both halves call it, so the two readings can never disagree about which comment is current.

Every reader says so

reader before after
H20 / H27 dispatch liveness probed the superseded branch, or (nothing parses) went silent as "no claim" H60, a new state-band row, fires for every open pm:dispatched card, naming the comment id, what governance did instead, and the remedy
check-clause2-carriers declaration limb read the superseded claim's Clause-② line as if current cardDeclaration returns claim-branch-unparsed before any line is read, from any comment (including the #17366 correction path)
--pair N exit 4 with a verdict taken from the wrong comment exit 2 (UNJUDGED), with the whole reading printed
the fold/lane rosters (H37/H38) built on governing unchanged — they need branches only, and H60 is the row that says the roster may rest on a superseded claim

The exit code is the file's own, not a new one. Its table already reads 2 as UNJUDGED — "an unread carrier is NOT a bare carrier and an unread thread is NOT an absent declaration (#4690)" — and an unresolvable carrier is exactly that. Rendering it as 4 would make an unclassified result an adverse verdict, which is the reading the card refuses by name. Rendering it as 0-with-a-message is what the whole file exists against.

The three questions the card asked

1. Where does the guard belong — carriers, half-states, or both?

Both, from one source. The state is produced once in check-half-states.mjs (which owns CLAIM_COMMENT_MARKER and claimedBranches) and consumed in both files. A second detector in the carriers file is the drift that file's own docblocks refuse by name ("imported rather than restated ... so the two readers cannot drift").

2. Which end?

Option 2 only. See the four-axis reading below.

3. Is the fleet's claim template the cause? — MEASURED on the live board

Read 2026-09-12T03:4xZ, REST, repo-scoped, over every open pm:dispatched card:

Four of the five claims were posted by ONE session (session_012GKcPZbMoGq7WPzKLfRBTU, the domain:devx execution PM seat) within three seconds of each other, 02:53:05Z–02:53:08Z, all in the same shape:

Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16310-orphan-locale-key-gateable
Clause-②: yes

That is not a run of typos; it is a template emitting a carrier no reader accepts. #16175 is the silent-fallback shape, live today: its newest claim (5642984850, 2026-09-12) parses to zero, so governance falls back to its 2026-09-06 claim (5557414924) — which names a different branch, claude/issue-16175-regen-sibling-stale-rules against the current claude/issue-16175-staleness-mtime-false-refusal. Every downstream reader is probing the wrong ref, and the two Clause-② values happen to AGREE, which is the #16589 near-miss recorded on #16322 reproducing itself.

⛔ No .claude/** edit is made from this card, per the dispatch. The template finding is handed to the skills-lane seat; after this PR lands, those five cards are visible rather than silent, which is the point.

Four-axis reading of option 1 vs option 2

⇒ Option 2, on all four. Option 1 is deliberately NOT also done: doing both would let the accept set absorb the measured spelling and leave the new state unexercised on the live board, which is the one way to ship this fix and still not know whether it works.

Verification

All exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?).

command verdict line exit
pnpm check:pm-half-states ✓ check-half-states self-test: 3656 cases pass. (was 3616) 0
pnpm check:pm-clause2-carriers ✓ check-clause2-carriers self-test: 493 cases pass (...) (was 465) 0
pnpm check:pm-dispatch-gates ✓ dispatch-gates self-test: 1678 cases pass. 0
the derived union, 41 commands ✓ dispatch-gates --ran: 41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of them is 3). all 0

The union was re-derived and re-run after the final commit, on head b2b55c2b4 (git rev-parse --short HEAD), with --repo objectstack-ai/objectstack asserted. The first derivation printed a STALE TREE clause naming check-skill-line-ratchet.mjs and check-widening-tells.mjs (PR #17760 had landed); origin/main was merged in, the list re-derived byte-identically at 41, and every command re-run on the merged head. Reconciliation was fed command :: exit N lines so the zero is derived, not claimed.

⚠️ NOT MEASURED, and named rather than implied: the 47 artifact-roster families, the 11 declared-wide-population families, the 4 families taking a value from the workflow, and the 1 path-scheduled CI job are each outside the derived total — CI's, not this run's.

Ablation — the new state can actually fail

Committed first, then mutated on disk, then restored; the mutation and the restore are both proved by git hash-object against the HEAD blob rather than by an exit code.

  • Mutation: the pre-fix silent discard restored (malformed never populated). Anchor occurrences 1 → 0, file hash f9f869cd013b485b.
  • Result: 20 of 3656 half-states cases and 14 carriers cases turn RED, every one of them in the new batteries. Direction: turns red, as pre-registered.
  • Restore: git checkout HEAD -- scripts/pm/check-half-states.mjs; git diff HEAD empty, hash back to f9f869cd. The script carries trap ... EXIT INT TERM.

Live verification — the fix, run against the real board

Both sweeps were run on the merged head, 2026-09-12T04:0xZ.

check-half-states.mjs (sweep, exit 0) emits exactly 5 H60 rows — the same five cards the independent REST census found, arrived at through the fix's own code path: #15234, #16175, #16251, #16268, #16310. Both sentence variants fire live: #15234 gets the "NOTHING governs this card" reading, #16175 gets "governance SILENTLY FELL BACK to an OLDER claim (2026-09-06...)".

check-clause2-carriers.mjs (sweep, exit 2) reports 5 of 18 card/PR pairs UNJUDGED — and two of them (#15627 via PR #17776, #16565 via PR #17310) are cards the pm:dispatched census could not see, because they are not in that population. Before this change the sweep exited 0 on all of them.

The before/after, measured rather than reasoned — the base tree at 813f8e9f materialised with git archive and its cardDeclaration run against the same live comment rows:

card BEFORE AFTER
#16565 declared no ("Clause-②: no") claim-branch-unparsed
#16175 declared no ("Clause-②: no") claim-branch-unparsed
#15627 missing claim-branch-unparsed

Two of the three read as a confident Clause-②: no taken off a comment whose governance was never established — the #16589 near-miss shape, twice, on today's board. The third read as missing, which sends the seat looking for a declaration line that is already written. Neither was a reading anybody could have found without opening the card by hand.

Fixtures

The measured bodies are quoted, never paraphrased, so a future widening cannot make these batteries pass by accident:

Acceptance notes


🤖 Generated with Claude Code

https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK


Generated by Claude Code

…rding it

`governingClaim` picked the newest claim comment from which at least one
branch parses and DISCARDED the rest, so a claim comment matching
`CLAIM_COMMENT_MARKER` that yields zero branches was indistinguishable from
an absent one: governance fell back to an older claim and said nothing.

`claimGovernance` is the two-part reading — `governing` (what
`governingClaim` has always returned, byte for byte) and `malformed` (the
newest claim-shaped comment when it parses to zero branches). H60 prints
the state for every open `pm:dispatched` card, and the clause-② declaration
limb returns `claim-branch-unparsed` and is accounted UNJUDGED rather than
reading a superseded comment's declaration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
H60's battery replays the measured inline spellings (2026-09-08/09 on one
card, and the four a second seat's template produced inside three seconds
on 2026-09-12) and pins that the branch reader's accept set did NOT move:
the state is the fix, so the next unrecognised spelling is loud on its
first occurrence instead of being bought one widening at a time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 12, 2026
@os-sales
os-sales marked this pull request as ready for review September 12, 2026 04:15
@os-sales
os-sales added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 1e5b5e0 Sep 12, 2026
40 checks passed
@os-sales
os-sales deleted the claude/issue-17149-zero-branch-claim-loud branch September 12, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants