Uh oh!
There was an error while loading. Please reload this page.
pm(patrol): seat-idle and blind-claim predicates, and make UNJUDGED survive the body trim - #12088
Merged
Merged
Conversation
…JUDGED survive the trim Three cards, one hot file, one surface (`scripts/pm/check-half-states.mjs` plus its `--self-test`). Report-only throughout: a completed sweep still exits 0 and no row writes a label. H32 — a HELD seat idle over a non-empty lane queue. The first predicate here that watches the SEAT rather than a card: the patrol could see a card nobody moved and could not see a lane whose seat had stopped moving it. Threshold derived the H30 way from the measured inter-claim distribution (n=169 over six active lanes: p50 7 min, p90 324, p95 731); 480 min sits above every measured within-shift interval, and all 12 gaps past it span a shift boundary. Legitimate waits are excluded structurally, not by clock — a marker naming a live blocker exempts regardless of elapsed time. H33 — a `pm:dispatched` card whose latest `Claim:` predates a triage ruling on the same thread, i.e. work in flight under an order that cannot carry the ruling now standing. Ruling anchors are a closed, measured set. Low-yield by construction: 0 on today's board, 4 of 107 over the closed window. H19 — the cross-repo half that can actually land. A genuine cross-repo READ needs a credential the patrol does not hold and by standing ruling will not be given, so the work is making UNJUDGED honest instead: one `GET /repos/<o>/<n>` per distinct sibling repo turns an ambiguous target 404 into a measured cause, per target. And the summary's "never dropped" promise about unresolved targets was measurably FALSE — the 2026-08-25T02:08Z sweep trimmed 199 rows and rendered no unresolved target at all — so unjudged rows now sort above the size trim. Also fixes a wiring regression found while editing the same object: `dispatchRefTargets`/`dispatchRefRead` were computed and never forwarded into `counts`, so the dispatch-liveness clause rendered `read on 0 of 0` on every live sweep. The forwarding is now an enumerated contract the self-test pins. Self-test 1193 -> 1336 cases, all passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
hotlong
marked this pull request as ready for review
August 25, 2026 09:16
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Closes#11706, closes#11724, closes#11218.
Three patrol-predicate cards folded into one PR because they share one hot file
and one gate family:
scripts/pm/check-half-states.mjsand its--self-test.Non-governed surface. Report-only is preserved end to end — a completed
sweep still exits 0, and no row here writes a label, a title or a body.
Self-test: 1193 → 1336 cases, all passing.
Per-item checklist
✅ #11706 — H32, a HELD seat idle over a non-empty lane queue
The first predicate in this file that watches the seat rather than a card.
The patrol could see a card nobody moved (H30) and could not see a lane whose
seat had stopped moving it.
every
Claim:comment on the six active lanes' openpm:dispatchedcardsplus the recently-closed window, n = 169 inter-claim gaps:
p50 7 min · p75 41 · p90 324 · p95 731 · max 6073.The distribution is bimodal — inside a wave a seat claims a batch
minutes apart, between waves it goes quiet for hours — and reading it as
one hump is the trap. Inspected individually, all 12 gaps above 480 min
span a shift boundary or a night (
domain:devx08-19T11:48 → 08-23T17:01;domain:engine08-24T15:22 → 08-25T01:47 across a 收班/开轮 pair).Nothing measured within a held shift reached 480.
SEAT_IDLE_STALE_MINUTES = 480therefore sits above the whole active-seat tail and below thecross-shift band. The derivation is written into the predicate's header.
marker naming 等 CI / 等裁决 / 等人工步骤 or an awaiting-class state exempts
the seat regardless of elapsed time.
not missing (「规则不缺,措辞也不含糊」); the row says so in as many words so
it cannot be re-read as one.
lane countable on this board · unclaimed queue > 0 · in-flight = 0.
Reverse verification, on real seat posts with real marker prose — only the
lane inventory was forced to the idle shape:
domain:specRound-start marker — R6.)domain:clidomain:devxdomain:enginedomain:servicesAgainst the live board it is correctly silent on all 12 seat posts: every
objectstack lane currently has work in flight.
✅ #11724 — H33, a claim written before the ruling that now stands
Prior rulings read:claim field stays declined per grading (a field 99% filled with
noneis ritual).
40 open
pm:dispatchedcards), anchored at the start of the first line— the same census carries prose that mentions a ruling without being one
(
Serial-constraint addendum … the triage comment says), and acontains-match would manufacture a row on every card that discusses its own
triage.
must be distinguishable from "cannot report anything": 0 on the live
board (of 54 dispatched cards, rulings correctly precede claims), 4 of
107 over the recently-closed window ([finding] The published
objectstack-dataskill cites bareADR-0057— a number two records share, in a file that ships wheredocs/adr/does not exist #11781, The*Whenskip list says why the parser could not read the layer, never which layer the fragment documents — so the list cannot be triaged from the list #11673, [finding] The pm-skill ratchet counts LINES while its header prices a per-session token read — a green 134/134 run grew platform-readings.md ~33% in bytes #11106, [finding]pnpm --filter <pkg> test -- <pattern>runs the WHOLE suite — the positional filter is swallowed, and the shared verify lock pays for it #10166).✅ #11218 — H19 cross-repo, scoped per the 2026-08-25 premise refresh
Half 1 (cross-repo read) — reported, not taken. A genuine cross-repo read
needs a credential the patrol does not hold and, by standing ruling, will not be
given:
.github/workflows/half-state-patrol.ymlstates it (「⛔ Each install usesits OWN
secrets.GITHUB_TOKENand reads its own repo. No cross-repo credential,no matrix over repos, no PAT」 — refused at grading, per-repo install chosen), and
an Actions token is repo-scoped by construction. ⛔ Token-scope widening is
routing/security's call and is not taken here.
What did land is the honest half, and it needs no credential change:
ambiguous — unreachable repo, or a number that is not there. One
GET /repos/<owner>/<name>per distinct sibling repo (cached, boughtlazily only when a cross-repo target actually failed) separates them, the
same two-stage shape
classifyRepoReadalready uses. Repo unreadable ⇒ therow says so per target and names it a standing accepted limit no re-run
will fix; repo readable ⇒ the number is not there; probe itself failed ⇒
neither side is claimed.
premise-refresh asked for exactly this). It now states that UNJUDGED is
not a quiet row and that the block is as unverified as if nothing had
been read.
claimed 「each unresolved target is named on its own card's row, never
dropped」. On the 2026-08-25T02:08Z sweep that was false: the run resolved
25 of 28 targets and rendered five H19 rows, all same-repo and closed-led, not
one carrying an unresolved target — because 199 rows were trimmed to fit the
body limit.
199 further row(s) omittedandevery unresolved target is namedsat in one body contradicting each other. That is #4690 wearing this item's own
uniform.
band above ordinary rows, so the trim can only ever fall on rows that made
a determination. Same reservation
renderTriggerIndexalready gets.and a banner announces unjudged rows and says a later sweep will not
resolve them.
✅ #11604 appendix verify-item — covered, no new row (card left open)
The shape (
pm:queue+ assignee + merged delivering PR + no remote branch) isalready covered by H24 (
h24QueuedWithAssignee), which fires on the purepm:queue∩ non-empty-assignee intersection with no threshold, no timestamp andno identity test. The extra conditions in the appendix only narrow that set, so
every card of that shape is already reported. No predicate added; the
appendix item is the row H24 was filed for (17 carriers across three repos at the
2026-08-23 census). #11604 is not addressed by this PR beyond this
verification and remains open for its other items.
Bounded in-place fix (declared)
While editing the
countsobject for the new coverage pairs I found a wiringregression in it:
dispatchRefTargets/dispatchRefReadwere computed by theH20/H27 pass and never forwarded, so the dispatch-liveness clause rendered
read on 0 of 0on every live sweep since it was added. Evidence: the2026-08-25T02:08Z run reports
remote branch read on 0 of 0 distinct claimed branch(es)while publishing two H20 findings, which only a non-empty refcache can produce.
Same defect class as this PR's #11218 work (a coverage pair going silent —
#4690), mechanical, the correct shape pinned by its ten sibling keys, same file,
same gate family, no new verification surface. Rather than adding two lines, the
forwarding is now an enumerated contract (
SWEEP_COUNT_KEYS) thatsweep()copies, so forgetting a key is no longer something the assembly can do — and the
self-test asserts the contract directly.
Not in scope — filed separately
CLAIM_COMMENT_MARKERrequires a colon, and 24 of the 54 openpm:dispatchedcards carry a well-formed claim written with an em dash(
Claim — <seat>, session …), which the marker cannot see. H2 consequentlyreports those 24 as carrying no claim comment at all, and H20/H27 are blind to
them. H33 inherits the blindness deliberately — a file that disagrees with
itself about what a claim is would be worse than the gap, and the direction is
under-reporting, never a fabricated row. Documented in the header and pinned by
two self-test cases; filed as its own card.
Verification
Run against final commit
b35f0f6e8:node scripts/pm/check-half-states.mjs --self-test→✓ check-half-states self-test: 1336 cases pass.(baseline onorigin/mainwas 1193)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(12 families), all green, exit codes captured before any pipe:
check:pm-half-states·check:nul-bytes·check:agent-test-spelling·check:cross-package-test-inputs·check:entry-guard·check:parse-guard·check:partof-closing-keyword·check:pnpm-filter-targets·check-ci-filter-parity·check-closing-keyword-parity·check-cross-package-test-inputs·check-partof-closing-keywordcheck:nul-bytes→OK (scanned 6704 text file(s) … no raw ASCII control bytes), plus a direct control-byte scan of the changed fileeslint . --no-inline-config→ 5090 files, 0 errors, 0warnings (the full population, not a narrowing)
No changeset: scripts-only, nothing user-visible is published (
skip-changeset).Generated by Claude Code