ADR-105: Recover stranded mergeable prove PRs (auto-merge enrolment backstop + cancellation-failed gate recovery)

Field Value
Decision ID ADR-105
Initiative throughput / finalization resilience
Proposed By unsorry maintainers
Date 2026-06-26
Status Proposed

Context

A live audit (2026-06-26) found the swarm’s merge ceiling is not Gate A/B compute — the gates run with slack (≈8 in flight vs a ≈20 cap). The bottleneck is finalization: completed, mergeable work strands after the checks. Of 38 open PRs, ~24 were stuck 12–113 h in two recurring patterns:

  1. Un-enrolled auto-merge. ~12 PRs were CLEAN + mergeable + gates green but had auto-merge OFF — the swarm’s enrol-at-creation step had failed (a known REFRESH_TOKEN PR-write gap), so the PR sat unmerged forever. fork_automerge.py / fork-automerge-enabler arm cross-repo PRs only (ADR-068); same-repo swarm PRs that miss enrolment have no backstop.
  2. Cancellation-failed gate. ~6 PRs were BLOCKED with gate-a = FAILURE, but the failure was a cascade of a CANCELLED sub-job — the concurrency cancel-in-progress killed an audit/replay leg, and the cover job reported failure. The proof is fine (mergeable), but dropped_gate_prs.py correctly treats any failed/cancelled gate as a real block and leaves it, and nothing else re-runs it. With no new commit, it never recovers.

These are recoverable mergeable PRs — the work is done; only the plumbing stalled. The existing janitor suite (dropped-gate / stale-failed / superseded / stale-branch) doesn’t cover either case.

Decision

Add a finalization-recovery-janitor that recovers stranded mergeable prove PRs in two conservative, bounded passes, driven by tools.repo.finalization_recovery (pure, unit-tested):

  1. ARM. For each open, non-draft, non-cross-repo prove PR (ADR-026 title) whose diff touches only the proof allow-paths (library/, goals/, proof-runs/), is not already armed, and is not DIRTY: arm auto-merge (or, if already CLEAN, squash-merge directly). GitHub still blocks the merge until Gate A/B are green — this only arms it (ADR-005).
  2. RERUN. For each open, non-draft, mergeable PR whose latest gate-a run is failure: re-run that run iff the failure is purely a cancellation cascade — gate_failure_is_ cancellation(jobs) = (≥1 job cancelled) AND (no NON-cascade job failure). A genuine leaf failure (a bad proof) or an admission policy failure (the per-author cap) is left alone.

Posture mirrors dropped-gate-janitor: workflow_run on gate-a is the primary trigger (every completion rescans all open PRs), cancel-in-progress: false so the firehose coalesces instead of cancelling the sweep, cron is a backstop, and it runs as REFRESH_TOKEN so the merge/rerun attribute to a real actor (degrades to report-only without it).

Consequences

Alternatives considered

References

ADR-068/SPEC-068-A (fork auto-merge — cross-repo only, the gap), ADR-005 (autonomous merge on green), ADR-026 (prove PR titles), ADR-019 (CODEOWNERS trust surface), ADR-058 (gate-a required context). Sibling janitors: dropped-gate / stale-failed-pr / superseded-pr / stale-branch. Audit + remediation tracked on #5678.