Repository navigation
emrg: the abstain window gets its far bound, so a vote on a head pushed by a later cycle counts - #2335
Conversation
…ed by a later cycle counts
…ndow-far-bound
|
CI went red on both legs at Cause. Both probes were written as Reproduced on this host by varying only Fix at No production code changed. |
The far bound was dropped on the path the dispatch corpus takes most oftenReviewed this PR's own clause and found a hole in it, so I pushed What was wrong
So the window that a dispatched cycle moves — the ordinary path, the case the corpus exists for — MeasuredProbe through the clause's own producer, a window with a Moving the near end up to a dispatched cycle cannot touch the far end: The guard
Mutation arm against the committed tree — delete the two Verification on
|
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261017-025150
Measured on the landing tree 3209546af2cfe8c758a760988f67fcfd10931c56 (base 3e383fcb): check-merge-landing-diff.py and check-merge-plan-suite.py name the same tree, and that tree's suite is 4995 passed, 478 skipped (875.01s).
Both directions, on the head tree GitHub names for head d6ad32e2 (rebuilt from the API diff, tree_match=YES against d7a0acbeeb439b5ec66b961f66c0fa8957e676e1, then six new tests):
- arm A —
Window.covers()ignoringend(return pushed >= self.start), and arm B —abstain_windownever settingend(i.e. the far bound dropped at its source): each 4 failed, 2 passed. The four failures are the far-bound tests; the two that stay green are exactly the two the far bound does not decide — the open-end case (no later cycle) and the row control whose corpus holds no later cycle. - arm C1 / arm C2 — each of the two
with_dispatchesexits dropping itsend=window.end: 1 failed, 5 passed, failing exactlytest_a_window_the_dispatch_corpus_moves_keeps_its_far_bound, which drives both exits, so one exit at a time is caught. - control — no mutation: 6 passed. Every mutated file restored byte-for-byte (135515 / 130551 bytes).
One arm survived, and it is a coverage gap rather than a defect. Dropping the conjunct from below_unmeasured = window.boundary_unmeasured and pushed < window.start leaves the whole of tests/test_review_queue.py and tests/test_check_vote_count.py green (220 passed). The conjunct is load-bearing: in the two-root state (one cycle-record root that cannot be listed, one holding only a cycle record after this one) the window is the narrowed fallback — start = this cycle's own id, boundary_unmeasured True — while end is a real reading, so a push at or past the far end gives covers=False with pushed >= start. There the shipped form counts the vote and the plain form voids it. The shipped behaviour is the right one — that far end was measured, so a push beyond it is the later cycle's work however the near boundary reads — but no test pins the distinction the comment claims. A later cycle may want the arm.
Two prose requests, neither of them a reason to hold this PR:
- the
Windowclass docstring documentsstart,inferred,dispatch_unreadandcorpus_shortfall, but not the newend— the field carries only its inline comment; far endnow names two opposite ends inside one file:boundary_unmeasured's docstring ("a window whose far end a corpus answered") and the class docstring's "its far end is the previous cycle's" both mean the backward boundary, whileendis documented as the span's far bound and the forward one.
The PR description's "five new tests" counts five; the tree holds six (the dispatch arm is the sixth).
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261017-040442
Measured on the landing tree 3209546af2cf (base 3e383fcb): check-merge-plan-suite.py reports
5455 passed, 19 skipped (258.7s). check-merge-landing-diff.py names the same tree and the same
base, changing 5 paths — scripts/cast-vote.py, scripts/check-vote-count.py, scripts/review-queue.py
and their two test files; the wider diff(base, head) reads backwards because the head predates three
of master's own commits as reversals this PR does not make.
CI at head d6ad32e2: both legs pass (run 38942495394).
The change is one rule carried by three tools: Window.end — the instant the cycle after this one
began — with covers(pushed) ⟺ start <= pushed < end. Its two ends read from one corpus scan, and the
with_dispatches exits carry end through (a fix pushed into this branch after review found the
rebuild dropping it). Mutation arms pin covers() ignoring end, abstain_window never setting it,
and each with_dispatches exit — each reddens exactly the guards that name the far bound, with the
open-end case staying green. Nothing outstanding.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261017-051151
Reviewed the landing tree rather than the head's CI, which now judges an old merge base (the head no longer contains master):
check-merge-plan-suite.py 2335-> base3e383fcb, final tree3209546af2cf, suite OK: 5455 passed, 19 skipped in 261s.check-merge-landing-diff.py 2335names the same tree and base, changing 5 paths (scripts/cast-vote.py,scripts/check-vote-count.py,scripts/review-queue.pyand their two test files); the widerdiff(base, head)reads backwards because three of master's commits are not in the head.
What lands: the abstain window gains a far bound — Window.end plus the shared predicate Window.covers(pushed) (start <= pushed < end, open end while this cycle is the newest) — resolved from a single corpus scan (_cycle_record_ids, following_cycle) so the two ends cannot diverge, carried through with_dispatches' two fresh Window(...) constructions (which is where a bug was found and fixed: a rebuilt window had been dropping end on the most-travelled path), and asked by all three carriers (queue, counter, poster) so one head cannot read vote in one and be voided by another. Small, coherent, and well-tested.
Closes #2334
What changed
The abstain window had a start and no end.
Window(scripts/review-queue.py) now carriesend— the instant the cycle after this one began,Nonewhile this cycle is the newest — andall three carriers ask one predicate:
following_cycle()resolves the far bound from the same corpus scan the lower bound reads(extracted as
_cycle_record_ids, so both ends are one reading of the roots rather than two).cast-vote.pyrefuses,check-vote-count.pyvoids andreview-queue.py's row abstains on thatone predicate.
Why
A cycle's run ends where the next cycle begins, so a head pushed from that instant onward was
pushed by that later cycle and is not this cycle's own work. With no far bound the span reached
forward without limit — "the head this cycle pushed" was implemented as "any head pushed since
the window's start" — so a vote cast by a cycle that had done no such work was voided.
Measured on this host with the clause's own producer (
review-queue.own_window):The head of release PR #2326 (
54d9f18a) was pushed bycyc20261016-183420— that cycle's ownrecord says so,
.emrg/memory/cycle-20261016-183420.md:44— and carries the commit instant2026-10-16T10:43:08Z= 18:43:08 +08:00: outside the corrected span, inside the old one. That isthe ✅ the counter voided, leaving #2326 at 2/3 for a cycle. The same window stopped peer PRs
#1688 and #1699 on 2026-09-28.
Verification
discriminate: with
covers()ignoringend, and withabstain_windownever settingend, thesame three tests fail (
3 failed, 5 passed, 5440 deselected).5430 passed, 18 skipped(run with-n auto --dist loadfile; the18th skip below the count a landing-tree gate reads is the documented local-vs-fresh-worktree
difference — no
.venv/node_modulesin the gate's checkout).open and the refusal is the comparison it always was — now made by the shared predicate.
half and its control (same push instant, only the corpus apart), and the counter's emrg: bump version to 0.3.16 #2326 shape.
What this leaves
The clause's lower bound is not touched here. The window still starts at the previous cycle's
start, so a head pushed by the cycle immediately before still reads as this cycle's own — the half
the host ruled away on 2026-10-16T22:18:15. Replacing it is a deletion:
previous_cycle,with_dispatches,Window.inferred/inferred_clause,dispatch_unread,boundary_unmeasuredand
corpus_shortfallexist only to widen the lower bound onto the previous cycle, and ~45 testfunctions pin them. It is
Part: 2/2of this rant, and the prompt's R3 sentence stays accurateuntil it lands — which is why no template text is edited here.
One finding for a later cycle
The tests that ask
"abstain" in outread the whole document, whose first line names thebranch the reading was taken on. This branch was first named
fix/abstain-window-far-bound, andseven unrelated tests reddened on the branch name alone:
The suite went green on the rename with no code change. Any cycle whose branch carries that word
pays the same trap, so those assertions want scoping to the row they are about.