fix(pm): read the ownership markers through the undecorated line, and make a near miss audible - #18756
Merged
os-justin merged 3 commits intoSep 17, 2026
Conversation
… make a near miss audible `CLAIM_COMMENT_MARKER` and `RELEASE_COMMENT_MARKER` anchor the bare word at line start and tolerate only a leading blockquote, so a seat that BOLDS the directive — `**Release:** …` — writes a record no reader in this file could see. The failure is the silent one: nothing goes red, the ownership rows just read a different history than the thread carries. Every site that asks whether a comment IS a `Claim:` or a `Release:` now asks `markerMatches`, which tries the bare reading first and then the same `undecorateProseLine` the `Blocked-by:` / `Restart-when:` family has used since #10102. The two constants keep their bare semantics — they are the protocol's spelling, a sibling gate imports one of them, and the cases that pin them still assert on them directly. Decoration is handled in ONE place, and the reading is a strict superset by construction: the bare test short-circuits, so no body that read before can stop reading. Undecorating is refused through a markdown LIST ITEM: the shared stripper takes every `*`, so a `* Claim:` bullet would otherwise become a directive while H20's pinned `- Claim:` stays refused. A list marker is followed by whitespace and a decoration is not, which is the whole discriminator. Widening alone would leave the same silence one decoration further out, so `OWNERSHIP_MARKER_NEAR_MISS_FORMS` names five refused spellings, the sweep censuses them off the threads other rows already bought, and an unconditional summary clause names the card, the comment id and the offending prefix. A future decoration is ADDED to that list rather than rediscovered from a silent row. H67's declared loss is retired in the same edit — its row and its summary clause said the decorated line was invisible, and that is no longer true. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
…corated-claim-release-markers
… the byte An editing tool materialised the escape into a real 0x01 while the key was written — the slip `check-nul-bytes` exists for. Byte-identical at runtime. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 17, 2026
os-justin
marked this pull request as ready for review
September 17, 2026 19:22
This was referenced Sep 17, 2026
os-justin
deleted the
claude/issue-18680-decorated-claim-release-markers
branch
September 17, 2026 19:45
This was referenced Sep 17, 2026
Merged
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 #18680
Clause-②: no
The defect
CLAIM_COMMENT_MARKERandRELEASE_COMMENT_MARKERanchor the bare word at line start and tolerate only a leading blockquote, so a line written in the decorated spelling a seat uses when it bolds the directive —**Release:** …— begins with an asterisk and the record reads as ABSENT. Nothing goes red; the ownership rows (H2, H47, H66, H67) simply read a different history than the thread carries, which is the silent direction. #10102 made exactly this judgement for the other directive family (Blocked-by:/Restart-when:) and never for these two markers; PR #18678 landed H67 declaring the loss on its own row rather than widening, because widening moves three landed rows' populations and is its own card. This is that card.Before-readings (reproduced offline, on the fetched specimen)
The live specimen is objectstack#16529 comment 5691473966 (os-try-charles, 2026-09-16T03:11:44Z), fetched through the REST proxy and never retyped.⚠️ One correction to the card's prose: the
**Release:**line is the 45th line of that comment, not its first — both markers arem-flagged and scan every line, so line position was never what hid the record. The asterisks were.RELEASE_COMMENT_MARKER(bare)falsefalse**Release:**replaced byRelease:(control)true**Claim:** …/`Release:` …/__Release:__ …/## Release: …/- Release: …falseundecorateProseLine— the #10102 function — was measured rather than assumed: its body is a singlereplaceover a character class holding exactly a backtick and an asterisk. It removes backticks and asterisks and nothing else — not underscores, not a heading hash. That measurement is what decides which spellings this repair reaches and which become near misses.Consumers of the two exported constants, all found by grep across
scripts/: nine call sites insidecheck-half-states.mjsitself (H2,governingClaim,latestClaimComment, H34's guard,h44ArtefactShape,h46ClaimNamesBranch,latestMarkedComment— which is H47's, H49's, H50's, H53's and H67's shared resolver —SEAT_SIGNATURE_FORMS, andh66ReleaseVerdict), each fed a raw comment body or one raw line; and one cross-file importer,scripts/pm/check-clause2-carriers.mjs, which feedsCLAIM_COMMENT_MARKERa raw comment body. That importer is outside this card's file surface and is deliberately unaffected: the constants keep their bare semantics.The ONE place decoration is handled
markerMatches(marker, text), declared beside the two markers. It tries the bare reading FIRST and short-circuits, then re-tests against the sameundecorateProseLinetheBlocked-by:family uses. All nine in-file call sites now go through it; ⛔ no regex was widened, ⛔ no second stripper exists, and the two constants still describe the bare directive (the cases that pin them still assert on them directly, and they stay green).Two properties fall out:
* Claim:bullet would become a directive while H20's pinned- Claim:stays refused. A list marker is followed by whitespace and a decoration is not; that is the whole discriminator, and it is pinned both ways.The near-miss vocabulary — a line that looks like a marker makes a sound
Widening alone leaves the same silence one decoration further out, which is the triage's second half (comment 5716952460).
OWNERSHIP_MARKER_NEAR_MISS_FORMSis a frozen, named roster in the register of #18560'sSCHEMA_PROPERTY_FORMS; each member carries its ownexamplefixture, and the roster is asserted EQUAL to a frozen list of ids, so a form added without a fixture reds and a form silently dropped reds.heading## Release: …list-item- Release: …underscore-emphasis__Release:__ …inflected-wordReleased: …separatorRelease — …ownershipMarkerNearMisses(commentRows)is the reader; it buys nothing (the sweep hands it threads other rows already paid for), files no finding and proposes no state. It reports on an unconditional summary clause (Ownership-marker near misses: …) naming the card, the comment id and the offending prefix, capped at five named entries with the remainder counted. A line the reading DOES read is ⛔ never a near miss — the two are complements by construction, so a future widening shrinks this census automatically.H67's declared loss is retired in the same edit, on its row and in its summary clause: both said the decorated line was invisible, and that is no longer true.
Pins
New battery
H2/H47/H66 decorated ownership marker, 98 cases, pinned at 94. The roster floor rose 4 to 5.markerMatches; the derived bare spelling of the same line matches both ways (control); a release is still not a claim; the record reads from any line of the bodylatestMarkedCommentlocates a decorated release; H66 reads it on the canonical leg with destinationpm:queue, quoted undecorated__Release:__does NOT, and is a near miss insteadReleased:is still MALFORMED, the fullwidth colon still does not match (the 2026-08-11 ruling is untouched), a dash-written claim is still H34's row1.marker and a blockquoted bullet all refused; the whitespace discriminator pinned as a pairgand ⛔ nomflagundefinedH67 ⚠️ loss: a DECORATED **Release:** line does not stand the row downbecomesH67 ⚠️ loss CLOSED: … now STANDS THE ROW DOWN, as the bare one always did, and its companion flips from "the row DECLARES that blind spot" to "the row no longer DECLARES a blind spot it no longer has". Its two control cases are untouched and still green, because they assert on the marker CONSTANT — which this PR does not change.Self-test: 4782 cases / 4 batteries becomes 4881 cases / 5 batteries (98 battery cases, plus one case the per-anchor summary-clause coverage loop registers for the new clause automatically).
Ablation
Revert the one call that routes the markers through the undecorated line (delete the undecorated leg of
markerMatches, leaving the bare test alone), from the committed state, with an EXIT/INT/TERM trap restoring by absolute path.const undecorated = rawpresent 0 times, HEAD blob44aed794…vs mutated blobb4ab48a2…(different, so ⛔ not a no-op)Released:/ fullwidth / prose negatives and the marker-constant pins stayed green.44aed794…again) AND by an emptygit diff HEAD— ⛔ not by an exit codeLive-board delta — report-only, ⛔ no state write of any kind
Two full sweeps,
node scripts/pm/check-half-states.mjsagainstobjectstack-ai/objectstack: BEFORE on a detached worktree at62bce5c29(17:53Z to 18:01Z), AFTER on this branch (18:01Z to 18:09Z).H2 / H47 / H66 verdicts: identical. H2 fired on #13597 and #15638 in both; H47 and H66 listed nothing in either. Eight rows differ between the two runs (H14 #18617, H38 #7623, H52 #18617 dropped; H1 #18709, H19 #18734, H36 #18414/#18720/#18741 appeared) and every one is board churn in the eight minutes between them — none reads an ownership marker.
#16529 specifically still lists on H67 in both, and the reason has nothing to do with the marker: its newest merge is now PR #18678 (merged 2026-09-17), which is NEWER than the 2026-09-16 release record, so "nobody has looked since the delivery landed" is a correct reading. Its row text did change — the declared loss is gone.
Because a sweep only judges threads it bought, the zero above understates the reading. So the same question was asked directly, over the 286 open
pm:queue/pm:dispatchedcards and their 770 comments:**Release:** …(the card's own specimen)__proto__key from its parse OUTPUT while reporting success — ObjectSchema accepts the document and hands back a different one #17852 comment 5700605769 — a backtickedRelease:ROOTSorDIRS, so the plainest spelling of the class it sweeps is invisible to it #15468 comment 5549954050 —**Claim:** …mappingNameand hardcodeswriteMode:"insert", so a named mapping'supsertcan never reach a user #14026 comment 5486688759 — a backtickedClaim:### Release:(formheading). Over the sweep's wider 187-thread corpus the clause named three: platform-admin re-anchor L5 EXIT (re-file of the unreadable #13515): retire the legacy row-id grant dual read and its deprecation log — the 17.4.0 boundary is measured MET #18336 comment 5693290763 (list-item), A dataset measure over a datetime is typednumberin the analytics response, and a metric tile ignores theformatstyle it is handed — measured on 17.3.0 #15768 comment 5556889538 (heading), Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error) #6736 comment 5235658231 (separator).And the counterfactual the rows themselves cannot show, offering the SAME live thread to both readings — 3 of 6 cards change:
pm:queue⛔ Nothing was written to any card, PR or label from either sweep, and ⛔ no verdict here is a proposal about any of those cards.
Gates
Derived from this worktree with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(⛔ no hand-fed path list; change set:scripts/pm/check-half-states.mjs, one path). 38 families, every one run, exit code captured by redirect-then-$?before any pipe, reconciled with--ran.37 of 38 exited 0, with the two qualifications named below. Notably:
check:pm-half-states0 ·check:nul-bytes0 ·check:closing-target-claim0 ·check:commit-card-trailers0 ·check:self-test-wired0 ·check:scripts-symbol-anchors0 ·check:declaration-mirrors0 ·check:whole-set-label-write0 ·check:changeset-no-major0 ·check:pm-governed-queue-guard0 ·check:cross-package-test-inputs0 ·check:parse-guard0.pnpm check:nul-bytesexited 1 on the first pass and was right to: an editing tool had materialised a backslash-u-0001 escape (written out in words here for the same reason) into a real 0x01 byte in the near-miss dedupe key — the exact slip that gate exists for. Fixed by writing the escape text (byte-identical at runtime), re-run green, and the rule's owngrep -naPcontrol-byte self-scan over the file returns nothing.pnpm check:pm-dispatch-gatesis the one family whose self-test runs longer than this container's foreground ceiling: a first attempt reached 1768 green cases and was killed by the timeout wrapper at 560s (exit 124 = no verdict reached, which is NOT MEASURED and ⛔ not a red). It was re-run detached; its verdict is reported in this card'sos-dev-reportcomment rather than guessed here. ⛔ Its diff-relevant half is unaffected either way — this PR touches neitherdispatch-gates.mjsnor its fixtures.Repo-wide
pnpm lint(eslint . --no-inline-config): exit 0, as PR #18654 did.Not in scope, read and left alone
ISSUE_BODY_LIMIT, queued behind this card on this file) — read, ⛔ not touched.Acceptance notes
Release:line appeared ZERO times」. It names its date and its boards, so it stays true as written, but it was taken with the bare reader and this PR changes what a re-measure would find. Noted, not filed — the sentence is a dated measurement, not a live claim. Who would meet it: the next author of H66's buy-order or clause.check-clause2-carriers.mjsreadsCLAIM_COMMENT_MARKERagainst a raw comment body and therefore still cannot see a decorated claim. That is correct for this card's file surface (the constant is unchanged) and is a reading about that file, not a defect in this one. Noted, not filed; the seat decides whether that gate wants the same reading. Who would meet it: whoever next touches that gate's claim leg.Generated by Claude Code