Skip to content

feat(pm): H61 reports a merged PR or closed card still carrying needs:contract-review - #17831

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-17510-h-row-merged-carrier-still-gated
Sep 12, 2026
Merged

claude[bot] merged 1 commit into
mainfrom
claude/issue-17510-h-row-merged-carrier-still-gated

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

The half-state patrol could see a contract-review gate on an OPEN carrier (H31 compares the card's and the PR's, H51 reads one outliving a verdict on the current head, H53 reads one with no increment behind it) and it could see a gate REMOVAL (H35). It could see nothing at the other end: a pull request that MERGED, or a card that CLOSED, with needs:contract-review still on it. 载体纪律's invariant is 「开着的载体恒 = 真实待审」, so every one of those labels still tells a later reader — a seat, a post-merge tier audit, any candidate query that forgets to scope state=open — that a real contract review is owed on work that is already in main. This adds H61, one report-only row per landed carrier, printing the merge or closure stamp and the PR's head sha, so the post-merge tier audit has a list instead of a memory.

It costs no request. Both populations were already in hand by the time the row runs: merged PRs from H8's 8-day window (listRecentlyMergedPullRequests) and closed cards from H22's 3-day window (listRecentlyClosedIssues), whose list rows already carry labels, merged_at / closed_at and head.sha. Exit codes, bands and every other row are untouched — a completed sweep still exits 0 whatever it finds, and the script still never writes a label: clearing a carrier is 清标即落地, a seat's audited write with its provenance comment, and a sweeper striking a review gate would be issuing the verdict.

Fixes #17510

Acceptance notes

The director seat's ask, verbatim

From comment 5597753733 on #17040 (os-bill, 2026-09-09T07:11Z, "What the mechanisable fix should now also cover"), quoted exactly as written:

