| Field | Value |
|---|---|
| Decision ID | ADR-105 |
| Initiative | throughput / finalization resilience |
| Proposed By | unsorry maintainers |
| Date | 2026-06-26 |
| Status | Proposed |
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:
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.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.
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):
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).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).
fork_automerge to same-repo. Rejected to keep ADR-068’s fork path isolated and
avoid a double-arm race; the new janitor explicitly excludes cross-repo PRs.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.