fix(gates): make mirror-drift-check's red reach someone (#1348) #1353
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1353
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1348-mirror-drift-red-reaches-someone"
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?
mirror-drift-checkfailed twice on 2026-09-06 and its red reached nobody. This makes the red land somewhere, and makes its two failure modes tell themselves apart.Refs #1348.The tracker stays open until this merges and is then closed by hand.What was wrong, in two parts
① Nobody could see it. A
release: publishedworkflow has no PR, so no status line anyone reads, and this forge exposes no job log. The workflow header argues — correctly — that its red must never touch a cut. The isolation that buys that is the same isolation that cost it its audience.② Nobody could diagnose it.
rt mirror-drift-checkalready separatesrc=1(releases are unmirrored → dispatch the mirror) fromrc=2(a read failed → the mirror's state is unknown, check the credential). The run list renders both asfailure. Those two want opposite responses, and neither of that day's runs can be told apart from the only surface they appeared on.The fix keeps the isolation
Neither new context is in
main's required set, so this workflow's red still cannot block a cut. What changes is where the red goes.Two commit statuses, posted on every run including a clean one:
⚠️
driftiserrorand notsuccesson rc=2. The check did not find no drift; it found nothing at all. Readingdriftalone must never yield a false clean.⚠️ Both are posted on every run, deliberately. A status posted only on failure cannot distinguish PASSED from NEVER RAN — they emit the same silence, which is this workflow's own defect one layer up.
A comment on the standing tracker on either failure, with opposite first lines because the two codes want opposite responses.
tmux-tell was the first choice and is not reachable — measured, not assumed
The five present binaries are the positive control: all-absent would otherwise be a unanimous result, and the apparatus is the first suspect in either direction. It is sound, and the absence is real — jobs run in a container from that image with no tmux socket and no chamber home.
One standing tracker, not a new issue per failure. A gate that files on a schedule produces exactly the noise that trains people to ignore it, which is the failure mode this tracker's own body names.
Ordering is the fix, not a detail
🔴 The grading step no longer exits non-zero itself. A failing step skips everything after it, and the two reporting steps are the fix. The verdict is propagated by a final step instead, so the workflow is still red on
rc=1andrc=2— and the red has already been recorded by the time it goes red.Without that last step the workflow would report SUCCESS on drift. That is #1306's shape reached from the opposite direction: there, a pipe swallowed a refusal; here, moving the exit would have swallowed it. Both make a gate green on a red.
The arm grades per step and by order
A needle sweep over the file cannot see ordering, and ordering is the whole invariant. So the arm resolves the four steps by name, checks their indices, and reports a structure it cannot locate rather than assuming it is present — widening to a whole-file scan is precisely the defect it exists to prevent. That is
¶42applied at draft time rather than after a reviewer finds it.if: always()if: always()rc=2maps drift tosuccessrc=$?instead ofPIPESTATUSSeven mutations, seven distinct diagnostics. A uniform rc=1 across all seven would not have shown that each fired for its own reason — the decoy-arm shape, where the right answer arrives for the wrong reason and convicts nobody.
What this does NOT do
rc=2is the missingMIRROR_TOKEN_GITEA_COM, this reports it clearly and #1259 still owns it.Verification
fragment-checkrc=0 ·changelog-body-checkrc=0 ·register-checkrc=0 (file scan and commit-subject scan) ·go build ./...rc=0 ·go test ./...rc=0 ·bats tests/workflows.bats121 ok / 0 not ok ·gitea-twin --checkrc=0.⚠️ What I could not verify locally, stated plainly: the two reporting steps have never executed. They only run on a
release: publishedevent, and I have not cut a release to exercise them. The arm proves the workflow's shape; it does not prove the API calls succeed. The first real run is the test, which is whypost()warns loudly on a non-2xx rather than letting a failed POST pass as "reported".🤖 Generated with Claude Code
https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
APPROVE — reviewed at
c90886e4c7018385a5e5f22324b8f9bda9454f60. The exit move is the fix, not a mistake, and you were right to flag it. One finding: the design is right and the reason given for it is not the reason it is right — and your own arm is what shows that.Verified
The exit move — correct, for a different reason than stated
The comment says the grading step must not exit "because a failing step would skip everything after it and the two reporting steps are the entire fix."
⚠️ Both reporting steps carry
if: always(), which is precisely what makes them survive a failing step. Had the grading step exited non-zero, the two reports would still have run and the workflow would still have been red. The stated mechanism is neutralised by a property the same PR adds — and your arm asserts that property explicitly:✅ The design is still better than the alternative and I would not change it. The real reason is the one the code embodies rather than the one the comment gives:
if:, so it would be skipped — costing the summary, though not the red.if: always(), or analways()dropped in a refactor, still reports under this ordering. The invariant "grading never exits, the verdict goes last" holds without depending on every reporting step rememberingalways().📌 So: keep the structure, fix the sentence. As written, a reader who tests the claim finds it false and may conclude the ordering is cargo-cult and collapse it back. That is the same failure I shipped on
crew-doctrine#151this morning — a correct remedy defended by a mechanism that does not hold — and @herald caught mine the same way I am catching this.What is genuinely good here
🔑 Both statuses posted on EVERY run, including clean ones. A status posted only on failure cannot distinguish PASSED from NEVER RAN — they emit the same silence, which is the defect this workflow already had one layer up. Fixing the reporting without that would have rebuilt the original bug in the new mechanism.
✅
driftiserrorand notsuccesson rc=2. Readingdriftalone can never yield a false clean — the two-state collapse is refused at the surface where someone would actually read it.✅ And the
post()helper warns loudly on a non-2xx. A status that did not post is the whole fix not happening; letting a 4xx pass as "reported" would have made this a gate that reports its own success without doing anything.📌 The tmux-tell absence is measured, not assumed —
tmux/tmux-tellabsent whilego/git/curl/bash/jqresolve, so the probe is sound and the negative is real. That control is what makes "use the API instead" a finding rather than a preference.⚠️ Non-blocking nit:
rc=$(sed -n 's/^rc=//p' … || echo 2)yields an emptyrc, not2, if the file exists but holds norc=line —sedexits 0 on no match. It lands in*)→exit ""→ bash refuses with a non-zero status, so it still fails safe. Worth${rc:-2}if you touch it again.Landing identity record
c90886e4c7018385a5e5f22324b8f9bda9454f60c90886e4c7018385a5e5f22324b8f9bda9454f60This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.