Skip to content

Commit e08fae8

Browse files
os-justinclaude
andauthored
fix(pm): the card-comment read pages to a declared cap, so a thread past 100 is UNJUDGED rather than a truncated claim pool (#18799)
Fixes #18683 Clause-②: no ## The defect `scripts/pm/check-clause2-carriers.mjs` read a card's comment thread with ONE request — `/issues/{n}/comments?per_page=100`, no `page=` ladder, no short-read check — while the two sibling list reads in the same file paged to a declared cap and answered `null` (UNJUDGED, never clean) when they hit it. One file, two OPPOSITE defaults on "I did not read everything", and the fail-OPEN one was the read that arbitrates OWNERSHIP: the governing-claim pool, its membership, and the `Clause-②` declaration read out of it all come from those rows. A thread past 100 comments handed the pool its first page and nothing said the tail had been dropped, so a claim written past row 100 was not superseded — it was never a candidate — and a superseded carrier governed in its place. ## The before-reading, on a 101-row fixture Driven end to end through the real CLI against a stubbed board (`--pair`, no network), on `main` `d9ba33df4c` (script blob `d753e2a8cf06d8f72c436e0a1917b2fecb8be813`). Two fixtures, both 101 rows, both differing from a complete thread only past the page boundary. | fixture | BEFORE (`main`) | AFTER (this PR) | |---|---|---| | 100 claim-free rows, the 101st the only `Claim:` | `card-comments: 100 row(s)` · `claim.selected: none — no comment on this thread carries a line beginning \`Claim:\`` · **exit 4, row C2 `absent`** | `card-comments: 101 row(s)` · the 101st claim is the pool · `claim.clause2-line: DECLARED \`no\`` · **exit 0** | | row 1 an older `Claim:` declaring `yes`, the 101st a newer one declaring `no` | `claim.clause2-line: DECLARED \`yes\`` from the SUPERSEDED carrier, which is not even listed as rejected · **exit 4, row C3** | the newer claim governs, the older is listed REJECTED/SUPERSEDED · `DECLARED \`no\`` · **exit 0** | The second row is the fail-OPEN direction stated as a measurement: one thread, two readings, and they disagree on the declaration itself. ## The ladder, and the cap All three list reads now go through one `pagedListRead` helper — it pages to a declared cap, stops on the FIRST short page (no wasted request), and on the cap files the one shared `pageCapNote` sentence and answers `null`. `readCarrierEvents` (`EVENT_PAGE_CAP` 10) and `readPullFiles` (`FILE_PAGE_CAP` 3) keep their caps to the number; what they gain is that the third read can no longer hold a different default. `COMMENT_PAGE_CAP` is **10** pages = 1,000 comments. Sized on this board, read 2026-09-17 off the open-issue list rows (550 rows listed, cross-checked against `open_issues_count` = 550): - longest open thread of any kind: seat post #6015 at **895** comments — nine pages; - next four: #12708 at 365, #6023 at 241, #6017 at 206, #6024 at 187; seat post #7623 at 71; - longest thread carrying a queue label: #13799 at **117** (`pm:queue`, p2, unassigned); - longest card in the clause-② population (28 pairs the sweep derived that day): #17534 at **14**. So ten pages clears the whole board today with a page to spare, and it is the same ten `EVENT_PAGE_CAP` uses — a reader comparing two caps in one file should have to remember one number. ## The input record The diagnosis key stays `comments`, so every sentence already keyed to it still finds its diagnosis. Two declared fields are added to `INPUT_RECORD_PAIR_FIELDS`, one per thread this file reads: ``` pair.1.card-comments: 101 row(s) pair.1.card-comment-pages: 2 of 10 page(s) requested — the ladder stopped on a SHORT page, so the thread is COMPLETE pair.1.pr-comment-pages: 1 of 10 page(s) requested — the ladder stopped on a SHORT page, so the thread is COMPLETE ``` and, when the cap is what stopped the read: ``` pair.1.card-comment-pages: CAPPED — 10 of 10 page(s) of 100 comments each were requested and EVERY ONE came back full, so the tail is past the cap and the thread is UNREAD (UNJUDGED) — ⛔ never a truncated pool, ⛔ never a clean reading ``` A thread of exactly 100 rows and a thread whose tail was dropped are the same `100 row(s)` in every other line the block prints; they differ here, because the complete one stopped on a short page and the truncated one did not stop at all. The request ledger PR #18681 added shows the same ladder from the other side — request #3 is now `…/comments?per_page=100&page=1` and #4 is `&page=2`. ## The pins A new `--self-test` battery, `#18683: the card-comment read pages to a cap — past 100 is UNJUDGED, ⛔ never a truncated pool`, 27 cases, declared in `SELF_TEST_BATTERIES` with the roster floor raised 29 → 30. It drives the ladder with an offline page server that reproduces GitHub's own semantics and counts the requests; ⛔ no network. What it holds: the 101st claim ENTERS the pool and GOVERNS, and its line is what the limb reads; the same thread cut at 100 reads `absent` (the CONTROL — the reading the un-paged read produced); the newer claim past the boundary supersedes the older one inside it, and cut at 100 the superseded carrier's `yes` is what the limb reads; a capped read is `null`, which is neither `missing` nor `absent` nor a carrier but `unreadable`; the ladder stops on the first short page (2 requests for 101 rows, 1 for a short thread, 2 for exactly 100 — a full page is indistinguishable from a finished one); a page that came back unread ends the ladder and the record says the cap was NOT what stopped it; the input record declares and prints both ladder fields; the sibling caps are untouched; and all three reads render ONE cap sentence. ## The census, and the triage's upgrade probe Report-only, no state write. Over the 550 open rows (521 issues, 29 PRs) read on 2026-09-17: - open `pm:queue` / `pm:dispatched` cards: **274**, of which **1** exceeds 100 comments — #13799 at 117; - all open issues over 100 comments: **8** — #6015 (895), #12708 (365), #6023 (241), #6017 (206), #6024 (187), #6021 (145), #6367 (127), #13799 (117). Seven are `pm:seat` posts; - open PRs over 100 comments: **0**; the longest is #18638 at 10. **The upgrade probe's result: the condition is NOT met today.** The clause-② population is what a `--pair`/sweep derivation actually pairs, not what carries a queue label: the sweep derived **28 pairs from 29 open PRs**, and the longest card thread among them is **14** rows (#17534). The two open PRs that mention a 100+-comment card in prose — #18786 (#6015, #7623) and #18765 (#6024) — deliver #18693 and #18652 respectively, both under 10 comments; driven live before and after, both answer exit 0 with an identical pair reading. So no recorded `--pair` verdict on this board today was taken on a truncated pool, and the triage's p1 condition (「找到任一进入条款②认领池、评论数 > 100 的卡并驱动一次」) has no live instance to drive. The exposure is one PR away rather than realised: #13799 is `pm:queue` at 117 and enters the population the moment a PR delivers it. The cost is unchanged by the ladder, measured on the same board: **64 reads for 28 pairs, before and after**, because every live thread fits one page and the ladder stops on a short page. The live `--pair 18765` input records differ in exactly three lines — the two request paths gaining `&page=1`, and the two new ladder fields. ⚠️ One thing the two full sweeps do NOT compare: the sweep's finding COUNT moved 4 → 3 between them, and that is the board, not this diff. `needs:contract-review` was hung on PR #18792 at `2026-09-17T21:04:46Z`, between the two runs, closing the C1 split on #18792 / #17541 on its own. The controlled A/B is the `--pair 18765` diff above. ## The ablation Two legs, each from the COMMITTED fix, each proving the mutation reached disk by blob hash and occurrence count before reading any result, each restored under a `trap` with `git checkout HEAD --` and verified by hash and an empty `git diff HEAD`. HEAD blob `ccd5ad7c9a00fe703d644be261a24f1ed847915a`. | leg | mutation | blob after | self-test | |---|---|---|---| | A — the ENTRY side | `COMMENT_PAGE_CAP` 10 → 1 | `1a0a952d07748101937420006b9475042d93a5cb` | **exit 1, 11 of 865 red** — the 101st-claim pins, the superseding pins, the request-count pins, the input-record pin | | B — the UNJUDGED side | the cap branch returns the pages that DID arrive (the pre-fix fail-OPEN default) | `c176dfe7e54e7de6bc45737487841a346f509b79` | **exit 1, 3 of 865 red** — a capped read is no longer `null`, no longer `unreadable`, and files no cap sentence | Every red in both legs belongs to the new battery; nothing pre-existing went red in either. A third, unplanned reading came for free: leg B's first attempt was a `perl -0pi` substitution whose anchor contained a `/`, so the edit silently did nothing — the on-disk proof refused it with `ABLATION VOID: the edit did not reach disk` instead of reporting a green as a measurement. ## Self-test ``` ✓ check-clause2-carriers self-test: 865 cases pass ``` 838 before, 865 after — the 27 the new battery registers, which is what its floor pins. ## Derived gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, no hand-fed path list, re-derived after each `origin/main` merge (identical list both times). All 34 run at head `993cb89e18`, each exit code captured by redirect-then-`$?`: ``` node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-changeset-no-major.mjs --self-test :: exit 0 node scripts/check-ci-filter-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 node scripts/check-scripts-symbol-anchors.mjs :: exit 0 node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0 node scripts/check-self-test-wired.mjs :: exit 0 node scripts/check-self-test-wired.mjs --self-test :: exit 0 node scripts/check-self-test-workflow-commands.mjs :: exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 node scripts/check-whole-set-label-write.mjs :: exit 0 node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:bash32-floor :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:cli-command-ids :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:entry-guard :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:parse-guard :: exit 0 pnpm check:pm-clause2-carriers :: exit 0 pnpm check:pm-dispatch-gates :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:ratchet-remedy-authority :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm lint :: exit 0 ``` `--ran` reconciles 34 derived / 34 run / 0 UNRUN. `pnpm check:pm-dispatch-gates` was run detached to a file — 1,788 cases, 748.6s on this box — and waited on in the foreground rather than under a timeout, so it is a measurement and not a SIGTERM. ## Out of scope, deliberately #18764 (a decorated `**Claim:**` never enters the pool — the ENTRY side) was read and NOT folded in: this card is WHICH rows reach the reader, not what the reader does with them, and the two repairs touch different lines. `claimRetractions` (PR #18770, the EXIT side) was read for the words it uses and not touched. The header's request-budget paragraph is amended in the same commit, because it stated "2 reads per card" as a fact and the thread is now a ladder — a cost statement that stopped being true is the shape this file exists against. `skip-changeset`: `scripts/pm/**` ships in no package's `files[]`, so nothing published moves. --- _Generated by [Claude Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ac720a9 commit e08fae8

1 file changed

Lines changed: 273 additions & 39 deletions

File tree

0 commit comments

Comments
 (0)