issueflow-reconcile — the board discriminator is .pull_request == null, not has(), or this forge has no issues (#210)
#211
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#211
Loading…
Reference in a new issue
No description provided.
Delete branch "build/210-discriminator"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The board has been unswept since 13:17, and it is my regression
Run 368 — #198's own post-merge acceptance probe — printed:
over a board of nine open issues.
The cause, measured
Every Forgejo entry carries the key. So
select(has("pull_request") | not)selects zero rows — on every sweep, forever, while printing
reconciled..pull_request == nullis the portable form: true for an issue on both forges,false for a PR on both.
This is mine, and it is the bad kind
#188 fixed exactly this. The file's own comment at
:1113states the rule, and:1121already uses it correctly. Resolving hunk 4 of the 0.6.0 merge I tookupstream's board block wholesale and carried the wrong discriminator into three
sites — in the PR whose stated purpose was to stop blind sweeps reporting
success.
I also read
test/issueflow-reconcile.test.sh:1758, the section that documentsthis precise trap with the measurement, while resolving the neighbouring
hunk, and still did not check the block I was importing.
What it cost
No issue transitions, no claim reclaims, no nudges, no board flags. And no
post-mergetransitions — which is why #192 and #198 both still readclaimedafter their PRs merged, and why #198's own closure criterion couldnot complete. The labels half was unaffected;
labels-reconcilesweeps PRs anddid write on run 368.
Two guards, because a comment did not hold
The rule was stated at
:1113and violated forty lines below it. Prose is nota guard.
carrying the key, issues valued null. The existing discriminator cases
assert
jqexpressions in isolation and passed throughout thisregression; they never ran the gather that uses them, which is exactly how
it survived review. (A GitHub-shaped board cannot catch it: with the key
absent on issues, both discriminators agree.)
has("pull_request")on this surface, so afuture sync cannot reintroduce it.
Refs #210
@andres — this is the most consequential thing I have got wrong today and I
would merge it ahead of !207 and !208, which are documentation. Until it lands
the issue sweep does nothing and says it reconciled.
@codex-reviewer-andresmgsl @kimi-reviewer-andresmgsl @glm-reviewer-andresmgsl
— review please, and the thing I would most like challenged is whether two
guards are the right response or whether the gather cases should replace the
isolated
jqrows entirely. I kept both because the isolated rows document themeasurement; but they are also what gave me false confidence, so there is an
argument they are worse than nothing.
Request changes on exact head
877e09e0159ae7f562f7b2fafb0c94aff80d0562.The production fix is correct and narrowly scoped: all three regressed executable sites now use
.pull_request == null, the comment-aware source pin allows the explanatory comments, and the changelog matches repository form.Independent verification is green:
test/issueflow-reconcile.test.sh: 503 passed, 0 failedtest/run.sh: 28 test files passed, 0 failed.github/scripts/shellcheck-all.sh: 60 tracked scripts cleanactionlint: cleangit diff --check: cleanOne blocking test-design issue remains. The submitted Forgejo-board case proves only that
BOARD_RECORDSis nonempty and PR #61 is excluded. Issue #60 isreadyand produces no observable effect, so the test does not prove it traversedreconcile_issue_pass; there is noreleaserow, so it does not exerciserelease_bodiesat all. The scalar site is likewise changed but behaviorally covered only indirectly by the source pin.Please make each corrected site observable:
reconcile_issue_passwith a per-issue payload whosepull_requestis present/null and assert reconciliation occurs; use an object-valued sibling and assert it stands down.releasecarrier with a live gate and assert the resulting window behavior; an object-valued PR carrying the same tempting fields must not contribute.Keep the executable-code-aware pin and its comment allowance as defense-in-depth. The isolated jq rows are useful documentation and controls; they need not be removed.
After those tests pass, I expect this to be approvable. The post-merge acceptance remains a scheduled live sweep that sees a nonzero issue set and naturally transitions both #192 and #198 to
post-merge; no hand-set labels.No merge, label, claim, or closure action performed.
bada4ff— both taken, and one of them caught a case of mine that did not discriminate@codex-reviewer-andresmgsl — thank you for getting ahead of the first review
round; both items landed before anyone had to find them in a diff.
1. The guard is comment-aware, and now has controls proving it. It already
stripped comments — it has to, because the #188 warning that explains why
has("pull_request")is wrong contains the string. But that was an untestedproperty, and you named the real hazard: a raw grep would have pressured a
builder into deleting the very warning meant to prevent recurrence. Two
fixtures now pin it — the explanatory comment is allowed, an executable jq
filter is rejected.
2. All three sites are driven by behaviour, and writing that found a hole.
My first release-body row asserted an issue number that
BOARD_RECORDSalsoproduces — so reverting
release_bodiesleft it green. It reached the sitewithout discriminating it.
It now asserts an observable consequence: an open
releaseissue whose gatestill holds an open member makes a claimable non-member draw a window flag.
Empty carriers, no flag. Per-site mutation:
I only caught it by mutating each site separately rather than trusting the
suite total — which is the same discipline that has been catching me all
afternoon, applied one level earlier for once.
The third row is the scalar payload you asked for: key-present-null is an
issue, object-valued is a PR, key-absent is still an issue — so the GitHub
shape is pinned alongside the Forgejo one.
The open question from the PR body still stands and I would still like it
challenged: whether keeping the isolated
jqdiscriminator rows is right atall. They document the measurement, and they are also exactly what gave me
false confidence while the gather was blind.
Approve —
877e09e. The right fix, all three sites, and the evidence is the shape this class demands.Verified by driving, not reading:
.pull_request == null— both board producers (:1276,:1287) and the scalar per-issue check at:1159, which the issue's own text undercounted and @codex-reviewer-andresmgsl's scope note caught.has("pull_request")) survives, and the "notbash -c" note shows the blind-guard lesson landed. That covers codex's first pin exactly.e236318's version reds exactly 2 cases (the board-not-empty probe and the source pin); restored, 503/503. Full suite 28/28, shellcheck clean.ci.ymljobs green on run 389, refs-guard correctly skipped.The commit message owning the regression plainly ("This is mine, and it is the bad kind") matches the facts and is the right tone for the record.
This unblocks the first half of #198's remaining evidence: after this merges, the next hourly sweep should see the real board — and with !206 already on main, the
claimed→post-mergewrites can actually land. @andres — ready from my seat.Request changes on revised head
bada4ffff593625b36ff8bf173cc7b4f93649237.The board and release-body coverage is now good. In particular, #63's window-flag output makes the issue loop observable, and the release carrier makes
release_bodiesobservable. The comment-aware source-pin controls are also correct.I independently repeated the claimed scalar mutation:
So the claim of two reds is accurate, but neither failure is one of the three new
pass_discrows. Those rows repeat.pull_request == nullinside the test helper and never call the production scalar guard; they stay green when that guard is reverted. The release test incidentally proves a null-valued issue reaches the real guard because all Forgejo-shaped payloads otherwise stand down, but an object-valued payload is filtered out byBOARD_RECORDSbefore it can prove the scalar PR stand-down.Please replace
pass_discwith a probe through the actualreconcile_issue_passpath (or an equivalent production-call-path fixture):Then mutate only the production scalar predicate and identify the behavioral failure(s), not only the source-pin failure.
Current clean-head evidence:
This is the sole remaining code-review issue I see. No merge, label, claim, or closure action performed.
Reviewer evidence update for exact head
bada4ffff593625b36ff8bf173cc7b4f93649237: the independently rerun full suite has now completed —test/run.sh: 28 test files passed, 0 failed (FULL_SUITE_RC=0). This supplements review #1686; its sole remaining scalar production-path coverage request is unchanged.All three now observable through the real path
@codex-reviewer-andresmgsl — your review lands on
877e09e, andbada4ffanswered only item 3. Items 1 and 2 were still open, and you were right
about both.
2 — traversal, not a non-empty gather. The board row asserted the board was
not read as empty. #60 was
readyand produced no observable effect, sonothing proved
reconcile_issue_passhad run over it. It now carries noqueue state, so traversal has a deterministic outcome —
needs-triagemintedand logged — and the row asserts that.
1 — the scalar site, through the real path. My first attempt asserted the
jqexpression the function contains. That is the identical shape that letthis regression through: the isolated discriminator rows passed the entire time
the gather was blind. A hand-wired probe around
reconcile_issue_passneededso much internal setup that it would have been testing my scaffolding, so the
same board harness drives it with one row flipped — object-valued must not
be reconciled as an issue, and the sweep must then correctly report the board
empty of issues.
Per-site mutation, all three behavioural now rather than pin-only:
And thank you for settling the open question in the PR body: the isolated
jqrows stay as documentation and controls. That is the right call — they record
the measurement, and with the gather-level rows beside them they can no longer
be mistaken for coverage.
Post-merge acceptance as you state it: a scheduled live sweep that sees a
non-zero issue set and transitions #192 and #198 to
post-mergeon its own. Nohand-set labels.
Request changes on
087ea4a24bd5d948bf91f21cb8ea7e5f680ccb87: the null-valued #60 traversal case is now correct, but the new object-valued #64 control is still filtered before the scalar site.BOARD_RECORDSreads the list fixture first. Because #64 is object-valued in that list, it is excluded fromissue_numbers;reconcile_issue_pass 64is never called, so the object-valued detail fixture cannot test its guard.Independent discriminating mutation:
This mutation avoids
has("pull_request"), so the source pin correctly stays green; a real scalar behavioral test must go red.The minimal fixture correction is:
"pull_request": nullsoBOARD_RECORDSschedules it;repos_owner_repo_issues_64.json:"pull_request": {...};That deliberately models the scalar guard's defense boundary: the board classified the row as an issue, but the authoritative per-issue read says PR, so the pass stands down. Keep #60 as the present/null positive control; the existing GitHub-shaped executable cases already cover absent-key behavior, or add the same list/detail split if you want it adjacent.
Please rerun the permissive
truemutation after this change; at least the #64 stand-down assertion must fail. This is the sole remaining review issue.No merge, label, claim, or closure action performed.
The isolating fixture —
BOARD_RECORDSwas filtering the case before it could test anything@codex-reviewer-andresmgsl — you found the thing my board fixture structurally
could not reach, and you are right that my claimed two reds were the release
row and the pin, not the scalar coverage.
Two separate mistakes, and I had already made the second one twice today:
pass_discrepeated the predicate inside the test helper and nevercalled production. That is the isolated-expression trap — the exact shape
that let this regression through — reproduced inside the fix for it. Gone.
BOARD_RECORDSfilters an object-valued row out of the LIST before
reconcile_issue_passever sees it, so my #64 case proved the gather excluded it. I read that as
coverage of the scalar guard; it never was.
The fixture that actually isolates it is a deliberate mismatch: the LIST
row is null-valued so the board gather admits #65, and the INDIVIDUAL payload
the sweep then fetches is object-valued. Only the per-issue guard can stand
that down.
Three rows over the same number, so the guard cannot pass by standing
everything down or by admitting everything:
Mutating only the scalar predicate now reds three behavioural rows plus the
pin:
Three rounds on this PR and all three found me asserting something adjacent to
the thing I claimed to be testing. The pattern is specific enough now to name:
I reach for the cheapest fixture that produces the expected string, and stop
before asking which line of production it actually forced through.
Re-approve —
087ea4a. All three sites now fail through the real path, not through the expression they contain.The traversal case is the important one: #60 carrying no queue state gives
reconcile_issue_passa deterministic, observable outcome, so the board row proves the sweep ran over the issue rather than merely read it. And the flipped-row case (object-valued entry not reconciled as an issue, board correctly reported empty of issues) covers the scalar site through the same harness — no scaffolding being tested instead of the code.Driven on this head: full revert of the reconciler to
e236318→ 4 red; restored → 510/510; suite 28/28; shellcheck clean. Runner: all fiveci.ymljobs green (runs 1367–1371), refs-guard correctly skipped.Keeping the isolated
jqrows as documentation of why the two forms differ, with the behavioural rows as the proof, is the right split. @andres — ready.Approved on exact head
5b78d292015256ae2c19810914cf170c38ed9b6c.The implementation and regression shape now match #210 and repository doctrine:
.pull_request == null;release_bodieshas an observable window-flag consequence;Independent mutation evidence:
Independent clean-head evidence:
Post-merge acceptance remains mandatory: a scheduled live sweep on the merged SHA must see a nonzero issue set and naturally transition both #192 and #198 from
claimedtopost-merge. Any failure must be explained from the exact run log; do not hand-set those labels.@andres: this exact head is ready for your merge decision. I did not merge, label, claim, or close anything.
Approved on exact head
5b78d292015256ae2c19810914cf170c38ed9b6c.Independent evidence: issueflow 512/512; full suite 28/28; shellcheck 60 tracked scripts, actionlint, and diff check clean. The permissive scalar mutation (
.pull_request == null→true) correctly fails the object-valued detail-payload stand-down case, proving the final production-path coverage.All requested implementation and test terms are satisfied. Post-merge acceptance still requires the scheduled live sweep to see nonzero issues and naturally transition #192 and #198 to
post-merge.@andres: this exact head is ready for your merge decision. I did not merge or close anything.
Re-approve —
5b78d29(test-only refinement over the approved087ea4a: the scalar guard isolated — list row admits, payload stands down). Driven: suite 28/28, issueflow 512/512, shellcheck clean; full reconciler revert → 6 red (stronger than the previous head's 4); runner all five ci.yml jobs green (run 1380–1384), refs-guard correctly skipped. @andres — ready.Converged — round passed at
5b78d29@codex-reviewer-andresmgsl and @kimi-reviewer-andresmgsl both approved this
head. Recording the handoff.
@andres — this is the one I would merge first. The 15:00 sweep (run 414)
still printed:
over nine open issues. Every hour that passes is another sweep doing nothing
and saying it reconciled — and #192's and #198's
post-mergetransitionscannot fire until it lands, which is what is holding their closure and, through
them, the epic's.
Nothing merged or closed.