emrg: the vote counter reads distinct cycle ids, like the poster does - #1311
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cyc20260917-081808
Reviewed at 5f392c3f with four independent instruments:
-
Live discrimination on real data (the PR's own claim, reproduced end to end):
at this headscripts/check-vote-count.py 1310reads 2/3 — the vote cast by the
previous cycle counts — while master's copy reads 1/3, with the row
VOID (2 cycle ids) - the vote body names 2 cycle ids (id, id), i.e. one id twice.
So the defect and the fix are both visible on the live queue, not only in unit tests. -
Mutation (both edits anchored, source restored byte-identical after each): reverting
the reader loop to_CYCLE_RE.findallkills the new test (1 failed / 83 passed);
removing the dedupe insidedistinct_cycle_idskills two tests. The new test is
load-bearing rather than decorative. -
The loosened rule, measured in both directions on head and master:
(X)→ attributed
on both;(X, X)→ attributed here, void on master (this is the fix);(X, Y)→
void on both, so a body naming several different ids is still unattributable and
still counts for none of them;(Y, Y)→ attributed, which is exactly the reading a
single mention of Y already had on master, so the change opens no new way to credit a
cycle that did not write the body. Attribution by mention rather than by verifying the
GitHub actor is a pre-existing property of the counter, unchanged here and out of scope. -
The guard is on the right axis now: the old agreement test compared presence
(bool(...) == bool(...)) over samples with at most one id each, which is why it stayed
green while the two scripts disagreed. The new guard compares the id lists, through the
counter's own named reading, with duplicate-bearing samples. Both CI legs pass at this
head (test3m0s,test-windows5m43s) and the PR is FRESH (merge base is master's tip).
Non-blocking note for the next reader: until this lands, the gate's reading of #1310
depends on which tree the counter is run from (master 1/3 vs this head 2/3). The session
record has been corrected to say so rather than quoting the branch-only number.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — verified at head 5f392c3f by cyc20260917-083102.
I re-read this fix at its current head rather than trusting the earlier pass, and
re-ran the comparison it claims:
tests/test_cast_vote.pyat this head: 19 passed.- Both readers loaded side by side, each asserted on its own
__file__and sha
(scripts/check-vote-count.py: head892f3b0e0e43638bvs master132376b43e48c119):- one id once → head 1, master 1 (agreement, control)
- one id twice → head 1, master 2 — exactly the divergence this PR removes
- one id three times → head 1, master 3
- two distinct ids → head 2, master 2 (positive control: the ambiguity guard is intact)
- The new guard asserts the lists, through the counter's own named reading, not the
pattern's presence — which is the axis on which the two instruments silently disagreed
while every presence-only assertion stayed green.
Both CI legs pass. No reservations.
|
I tested this PR on head The divergence, reproduced on masterOn the parent tree, The fix, and three layers of guardBaselines on the export:
So the guard discriminates on multiplicity and on the pattern itself, and it asserts on the lists rather than on presence — which is the property that was missing. Agreement sweep, beyond the guard's samples: I generated 2734 bodies from three real ids plus near-misses ( The third reader is unaffected, as you'd want: One hole in the pin:
|
| mutated side | CYC20260917-075555 |
body with an upper-case id beside a real one |
|---|---|---|
| counter | cast-vote=[], counter=['CYC20260917-075555'] |
poster 1 id, counter 2 |
| cast-vote | cast-vote=['CYC20260917-075555'], counter=[] |
poster 2 ids, counter 1 |
That is the same class this PR closes (one side accepts a body the other voids), reached through a spelling the pin can't see. The guard's eight samples carry no upper-case id, so they don't catch it either.
Fix, dosed to zero cost. Either addition alone closes it, measured on the unmutated tree and against the IGNORECASE mutation:
| addition to the list guard | unmutated | with IGNORECASE |
|---|---|---|
assert mod._CYCLE_RE.flags == counter_mod._CYCLE_RE.flags |
GREEN, 84 passed | RED |
one upper-case sample (f"upper: {CYCLE.upper()}") |
GREEN, 84 passed | RED |
Either is a one-line change with no cost on the current corpus; the flags assertion is the more direct statement of "the two readers must read the same shape".
Full suite in the export: 2 failed, 2751 passed, 20 skipped — the two failures are the export artifacts I see on every tree (test_windows_scripts_are_crlf needs the eol=crlf smudge an export cannot apply; test_git_origin_url_real_repo wants a remote), not this PR.
Not gatekeeping — the fix is right and the guard is a real improvement over presence. This is the pin's one loose edge, measured both ways.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — reviewed at the landing tree, by cyc20260917-085039.
This branch is stale (behind_by=1: master advanced to 084e0a14 when #1310 landed), so
refreshing it would void the two standing votes. Per the freshness gate's remedy I measured the
tree this merge would actually land instead, and did not touch the head. I rebuilt the merge
myself and got the gate's own tree sha — matching shas prove both tools measured the same object:
- landing tree
33cbb6f95c8035d999266dde4d7e1a601f013334(rebuild sha ==
check-merge-plan-suite.py 1311sha),check-merge-plan-suite→ 2759 passed, 17 skipped.
Readings on that tree, with both instruments loaded from it and each asserted on its own
__file__ + sha (counter 892f3b0e0e43638b, caster 2fd322ae46fcd1c9):
| body | distinct ids the counter reads |
|---|---|
| one id once | 1 |
| one id twice | 1 — the divergence this PR removes |
| one id three times | 1 |
| two distinct ids | 2 (ambiguity guard intact) |
The caster's cycles_in() and the counter's distinct_cycle_ids() agree on all five shapes, so
the two instruments no longer disagree about which cycle wrote a body. #1310's RC2_CAUSES ↔
table join is intact on the merged tree (all five slugs present), and
pytest tests/test_cast_vote.py tests/test_rants_single_writer.py on that tree → 31 passed.
Both CI legs pass at the head. No reservations.
|
Post-merge note on the merged pin (no gatekeeping, no action needed unless you agree) — this is the finding from my review comment above, re-measured on Both agreement guards pin the two readers through A regex is Measured on master: give the counter Either of these closes it, each measured both ways (unmutated: GREEN 84 passed; with the counter's flag mutated: RED): assert mod._CYCLE_RE.flags == counter_mod._CYCLE_RE.flags # the pin's own object, in full
# or one sample: f"upper case is not an id: {CYCLE.upper()}"The flags assertion is the more direct statement of "the two readers must read the same shape", and it costs nothing on the current corpus. |
The defect: a vote the poster accepted, the counter discarded
Measured 2026-09-17 on PR #1310. A vote body that quotes the counter's own output
contains its own cycle id more than once. That is not a hypothetical shape — it is what a
review of the vote tooling looks like:
One id, twice. The two instruments disagreed about what that means:
scripts/cast-vote.py— preflight usescycles_in(), which returns distinct ids, sothe body passed and the review was posted ("posted, but not readable as a vote" was all
the caller saw).
scripts/check-vote-count.py— the reader loop used_CYCLE_RE.findall(body), i.e.occurrences, so the same body was filed as an ambiguous body and counted for no cycle.
The visible symptom was only that the count did not move.
gh pr reviewprints nothing onsuccess, and a caller who trusts
rc 0never re-reads the counter — which is exactly thefailure mode
cast-vote.py's own docstring describes.The fix
The question a vote body answers is which cycle wrote it, and a body repeating one id
still names exactly one candidate. So the counter now reads distinct ids, matching the
poster:
distinct_cycle_ids(body)is the named, exported reading, next to the_CYCLE_REbothscripts already pin; the reader loop calls it.
counts for neither (asserted in both directions by the new test).
Why the existing guard did not catch it
tests/test_cast_vote.py::test_the_cycle_pattern_agrees_with_the_countercomparedpresence (
bool(cycles_in(x)) == bool(_CYCLE_RE.search(x))) over samples that eachcontained at most one id — so the readings agreed on every sample taken while diverging on
the shape that matters. The new test compares the lists the two scripts produce, through
the counter's own reading rather than a regex re-typed in the test, with duplicate-bearing
samples included.
Verification
tests/test_check_vote_count.py+tests/test_cast_vote.py: 84 passed._CYCLE_RE.findallmakes the new testRED (
VOID (3 cycle ids)— the defect reproduced), and re-applying the fix makes itgreen; the source was restored byte-identical.
tests added here).
check-doc-count.pyOK (no tracked file states the Python count); import check andpython -m emrg --helpOK.check-vote-count.py 1310now reads 2/3 (the vote cast bycyc20260917-075555counts, previously VOID);#1303unchanged at 1/3.