fix(pm): re-pin H22 closed-issue update rate from a card-only measurement of the stream the patrol actually pages - #17627
Merged
Conversation
…rement `MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY` stood at 415.1/day, measured 2026-09-06, and every sweep since has printed `RATE PREMISE DRIFTED` — so the coverage sentence H22 writes about itself was misdescribed by that factor, four times a day. Re-measured by the docblock's own recipe, and over the population the sweep is actually rated against: the scheduled runner's `/issues` pages come back card-only, while the byte-identical request from an agent container returns 49.6% pull requests. The 2026-09-11T01:55Z run reports 5 pages, 428 in-window closures and ~141.3/day; replaying this pager over a container read of the same hour reproduces 5 pages / 426 / ~139.4 on the card-only slice and 9 pages / 426 / ~256.2 on the whole one, and 428 in-window closures cannot come out of 5 PR-inclusive pages at all. The pin it replaces was taken PR-inclusive, so the 0.34 factor the alarm printed was a real slowdown times a population difference. Pinned at 139.4/day (500 card-only rows spanning 3.586 days — the five pages a 3-day horizon costs), dated 2026-09-11, with the 200 / 400 / 605-row re-reads recorded in the same shape the old record used. The alarm reads `ok` against the last run's own observation (factor 1.01). The divisor stays a HAND pin: the alarm reports, it never writes. Self-test: 3606 cases pass before and after — no case added or removed; the pins that hardcode a number derived from the constant's value are re-derived from it. Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK Co-authored-by: Claude <noreply@anthropic.com>
os-litant
marked this pull request as ready for review
September 11, 2026 05:27
os-litant
enabled auto-merge
September 11, 2026 05:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17254
Dispatched to the
domain:skillsos-dev seat by the skills seat, PM loop R1 wave 11 — sessionsession_01YKEjmbYNvYWJvWGSWx26zK, branchclaude/issue-17254-h22-rate-pin-remeasure.H22's
MEASURED_CLOSED_ISSUE_UPDATES_PER_DAYis re-pinned by hand from a fresh measurement. ⛔ The divisor stays a hand pin — see the one-sentence answer to the triage seat below.B — the premise clause as the patrol renders it TODAY
One read of the anchor's body (
GET /repos/objectstack-ai/objectstack/issues/9857), swept 2026-09-11T01:55:32.270Z, run 34552285557, commit2f8ad091466686cc81a723f7e7b6c695a8ddf8dd, triggerschedule. Verbatim:State⚠️ The card was filed off the 2026-09-09 run (
drifted, observed141.3, factor0.34.~167.5/day, factor0.40); today the factor is0.34, so the drift is 2.9x, not the 2.5x the card quotes — and the same clause names 5 pages, 428 in-window closures and a reach of 2026-09-07, which is the reading the measurement below turns on.⛔ The post-merge clause is the seat's to quote in the landing record; this one is pre-merge.
The measurement — and the population, which is the whole finding
stats.closedRateObserved = observedRatePerDay(rateRows, 'updated_at'), andrateRowsis every row the pass READ — PRs not dropped, projected toupdated_at— offclosedWindowPagePath:GET /repos/{repo}/issues?state=closed&sort=updated&direction=desc&per_page=100&page=N. So the recipe is "every row of that stream", and the only question is which stream.Read 12 pages, 2026-09-11T04:22:36Z–04:22:49Z, every page full at 100 rows (1,200 rows, 1,200 distinct numbers, one non-monotonic stamp). 595 of the 1,200 are pull requests — 49.6%.
listRecentlyClosedIssuesover those captured pages withnowMs= the read instant:The card-only replay reproduces the live run on all four numbers, to within 1.4% on the rate. The PR-inclusive one does not, and cannot: 428 in-window closures will not come out of 5 PR-inclusive pages, because only ~250 of those 500 rows are cards. The patrol runner's token declares
contents: read+issues: writeand no pull-request scope, and its/issuespages come back card-only; the byte-identical request from this container returns 49.6% pull requests. ⛔ The permission block is the reading that fits, not a measurement — the runner's raw response is not readable from a seat. The population difference itself is measured.So the pinned depths are re-taken on the card-only stream (605 such rows inside the 12 pages):
⚖️ Deviation from Zone 1, declared. The ruling said "pin the leading-1200-row rate … at the depth where the budget is spent, as the last re-pin did". Two of its premises measured false. (1) The previous re-pin did not pin the 1200-row rate: its own record reads
window 400 rows spanning 0.964 days / rate 400 / 0.964 = ~415, and the deeper re-reads that day were ~390 and ~388 — 415.1 is the 400-row figure, and the 1200-row reading was the diagnostic that showed it was light. (2) The 1200-row depth is a container-read depth; the runner spends 5 pages, and 1,200 card-only rows are not reachable inside the 12-read budget. Zone 2(b) is the clause that decides it — "measure the SAME population … dropping whatever the sweep drops" — so the pin is the rate at the depth the budget is actually spent, on the population it is spent on: 500 card-only rows over 3.586 days, past the 3-day horizon, not a one-day sample. Pinning 301.8 instead would have left the alarmdriftedat 0.47 on every future run and left H22's coverage sentence wrong by 2.2x — the card unfixed with a new number in it.The pin, before and after
MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY415.1139.4..._AT'2026-09-06''2026-09-11'Against the new pin the last run's own observation classifies
ok:rate premise OK — observed ~141.3/day against pinned MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY = 139.4/day, measured 2026-09-11 (1d ago) (factor 1.01, band 2x).The 2026-09-09 run's ~167.5/day also readsok(factor 1.20).The value-derived self-test pins, re-derived with the arithmetic
The claim declared three. There are eleven; the other eight are the same kind of pin (an expected value that moves with the constant) and the self-test is red without them. Declared here rather than silently widened:
windowCoverageDays(400, PIN)400 / 139.40.962.87PIN / 10.7139.4 / 10.738.813windowCoverageDays(400, PIN) < N2.87no longer under2.5< 2.5< 3— still short of the 3-day horizon, which is the claim the case carries942.76 / 139.4'a factor of 2.27''a factor of 6.76''= 415.1/day''= 139.4/day'415.1139.4'2026-09-06''2026-09-11'0.25gave 433.33/day (3.11x the new pin)rateStream16393(0.25), 13 pages,433.33rateStream16393(0.75), 5 pages,166.67(factor 1.20)clause16393(415.1, …)clause16393(139.4, …)clause16393(433.33)clause16393(166.67)'observed ~433.3/day','= 415.1/day''observed ~166.7/day','= 139.4/day'The pins that read the constant RELATIVELY are byte-identical: the date-shape case, and every
h22Premise(PIN * k)band case.The expiry, stated
RATE_PREMISE_STALE_DAYSis 30 andclassifyRatePremiseturnsexpiredatageDays > 30, so the new pin expires 2026-10-11 — measured, not computed by hand:classifyRatePremiseanswersokat 2026-10-10T00:00:01Z (age 29.00) andexpiredat 2026-10-11T00:00:01Z (age 30.00). It is one line on the..._ATdocblock, inside the record's own shape.The triage seat's run-time suggestion, answered in one sentence
⛔ A run-time divisor is the one thing this file rules out —
CLOSED_UPDATE_RATE_PREMISE's docblock: 「⛔ The alarm REPORTS; it never writes. H8's rule — a pinned premise is CHECKED against a sweep, never overwritten by one … A self-updating divisor would make every window's stated coverage true by construction and worth nothing」 — and the fallback it asked for instead, a staleness check, already exists and is what filed this card:classifyRatePremise(band 2x, four statesok/drifted/expired/unobserved) is rendered inside the census clause on EVERY run, in every state.The one judgement, on the four axes
实际业务需求 — the consumer is real and measured, not speculative: the census clause the loop reads to decide a sweep was complete divides a page budget by this constant, four times a day, and the 2026-09-11T01:55Z run is the instance. What the measurement changed is whose stream: the only reading the pin is ever checked against is the runner's, so a pin taken from a container serves nobody's actual read. 项目长远合理性 — a hand pin with a dated record and an alarm watching it is the sustainable shape already chosen here (no workaround, contract-first: the record states its population so the next re-measure is reproducible); a run-time divisor is the temporary patch, and its long-term cost is that no window's stated coverage can ever be wrong, hence never checked. 防 AI 写代码犯错 — the fix that makes the next agent structurally less likely to err is the one that writes the population INTO the recipe: an agent following 「400 rows spanning N days」 from a container reproduced this defect exactly once already, and a recipe that names its population refuses the wrong reading loudly instead of tolerating it; the alternative (a tolerant self-updating divisor) is precisely the silent-fallback shape that hides bulk error. 创业阶段不扩散需求 — no new mechanism, no new row, no new constant, no band change, no exit-code change: one number, one date, one record, and the self-test pins that move with them. The three-depth record is the answer to the card's sampling question, and it costs nothing to carry.
⚖️ The axes do not conflict here; the only tension is with the dispatch's Zone-1 depth, declared above and resolved by Zone 2(b)'s own population clause.
Verification
Every exit code captured before any pipe (
cmd > log 2>&1; code=$?), on head11e23caca— the final commit, with a clean working tree.node scripts/pm/check-half-states.mjs --self-test— before (origin/main0918c441, run in-tree):✓ check-half-states self-test: 3606 cases pass.exit 0. after:✓ check-half-states self-test: 3606 cases pass.exit 0. ⛔ No case added or removed; the count is identical because every change is an expected VALUE, never a new case.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 39 commands from the final diff (1 path, three-dot vs merge base0918c4411). All 39 ran; all 39 exited 0. Representative verdict lines:✓ check-half-states self-test: 3606 cases pass.·✓ dispatch-gates self-test: 1678 cases pass.·check-nul-bytes: OK (scanned 8366 text file(s) -- 8366 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).node scripts/pm/dispatch-gates.mjs --ran … --repo objectstack-ai/objectstack—✓ dispatch-gates --ran: 39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3).exit 0.pnpm lint(repo-wideeslint . --no-inline-config) — exit 0, no output. Run whole rather than narrowed; no narrowing argument is owed.node scripts/pm/check-governed-merges.mjs --branch claude/issue-17254-h22-rate-pin-remeasure—✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.exit 0.grep -naPover the changed file for the C0 set — no hits.check-half-states.mjs --format=markdown --provenance=…, the live sweep), 1 CI-measured-only family, 1 path-scheduled CI job's 5 steps, 11 wide-population families, 47 artifact rosters, and the 10 pending-changeset families — which do not apply, since this PR carries no changeset (below).Changeset: none —
scripts/pm/**publishes nothing from any released package (it is in no package'sfiles[]), soskip-changesetapplies, exactly as this file's predecessors this shift carried it.Clause-②: no
Acceptance notes
noted, not filed:the card's acceptance item 2 — a patrol run whose census line no longer carriesRATE PREMISE DRIFTED— cannot be quoted pre-merge: the scheduled sweep readsmain. The classifier's answer against the last run's own observation is quoted above (factor 1.01,ok); the run itself is the seat's to quote in the landing record. Carrier: this PR's seat review.filed as #17626:the population divergence itself — the patrol runner pages a card-only stream while an agent container pages a PR-inclusive one — with the three statements it makes false, all of them outside this card's region-declared surface and therefore byte-identical here:CLOSED_ISSUE_WINDOW_DAYS'slistRecentlyClosedIssues's 「measured over the RAW stream (400 rows / 0.964 days)」.CLOSED_ISSUE_WINDOW_PAGE_CEILING's 40 was sized on the container stream too, so its real headroom is about double the docblock's arithmetic — safe direction, wrong number.noted, not filed:the claim's enumeration of the value-derived self-test pins named three of eleven. Not a surface breach — the claim's own words are "the self-test pins that hardcode a number derived from the constant's VALUE and therefore change with it", which all eleven are — but the enumeration is worth correcting in the next claim for a pin of this kind. Carrier: the skills seat's dispatch template.noted, not filed:the 1200-row container reading (301.8/day) is raised by a boundary burst: rows 1,101–1,200 span 11 minutes, while rows 1–1,100 run at 277.1/day. It does not affect the pin (card-only), and every card-only depth agrees inside the band (139.4 to 165.9, max ratio 1.19). Carrier: none — it is an artefact of one read, not of the board.维护者速读(草稿)
改了什么 —
scripts/pm/check-half-states.mjs一个文件:H22 的除数常量MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY从 415.1(2026-09-06)重新手工测量为 139.4(2026-09-11),连同它的测量记录(读取命令、窗口、跨度、三个深度的复读),以及十一个「期望值随该常量变动」的 self-test pin。⛔ 没有自更新除数、没有改带宽、没有改页数上限、没有新行、没有改退出码。为什么改 — 巡逻每天四次在自己的普查句里报
RATE PREMISE DRIFTED,即它对自身窗口覆盖范围的描述错了 2.9 倍。测量过程中查明了根因:巡逻 runner 拿到的/issues流不含 PR,而任何座位从容器里读同一个 URL 拿到的流有 49.6% 是 PR。上一个 pin 是从容器读出来的,于是它被拿去和一个密度只有一半的总体对比——「漂移」里有一半根本不是节奏变化。新 pin 按 sweep 真正分页的那个总体(card-only)在预算实际花到的深度(5 页 / 500 行 / 3.586 天)取值。风险与代价(含回滚) — 风险低:该常量只进两处——普查句里的一个描述性数字,和
classifyRatePremise的对比基准;不参与分页、不参与选择、不改退出码。self-test 前后都是 3606 例全过,没有新增或删除用例。回滚 = revert 本 PR 的单个 commit,常量回到 415.1,警报回到drifted。席位意见 — (待填)
你要做的 — 合并后从下一次巡逻运行(
half-state-patrol.yml,cron37 1,7,13,19)的普查句确认RATE PREMISE DRIFTED已消失、改读rate premise OK;并决定 #17626 的走向:是把「card-only」写进测量配方,还是给 workflow 加pull-requests: read让两边读同一个流(代价是 H22 的页数账单翻倍)。Generated by Claude Code