0079 — A red sweep reverts its culprit and release keeps shipping; a red with no culprit is never reverted

The problem

Since 2026-10-29 the full unit suite and guard pins run as a sweep on main, once per 30-minute merge batch. A red sweep holds every release advance until a new green sweep (release-promote-gate.mjs, condition 3). The revert of the bad merge is a fix, but it too waits: the revert PR goes through the queue like any PR, then push:main CI, then a fresh 18–21 minute sweep. That is the freeze.

The CI plan (explore-test-once) wants the queue to test less, which means more breaks reach main and are caught by the sweep. That is safe only if a red sweep costs a revert, not a freeze, and only if a red with nobody to blame does not turn into reverts of innocent merges.

Why — measured, 2026-10-18 → 2026-11-01

The decision

  1. A culprit is clear when the bisection names it. find-culprit.ts names a merge only after three checks. Each failing test is re-run at the red SHA, and one that passes is a flake. It is run at the last green SHA, and one that fails there is environmental. The rest are bisected over first-parent history to the first merge where they fail. That merge is the culprit, and the sweep also retries failed files once before any of this. propflowai's auto-revert already reverts each culprit with that verdict, not only a one-merge range. This decision records that as the rule. The one-merge range of Amendment 2026-10-07 still decides one thing: whether the infrastructure gates (a cancelled shard, a missing results file) may stand the revert down.
  2. What reverts: the culprit's squash merge, by the heavy lane, on the run that names it, as revert/auto-<pr>, auto-merge armed, culprit PR commented. These rules are unchanged and still refuse a revert: never revert a revert; never revert one already reverted on main; never re-open a revert a human closed; a red made only of bookkeeping guards (a count or ratchet) gets an issue, no revert; at most 5 bot reverts in 24 h.
  3. No merge to blame (6 of 7) is never answered by a revert. Flake, environmental and unresolved verdicts revert nothing, ever. The path is: the sweep's retry-once; the Heavy lane red: no merge to blame issue; and the red-main owner fixes the test or deletes it (Gera's flaky-test rule: a real root-cause fix or a delete, never skip, quarantine or retry-to-green). A date bomb is fixed forward (Amendment 2026-10-07 decision 4). release stays held until a green sweep, as today. The freeze is still correct there: nothing has been fixed.
  4. Revert-not-freeze. A red sweep is ANSWERED once every culprit its own bisection named is reverted on main, and an answered red stops holding release. The gate reads the red sweep's heavy-culprit-report artifact. It needs verdict culprit, no environmental test, and, for every culprit, a commit at or before the tip that reverts it and was not itself reverted. A revert is found by git's This reverts commit <sha> line, or by a Revert "<culprit title>" subject, the two markers alreadyRevertedOnMain reads. The tip must still have a green merge-queue train and green push CI, so nothing else about the gate changes. Anything missing or unreadable holds. The dispatched re-test still runs, and if that sweep is red, the new red holds on its own.
  5. A revert commit is not held by the main-red deploy hold. The auto-revert's PR title starts [urgent], but the squash subject is Revert "…" (#22529 landed without [urgent]). So the hold in main-red-hold.sh cancelled the revert's own push CI, and decision 4 needs that run green. A Revert " subject now counts as urgent there, the same way deploy-web's urgent path already treats it. This narrows ADR-0045 decision 6 for the normal release path only. Its urgent-check on the hotfix path is unchanged.
  6. The claim. The owner is still one owner, recorded as ADR-0015 and ADR-0074 say: a main-status declaration, and the heavy lane's BRIDGE CLAIM v1 with driver: smith. GitHub Actions can read neither. Inside GitHub, the open heavy-lane-red issue whose title names the culprit is the readable form of "this red is claimed". That is the same move Amendment 2026-10-01 made for the fix-forward label. The heavy lane acts for the owner on one thing only: the revert of a culprit it named.
  7. The bot's revert jumps the merge queue while that culprit's issue is open. The revert PR gets the auto-revert label when it opens. Once it can be queued (review verdict and PR checks green), it is enqueued with jump: true. If it is already in the queue, it is dequeued and re-enqueued with the jump. Four things refuse the jump, and the PR then waits its turn like any other: the culprit's heavy-lane-red issue is closed; the head branch is not revert/auto-<pr>; the PR's commit is already on main (re-queueing would merge an empty second commit); or the PR is not open. The jump cannot happen when the revert opens, because enqueuePullRequest refuses a PR that is not mergeable yet.
  8. The standing rule is unchanged for everyone else: no jump: true without holding the red-main claim. Decision 7 is the claim holder's own action, taken by the lane that holds the GitHub-side form of it. It is not a door for sessions or humans. A session that wants a jump still declares the red first.

Why a jump is worth its cost

A jump restarts every train already in line. On 2026-10-06, #17178's jump rebuilt 8 trains. While main is red, every one of those trains carries the culprit, so a train that merges first does not change main's state, and the revert has to land before anything is fixed. Restarting trains behind a red main costs runner minutes. Leaving the revert at the back of a 20-PR queue costs release the whole queue. The jump is used once per culprit, only while the culprit's issue is open, and only for the bot's own revert.

On #22502 the jump would have saved little: the queue was quiet, and the revert's train ran in 3 minutes (18:58 → 19:01). Its 12-minute wait before that was the review and PR checks, which the jump does not touch. The jump matters on a busy afternoon, which this incident was not.

Rejected

Stated gaps

Build

Part Repo What State
ADR agentflow this file; register update for the 0079 citation that was propflowai's this PR
1 propflowai release-promote-gate.mjs redSweepAnswered + gather reads the red sweep's report; main-red-hold.sh lets a revert's push CI run; tests incl. the #22502 replay and its controls PR opened with this ADR (see the PR list on ci/release-answered-red)
2 propflowai auto-revert label + jump with the claim held (decision 7) PR opened with this ADR (branch ci/auto-revert-jump)

Relation to other ADRs