Skip to content

fix(pm): time-cap H8's merged window, add the seat-staleness leg and the closed pm:* census - #13601

Merged
zhuangjianguo merged 6 commits into
mainfrom
claude/issue-13499-half-states-patrol
Aug 31, 2026
Merged

fix(pm): time-cap H8's merged window, add the seat-staleness leg and the closed pm:* census#13601
zhuangjianguo merged 6 commits into
mainfrom
claude/issue-13499-half-states-patrol

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #13499
Fixes #13493
Fixes #13526

Three blindness defects in the patrol instrument scripts/pm/check-half-states.mjs, folded into one PR because all three land in that one file and are the same class: a patrol that reported nothing and was read as finding nothing.

Pure code. No .md in the diff.

1. H8's merged-PR window: a COUNT cap becomes a TIME cap (#13499)

Premise correction first, because the card's mechanism quote is stale. #13499 was filed against page 2 and a ~18 merges/day comment. Neither is on main any more — #11118 had already widened the cap to 4 pages and replaced the comment with a measured, executable MEASURED_MERGES_PER_DAY = 137.5. So repair ② was half-landed before this PR started.

The defect the card names is nevertheless live, and it is the half that did not land: the boundary was still a page cap, and a page cap only becomes a statement about time after dividing by a rate nobody watches. Measured on the same endpoint, same cap, eight days apart:

read rows coverage
2026-08-23 400 rows, 397 merged oldest merge 2026-08-20 = 2.96d
2026-08-31 400 rows, 397 merged oldest merge 2026-08-26 = 4.86d

Same cap, 64% more window, because the observed rate fell from 137.5/day to ~82/day. Nothing in the file changed and nothing could have reported it. Note the direction: a page cap is not a conservative bound that occasionally underdelivers — it is an unmanaged quantity, and the run where it is too small looks exactly like the run where it is too big.

  • MERGED_WINDOW_DAYS = 8, derived from the longest measured unexecuted-verdict latency: finding: H8 的 merged-PR 窗口按「~18 merges/day」定成两页,实测已达 ~196/day —— 超过约 24 小时的交付对巡查结构性不可见(本轮实测 14 张滞留 4–6 天) #13499's census found 14 deliveries stranded 4.6–6.0 days, worst objectui#5174 at 6.0d. 8 = that floor plus a weekend, because a Friday merge has its paired write looked at on Monday at the earliest.
  • MERGED_WINDOW_PAGE_CEILING = 30 is a quota backstop only, and announces itself: the summary line says horizon reached or TRUNCATED, so a ceiling-bound pass can never read as a completed time window.
  • Paging termination reads updated_at; row selection reads merged_at — deliberately different fields. The listing is sort=updated, so "stop when this row's merge is old" would be wrong. Merging updates a PR, so updated_at >= merged_at; once a page's oldest updated_at predates the horizon, no later page can hold an in-window merge. That monotonicity is what makes the bound exact.

