Skip to content

fix(pm): re-pin H22 closed-issue update rate from a card-only measurement of the stream the patrol actually pages - #17627

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-17254-h22-rate-pin-remeasure
Sep 11, 2026
Merged

fix(pm): re-pin H22 closed-issue update rate from a card-only measurement of the stream the patrol actually pages#17627
os-litant merged 1 commit into
mainfrom
claude/issue-17254-h22-rate-pin-remeasure

Conversation

@os-litant

@os-litant os-litant commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17254

Dispatched to the domain:skills os-dev seat by the skills seat, PM loop R1 wave 11 — session session_01YKEjmbYNvYWJvWGSWx26zK, branch claude/issue-17254-h22-rate-pin-remeasure.

H22's MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY is 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, commit 2f8ad091466686cc81a723f7e7b6c695a8ddf8dd, trigger schedule. Verbatim:

⚠️ RATE PREMISE DRIFTED — this sweep observed ~141.3 closed-issue updates/day against a pinned MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY = 415.1/day, measured 2026-09-06 (5d ago): a factor of 0.34, outside the 2x band. H22's window states its reach in days by dividing by that constant, so the coverage this very line quotes is misdescribed by the same factor. ⛔ A sweep never overwrites the pin: it is re-pinned by hand, from a fresh measurement. Re-measure.

State drifted, observed 141.3, factor 0.34. ⚠️ The card was filed off the 2026-09-09 run (~167.5/day, factor 0.40); today the factor is 0.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'), and rateRows is every row the pass READ — PRs not dropped, projected to updated_at — off closedWindowPagePath: 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%.

depth newest oldest span (d) rate/day
400 2026-09-11T04:15:19Z 2026-09-09T15:55:11Z 1.514 264.2
800 2026-09-11T04:15:19Z 2026-09-08T05:44:23Z 2.938 272.3
1200 2026-09-11T04:15:19Z 2026-09-07T04:48:52Z 3.977 301.8

⚠️ That is not the population the sweep is rated against. Replaying listRecentlyClosedIssues over those captured pages with nowMs = the read instant:

stream replayed closedPages in-window closures reach observed rate
as captured (PR-inclusive) 9 426 2026-09-07 256.2/day
card-only slice of it 5 426 2026-09-07 139.4/day
the live 01:55Z run 5 428 2026-09-07 141.3/day

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: write and no pull-request scope, and its /issues pages 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):

depth (card-only) oldest span (d) rate/day
200 2026-09-09T19:47:53Z 1.352 147.9
400 2026-09-08T09:48:23Z 2.769 144.5
500 2026-09-07T14:11:02Z 3.586 139.4
605 2026-09-07T04:59:18Z 3.969 152.4

⚖️ 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 alarm drifted at 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

before after
MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY 415.1 139.4
..._AT '2026-09-06' '2026-09-11'
record 400 rows / 0.964 d; re-reads 800 / 2.051 d and 1200 / 3.092 d 500 card-only rows / 3.586 d; re-reads 200 / 1.352 d, 400 / 2.769 d, 605 / 3.969 d

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 reads ok (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:

pin derivation before after
windowCoverageDays(400, PIN) 400 / 139.4 0.96 2.87
PIN / 10.7 139.4 / 10.7 38.8 13
windowCoverageDays(400, PIN) < N 2.87 no longer under 2.5 < 2.5 < 3 — still short of the 3-day horizon, which is the claim the case carries
drift factor in the message 942.76 / 139.4 'a factor of 2.27' 'a factor of 6.76'
pinned rate in the message the constant, rendered '= 415.1/day' '= 139.4/day'
the value literal ⛔ the no-self-rewrite pin 415.1 139.4
the date literal ⛔ same pin, its date '2026-09-06' '2026-09-11'
the in-band stream fixture step is relative to the pin: 0.25 gave 433.33/day (3.11x the new pin) rateStream16393(0.25), 13 pages, 433.33 rateStream16393(0.75), 5 pages, 166.67 (factor 1.20)
clause: expired case observed stated at the pin clause16393(415.1, …) clause16393(139.4, …)
clause: in-band cases (x2) same reason as the fixture clause16393(433.33) clause16393(166.67)
clause: both rates named rendered observed + pin '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_DAYS is 30 and classifyRatePremise turns expired at ageDays > 30, so the new pin expires 2026-10-11 — measured, not computed by hand: classifyRatePremise answers ok at 2026-10-10T00:00:01Z (age 29.00) and expired at 2026-10-11T00:00:01Z (age 30.00). It is one line on the ..._AT docblock, 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 states ok/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 head 11e23caca — the final commit, with a clean working tree.

  • node scripts/pm/check-half-states.mjs --self-testbefore (origin/main 0918c441, 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/objectstack derived 39 commands from the final diff (1 path, three-dot vs merge base 0918c4411). 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-wide eslint . --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.
  • Control-byte self-scan beyond the gate: grep -naP over the changed file for the C0 set — no hits.
  • ⊘ NOT MEASURED, by the derivation's own classification: 4 families that take a value from the workflow (including 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: nonescripts/pm/** publishes nothing from any released package (it is in no package's files[]), so skip-changeset applies, 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 carries RATE PREMISE DRIFTED — cannot be quoted pre-merge: the scheduled sweep reads main. 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's ⚠️ Cost note (「46% of the rows this stream returns are PULL REQUESTS … roughly twice the pages a card-only stream would need」), the same docblock's 「the divisor below now reads 415.1」, and listRecentlyClosedIssues'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,cron 37 1,7,13,19)的普查句确认 RATE PREMISE DRIFTED 已消失、改读 rate premise OK;并决定 #17626 的走向:是把「card-only」写进测量配方,还是给 workflow 加 pull-requests: read 让两边读同一个流(代价是 H22 的页数账单翻倍)。


Generated by Claude Code

…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 os-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 11, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review September 11, 2026 05:27
@os-litant
os-litant enabled auto-merge September 11, 2026 05:27
@os-litant
os-litant added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit c1123cf Sep 11, 2026
50 checks passed
@os-litant
os-litant deleted the claude/issue-17254-h22-rate-pin-remeasure branch September 11, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants