Commit b302aab
fix(pm): widen H8's inputs — branch-name delivery, closed-card residue (H22), and open-PR suppression (#11107)
* fix(pm): H8 counts a merged PR's branch name as delivery when its body declares none
`h8MergedPrStillDispatched` read delivery from the PR body alone — `Part of #N`
or a closing keyword bound to `#N`. A merged delivery spelled any third way was
invisible: the measured specimen was a merged PR whose body said only
`Refs #10757`, leaving that card carrying `pm:dispatched` unreported for ~22h in
a sweep that reported six other H8 rows correctly.
Every dev branch here is `claude/issue-<n>-<slug>` by protocol and every PR row
already carries `head.ref`, so the recovery costs no API call. It is spelled as
a FALLBACK rather than a third `||` term, because widening the delivery relation
has a cost: a branch cut for card N and later re-scoped would otherwise be
counted as delivering N forever, on the authority of a name nobody updated. The
body is the channel an author maintains, so a body declaring ANY delivery is
authoritative and the branch name is consulted only for bodies declaring none.
Both directions are pinned in the self-test per the card's binding caveat: the
`Refs #N` specimen shape reports, and a re-scoped branch does not false-fire.
Fixes #11036
* fix(pm): H8 consults the open-PR list and stops prescribing a destructive drop on half-delivered cards
`h8MergedPrStillDispatched`'s entire input was the merged-PR window, so it had
no way to ask the question that decides the answer: is there ALSO an unmerged PR
delivering this card? On a card delivered in halves the first half's merge made
the row fire, and it kept firing on every subsequent sweep until the last half
landed — pointing at the card whose remaining work was most active, and
prescribing a destructive write against it ("drop `pm:dispatched`"). A reader
who followed it de-labelled a card with live work, which then read as
un-dispatched and was liable to be re-dispatched: two agents on one card.
Measured on #9834, whose error-counter half sat open as draft #10226 while its
duration half had merged as #10004.
The open-PR list is already in hand from the H7/H12/H21 pass, so this adds no
API call; delivery is read through the same relation, branch-name fallback
included. Drafts are deliberately not filtered — the specimen was one.
The half-delivered case DOWNGRADES rather than falling silent. Silence would fix
the harm and buy a new one, per the card's binding caveat: it loses the genuine
#8683 case where the last half is later abandoned. The quieter sentence names
both sides, counts them, and says `pm:dispatched` is correct and must not be
dropped. The destructive prescription now fires only when every delivering PR
has merged, and that path is pinned unchanged.
Fixes #10468
* feat(pm): H22 — a bounded closed-card sweep for `pm:*` state residue
Every collector in this sweep is open-only by construction, and for most items
that is right. It is a gap for H8 specifically: H8's subject is a write that has
not happened yet, but the card is usually closed by the same merge that
discharges the PR — often by a `Closes #N` in the same instant. So whether H8
ever fired was decided by a race it normally loses. Once the card closes, no run
looks at it again and the duty is discharged by disappearance.
Measured at filing: 129 of the 500 most recently updated closed cards carried a
live `pm:` label, 118 of them `pm:dispatched` — the signature of exactly the
write H8 was built to catch, unmet at scale because the card closed first.
This is direction A: ONE bounded closed reader (`sort=updated`, two pages, the
same convention and quota decision as the merged-PR window), every other
collector unchanged and still open-only, so the race closes without widening the
sweep. The window is the stated boundary and it is load-bearing — a 2026-08-22
re-measure paged past 500 closed `pm:dispatched` carriers repo-wide, so an
unbounded read would bury every other item under one-time historical residue.
The residue vocabulary is its own constant. It is deliberately NOT H13's
`PM_STATE_LABELS`, which answers a different question ("does this label make the
card visible to a reader?") and diverges in both directions: `finding` and
`needs-user-decision` are fine states to close in, while `pm:blocking` is a
live-work claim H13 omits. Sharing one list would make H22 report every closed
finding card and miss the blocking-cache residue entirely; the divergence is
pinned in both directions.
Fixes #10688
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f59035c commit b302aab
1 file changed
Lines changed: 494 additions & 16 deletions
0 commit comments