Skip to content

fix(pm): convert the patrol's remaining count windows to announced time horizons - #13628

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-13606-window-premise-rot
Aug 31, 2026
Merged

fix(pm): convert the patrol's remaining count windows to announced time horizons#13628
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-13606-window-premise-rot

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #13606

Converts the half-states patrol's remaining COUNT-shaped windows to announced time horizons, following the two in-file precedents: H8's conversion (landed in PR #13601) and H35's pre-existing shape.

All measurements below were taken live in this container at 2026-08-31T03:33-03:36Z. Nothing here is copied from an earlier report — the board's tempo had moved, and re-measuring is what produced the two premise corrections.

H22 — the worst one, and its divisor named honestly

CLOSED_ISSUE_WINDOW_PAGES = 4 becomes CLOSED_ISSUE_WINDOW_DAYS = 3 with CLOSED_ISSUE_WINDOW_PAGE_CEILING = 12 behind it as an announcing quota backstop.

The divisor, named: this window's rows are consumed by closed-issue UPDATE ACTIVITY, not by closures. Measured on the live endpoint:

update activity   400 rows / 2.125d  = ~188 closed-issue updates/day
actual closures   226 cards / 21.06d = ~11 closures/day

The consequence was not a slightly-wrong number but a ragged frontier: 28 of 225 adjacent card pairs in the window were out of closure order. A card closed 3 days ago and untouched since fell out while a card closed 3 weeks ago that got one comment today sat at the front. What that cost, measured the same instant — findings the row should have reported and structurally could not:

closed within 2d   70 of 70 residue carriers seen   (the cap was enough)
closed within 3d   77 of 96      -- 19 missed
closed within 4d   77 of 117     -- 40 missed
closed within 7d   77 of 342     -- 265 missed

Why 3 days, bounded on both sides by measurement: the floor is that the window must not lose what four pages already delivered (it covered closures within ~2 days completely, 199 of 199). The ceiling is the docblock's own anti-drowning argument, in numbers: residue density measured 28-35% at every horizon, so findings are roughly linear in the horizon — 3d = 96, 5d = 175, 7d = 342, 8d = 370. The sibling install's experience is the calibration: at ~347 rows H22 exhausted the anchor body budget and trimmed every other predicate out of the report, which is why that install shipped with the reader switched OFF. A horizon that turns this row into the report is not a wider window, it is a disabled patrol.

The residual, stated rather than papered over: H8's window is 8 days and this one is 3, so a card that closed between 3 and 8 days ago carrying pm:* residue is visible to neither row. That band is the price of the anti-drowning ceiling; closing it needs a counting shape like H39's census, not a wider list.

H23 — a page cap kept honest by hand, and still the wrong shape

COMMIT_WINDOW_PAGES = 3 becomes COMMIT_WINDOW_DAYS = 3 with COMMIT_WINDOW_PAGE_CEILING = 12.

This docblock was the only one that derived its cap from a measured rate rather than a remembered one, and the only one that survived a re-measure — so it is the strongest case that hand-maintenance works, and it still drifted:

2026-08-23   300 commits = 2.18d at ~137.5/day
2026-08-31   300 commits = 2.73d at ~110/day

Same three pages, 25% more window, eight days apart. N = 3 days comes from this row's own overlap argument (it targeted ~9x; 3 days is 12 consecutive runs at the 6-hourly cadence) and is close to what the cap delivers today, so the conversion barely moves the window now — the point is that it stays 3 days when the rate moves.

The three bare 10-page caps — premise falsified, and the repair is the other half

This is the one place I did not do what the card's survey line proposed, and the measurement is why. The card asked for a time horizon here too. Measured:

listIssues(LABEL)    largest population 91 open cards (pm:on-hold), 1 page
listAllOpenIssues      354 open cards + 21 PRs, 4 pages
listOpenPullRequests   21 open PRs, 1 page

None binds the cap, and none is a recency window: each pages to exhaustion and the 10 is a pure quota backstop that has never been reached. There is no coverage in days to state, because the boundary is the end of the population, not an instant.

A time horizon here would be a defect, not a repair. These streams come back newest-created first, so a created-time horizon truncates the OLDEST open rows — precisely the population every predicate in this file exists for, per H8's own damage model ("the paired write NOBODY noticed correlates with how long nobody noticed it"). Measured:

an 8-day  created horizon drops 104 of 354 open cards (29%)
a 14-day  created horizon drops  72 of 354 open cards (20%)
a 30-day  created horizon drops  12 of 354 open cards ( 3%)

The oldest open card is 76.9 days old; the oldest open priority:p0 is 52.9. An 8-day horizon — the obvious number to copy from H8 — would make a 52-day-old open P0 invisible to the entire patrol.

So these three get the half of the target shape they were actually missing: exhaustive paging kept, the ceiling named as OPEN_LISTING_PAGE_CEILING, and truncation ANNOUNCED, with the ceiling-bound listings named individually on the summary line. A ceiling-bound inventory must never read as the whole board.

The shared-helper question, re-derived after the conversion

PR #13601's report measured, before converting anything, that a shared window helper "does not fall out naturally". That holds and is confirmed: the divisors are genuinely different (merges/day, closed-issue updates/day, commits/day) and so are the paging shapes — H8 and H22 order by updated_at while selecting on merged_at/closed_at; H23 and H35 order by the field they select on; the open listings have no horizon at all.

What the conversion makes visible is a different commonality, and it is the one this card is about: every bounded pass ends for exactly one of two reasons, and that sentence is the same sentence every time. So what is factored is the DISCLOSURE (describeWindowBound), not the paging. Three hand-copies of an announce clause is the likelier failure mode — hand-copied disclosures drift, and a report where one window says TRUNCATED and another silently says nothing is worse than either wording alone.

One genuine paging abstraction did fall out for two real callers: pageExhaustsWindow. This is the only line of #13601's landed repair this PR touchesmergedPageExhaustsWindow now delegates to it. Same name, same export, same signature, byte-identical behaviour; #13601's own direction cases pin it unchanged and still pass.

Note the deliberate wording split: the shared helper says "boundary reached" where H8 says "horizon reached". #13601 pinned H8's truncated case with a whole-line substring check, written when H8 was the only window that spoke. Reusing its exact phrase on the now-shared line would satisfy that check from a neighbouring clause and quietly retire it — a check that cannot fail, which is the defect this family is about.

Verification

The file's own self-testpnpm check:pm-half-states, its own verdict line:

✓ check-half-states self-test: 1749 cases pass.

Baseline on origin/main was 1685 cases pass, so this adds 64 cases.

Ablation, one per repair leg. Predictions were fixed in the harness before any leg ran. Each leg: mutate by exact anchor, prove the mutation on disk by RE-READING the file and counting the exact multi-line anchor, run, then restore with git checkout HEAD -- (absolute path) and prove the restore by empty git diff HEAD and blob hash equal to the HEAD blob. No build step exists for this leg (node runs the script directly), so a mutation is live the moment it is on disk; the on-disk proof is still unconditional.

leg mutation predicted observed
A H22 closure horizon removed RED RED — H22 window: a 4-day-old closure is outside, H22 divisor: a stale closure bumped by today's activity is NOT admitted
B H23 commit horizon removed RED RED — H23 window: a 4-day-old commit is outside
C disclosure never announces truncation RED RED — 8 cases across the shared bound, H22's summary and H23's summary
D open-listing truncation not recorded RED RED — open listings: a ceiling-bound listing is named, not just counted

Restore proof after every leg: git diff HEAD EMPTY and blob 109b59158c8075e6f147dc06fc927fa9a9fe7244 == HEAD blob.

One prediction was wider than the result and is reported as such: leg D also predicted the two open-summary truncation cases would redden. They did not, correctly — those cases feed summaryLine a fixture directly and so pin the renderer, which leg C covers, while leg D pins the recorder. The legs are independent, which is the better outcome, but the prediction overstated the blast radius.

Gate families, derived from the ACTUAL diff via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (17 path-derived + 2 convention-triggered for editing a gate script). 16 ran and passed; each gate's own verdict line is in the run log. A sample:

✓ check-entry-guard self-test: 54 cases pass ...
✓ check:entry-guard: 187 scripts/ file(s) — every entry guard goes through invoked-as.mjs ...
✓ dispatch-gates self-test: 1017 cases pass.
✓ check-bash32-floor self-test: 153 cases pass.
OK  self-test: 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted ...

Two did NOT run and are reported as NOT MEASURED rather than as passes or failures, in each case quoting the gate's own words:

  • node scripts/check-test-completeness.mjsPREREQUISITE NOT MET ... the local reading for this gate is NOT MEASURED. It is not a red, and there is nothing here to fix. (exit 3; it grades a saved turbo run test log that only CI produces.)
  • node scripts/check-partof-closing-keyword.mjsNOT WIRED — neither PR_BODY nor PR_NUMBER is set. Re-run against this PR body with PR_BODY set; result quoted in the report comment. Its pnpm spelling passed: ✓ check-partof-closing-keyword self-test: 28 cases pass.

Repo-wide ESLint: pnpm lint (eslint . --no-inline-config) run in full rather than narrowed — LINT_EXIT=0. No narrowing justification is needed because no narrowing was applied.

NUL bytes: check-nul-bytes: OK (scanned 7541 text file(s) ... no raw ASCII control bytes), plus a direct control-byte scan of the changed file: 0 hits.

Live fire (read-only) at the final commit d440445c, the summary line's new disclosures:

H22's closed-card window is a TIME cap of 3 day(s), read in 6 page(s) (boundary reached: ...).
  It is a CLOSURE horizon: rows are selected on `closed_at`, while paging is bounded by
  `updated_at` — the stream is consumed by closed-issue ACTIVITY (~188.3/day, measured
  2026-08-31), not by closures (~11/day) ...
H23's commit window is a TIME cap of 3 day(s), read in 4 page(s) (boundary reached: ...).
The open listings (H1–H18 inventory, H13 unscoped pass, open PRs) is an EXHAUSTIVE listing,
  read in 12 page(s) (complete: paging reached the end of the stream, so this IS the whole
  population).

Page costs match the derivation exactly (H22 predicted ~6, read 6; H23 predicted ~4, read 4). H8's untouched clause still reads TIME cap of 8 day(s), read in 10 page(s) (horizon reached ...).

The anti-drowning constraint, verified end-to-end on the live board — same board, baseline vs this branch:

H22 rows:        77 -> 94       (pre-measured prediction at a 3-day horizon: 96)
total findings: 215 -> 233
every other row unchanged (H30 23, H14 20, H9 18, H19 12, H5 10, H24 9)

The finding count lands within 2 of what the residue-density measurement predicted before the code was written, and stays far below the ~347 flood that disabled the row on the sibling install.

Scope

scripts/pm/check-half-states.mjs only — self-tests live in the same file. Report-only legs stay report-only; no relabelling, no behaviour changes beyond the window boundaries and their disclosure. No .md files in the diff. No changeset: PM tooling under scripts/pm/** publishes nothing from any package, so skip-changeset applies.

Left DRAFT deliberately for PM self-review at contract-review tier, per the direct-landing lane.

Generated by Claude Code


Generated by Claude Code

…me horizons

H22's closed-card window and H23's commit window become TIME horizons with
announcing quota ceilings, following H8's conversion and H35's shape. H22's
divisor is named honestly: its rows are consumed by closed-issue UPDATE
activity (~188/day measured) rather than by closures (~11/day), which is why
its four-page cap covered a ragged 2.1 days of update-recency while reading
as a closure window — it missed 19 residue carriers closed within 3 days,
40 within 4, 265 within 7.

The three bare 10-page caps convert to the OTHER half of the shape. Measured:
none of them binds, and none is a recency window — each pages to exhaustion,
newest-created first. A time horizon there would discard the OLDEST open rows,
which is the population every row in this file exists for (an 8-day created
horizon drops 104 of 354 open cards; the oldest open card is 76.9 days old,
the oldest open P0 52.9). So they keep exhaustive paging and gain the
announcement they were actually missing.

What fell out as shared is the DISCLOSURE, not the paging: the divisors and
termination arguments genuinely differ, but "which of the two bounds ended
this pass" is one question in every case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
@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 04:03
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit 7483afd Aug 31, 2026
35 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-13606-window-premise-rot branch August 31, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants