# MCP maintenance-drain mode (#659) Graceful **maintenance drain** stops new work assignment and defers non-allowlisted mutations so sessions can finish critical handoffs and checkpoint before a restart. It is **not** a restart authorization: the drain *proof* and apply gate remain #661. ## State Per repository scope (`remote`/`org`/`repo`) in the control-plane DB table `maintenance_drain` (schema v6): | State | Meaning | |-------|---------| | `inactive` | Normal operation (also: no row) | | `draining` | Assignment stopped; non-allowlisted mutations deferred | Enter/exit transitions are audited as `maintenance_drain_enter` / `maintenance_drain_exit` events. ## Tools | Tool | Permission | Effect | |------|------------|--------| | `gitea_maintenance_drain_status` | `gitea.read` | Observe drain (every session) | | `gitea_enter_maintenance_drain` | `runtime.maintenance_drain` | Enter drain (capability-gated) | | `gitea_exit_maintenance_drain` | `runtime.maintenance_drain` | Exit drain | `runtime.maintenance_drain` is intentionally **not** a `gitea.*` op, so ordinary author profiles cannot enter drain by accident. ## Enforcement 1. **Allocator** (`allocate_next_work`): while draining, returns `outcome=wait` with `reason_code=maintenance_drain_assignment_stopped` for dry-run and apply. 2. **Mutation preflight** (`verify_preflight_purity`): non-allowlisted mutation tasks raise `MaintenanceDrainError` with a typed next action. 3. **Allowlist** (safety only): heartbeats, lease release/abandon, session checkpoints, enter/exit drain. Reads always work. ## Restart relationship Drain mode prepares the blast radius. Restart apply still requires a clean `DrainProof` (#661) or authorized break-glass. Status payloads never claim restart permission.