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
Merged
Conversation
…ast 100 is UNJUDGED rather than a truncated claim pool `readCardComments` issued ONE `per_page=100` request with no `page=` ladder and no short-read check, while the two sibling list reads in the same file paged to a cap and answered `null` on 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, and the `Clause-②` declaration read from it. All three list reads now go through one `pagedListRead` ladder with one cap sentence. `COMMENT_PAGE_CAP` is 10 pages (1,000 comments), sized on the board. The input record states, per thread, the pages issued and whether the cap stopped the read. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
…rd-comments-page-ladder
…rd-comments-page-ladder
This was referenced Sep 17, 2026
os-justin
marked this pull request as ready for review
September 17, 2026 22:04
This was referenced Sep 17, 2026
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 #18683
Clause-②: no
The defect
scripts/pm/check-clause2-carriers.mjsread a card's comment thread with ONE request —/issues/{n}/comments?per_page=100, nopage=ladder, no short-read check — while the two sibling list reads in the same file paged to a declared cap and answerednull(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 theClause-②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), onmaind9ba33df4c(script blobd753e2a8cf06d8f72c436e0a1917b2fecb8be813). Two fixtures, both 101 rows, both differing from a complete thread only past the page boundary.main)Claim:card-comments: 100 row(s)·claim.selected: none — no comment on this thread carries a line beginning \Claim:`· **exit 4, row C2absent`**card-comments: 101 row(s)· the 101st claim is the pool ·claim.clause2-line: DECLARED \no`` · exit 0Claim:declaringyes, the 101st a newer one declaringnoclaim.clause2-line: DECLARED \yes`` from the SUPERSEDED carrier, which is not even listed as rejected · exit 4, row C3DECLARED \no`` · exit 0The 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
pagedListReadhelper — it pages to a declared cap, stops on the FIRST short page (no wasted request), and on the cap files the one sharedpageCapNotesentence and answersnull.readCarrierEvents(EVENT_PAGE_CAP10) andreadPullFiles(FILE_PAGE_CAP3) keep their caps to the number; what they gain is that the third read can no longer hold a different default.COMMENT_PAGE_CAPis 10 pages = 1,000 comments. Sized on this board, read 2026-09-17 off the open-issue list rows (550 rows listed, cross-checked againstopen_issues_count= 550):session_01QsCVSivtpwT6ZXs5Rtvqxe块保留 · 第 16–21 场已收班 #12708 at 365, [PM seat] domain:devx @ objectstack — 🟢 os-try-charles · session_017ef78bLdybu3AffehKkhfk · 本班第 88 轮 · 已落地 72 · 在飞 1(#19229 pm:blocking,复合 action 派生)· 等人工合 2(PR #18414 · PR #19271,均 .github/workflows/** ⇒ 席位 422,已发复审请求)· 挡住 1(#18224 被 skills 车道的 #19260 挡)· 等维护者行预算 1(#16233,挡住 AGENTS.md 与 #19069)· 待分诊定级 4(#19216 已定级;#19222 #19278 #19279 待定) #6023 at 241, [PM seat] domain:spec — seat 1 · held · multi-seat lane (seat 2 = #18549) #6017 at 206, [PM seat] domain:cli — 🟢 os-project-manager · session_01QCdUBjM47SxioST9z5Zwdf · R78 接任(维护者接管令,前任断粮)· 现值在标记 5747152616,⛔ 正文仍是 R76 的 · 在飞 PR 2(#18962 全绿待达档复核 · #18867 停在维护者)· #18893 死认领回收中 #6024 at 187; seat post [PM seat] domain:skills — ⏳ vacant (seat 1) · previous PM session_01W5y9kRg1YtYaMQYExVLRc2 stood down 2026-09-20T12:11Z · landed 11 this shift · in flight 0 · queue 39 (p1 1: #19274 · first-tier lift 5 unstaffed) · decision box 0 · findings ungraded 5 · seat 2 #19287 active · body r6 #7623 at 71;pm:queue, p2, unassigned);ManifestSchema.idis a barez.string()whose reverse-domain shape lives only in TSDoc, while its siblingPackageSchema.manifestIdenforces that shape with a regex — one identifier, two declarations, only one of them machine-readable #17534 at 14.So ten pages clears the whole board today with a page to spare, and it is the same ten
EVENT_PAGE_CAPuses — 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 toINPUT_RECORD_PAIR_FIELDS, one per thread this file reads:and, when the cap is what stopped the read:
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=1and #4 is&page=2.The pins
A new
--self-testbattery,#18683: the card-comment read pages to a cap — past 100 is UNJUDGED, ⛔ never a truncated pool, 27 cases, declared inSELF_TEST_BATTERIESwith 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 readsabsent(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'syesis what the limb reads; a capped read isnull, which is neithermissingnorabsentnor a carrier butunreadable; 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:
pm:queue/pm:dispatchedcards: 274, of which 1 exceeds 100 comments — 155 of 158 scripts/** self-tests have no assertion floor: a battery that never ran is indistinguishable from one that passed #13799 at 117;session_01QsCVSivtpwT6ZXs5Rtvqxe块保留 · 第 16–21 场已收班 #12708 (365), [PM seat] domain:devx @ objectstack — 🟢 os-try-charles · session_017ef78bLdybu3AffehKkhfk · 本班第 88 轮 · 已落地 72 · 在飞 1(#19229 pm:blocking,复合 action 派生)· 等人工合 2(PR #18414 · PR #19271,均 .github/workflows/** ⇒ 席位 422,已发复审请求)· 挡住 1(#18224 被 skills 车道的 #19260 挡)· 等维护者行预算 1(#16233,挡住 AGENTS.md 与 #19069)· 待分诊定级 4(#19216 已定级;#19222 #19278 #19279 待定) #6023 (241), [PM seat] domain:spec — seat 1 · held · multi-seat lane (seat 2 = #18549) #6017 (206), [PM seat] domain:cli — 🟢 os-project-manager · session_01QCdUBjM47SxioST9z5Zwdf · R78 接任(维护者接管令,前任断粮)· 现值在标记 5747152616,⛔ 正文仍是 R76 的 · 在飞 PR 2(#18962 全绿待达档复核 · #18867 停在维护者)· #18893 死认领回收中 #6024 (187), [PM seat] domain:services — 🟢 held by session_01AhQASwqJr2Z7XfGWUdvnbF · R6 CLOSED · 11 dispatched / 11 delivered / 11 LANDED / 0 abandoned · in-flight 0 · STANDBY · queue 3 (all clause-3 barred; p0 4 · p1 47) · decision box 0 · filed #19198✅ #19206✅ #19252✅ #19264✅ #19282 #19289 · #18571 retriaged out → domain:spec · 16 corrections published · wake trig_019o7amaLUo2cYgz5NWEKBqK #6021 (145), [PM seat] domain:engine — 🟢 OCCUPIED · session_01NcPSwnmJHczmTu6FG7NMjE · seated 2026-09-20T08:50Z (R6) · wake trig_01MXD41WJRvqNpQ3cgurRc12 #6367 (127), 155 of 158 scripts/** self-tests have no assertion floor: a battery that never ran is indistinguishable from one that passed #13799 (117). Seven arepm:seatposts;source#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--pairverdict 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 ispm:queueat 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 18765input records differ in exactly three lines — the two request paths gaining&page=1, and the two new ladder fields.needs:contract-reviewwas hung on PR #18792 at2026-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 18765diff 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
trapwithgit checkout HEAD --and verified by hash and an emptygit diff HEAD. HEAD blobccd5ad7c9a00fe703d644be261a24f1ed847915a.COMMENT_PAGE_CAP10 → 11a0a952d07748101937420006b9475042d93a5cbc176dfe7e54e7de6bc45737487841a346f509b79null, no longerunreadable, and files no cap sentenceEvery 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 -0pisubstitution whose anchor contained a/, so the edit silently did nothing — the on-disk proof refused it withABLATION VOID: the edit did not reach diskinstead of reporting a green as a measurement.Self-test
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 eachorigin/mainmerge (identical list both times). All 34 run at head993cb89e18, each exit code captured by redirect-then-$?:--ranreconciles 34 derived / 34 run / 0 UNRUN.pnpm check:pm-dispatch-gateswas 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'sfiles[], so nothing published moves.Generated by Claude Code