forked from heavy-duty/ceremony
Merge pull request #140 from claude-bot-andresmgsl/build/139-cancelled-not-verdict
fix: a queue-cancelled duplicate check is not a verdict — checks_state discards it when a real one stands
This commit is contained in:
commit
089f2dba29
3 changed files with 58 additions and 2 deletions
|
|
@ -205,6 +205,22 @@ checks_state() { # rollup JSON on stdin → SUCCESS | FAILURE | PENDING | NONE |
|
||||||
# the exact shape of #136. The cost of being wrong is symmetric in form and
|
# the exact shape of #136. The cost of being wrong is symmetric in form and
|
||||||
# not in consequence: a false FAILURE parks the PR on the agent, who looks;
|
# not in consequence: a false FAILURE parks the PR on the agent, who looks;
|
||||||
# a false SUCCESS invites a human to merge a tree that will not merge.
|
# a false SUCCESS invites a human to merge a tree that will not merge.
|
||||||
|
#
|
||||||
|
# The list-what-passes rule has exactly one carve-out, and it is narrower
|
||||||
|
# than an outcome: a CANCELLED entry is discarded when its context holds at
|
||||||
|
# least one non-cancelled sibling (#139). The reconcile job queues in one
|
||||||
|
# repo-global concurrency group, so any repo event — a sibling PR's push,
|
||||||
|
# triage labelling an issue — evicts the queued duplicate AFTER it has
|
||||||
|
# attached a check run to this PR's head, and that cancelled entry became
|
||||||
|
# the context's newest word: blocker:ci-red on a PR whose real checks were
|
||||||
|
# all green (#133/#136, evictable only by an empty commit). A cancelled run
|
||||||
|
# said nothing about this head; a non-cancelled sibling is a real verdict
|
||||||
|
# about exactly these bytes, whatever order the two arrived in — and for
|
||||||
|
# this workflow the evictor performs the duplicate's work anyway, since
|
||||||
|
# every sweep covers every open PR. This does not widen unknown-into-green:
|
||||||
|
# a context whose entries are ALL cancelled never reported at all (a killed
|
||||||
|
# or timed-out required job), so it keeps CANCELLED and still blocks —
|
||||||
|
# discard needs a surviving verdict, never an empty context.
|
||||||
jq -r '
|
jq -r '
|
||||||
if (has("statusCheckRollup") | not) then "UNREADABLE" else
|
if (has("statusCheckRollup") | not) then "UNREADABLE" else
|
||||||
|
|
||||||
|
|
@ -253,8 +269,14 @@ checks_state() { # rollup JSON on stdin → SUCCESS | FAILURE | PENDING | NONE |
|
||||||
and (startswith("0001-01-01") | not)))
|
and (startswith("0001-01-01") | not)))
|
||||||
| first // ""),
|
| first // ""),
|
||||||
outcome: ((.conclusion // .state // "") | ascii_upcase) } ]
|
outcome: ((.conclusion // .state // "") | ascii_upcase) } ]
|
||||||
|
# The #139 carve-out (header above): drop CANCELLED entries only when the
|
||||||
|
# context keeps a non-cancelled survivor — BEFORE the sort, so a cancelled
|
||||||
|
# entry that arrived newest cannot outvote the real verdict it displaced.
|
||||||
|
# An all-cancelled context is left intact and still classifies FAILURE.
|
||||||
| group_by(.ctx)
|
| group_by(.ctx)
|
||||||
| map(sort_by([(.at == ""), .at]) | last | .outcome) as $latest
|
| map( map(select(.outcome != "CANCELLED")) as $live
|
||||||
|
| (if ($live | length) > 0 then $live else . end)
|
||||||
|
| sort_by([(.at == ""), .at]) | last | .outcome ) as $latest
|
||||||
|
|
||||||
| if ($latest | length) == 0 then "NONE"
|
| if ($latest | length) == 0 then "NONE"
|
||||||
elif (($latest - $passing - $waiting) | length) > 0 then "FAILURE"
|
elif (($latest - $passing - $waiting) | length) > 0 then "FAILURE"
|
||||||
|
|
|
||||||
1
changelog.d/139.md
Normal file
1
changelog.d/139.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- `labels-reconcile` — a queue-cancelled duplicate check is discarded when its context holds a real verdict, so a sibling PR's eviction no longer reds a green PR; an all-cancelled context still blocks (#139).
|
||||||
|
|
@ -385,7 +385,15 @@ expect "NEUTRAL and SKIPPED are not failures" SUCCESS \
|
||||||
expect "a re-run supersedes the cancelled original" SUCCESS \
|
expect "a re-run supersedes the cancelled original" SUCCESS \
|
||||||
"$(rollup "[$(run_ scope CANCELLED 2026-07-20T15:19:39Z),\
|
"$(rollup "[$(run_ scope CANCELLED 2026-07-20T15:19:39Z),\
|
||||||
$(run_ scope SUCCESS 2026-07-20T15:19:45Z)]" | checks_state)"
|
$(run_ scope SUCCESS 2026-07-20T15:19:45Z)]" | checks_state)"
|
||||||
expect "...and the reverse order is not a re-run passing, it is one failing" FAILURE \
|
# The reverse order once pinned FAILURE — "the reverse order is not a re-run
|
||||||
|
# passing, it is one failing". That fixture imagined a cancelled run REPLACING
|
||||||
|
# a success; #139 recorded a cancelled run that replaced NOTHING: the
|
||||||
|
# repo-global reconcile queue keeps one pending run per group, so a sibling
|
||||||
|
# PR's event evicts the queued duplicate after it has already attached a
|
||||||
|
# check to this head. A run that never executed a step said nothing about
|
||||||
|
# these bytes — it is not a verdict, and the success beside it is. The
|
||||||
|
# survivor decides, whatever order the two arrived in (#139).
|
||||||
|
expect "a cancelled entry beside a success is not a verdict, the success is" SUCCESS \
|
||||||
"$(rollup "[$(run_ scope SUCCESS 2026-07-20T15:19:39Z),\
|
"$(rollup "[$(run_ scope SUCCESS 2026-07-20T15:19:39Z),\
|
||||||
$(run_ scope CANCELLED 2026-07-20T15:19:45Z)]" | checks_state)"
|
$(run_ scope CANCELLED 2026-07-20T15:19:45Z)]" | checks_state)"
|
||||||
# same job name in a different workflow is a different context, not a re-run
|
# same job name in a different workflow is a different context, not a re-run
|
||||||
|
|
@ -393,6 +401,31 @@ expect "same name in another workflow does not supersede" FAILURE \
|
||||||
"$(rollup "[$(jq -n '{__typename:"CheckRun",workflowName:"labels",name:"scope",conclusion:"FAILURE",completedAt:"2026-07-20T15:00:00Z"}'),\
|
"$(rollup "[$(jq -n '{__typename:"CheckRun",workflowName:"labels",name:"scope",conclusion:"FAILURE",completedAt:"2026-07-20T15:00:00Z"}'),\
|
||||||
$(run_ scope SUCCESS 2026-07-20T15:19:45Z)]" | checks_state)"
|
$(run_ scope SUCCESS 2026-07-20T15:19:45Z)]" | checks_state)"
|
||||||
|
|
||||||
|
# -- the #139 carve-out, pinned by the recorded shape that bought it. PR #136
|
||||||
|
# head a17e497: the reconcile succeeded 12:16:17→12:17:06, and the queued
|
||||||
|
# duplicate — evicted by ANOTHER PR's run in the same repo-global group —
|
||||||
|
# attached CANCELLED at 12:16:41, started_at == completed_at, no step ever
|
||||||
|
# ran. Recorded payloads, not live fetches: the heads have moved on.
|
||||||
|
rec_() { jq -n --arg o "$1" --arg s "$2" --arg c "$3" \
|
||||||
|
'{__typename:"CheckRun", workflowName:"labels", name:"reconcile",
|
||||||
|
conclusion:$o, startedAt:$s, completedAt:$c}'; }
|
||||||
|
expect "a queue-cancelled duplicate beside the success that did its work (a17e497)" SUCCESS \
|
||||||
|
"$(rollup "[$(rec_ SUCCESS 2026-07-24T12:16:17Z 2026-07-24T12:17:06Z),\
|
||||||
|
$(rec_ CANCELLED 2026-07-24T12:16:41Z 2026-07-24T12:16:41Z)]" | checks_state)"
|
||||||
|
# ...but the discard needs a surviving verdict. A context that is ONLY
|
||||||
|
# cancelled never reported at all — a killed or timed-out required job — and
|
||||||
|
# certifying that green is the unknown-as-green shape checks_state refuses.
|
||||||
|
expect "a context whose only entry is CANCELLED still blocks" FAILURE \
|
||||||
|
"$(rollup "[$(run_ b CANCELLED)]" | checks_state)"
|
||||||
|
expect "...and so does a context of two cancelled entries" FAILURE \
|
||||||
|
"$(rollup "[$(rec_ CANCELLED 2026-07-24T12:16:17Z 2026-07-24T12:16:20Z),\
|
||||||
|
$(rec_ CANCELLED 2026-07-24T12:16:41Z 2026-07-24T12:16:41Z)]" | checks_state)"
|
||||||
|
# ...and discarding the cancelled entry must never discard a real red: the
|
||||||
|
# survivor rule keeps the FAILURE, it does not resurrect anything green.
|
||||||
|
expect "a cancelled newest over an earlier FAILURE is still that failure" FAILURE \
|
||||||
|
"$(rollup "[$(rec_ FAILURE 2026-07-24T12:16:17Z 2026-07-24T12:17:06Z),\
|
||||||
|
$(rec_ CANCELLED 2026-07-24T12:16:41Z 2026-07-24T12:16:41Z)]" | checks_state)"
|
||||||
|
|
||||||
# -- a run still IN FLIGHT. `run_()` cannot express this: it always carries a
|
# -- a run still IN FLIGHT. `run_()` cannot express this: it always carries a
|
||||||
# real completedAt, which is exactly why the supersede rule shipped dating
|
# real completedAt, which is exactly why the supersede rule shipped dating
|
||||||
# runs by completion and nothing caught it. Both spellings of "no
|
# runs by completion and nothing caught it. Both spellings of "no
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue