Files
Gitea-Tools/docs/mcp-restart-coordinator.md
T
sysadminandClaude Opus 4.8 2d0d8a682b feat(mcp-health): add MCP restart coordinator and impact analysis (Closes #658)
Child of umbrella #655 (governed MCP restart coordination); builds on the
#657 restart-path inventory. Adds a central coordinator that evaluates live
control-plane state before a restart and returns a blast-radius impact
preview, so operators and the web console (#642/#652) can see what a restart
would disrupt before concurrent LLM work is destroyed.

Changes
- restart_coordinator.py (new) — pure classification: inventory -> impact
  report DTO (RestartImpactReport/SessionImpact/LeaseImpact). Verdicts:
  safe / unsafe / override. Never restarts anything; fails closed on an
  incomplete inventory.
- control_plane_db.py — additive ControlPlaneDB.list_sessions() read-only
  session inventory (the process-level unit a restart kills).
- gitea_mcp_server.py — new dry-run MCP tool gitea_request_mcp_restart:
  gathers sessions/leases/terminal-lock from the #613 DB, calls the
  coordinator, returns the report. Override authority is read from the
  environment, never self-asserted (#630/#710 F1 pattern). Apply is gated
  by a later drain proof (non-goal here).
- docs/mcp-restart-coordinator.md + docs/mcp-restart-impact-sample.json — doc
  and a real dry-run sample report.
- docs/mcp-tool-inventory.md — register the new tool (inventory sync).
- tests/test_restart_coordinator.py (new) — 15 tests: multi-session fixtures,
  deny-when-critical-section-open, fail-closed deny, override, terminal lock,
  stale heartbeat, JSON-serializable DTO, list_sessions.

Tests: pytest tests/test_restart_coordinator.py -> 15 passed. Full suite:
13 failed / 4753 passed; all 13 reproduce identically on clean master
@ef14622 (0 regressions). The residual test_issue_781 doc-registry failure
is a pre-existing baseline gap for gitea_rebind_dirty_same_claimant_author_session
(merged #864, undocumented on master) — out of scope for #658.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-24 02:14:27 -04:00

4.7 KiB

MCP restart coordinator and impact analysis (#658)

Before any sanctioned MCP restart, a central coordinator evaluates the live control-plane state and produces an impact preview so operators and the web console (#642 / #652) can see the blast radius before concurrent LLM work is disrupted. Uncoordinated restarts destroy in-flight author/reviewer/merger work and give operators no way to see what they are about to break.

This lands the coordinator + impact DTO + a dry-run MCP tool. It is the single sanctioned entry point for restart evaluation post-#657 (which inventoried the restart/reload/kill paths). The mutative apply path — actually performing a restart — is a later child gated by a drain proof and is explicitly out of scope here.

Components

Piece Where Responsibility
restart_coordinator.evaluate_restart_impact restart_coordinator.py Pure classification: inventory → impact report DTO. No I/O, no restart.
RestartImpactReport / SessionImpact / LeaseImpact restart_coordinator.py Console-facing DTO (.as_dict() is JSON-serializable).
ControlPlaneDB.list_sessions control_plane_db.py Read-only session inventory (the process-level unit a restart kills).
gitea_request_mcp_restart gitea_mcp_server.py MCP tool: gathers inventory from the #613 DB, calls the coordinator, returns the report. Dry-run only.

Dimensions evaluated

The coordinator classifies the inventory across the dimensions #658 requires:

  • Sessions — every active MCP session; a restart terminates all of them. Liveness = status == active and the owner pid is alive and the heartbeat is fresh (default window 15 min). Dead/stale sessions do not count toward blast radius.
  • Leases / locks — control-plane leases joined with work items and their freshness (lease_lifecycle.classify_lease_freshness). Only active (live owner) leases are disruptive; expired / released / dead-process leases never withhold a restart.
  • Issue / PR work — the issues and PRs behind disruptive leases.
  • Mutations / critical sections — a live lease carrying an author worktree or a mutating phase (implementing, publishing, merging, …) is a critical section a restart must not sever.
  • Terminal (merge) lock — an active terminal lock always makes a restart unsafe.
  • Prior recovery attempts — narrower recovery already tried (e.g. sanctioned client reconnects) is echoed so the operator sees the escalation history.

Verdict

Exactly three verdicts, matching the acceptance criteria:

Verdict allow_restart Meaning
safe true No other live sessions, no live leases, no terminal lock.
unsafe false Live work would be disrupted and no operator override is present — or the inventory could not be completed (fail closed).
override true Live work present, but an operator override accepts the blast radius.

override_would_allow tells the console whether an override path exists for the current state. blast_radius is a none / low / medium / high severity band derived from the affected session and work counts.

Fail closed

If the control-plane inventory cannot be completed (DB unavailable, a listing failed), inventory_complete is false and the verdict is unsafe / deny. An incomplete evaluation must never green-light a restart.

Operator override authority

Override authority is read from the environment variable GITEA_OPERATOR_RESTART_OVERRIDE_AUTHORIZATION and never from a tool argument. A worker session cannot set an environment variable on an already-running daemon, so override cannot be self-asserted (same pattern as the #630 daemon-maintenance authorization). The request_override tool argument only expresses caller intent; it takes effect solely when the environment authorization is present.

The tool

gitea_request_mcp_restart(remote, host, org, repo,
                          dry_run=True, request_override=False,
                          session_id=None, limit=200)

Read-only, dry-run, and it never restarts anything. apply_supported is always false; passing dry_run=False performs no restart and reports that apply is gated by a drain proof (a separate child).

Audit

Every evaluation carries an audit_record (event, coordinator version, verdict, allow decision, blast radius, counts, timestamp) so restart decisions are auditable. No secrets flow through the coordinator — session ids, pids, and profiles are operational metadata only.

A representative dry-run report is in mcp-restart-impact-sample.json.