fix(pm): report a claim that parses to ZERO branches instead of discarding it - #17766
Merged
Conversation
…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
…ro-branch-claim-loud
This was referenced Sep 12, 2026
os-sales
marked this pull request as ready for review
September 12, 2026 04:15
This was referenced Sep 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 (wideningclaimedBranchesfor 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
governingClaimpicked the newest claim comment from which at least one branch parses, andif (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— whatgoverningClaimhas always returned.governingClaimis 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 thecreated_atof 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
state-band row, fires for every openpm:dispatchedcard, naming the comment id, what governance did instead, and the remedycheck-clause2-carriersdeclaration limbClause-②line as if currentcardDeclarationreturnsclaim-branch-unparsedbefore any line is read, from any comment (including the #17366 correction path)--pair NgoverningThe 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 ownsCLAIM_COMMENT_MARKERandclaimedBranches) 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:dispatchedcard:pm:dispatchedcards.translation-target-unknownis warning-only and its rule id carries no namespace, so no app can gate it #16310, dashboards.mdx: the Global Filters example filters onregionwhile the page's dataset declares a dimension namedregion(account.region) — the shared spelling implies one namespace #16268, docs(i18n): the normative i18n protocol page has the same inlineI18nLabellocale-map omission the translations guide just fixed #16251,schemaTreeIsStaleandbundlesAreStalecarry the same mtime false refusal #14985 fixed fordistIsStale— measured, both refuse on a tree whose bytes never moved #16175, [finding] two gates in the same derived family cannot share one NODE_OPTIONS:check-required-contexts --verify-required-setprescribes--use-env-proxy, andcheck-cross-package-test-inputs --self-testgoes red under it #15234.Four of the five claims were posted by ONE session (
session_012GKcPZbMoGq7WPzKLfRBTU, thedomain:devxexecution PM seat) within three seconds of each other, 02:53:05Z–02:53:08Z, all in the same shape: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-rulesagainst the currentclaude/issue-16175-staleness-mtime-false-refusal. Every downstream reader is probing the wrong ref, and the twoClause-②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
dateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322 cost (two rounds, a director re-review, a re-issued claim) and its near-miss sibling. Option 1 addresses the one spelling in front of us; option 2 addresses the population that produces them.⇒ 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=$?).pnpm check:pm-half-states✓ check-half-states self-test: 3656 cases pass.(was 3616)pnpm check:pm-clause2-carriers✓ check-clause2-carriers self-test: 493 cases pass (...)(was 465)pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1678 cases pass.✓ 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).The union was re-derived and re-run after the final commit, on head
b2b55c2b4(git rev-parse --short HEAD), with--repo objectstack-ai/objectstackasserted. The first derivation printed a STALE TREE clause namingcheck-skill-line-ratchet.mjsandcheck-widening-tells.mjs(PR #17760 had landed);origin/mainwas merged in, the list re-derived byte-identically at 41, and every command re-run on the merged head. Reconciliation was fedcommand :: exit Nlines so the zero is derived, not claimed.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-objectagainst the HEAD blob rather than by an exit code.malformednever populated). Anchor occurrences 1 → 0, file hashf9f869cd→013b485b.git checkout HEAD -- scripts/pm/check-half-states.mjs;git diff HEADempty, hash back tof9f869cd. The script carriestrap ... 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 thepm:dispatchedcensus 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
813f8e9fmaterialised withgit archiveand itscardDeclarationrun against the same live comment rows:declaredno("Clause-②: no")claim-branch-unparseddeclaredno("Clause-②: no")claim-branch-unparsedmissingclaim-branch-unparsedTwo of the three read as a confident
Clause-②: notaken off a comment whose governance was never established — the #16589 near-miss shape, twice, on today's board. The third read asmissing, 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:
dateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322's two claims (5593513389Clause-②: no, 5594909614Clause-②: yes) — the inline spelling, and the two values DISAGREE, which is why reading the wrong one was a wrong answer and not merely an unlucky one.cardDeclaration'spool = claimRowsfallback read the FIRST claim in thread order — a second silent fallback the card did not name, and the same fix reaches it.schemaTreeIsStaleandbundlesAreStalecarry the same mtime false refusal #14985 fixed fordistIsStale— measured, both refuse on a tree whose bytes never moved #16175, with its different-branch fallback.absent; an unreadable thread still readsunreadable.CLAIM_COMMENT_MARKERstill matches it.Acceptance notes
skip-changesetapplies:scripts/pm/**ships in no package (fast-track path, no measurement owed).Clause-②: no, as the claim declares — nothing published moves.check-clause2-carriersT1 reads a function PARAMETER annotatedctx: z.RefinementCtxas a new authorable key — so every PR that adds an object-level refusal raises a widening tell for the refusal itself #17618), no.md, no.claude/**.stateband, beside H34 — H34 reads a claim whose SEPARATOR the marker refuses, this one reads a claim whose BRANCH line the directive reader refuses; both are a live card contradicting itself, repaired on the board. ⛔ Notstall: the row does not claim the card is stopped, and the dev may be working perfectly well — what is broken is the READING.Claim:. Pinned in both directions.cardDeclaration'spool = claimRowspath (used when nothing on the thread parses) picks the FIRST claim comment in thread order rather than the newest. This PR makes that path unreachable for the branchless case, so the residue is a recency question on a path no live shape now reaches. Successor: whoever next touchescardDeclaration— [finding]check-clause2-carriersreads a key-INITIAL describing line as a declaration — its own self-test asserts the general property "only DESCRIBES ⇒ MISSING", and the fixture pinning it covers only the other half #17098 is open against that function's neighbourhood.🤖 Generated with Claude Code
https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
Generated by Claude Code