A check-half-states row for a closed card still carrying pm:* or a carrier (row 1 left #16231 closed with pm:queue + needs:contract-review; row 2 left the carrier on a merged PR).

The pm:* half of that sentence is H22's and stays there. The CARRIER half is H61. The card paraphrases it as 「merged PR / closed card still carrying a gate label」; the sentence above is the actual wording.

Premises, re-checked on origin/main at f830fa21

  1. Holds. No predicate named a merged PR or a closed card still carrying the gate. h51SpeaksAbout excludes a merged or closed PR by name; H31's population test excludes a merged delivering PR and a closed card by name; H35 reads removal events; H8 reads pm:dispatched; H22 reads pm:* on closed cards and files no row at all since [finding] H22 files 94 of the patrol's 247 rows against a population the 2026-08-31 ruling says needs no cleanup — the census clause and the row family now disagree inside one file #14072. CONTRACT_REVIEW_LABEL is reused — no second spelling, pinned the way H51 and H53 pin it.
  2. Half false, and corrected in place. H60 is indeed the highest number in use and H99 is the self-test sentinel, so H61 is the next free one. But the parenthetical is wrong: the top docblock's "## The invariants" index stops at H28. H29 through H60 have no entry there at all. H59 and H60 were added as a banner comment block above their predicate plus a HALF_STATE_FAMILY_BAND entry, and familyRegistryCoverage is the mechanical index that holds the band registry equal to the codes the sweep actually pushes. H61 follows that idiom; adding a lone H61 line to an index that stops thirty-three rows earlier would have been worse than leaving it out.
  3. Holds. 33e07f7c (PR feat(pm): refuse a merge group whose queued PR still carries needs:contract-review #17484) is an ancestor of origin/main. The front end is closed, so this row is residue reporting, not a bypass, and the row says so in its own text.
  4. Updated, larger, and measured. See below.

The measured population, 2026-09-12

GET /repos/{owner}/{repo}/issues?state=closed&labels=needs:contract-review, one page per repo, both complete (39 and 29 rows, under the 100-row page size):

repo total merged PRs closed-UNMERGED PRs closed cards
objectstack 39 23 2 14
objectui 29 17 0 12
both 68 40 2 26

Oldest closure still wearing the label: 2026-08-20T23:58Z (objectstack), 2026-08-30T17:32Z (objectui). The card named three specimens; the class is an order of magnitude larger.

What the row reaches, and what it does not. H61 reads the two windows the sweep already holds, so a merged carrier is visible for 8 days after it lands and a closed-card carrier for 3. At the patrol's 6-hour cadence that is roughly 32 and 12 consecutive sweeps: every NEW carrier is seen, repeatedly. What ages out is the standing backlog above. That boundary is deliberate — the alternative is a state=closed&labels=… listing, which is both a new fetch class and a second closed-card reader beside pmLabelListingPath, whose state=open scoping is the entire mechanism by which maintainer ruling 批 #13 holds. The header records the number so a reader can tell a short reach from a clean board.

Work item B — closed-UNMERGED PRs are EXCLUDED

Stated, not folded. Two of the 68 (objectstack #16733 and #14923; objectui 0 — 2.9%) are pull requests closed without merging, carrier still on. They are out of this row for three reasons:

  1. The harm model does not reach them. This row exists because a gate on a LANDED increment says a review is owed on work already in main; the post-merge tier audit is the consumer. A PR closed unmerged delivered nothing and owes no post-merge verdict — the shape H51's own header already calls moot, 「载体不迁移」.
  2. The data path does not hold them. listRecentlyMergedPullRequests SELECTS on merged_at, and its header says why in as many words: an unmerged closed PR is an abandoned attempt, not a delivery. Widening that selector would change what H8 and H49 are handed — two other rows' inputs, and out of scope here.
  3. Size. 2 of 68, both on one install, neither inside the window this row reads. A separate count rendered on every run for a class that contributes nothing to it is a number a reader learns to ignore.

Both legs refuse them, and both refusals are pinned. Writing that pin is what found a real hole in my own first draft: a closed-unmerged PR arriving from the PULLS listing carries no pull_request field at all, while state is closed and closed_at is readable — so the card leg accepted it. h61SpeaksAboutClosedCard now refuses a row carrying head as well. Live, the two collections never cross; a population test that is only correct because of its caller is one refactor from being wrong.

The sha is the HEAD, not the merge commit

The row prints head.sha. 载体纪律 says a clearing write cites 「所判 head」 and H51 resolves a verdict against pr.head.sha, so the head is the only sha a contract-review record can be matched on. Measured on the three specimens that filed this card: #16783 head 47eea7a8 / merge commit 854639b3; #17036 head 530469ff / merge commit e4fd55d9; objectui#8779 head a04441f7 / merge commit 6cc48c4e. The three shas quoted on the card and in the dispatch are the MERGE COMMITS — a reader searching a thread for one of them finds nothing. Noted, not filed: a card-text inaccuracy, not a defect in any code.

Band, and the repo column

inventory. Refused in turn: not gate (that band is for a row that can tell a STRIPPED gate from an ungated one — an absence reading as a green light; here the carrier is present), not stall (nothing is stopped — the increment merged), not state (whose definition says a LIVE card, while every subject here is merged or closed, ARCHIVE in ruling 批 #13's own word for this population). Being first in line for the body trim is correct for it, and the ledger callout names the family and the omitted count rather than dropping rows silently. The row text is deliberately short for this file for the same reason: it fires per carrier, and 20 of the 23 objectstack merged carriers sit inside the 8-day window today.

No repo column: each install sweeps its own repository through resolveSweepRepo, one token and one board, so this row can never see more than one and the column would be a constant. The two-install figures above are a hand measurement taken for the header, not something a run reproduces.

Verification

  • node scripts/pm/check-half-states.mjs --self-test :: exit 0 — 3748 cases pass (3707 before this change, 41 new). Both directions per the dispatch: a merged PR carrier fires, a closed card carrier fires, an OPEN PR carrier does not (and H51 still speaks about exactly that PR, so nothing fell between the two rows), a merged PR without the label does not, a closed-unmerged PR does not through either leg, unreadable labels is excluded rather than read as unlabelled, an unreadable stamp yields no row. The row text is pinned on the invariant, the remedy (清标即落地 / 同笔剥双载体 / the provenance comment / who acts), the "never a label written from this script" clause, the neighbouring rows it defers to, and the fact that it prints CONTRACT_REVIEW_LABEL rather than a re-typed literal.
  • Exit codes and bands pinned unchanged: EXIT_PREREQUISITE_NOT_MET is still 3, HALF_STATE_FAMILY_BANDS still spells gate,unregistered,stall,state,inventory, isLoudFinding is false on the row, and familyRegistryCoverage().missing.length is 0.
  • All 41 gate families derived by node scripts/pm/dispatch-gates.mjs --commands in this worktree were run; each exit code is in the report on [finding] three PRs merged on 2026-09-09 still carrying needs:contract-review — no H row names a merged PR or closed card that still wears a gate label #17510.
  • No changeset: scripts/pm/** publishes nothing from any released package, which lint.yml calls the textbook skip-changeset case in as many words.

Out of scope, noted not filed


Generated by Claude Code

…s:contract-review`

The half-state patrol could see a contract-review gate on an OPEN carrier
(H31, H51, H53) and a gate REMOVAL (H35). It could see nothing at the other
end: a pull request that MERGED, or a card that CLOSED, with the label still
on it. 载体纪律's invariant is 「开着的载体恒 = 真实待审」, so each of those
labels still tells a later reader that a real contract review is owed on work
already in `main`.

H61 is one report-only row per landed carrier, printing the merge or closure
stamp and the PR's head sha. It costs no request: merged PRs come from H8's
8-day window and closed cards from H22's 3-day window, whose list rows already
carry `labels`, `merged_at` / `closed_at` and `head.sha`. Closed-UNMERGED PRs
are excluded by decision (measured: 2 of 68 carriers) and both legs pin the
refusal. Exit codes, bands and every other row are unchanged, and the script
still never writes a label.

Co-authored-by: Claude <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
@claude
claude Bot marked this pull request as ready for review September 12, 2026 09:46
@claude
claude Bot added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit e3411ed Sep 12, 2026
39 checks passed
@claude
claude Bot deleted the claude/issue-17510-h-row-merged-carrier-still-gated branch September 12, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

1 participant