Skip to content

pm(half-states): H67 reads the queue for cards a PR already landed on - #18678

Merged
os-justin merged 5 commits into
mainfrom
claude/issue-18372-queue-close-condition-reading
Sep 17, 2026
Merged

os-justin merged 5 commits into
mainfrom
claude/issue-18372-queue-close-condition-reading

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18372

Clause-②: no

skip-changeset — the diff is one file under scripts/pm/**, which no released package ships (files[] on every published manifest excludes it), so nothing publishes.

The defect

On 2026-09-09 a batch state transition moved a bucket of cards back onto pm:queue. The transition set the label correctly and nobody re-read each card's own closing condition, and part of that bucket had already been delivered by another PR during the wait — so the queue length every seat reads (取卡全序, the priority sort, a batch decision) is an upper bound rather than a to-do count, and a seat can be handed work that is already done. The filer measured one lane: 63 open unassigned pm:queue cards, 38 of them with at least one merged cross-referenced PR and zero open ones, 25 with no linked PR at all. It hit the shape three times in a row in one shift and caught all three only by a rule of its own invention — 「派发前先重读关闭条件」 — which is in no repo text. This PR is acceptance ④ and nothing else: that rule made a repeatable, report-only reading. It judges none of the 38, and it closes nothing anywhere.

The row — H67

Population. Open, unassigned pm:queue cards. h67SpeaksAbout CALLS h66SpeaksAbout rather than restating it (refsOnlyLinksFor's discipline): the two rows read one population from two channels, and a population that drifted apart would let a card be "in the pool, unowned" for one row and not the other with nothing saying so.

Tell. The card's own timeline carries at least one MERGED cross-referenced PR (source.issue.pull_request.merged_at parses) and no OPEN one — the filer's own instrument, taken as-is rather than re-derived, because the card's readings are stated against it. A closed-never-merged PR is a third class: it delivered nothing and it is not an open PR, so it neither fires nor clears, and the row names it.

Discriminators, per row, at ZERO extra requests. A cross-referenced event carries the whole source issue INCLUDING ITS BODY, and commented events ride the same page, so all three legs read off the page that was already bought:

  1. What each merged PR declared to THIS card — a closing keyword, Part of, Refs, a bare mention, or nothing at all. All four grammars are this file's own (closingKeywordTargets / partOfTargets / refsTargets / referencedNumbers), CALLED and never restated, and bound PER CARD NUMBER as H7 binds.
  2. The card's own closing condition, QUOTED — a frozen, capped anchor set (H67_CLOSING_CONDITION_ANCHORS), each entry beside the live instance it was measured on, read over the body first and then the thread newest-first. A card that states none gets that said on its row: the hand read then has nothing to check the landing against, which is itself the reading.
  3. The newest seat marker, dated against the merge — H49's stand-down, reused: a Claim: or a Release: newer than the newest merge is a seat that has already looked at this card SINCE the delivery landed, and either stands the row down structurally.

Remedy, on every row. Judge by hand, card by card. 「有已合 PR」 is not a closing criterion. And before closing anything, carry the thread's residual readings out into a NEW card first — they are other seats' after-the-fact measurements and closing buries them, which is why the filer's two closures filed #18343 and #18364 first.

⛔ Part of cards are never listed as candidates for closing (acceptance ③). They are listed, in a bucket whose sentence opens 「⛔ NOT A CANDIDATE FOR CLOSING」, names H49 as the row that owns the shape, quotes the partial-landing rule verbatim, and says what is owed instead: a re-read of the remainder, never a close. The reason the bucket has to speak rather than the row stay silent is measured and is the load-bearing fact of this card: #15815 (work done, closed) and #16529 (a true remainder, correctly kept) carry identical machine evidence — both reached only by a merged PR declaring Part of #N. No bucket may read as "close this", and the pair is pinned as a case.

Disjointness, argued on each neighbouring row's own criterion

row why it cannot be this row
H8 requires pm:dispatched; this population is pm:queue without it (PM_EXCLUSIVE_STATE_LABELS excludes the pair, which is H3's row). Disjoint by label, in every input.
H47 leg (a) requires an assignee; this population has none. Leg (b) asks about the OWNERSHIP RECORD's shape with no PR in the input at all; this row asks whether a PR LANDED and reads the record only to date it against that merge. A card can fire both — two readings, not one restated.
H49 requires pm:dispatched AND a non-empty assignee. Disjoint by both fields. It is the nearest neighbour in substance, which is why the Part of/Refs bucket NAMES H49's rule instead of inventing a second disposition for the same shape.
H58 reads the card's own declaration that its deliverable is a RULING, in a heading, over the same population; it buys nothing and never looks at a PR. This row reads the DELIVERY.
H59 direction (b) is the one real overlap and it is stated on the row rather than hidden. H59 starts from H8's merged WINDOW (MERGED_WINDOW_DAYS, 8 days) over every open card, so a delivery older than the window is invisible to it — which is exactly the tail this card is about. This row starts from the CARD and reads its whole timeline with no time bound, over the unassigned queue only. In the intersection both fire, with different sentences and different remedies. Neither is narrowed to serve the other.
H66 reads the NEWEST COMMENT for a RELEASE and buys a COMMENT page; this reads the TIMELINE for a MERGE and buys a TIMELINE page. The stand-down legs point opposite ways, which is the cleanest statement of the difference: a release as the newest word FIRES H66 and CLEARS this row. Pinned as a case.

Budget

One timeline page per card, page 1, per_page=100. The page is ONE for H59's reason: the timeline is served oldest-first and no listing payload carries an event count, so a newest page cannot be located the way H65/H66 locate a newest comment page — and a page that came back FULL may hide a later merge, a later open PR or a later Claim:, so that card is UNJUDGED and counted, never clean.

H67_TIMELINE_READ_CAP is 120 — a little over a tenth of one hour's Actions budget added to a run that happens at most once every six hours. Nothing in this file caches a timeline, so there is no free half of the kind H66 has: every judged card costs its page, and the cap is therefore the row ceiling too.

Order: OLDEST-FIRST, the opposite of H66's, deliberately. H66 chases a fresh write because a release is one. Here the evidence is a merge that may be weeks old, so recency says nothing about where to look; what decides is who gets hurt. The order in force is 「队列每轮清空时最新优先;清不空的那一刻起改最老优先」 and the queue has not emptied, so the cards a seat reaches NEXT are exactly the cards this row must have read. The plan sorts by the two legs of 取卡全序 the label page gives for free — priority rank, then card age — and declares what it does not model (target: board membership, the same-priority 「先 Bug」 tiebreak). An unreadable created_at sorts LAST, never first.

Coverage quadruple on the summary line, unconditionally: candidates / read / listed / not attempted, plus the unjudged count as its own number. A partial read is never published as a zero.

Pins

Battery H67 queued merged-delivery reading, 151 registered, pinned at 142 (the neighbours' headroom: H66 183/172, H19 37/34). SELF_TEST_BATTERY_FLOOR rises 2 to 3 with it. The battery floors a LISTING whose whole value is its discriminators, plus the two negatives the card rules on twice: a Part of card must never read as a candidate for closing, and an OPEN PR must stand the row down. Cases include the three instances from the card (#15815-shaped listed with its condition quoted; #16529-shaped Part of listed under 「⛔ NOT A CANDIDATE FOR CLOSING」 and cleared outright once a release answers the merge; #16565-shaped bare-mention listed under the weakest bucket), the open-PR and no-PR silences, the abandoned-PR class, the three page states, the seven population gates driven whole, the foreign-repo refusal, the relation ladder, the closing-condition reader and the four shapes it structurally refuses, the read plan and its cap, the pass audit, the disjointness pairs, and the quadruple.

check-half-states self-test: 4735 cases pass.
Batteries: H66 released queue card 183/172, H19 judged-set founding 37/34,
           H67 queued merged-delivery reading 151/142.

Ablation — from the committed fix, on-disk proof both legs

The guard removed is the OPEN-PR stand-down, if ((timeline.open ?? []).length > 0) return null;. Run under scripts/pm/os-verify-lock.sh from the committed state, with an EXIT INT TERM trap restoring git checkout HEAD -- against the ABSOLUTE path. There is no build or dist/ leg: this script is executed from source, so the on-disk proof IS the whole preflight.

HEAD blob         = 21a44d9a7ab313976b43c00dff1b5b37fd6614a5
on-disk pre       = 21a44d9a7ab313976b43c00dff1b5b37fd6614a5
guard occurrences BEFORE = 1      (anchor asserted unique, or the run aborts)
guard occurrences AFTER  = 0      injected marker count = 1
on-disk mutated   = ef47d63835f3feba6aa8d3b00bb3a892e197ef03
MUTATED self-test exit = 1 -- 2 of 4735 case(s) failed:
  x H67 B bucket: an OPEN linked PR stands the row down
  x H67 B bucket: ...even when it declares nothing about this card
on-disk restored  = 21a44d9a7ab313976b43c00dff1b5b37fd6614a5
git diff HEAD: EMPTY (restore proven)   blob hash matches HEAD: YES
RESTORED self-test exit = 0 -- 4735 cases pass.

The direction is the expected one (red), and the failure is SCOPED: exactly the two cases that assert the guard, and no neighbour's case moved.

Live sweep — report-only, from this worktree, under the verify lock

node scripts/pm/check-half-states.mjs over objectstack-ai/objectstack, one run, exit 0, lock held 529s (a shared-box wall clock, not an idle-box figure).

Queued cards with a merged delivery (H67):
  207 open unassigned `pm:queue` card(s) could be spoken about
  120 had ONE timeline page READ this run (cap 120 page(s))
   55 LISTED
   87 NOT ATTEMPTED at that cap
    1 card(s) stayed UNJUDGED

Lane breakdown of the 55, joined against a pm:queue label page re-read right after the sweep: domain:spec 25, no domain: label 14, domain:devx 12, domain:skills 2, domain:cli 1, and one card (#12511) that had already left the queue page by the time of the join.

The devx reading beside the card's 38 — a reading, not a judgement of those cards. The devx lane's open unassigned pm:queue population is 53 at the time of this sweep (the card measured 63 on 2026-09-16), and 12 of them are LISTED by this row. ⛔ That 12 is NOT the card's 38 re-measured and must not be read as a correction of it. Three declared reasons it cannot be compared directly:

  1. The cap read 120 of 207 candidates, oldest-first across ALL lanes. An offline replay of h67ReadOrder over a pm:queue page read after the sweep (222 candidates then) puts 18 of the lane's 53 inside the cap and 35 outside it — so most of the lane is NOT ATTEMPTED, not clean.
  2. The filer's A bucket is the RAW 「has a merged PR」 count with no stand-down, while this row clears a card whose Claim: or Release: is newer than the merge, and clears one with an open PR.
  3. The board moved between 2026-09-16 and this run.

⛔ Nothing here closes, relabels or proposes a state for any of the 55. Acceptance ①②③⑤ bind whoever closes them, with this reading in hand.

Gates

Derived from this worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no hand-fed path list). The first derivation ran on a tree three commits behind origin/main with scripts/pm/dispatch-gates.mjs itself stale in that range; origin/main was merged in and the derivation re-run — the family list is identical (38, none added, none gone) and the tree is no longer stale. Reconciliation: --ran reports 38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN.

Every exit code below was captured with redirect-then-$?, never across a pipe.

exit 0  node scripts/check-changeset-no-major.mjs --base origin/main
exit 0  node scripts/check-changeset-no-major.mjs --self-test
exit 0  node scripts/check-ci-filter-parity.mjs
exit 0  node scripts/check-closing-keyword-parity.mjs
exit 0  node scripts/check-closing-keyword-parity.mjs --self-test
exit 0  node scripts/check-comment-mask-corpus.mjs
exit 0  node scripts/check-declaration-mirrors.mjs
exit 0  node scripts/check-declaration-mirrors.mjs --self-test
exit 0  node scripts/check-scripts-symbol-anchors.mjs
exit 0  node scripts/check-scripts-symbol-anchors.mjs --self-test
exit 0  node scripts/check-self-test-wired.mjs
exit 0  node scripts/check-self-test-wired.mjs --self-test
exit 0  node scripts/check-self-test-workflow-commands.mjs
exit 0  node scripts/check-self-test-workflow-commands.mjs --self-test
exit 0  node scripts/check-whole-set-label-write.mjs
exit 0  node scripts/check-whole-set-label-write.mjs --self-test
exit 0  node scripts/pm/bare-root-worklist.mjs --self-test
exit 0  node scripts/pm/board-snapshot.mjs --self-test
exit 0  node scripts/pm/check-governed-queue-guard.mjs --self-test
exit 0  node scripts/pm/sweep-closed-cards.mjs --self-test
exit 0  pnpm check:agent-test-spelling
exit 0  pnpm check:bash32-floor
exit 0  pnpm check:changeset-gate-self-tests
exit 0  pnpm check:cli-command-ids
exit 0  pnpm check:closing-target-claim
exit 0  pnpm check:commit-card-trailers
exit 0  pnpm check:cross-package-test-inputs
exit 0  pnpm check:driver-memory-census
exit 0  pnpm check:entry-guard
exit 0  pnpm check:nul-bytes
exit 0  pnpm check:parse-guard
exit 0  pnpm check:partof-closing-keyword
exit 0  pnpm check:pm-dispatch-gates
exit 0  pnpm check:pm-half-states          (under the verify lock)
exit 0  pnpm check:pnpm-filter-targets
exit 0  pnpm check:ratchet-remedy-authority
exit 0  pnpm check:refd-timer-probe
exit 0  pnpm check:watch-hint-literal

exit 0  pnpm lint                          (repo-wide, `eslint . --no-inline-config`)
exit 1  grep -naP '[control-byte class]' scripts/pm/check-half-states.mjs
        (exit 1 = no match = no control byte; captured before any pipe)

The gate union above was run on this branch's final content; pnpm check:pm-half-states and pnpm lint were re-run after the origin/main merge.

Two deviations from the dispatch order, both argued rather than taken silently

  1. The read order is OLDEST-FIRST, where the dispatch said to bound it the way H66's buy leg is (a cap, newest-touched first, a coverage quadruple). The cap and the quadruple are H66's exactly; the ordering is not, and the row header carries the argument in full: the evidence here is a merge that may be weeks old, so newest-touched sorts on a signal that does not exist for this row, while 最老优先 is the order a dispatching seat actually takes cards in.
  2. Part of cards are LISTED, in a bucket that says 「⛔ NOT A CANDIDATE FOR CLOSING」. The dispatch left the choice open ("state which bucket they land in or that they are named as such"). Listing them is what the filing card asks for — its A bucket is 「必须逐张验的候选集」 and its instance three ([finding] Nothing an app author reads says a type: 'app' package may hold only ONE app — the ui skill and ui/audience-based-interfaces both omit it, and ADR-0019 is two different records #16565) was a bare-reference card whose work really was done — and the acceptance-③ refusal is met by the bucket's own words rather than by silence.

Out of scope, noted and not fixed here

RELEASE_COMMENT_MARKER and CLAIM_COMMENT_MARKER anchor the word at line start and tolerate only a leading blockquote, so a DECORATED ownership line is invisible to them — measured live on objectstack#16529 comment 5697022358, which is a real release record H2, H47 and H66 cannot see. That reach is shared with three landed rows and widening it is a different card; this row declares the loss on its own sentence and in its summary clause instead, and a control case pins that the decorated spelling really is refused while the bare one still matches. Filed to the triage seat in this PR's report comment; carrier for whoever picks it up: scripts/pm/check-half-states.mjs, the two marker constants.


Generated by Claude Code

Base merge for the H65 tier-declaration landing (#18672). One conflict hunk,
resolved by keeping BOTH batteries: the roster now declares four, so
SELF_TEST_BATTERY_FLOOR rises to 4 rather than staying at the 3 each side
wrote for its own third battery.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit f8eaf67 Sep 17, 2026
34 checks passed
@os-justin
os-justin deleted the claude/issue-18372-queue-close-condition-reading branch September 17, 2026 15:36
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… make a near miss audible (objectstack-ai#18756)

Fixes objectstack-ai#18680

Clause-②: no

## The defect

`CLAIM_COMMENT_MARKER` and `RELEASE_COMMENT_MARKER` anchor 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. objectstack-ai#10102 made exactly this judgement for the other directive
family (`Blocked-by:` / `Restart-when:`) and never for these two
markers; PR objectstack-ai#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 are `m`-flagged and scan every line, so line position was
never what hid the record. The asterisks were.

| probe | `RELEASE_COMMENT_MARKER` (bare) |
|:--|:--|
| the specimen's whole comment body | `false` |
| the specimen's release line alone | `false` |
| the SAME line with `**Release:**` replaced by `Release:` (control) |
`true` |
| `**Claim:** …` / `` `Release:` … `` / `__Release:__ …` / `## Release:
…` / `- Release: …` | all `false` |

`undecorateProseLine` — the objectstack-ai#10102 function — was measured rather than
assumed: its body is a single `replace` over 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 inside `check-half-states.mjs` itself (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`, and
`h66ReleaseVerdict`), each fed a raw comment body or one raw line; and
one cross-file importer, `scripts/pm/check-clause2-carriers.mjs`, which
feeds `CLAIM_COMMENT_MARKER` a 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
same `undecorateProseLine` the `Blocked-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:

- **Strictly additive, by construction rather than by inspection.** The
bare test short-circuits, so ⛔ no body that read before can stop
reading. Measured on 770 live comments below: 0 regressions.
- **It refuses to undecorate through a markdown LIST ITEM.** The shared
stripper takes every asterisk, so a `* 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_FORMS` is a frozen, named roster in the
register of objectstack-ai#18560's `SCHEMA_PROPERTY_FORMS`; each member carries its
own `example` fixture, 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.

| id | what | example |
|:--|:--|:--|
| `heading` | the directive written as a markdown heading | `## Release:
…` |
| `list-item` | the directive written as a markdown list item | `-
Release: …` |
| `underscore-emphasis` | emphasised with underscores, which the shared
stripper does not remove | `__Release:__ …` |
| `inflected-word` | a spelling the marker's vocabulary does not carry |
`Released: …` |
| `separator` | the canonical word with a separator that is not the
canonical colon | `Release — …` |

`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.

- the fetched objectstack-ai#16529 specimen: refused by the bare marker, READ through
`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 body
- the rows: H2 goes clean on a decorated claim (with the no-claim
control still firing); `latestMarkedComment` locates a decorated
release; H66 reads it on the canonical leg with destination `pm:queue`,
quoted undecorated
- what the stripper measures: backticked and bold-italic directives
read; `__Release:__` does NOT, and is a near miss instead
- the firing controls of a widening, inside the same battery: seven bare
spellings still read, prose containing the word still does not,
`Released:` 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 row
- the bullet guard: an asterisk bullet, a hyphen bullet, an ordered `1.`
marker and a blockquoted bullet all refused; the whitespace
discriminator pinned as a pair
- the vocabulary: every member driven against its own fixture — not read
by either marker, reports naming its own form, with the comment id and
the offending prefix — plus the counterfactual roster-equality pin,
frozen-ness, distinct ids, and ⛔ no `g` and ⛔ no `m` flag
- the summary clause: counts, named entries, the cap clause,
unconditional rendering, render order, the forwarding contract, and ⛔ no
`undefined`
- **the PR objectstack-ai#18678 pin, EDITED and not deleted**: `H67 ⚠️ loss: a
DECORATED **Release:** line does not stand the row down` becomes `H67 ⚠️
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.

- on-disk proof before reading any result: injected marker present 1
time, deleted text `const undecorated = raw` present 0 times, HEAD blob
`44aed794…` vs mutated blob `b4ab48a2…` (different, so ⛔ not a no-op)
- result: **17 red, 4864 green**. Every red is a case about the new
reading — the four objectstack-ai#16529 cases, H2/H47/H66 on a decorated record, the
five decoration cases, the two bullet-discriminator cases, and the
flipped H67 pin. Every bare-spelling control, every near-miss vocabulary
case, the `Released:` / fullwidth / prose negatives and the
marker-constant pins stayed green.
- restore verified by hash (`44aed794…` again) AND by an empty `git diff
HEAD` — ⛔ not by an exit code

## Live-board delta — report-only, ⛔ no state write of any kind

Two full sweeps, `node scripts/pm/check-half-states.mjs` against
`objectstack-ai/objectstack`: BEFORE on a detached worktree at
`62bce5c29` (17:53Z to 18:01Z), AFTER on this branch (18:01Z to 18:09Z).

**H2 / H47 / H66 verdicts: identical.** H2 fired on objectstack-ai#13597 and objectstack-ai#15638 in
both; H47 and H66 listed nothing in either. Eight rows differ between
the two runs (H14 objectstack-ai#18617, H38 objectstack-ai#7623, H52 objectstack-ai#18617 dropped; H1 objectstack-ai#18709, H19
objectstack-ai#18734, H36 objectstack-ai#18414/objectstack-ai#18720/objectstack-ai#18741 appeared) and every one is board churn
in the eight minutes between them — none reads an ownership marker.

**objectstack-ai#16529 specifically** still lists on H67 in both, and the reason has
nothing to do with the marker: its newest merge is now PR objectstack-ai#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:dispatched` cards and their 770 comments:

- **NEWLY READ as an ownership record: 4, on 4 cards; REGRESSIONS: 0.**
- objectstack-ai#16529 comment 5691473966 — `**Release:** …` (the card's own specimen)
  - objectstack-ai#17852 comment 5700605769 — a backticked `Release:`
  - objectstack-ai#15468 comment 5549954050 — `**Claim:** …`
  - #14026 comment 5486688759 — a backticked `Claim:`
- **Near misses over the same population: 2** — objectstack-ai#16233 comment
5704218834 and objectstack-ai#18143 comment 5707808263, both `### Release:` (form
`heading`). Over the sweep's wider 187-thread corpus the clause named
three: objectstack-ai#18336 comment 5693290763 (`list-item`), objectstack-ai#15768 comment
5556889538 (`heading`), objectstack-ai#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**:

| card | H47 | H66 |
|:--|:--|:--|
| objectstack-ai#16529 | FIRES becomes **clean** (the release now answers the claim) |
none becomes **`pm:queue`** |
| objectstack-ai#17852 | FIRES becomes **clean** | none becomes **the maintainer** |
| objectstack-ai#15468 | clean becomes **FIRES** (a bolded claim nobody has answered)
| unchanged |
| #14026, objectstack-ai#16233, objectstack-ai#18143 | unchanged | unchanged |

⛔ 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-states` 0 · `check:nul-bytes` 0 ·
`check:closing-target-claim` 0 · `check:commit-card-trailers` 0 ·
`check:self-test-wired` 0 · `check:scripts-symbol-anchors` 0 ·
`check:declaration-mirrors` 0 · `check:whole-set-label-write` 0 ·
`check:changeset-no-major` 0 · `check:pm-governed-queue-guard` 0 ·
`check:cross-package-test-inputs` 0 · `check:parse-guard` 0.

⚠️ `pnpm check:nul-bytes` exited **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 own `grep -naP` control-byte self-scan over the file returns
nothing.

⚠️ `pnpm check:pm-dispatch-gates` is 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's `os-dev-report`
comment rather than guessed here. ⛔ Its diff-relevant half is unaffected
either way — this PR touches neither `dispatch-gates.mjs` nor its
fixtures.

Repo-wide `pnpm lint` (`eslint . --no-inline-config`): **exit 0**, as PR
objectstack-ai#18654 did.

## Not in scope, read and left alone

- **objectstack-ai#18664** (`ISSUE_BODY_LIMIT`, queued behind this card on this file)
— read, ⛔ not touched.
- **H67's own row logic** beyond the pin flip and the two sentences that
declared a loss which no longer exists.
- **The objectui copy of this file** — byte-pinned, re-synced only
through objectui#9395, ⛔ never hand-mirrored.
- **How a verdict is written to a card** — unchanged; this file still
writes nothing.

## Acceptance notes

- H66's summary clause still carries the dated reading 「Measured
2026-09-16 over 29 threads on two boards, the canonical `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.mjs` reads `CLAIM_COMMENT_MARKER` against 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](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants