Skip to content

tooling(pm): H22's closed pass pages to its horizon under a 40-page cap, and states the horizon it reached - #16391

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-16217-h22-closed-pass-horizon
Sep 6, 2026
Merged

tooling(pm): H22's closed pass pages to its horizon under a 40-page cap, and states the horizon it reached#16391
os-steve merged 2 commits into
mainfrom
claude/issue-16217-h22-closed-pass-horizon

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #16217

H22's closed-card pass was TRUNCATED at its 12-page quota ceiling on this board, on every run, and said so in its own words while saying nothing about how short it was. This raises the backstop to a measured 40 and makes the clause self-describing: it now prints the horizon it actually REACHED, as a date, on every run.

改前 → 改后

Same board, same instant (2026-09-06, live read-only sweep on objectstack-ai/objectstack), the pre-change file swapped in and restored byte-identically around the reading:

改前

H22's closed-card window is a TIME cap of 3 day(s), read in 12 page(s) — ⛔ TRUNCATED at the 12-page quota ceiling BEFORE reaching that horizon, so this pass is a page cap and a card that closed earlier than what those pages reached is invisible; the ceiling needs raising.

改后

H22's closed-card window is a TIME cap of 3 day(s), reaching back to 2026-09-03, read in 13 page(s) (boundary reached: paging ran past the horizon, not merely the first N rows).

The old ceiling was one page short. Page 12's oldest updated_at that day was 2026-09-03T17:41:14Z, about an hour newer than the 3-day horizon — which is exactly the reading nobody could take from the old clause, because a pass an hour shy of its horizon and one that read a third of it printed the same sentence.

objectstack-ai/objectui, same session, same policy:

H22's closed-card window is a TIME cap of 3 day(s), reaching back to 2026-09-03, read in 6 page(s) (boundary reached: paging ran past the horizon, not merely the first N rows).

Neither board truncates at 40. The card asked for that number if it still did; it does not.

What changed

One file, scripts/pm/check-half-states.mjs, H22's leg only.

  • The horizon-driven stop was already there and is unchanged: the pass selects on closed_at and pages on updated_at, stopping once a page's oldest update predates the horizon (pageExhaustsWindow, shared with H8). The defect was never the pager — it was the backstop in front of it.
  • CLOSED_ISSUE_WINDOW_PAGE_CEILING 12 → 40, and sized in the unit it is spent in. The old 12 was derived as "twice the ~6 pages the pinned 188.3 updates/day needs"; that divisor was measured at depth, over a span whose older half is quiet, while the pages a 3-day horizon costs all come out of the fastest part of the stream. Re-measured at depth on 2026-09-06 — page 1 2026-09-06T08:25:42Z, page 12 2026-09-03T17:41:14Z, page 20 2026-09-01T06:54:23Z, page 40 2026-08-24T21:44:57Z — 4,000 rows reach 12.8 days, so 40 is ~4.3x headroom on the horizon and 40 REST calls is the pass's budget. The docblock carries the history (2 → 4 → 12 → 40) and says in terms why a bind is a signal to narrow the stream rather than to raise the number again. No second constant.
  • The reach, reported. oldestPageStamp is a new pure reading — the oldest READABLE ordering stamp on a page, a minimum rather than the last row. It is deliberately not the stop predicate's reading: the stop predicate must stay conservative on an unreadable last row so one bad stamp cannot end the pass early, while the reach must not let one bad last row erase a reach the page already proved. reachedDate renders it, and null stays null — a pass that read nothing dateable reached nowhere and must never render today.
  • closedReachedAt rides SWEEP_COUNT_KEYS, so the reading gathered in the pager cannot be dropped on the way to the summary.
  • describeWindowBound takes an optional reached, printed beside the day count in both directions. Passing it also retires this clause's the ceiling needs raising remedy — by coupling, not by a second flag: that sentence was the only thing a truncated pass said about what to do, and it was wrong as often as not. Callers that cannot state a reach keep it, so H23's clause and the open-listings clause are byte-identical; two cases pin that.
  • The pinned divisor MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY is not overwritten (H8's rule: constants here are checked against a sweep, never updated by one). The stale ~6 pages for 3 days cost note now states the disagreement — ~13 pages measured, leading rows at ~414/day against the pinned 188.3 — instead of quietly editing it.

Self-test

2553 → 2588 cases (+35), all green. selfTest() became async (one call site) so the three shapes could be driven through the REAL pager rather than a re-typed copy of it — listRecentlyClosedIssues takes its page reader as a parameter, which is the seam.

The three shapes the ruling named, plus the one with teeth:

  1. horizon reached under the cap — 3 pages, not truncated, reach dated, only in-horizon closures admitted, clause says boundary reached
  2. the 13th page — the run the old 12-page ceiling structurally could not finish, now completing its horizon
  3. the cap biting first — the whole cap spent, ⛔ TRUNCATED, the new ceiling named, the reach stated anyway, and no remedy prescribed
  4. an empty board — no rows, the one page it costs to learn that, a clean pass, and closedReachedAt null so the clause invents no date

Ablation, each leg restored to a byte-identical tree (git checkout HEAD --, verified by blob hash and an empty git diff HEAD):

mutation red cases
stats.closedReachedAt never assigned 5
ceiling put back to 12 3
the clause's reach fragment emptied 2

Gates

Run at the final commit 680df51ecc.

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 36 commands; all 36 run, all exit 0. --ran reconciles: 36 derived, 36 run, 0 NOT-MEASURED, 0 UNRUN.

Named beyond the derivation, all exit 0: check:pm-clause2-carriers (it imports this module), check:pm-skill-id-lint, check:pm-label-desc-cap, check:pm-governed-prose, check:pm-skill-ratchet, check:engine-double-contract, check:console-injection, check:i18n-stale-fill, check-published-list-mirrors. check:nul-bytes is inside the 36; the diff was also scanned by hand for control bytes, and carries none.

node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-half-states.mjs, verbatim:

governed-surface predicate: 0 of 1 path(s) hit the register (5 surfaces, repo-agnostic).
✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.

check:pm-skill-id-lint cannot reach this file, checked rather than recalled: its corpus is SCAN_ROOT = '.claude/skills/pm-dispatch' plus EXTRA_FILES = ['.claude/agents/os-dev.md', 'AGENTS.md'], .md only. It ran green regardless.

Whole-repo pnpm lint (eslint . --no-inline-config) through scripts/pm/os-verify-lock.sh, slot issue-16217: VERDICT command-exit 0 · held the lock 110s · waited 0s. No narrowing — the whole farm ran.

Two artefact-roster gates report NOT MEASURED, not red: check:published-readme-exports and check:dts-closure both exit 3 PREREQUISITE NOT MET because no package has a dist/ in this worktree. Both need a whole-repo build that a one-file scripts/pm diff cannot affect; CI runs them after its build.

One staleness reading, stated rather than papered over: origin/main moved to 0374bcba9f after the battery, and two files the derivation reads changed in that range (scripts/check-partof-closing-keyword.mjs and its workflow). The derived command list is byte-identical either way, and CI evaluates the merge result, so it runs the current checker.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

…ap, and states the horizon it reached

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 6, 2026
@github-actions github-actions Bot added the size/m label Sep 6, 2026
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — in-seat review of PR #16391, head 680df51ecc (skills seat, 2026-09-06T17:1xZ).

Implemented-by: claude/issue-16217-h22-closed-pass-horizon (os-dev subagent, opus — no path-derived tier mandate on scripts/pm/**)
Reviewed-by: session_019RfFHiRCSs3JXLK4cwcfox (skills seat)

Verified in a detached compare worktree at the PR head, not from the report:

  • One file, scripts/pm/check-half-states.mjs (+269/−18), one leg, no new rows, family markers and PM_RESIDUE_LABELS untouched, no SKILL.md line citations. CLOSED_ISSUE_WINDOW_PAGE_CEILING 12 → 40 (the one constant, no second one); the horizon-driven stop was already right (select on closed_at, page on updated_at through the shared pageExhaustsWindow) — the defect was the backstop in front of it, sized from a divisor measured at depth while a 3-day horizon's pages come from the fastest part of the stream, and the docblock now says a future bind means narrowing the stream, not raising the number. Two pure readings, oldestPageStamp and reachedDate, feed stats.closedReachedAt (on SWEEP_COUNT_KEYS), and describeWindowBound takes an optional reached that prints the date beside the day count in both directions and, by that coupling, retires the clause's 「the ceiling needs raising」 remedy only where a reach is stated — H23's clause and the open-listings clause are byte-identical, two cases pin it. The pinned divisor MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY is not overwritten (H8's rule); its stale cost note states the disagreement instead.
  • Gates at the head: check-half-states --self-test exit 0, 2588 cases (2553 before, +35): the three ruled shapes are driven through the real pager (listRecentlyClosedIssues takes its page reader) — horizon reached under the cap (and the teeth: a horizon on page 13, which the old 12 could not finish), the cap biting first (TRUNCATED with the reach stated, no remedy sentence), an empty board (one page, no invented date); check-clause2-carriers --self-test 190 (it imports the module); check-governed-merges --test exit 0 (NOT governed ⇒ queue landing); check-skill-id-lint exit 0. The dev's ablations restore byte-identically under a trap and each leg went red as intended (5 / 3 / 2). Live, read-only, same instant, pre-change file swapped in: objectstack BEFORE 「read in 12 page(s) — ⛔ TRUNCATED」, AFTER 「reaching back to 2026-09-03, read in 13 page(s) (boundary reached…)」 — the old ceiling was one page short; objectui reaches in 6; neither board truncates at 40. The derived union 36/36 with --ran at 0 NOT-MEASURED; whole-repo pnpm lint through the verify lock (exit 0, 110 s, unnarrowed). PR checks on 680df51ecc: 24 success, 11 skipped, 0 failed (35 runs, converged; Live half-state sweep green).
  • Report hygiene: os-dev-report on check-half-states: H22's closed-card pass is TRUNCATED at the 12-page quota ceiling on this board, so the closed-residue census is silently short #16217, JSON closes; PR body opens Fixes #16217; no model identifier; skip-changeset read back comparatively; zero MCP calls.

The one noted, not filed item is promoted by the seat: H22's divisor is a pinned premise with no staleness alarm (the board now runs 1.7–2.2× the 2026-08-31 figure), while H8 carries exactly that alarm for its own rate premise — a gate that can drift silently is the class this file treats as worse than a row; filed as its own card behind this PR.

Landing regime: pure code ⇒ the seat flips ready and arms SQUASH auto-merge in this same act; the merge is read on the PR object, never inferred. On the merge #16217 closes by Fixes.


Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

check-half-states: H22's closed-card pass is TRUNCATED at the 12-page quota ceiling on this board, so the closed-residue census is silently short

2 participants