Repository navigation
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 was referenced Sep 12, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…sured on (objectstack-ai#17793) Fixes objectstack-ai#17626 H22 pages its closed-card window through `closedWindowPagePath` — `GET /repos/{repo}/issues?state=closed&sort=updated&direction=desc&per_page=100&page=N`. Two callers issue the byte-identical request and get different POPULATIONS: the scheduled patrol runner's pages come back **card-only**, a proxied agent container's come back **49.6% pull requests**. Four prose sites in `scripts/pm/check-half-states.mjs` quoted the container's stream while describing the patrol's, so the measurement recipe they carry — followed correctly — yields the wrong number. This is prose only: **no constant moves** (`MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY` stays 139.4, `CLOSED_ISSUE_WINDOW_PAGE_CEILING` stays 40), no H-row, no self-test change. ## The boundary this card asked to survive the fix The population difference itself is **MEASURED**, through the sweep's own numbers. **The runner's `permissions:` block as the MECHANISM is NOT claimed** — the runner's raw response was never read. Every new sentence below is written that way, and the⚠️ Cost note says it in those words. ## The four sites, before and after **1. The⚠️ Cost note under `CLOSED_ISSUE_WINDOW_DAYS`** - before: «46% of the rows this stream returns are PULL REQUESTS, filtered out after paging. The horizon is therefore reached in roughly twice the pages a card-only stream would need.» - after: the 46% is named as a **container** reading (49.6%, read 2026-09-11T04:22:36Z, 12 pages / 1,200 rows), the patrol's stream is named card-only, and the runner side is quoted from the sweep's own numbers — the 2026-09-11T01:55:32Z run (34552285557, `2f8ad091`) reports 5 pages, 428 in-window closures, reach 2026-09-07; 428 in-window closures cannot come out of 5 PR-inclusive pages, since only ~250 of those 500 rows would be cards. The 2026-09-09 run is the same shape at 6 pages. **2. The same docblock's present-tense divisor sentence** - before: «re-pin (objectstack-ai#16419: the divisor below now reads 415.1)» — present tense against a divisor that now reads 139.4. - after: restated as history — objectstack-ai#16419 re-pinned 415.1 on 2026-09-06 from the container stream; objectstack-ai#17254 re-pinned on 2026-09-11 from the card-only slice, and the divisor below now reads 139.4. The `~13 pages` this note derives is also marked as a 2026-09-06 container reading, and explicitly **not** comparable to today's 5 pages on population alone — the two readings are five days AND one population apart, with the decomposition left where PR objectstack-ai#17627 put it, in `MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY`. **3. `CLOSED_ISSUE_WINDOW_PAGE_CEILING`'s derivation** - before: «4,000 rows reached 12.8 days on the read above (~312/day averaged over that depth), against a 3-day horizon: ~4.3x headroom.» - after: the derivation stands, with a⚠️ POPULATION note beneath it — that read was container-taken, so only ~2,000 of its 4,000 rows were cards. 40 pages return 4,000 rows either way, but on the runner all 4,000 are cards, so the same ceiling reaches **about twice** as far back: ~25 days rather than 12.8, ~8x headroom rather than ~4.3x. Written as a **note, not a change**: the direction is more headroom and not less, 40 is unchanged, and re-deriving it properly needs a card-only read at depth that nobody has taken. **4. `listRecentlyClosedIssues`'s `rateRows` comment** - before: «the pin this rate is checked against was measured over the RAW stream (400 rows / 0.964 days)» — a description of the 188.3 pin, two re-pins ago. - after: the pin's population as it now stands (500 rows / 3.586 days, read 2026-09-11), plus why the distinction lives at this line: on the patrol runner the stream is card-only, so `rateRows` holds cards and the observed rate is counted on the same population the pin was; the same request from a container returns 49.6% pull requests and `rateRows` would count those too — a rate on one population checked against a pin on another. The `READ` vs `ADMITTED` argument (objectstack-ai#4690 with the numbers the right way up) is unchanged. ## The re-measure recipe the file now carries Stated once, in the⚠️ Cost note, and pointed at from sites 3 and 4: > A container read is NOT the patrol's stream. Filter pull requests OUT of every page BEFORE counting pages or rating rows, and quote the card-only slice. On the 2026-09-11T04:22:36Z container read that slice gives **5 pages / 426 rows / ~139.4 per day**, against **9 pages / 426 / ~256.2 per day** on the whole stream. The slice reproduces the runner's own 5 pages; the raw stream does not. That is the operative half — a seat following the old note from a container reproduced objectstack-ai#17254's false `RATE PREMISE DRIFTED` alarm every time. ## Verification All of it taken on the final commit `034c09ace`. Gate families derived with `node scripts/pm/dispatch-gates.mjs --commands` (no paths passed — it takes its own change set from the merge base), each command run with its exit code captured **before any pipe**, then reconciled: > `✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3).` Every one of the 40 exits 0, including `pnpm check:pm-dispatch-gates` (detached — it runs past the foreground cap — and waited on with `tail --pid`). Named readings: - `pnpm check:pm-half-states` — self-test **3656 cases pass** on the base file (`8fa3fe63d`) and **3656 cases pass** after; the count is unchanged because no pin moved and no self-test assertion names any of the rewritten sentences (grepped `46% of the rows`, `415.1`, `0.964`, `312/day` across the tree — the only hits outside this file's own prose are two historical mentions this PR does not touch). - `pnpm check:nul-bytes` — `check-nul-bytes: OK (scanned 8451 text file(s) ... no raw ASCII control bytes)`; plus a self-scan of the edited file, `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'`, zero hits. - `node scripts/check-comment-mask-corpus.mjs` — `6640 files, 0 disagree, 0 unparseable`. - `node scripts/check-scripts-symbol-anchors.mjs` — `3186 anchors across 248 scripts resolve`. - The remaining 36 derived commands exit 0. Repository-wide scans (`pnpm lint` first among them) are CI's run, not this PR's. ## Acceptance notes - noted, not filed: `MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY`'s docblock (landed by PR objectstack-ai#17627, outside this card's declared surface) states the runner's token scopes as part of its population paragraph. It does label the reading «Measured, not inferred from the workflow file», so it is not a mechanism claim, but it sits closer to one than the⚠️ Cost note now does. Successor: the next seat re-pinning that divisor. - noted, not filed: the card names a second possible route — declaring `pull-requests: read` on `half-state-patrol.yml` so the runner and a container read the same stream. This PR takes the recipe route the dispatch chose; the permission route is untouched and its cost (roughly doubling H22's page bill) is unmeasured. - `skip-changeset`: `scripts/pm/**` is not published — no package's `files[]` ships this script. --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
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