Repair ② completed — the rate premise is now self-checking. Every sweep derives the observed rate from rows it already holds and compares it to the pinned constant, which now carries its measurement date. classifyRatePremise reports drifted (outside a 2x band), expired (pin older than 30 days regardless of drift), unobserved (⛔ not agreement — #4690), or ok. ⛔ The pinned constant is deliberately not self-updated: a premise nobody reviews is the thing being fixed.

Live: rate premise OK — observed ~120.5/day against pinned 137.5/day, measured 2026-08-23 (8d ago) (factor 0.88, band 2x).

Adjacent #11036 (Refs-spelling blindness) is not addressed here — out of scope: H8's delivery-recognition logic is untouched.

2. H38 — a seat post a whole shift ran behind (#13493, half ① only)

New report-only leg. T_seat is the post's last event (body edit or newest comment); if the newest Claim: on that seat's lane is newer, the post is stale. An ordering, not a threshold — no tuned constant.

  • The Claim: predicate is reused, not re-spelled: latestClaimComment, the established machine criterion. The card says do not widen it; it is equally important not to narrow it, or H2 would call a card claimed while H38 called the same comment invisible.
  • labels filters are OR not AND, so the lane is intersected locally.
  • Report-only. Writes nothing, blocks nothing.

It fired on its first live run, on the exact seat post the card was filed from: H38 #6021 pm:seat post is STALE — its lane domain:services carries a Claim: on #13551 written 9.8h AFTER this post's last event. The card predicted precisely this ("whether other seat posts show the same signature is exactly what the proposed check would answer on its first run").

Half ② (a 收班中 marker) is a protocol change and stays out, per the card's own boundary.

3. H39 — the closed pm:* residue census (#13526)

Leg ① (runnability) was landed by #13574 while this branch was open — the same repair, a re-exec routing node's fetch through the session proxy. This PR merges main and drops its own duplicate implementation; that mechanism here is entirely #13574's. What this PR keeps is the sentence the card asked for, now in the gate's own refusal output, carrying the real numbers below instead of a slogan.

Leg ② is new: the first honest census. Fully paginated, one label per request with local intersection, PRs filtered out, plus the control the card prescribed. Measured at this PR's head:

label closed cards still carrying it oldest
pm:dispatched 2,064 2026-08-02 (#4551)
pm:queue 846 2026-08-02 (#4551)
pm:blocking 27 2026-08-14
pm:blocked 18 2026-08-06
pm:on-hold 13 2026-08-06
pm:awaiting-maintainer 2 2026-08-25

555 closed cards carry pm:queue and pm:dispatched. The filing seat could only say "at least 100 per lane per label" because a first page is not a count and the gate could not be run to get one; it also dated the oldest at 2026-08-05, and the real floor is three days earlier.

A page cap is never rendered as a count. A truncated corpus renders ≥N (PAGE CAP … NOT a count), censusIsCountable gates the claim, and an empty control makes the whole census refuse the "clean" reading. This run was fully paginated, so the figures above are real counts.

No relabel rides this PR. Census only; the cleanup is a maintainer decision on its own card, per the grading.

Survey (reported, not fixed): the sibling page-capped windows

#13499 asked whether the other windows carry the same premise-rot shape. They do, but less acutely, and none is fixed here:

  • CLOSED_ISSUE_WINDOW_PAGES = 4 (H22) — same shape, and worse divisor confusion: it is ordered by updated, so its rows are consumed by issue activity, not closures. Its own docblock already records that surprise.
  • COMMIT_WINDOW_PAGES = 3 (H23) and the 10-page listIssues / listAllOpenIssues / listOpenPullRequests caps — page caps whose reach moves with the board's tempo.
  • listRecentIssueEvents (H35) is already time-bounded with a page cap behind it and reports truncation; it is the shape the H8 repair converges on.

They are not converted here because the shared-helper refactor does not fall out naturally — H22's divisor is a different quantity — and widening this diff past its fence is not worth it. Worth its own card.

Verification

  • Self-test green at the final commit: 1,685 cases pass (1,551 at branch point; +101 from this PR, +33 from the merged fix(pm): route check-half-states' probe through the session proxy #13574).
  • Reverse verification, one ablation per repair leg, each mutation proven on disk and each restore proven by git hash-object against the HEAD blob plus an empty git diff HEAD:
    • re-shrink the window to 2 days → the 5-day and 6-day in-window cases go RED; the 1-day case stays GREEN (that is the case the old count cap would also have passed, which is why it proves nothing alone).
    • neuter the staleness predicate → all 6 H38 firing cases go RED.
    • render a page cap as a plain number → the page-cap cases go RED.
    • the fourth ablation (proxy) correctly self-reported MUTATION DID NOT LAND / reading void after the duplicate was dropped — the guard refusing to report a no-op as a pass.
  • 16 of 18 derived gate families pass. The 2 non-passes are NOT MEASURED by the scripts' own verdict lines, not reds: check-partof-closing-keyword.mjs run bare has no PR_BODY/PR_NUMBER ("a wiring or usage failure, NOT a verdict"; its pnpm check:partof-closing-keyword sibling passes), and check-test-completeness.mjs was handed no turbo log ("⛔ It is not a red, and there is nothing here to fix").
  • Both gate-script convention obligations run green: bare-root-worklist --self-test and check:pm-dispatch-gates.
  • ESLint narrowed to the one changed file and the narrowing measured, not assumed: eslint.config.mjs states this repo "never enables type-aware linting … for ANY file", so no untouched file's verdict can depend on this diff; --format json reports 1 file linted, 0 errors, 0 warnings.
  • check-nul-bytes green over 7,535 files; a separate control-byte scan of the changed file is clean.
  • Live-fired read-only against the real repo at the final commit — 2m41s, exit 0, 962 merged PRs read in 10 pages, horizon reached, 226 half-states found.

Generated by Claude Code

@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review August 31, 2026 03:03
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit 52bc918 Aug 31, 2026
35 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-13499-half-states-patrol branch August 31, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment