# ADR: Stable control runtime vs dev runtime (Gitea MCP) - **Status:** Accepted (policy effective immediately for LLM sessions; tooling may lag) - **Date:** 2026-07-09 - **Tracking issue:** [#615](https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/615) - **Related:** - [#543](https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/543) / `docs/mcp-namespace-health.md` — client-namespace health - `docs/mcp-namespace-eof-recovery.md` — reconnect-only EOF recovery (no PID kill) - [#558](https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/558) / `docs/mcp-daemon-import-guard.md` — sanctioned daemon - [#557](https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/557) / `docs/bootstrap-review-path.md` — controller bootstrap for self-hosted fixes - Allocator / control-plane ADR: `docs/architecture/mcp-allocator-control-plane-observability-adr.md` (#613 / PR #614) ## 1. Context The Gitea MCP server is the **control plane** for real issue/PR mutations (create, comment, lock, review, merge, etc.). When author/reviewer/merger/reconciler sessions kill or restart that process, relaunch it from a feature worktree, or edit the checkout that process loads, operators observe: - Mid-session identity/preflight resets - Stale-runtime vs master parity failures - IDE transport EOF / “tool not found” while code on disk has changed - Accidental production mutations from experimental code This ADR separates **stable control runtime** from **dev/test runtime** and defines promotion proof. ## 2. Decision ### 2.1 Stable control runtime The Gitea MCP server used for **real workflow mutations** is the **stable control runtime**. Characteristics: - Loads a known, promoted revision of Gitea-Tools (or the packaged release layout operators designate) - Registered in the IDE/client as the production namespaces (`gitea-tools`, `gitea-reviewer`, `gitea-merger`, `gitea-reconciler`, etc.) - Holds production profile credentials via sanctioned keychain/env paths only ### 2.2 Dev / test runtime MCP **server code** development and testing: - Happens in isolated **`branches/`** worktrees (or other non-stable checkouts) - May use a **separate** dev/test MCP runtime/process when process-level testing is required - **Must not** be used for real Gitea mutations on production issues/PRs ### 2.3 Forbidden actions (normal sessions) Normal **author, reviewer, merger, and reconciler** LLM sessions **must not**: | Forbidden | Why | |-----------|-----| | Kill the running MCP server process | Drops all concurrent sessions; loses preflight state | | Restart / relaunch the MCP server process | Same as kill; causes stale/identity churn mid-workflow | | Relaunch MCP from a development worktree | Runs unpromoted code against production mutations | | Edit files in the stable runtime checkout | Hot-mutates control plane under concurrent users | | Use experimental/dev MCP for real Gitea mutations | Bypasses promotion proof and audit expectations | | Bypass or self-reset a stale master-parity gate | The gate is fail-closed; only an operator reload restores parity | **LLM-allowed vs operator-owned (authoritative split):** | Actor | May do | Must not do | |-------|--------|-------------| | **LLM session** | Call tools on the already-running stable namespaces; **client reconnect** after transport EOF (no process kill); pass `worktree_path` / role worktree args; report blockers and stop mutations when unhealthy/stale | Kill, restart, or relaunch any MCP process; bump config mtimes to force reload; edit the stable checkout; switch to a dev MCP for production mutations | | **Operator / release-manager** | Supervised restart/reload of the **stable** control runtime; dual-namespace client configuration; §2.4 promotions; incident recovery | — | EOF / transport recovery for LLM sessions: **client reconnect only** (see `docs/mcp-namespace-eof-recovery.md`). Do not “fix” health by killing PIDs or bumping MCP config mtimes as a normal session procedure. This ADR **supersedes** any older runbook wording that told the LLM to relaunch or restart the client/MCP as a self-service step. Where `docs/llm-workflow-runbooks.md` (or wiki runbooks) discuss dual-namespace setup or workspace rebind, **process restart/relaunch is operator-owned**; the LLM stops, reports, and waits. ### 2.4 Promotion (operator / release-manager only) Promotion of a new revision into the stable control runtime is an **explicit operator/release-manager action**, not an LLM self-service step. A promotion **must record** (issue comment, release note, or promotion ledger): | Field | Description | |-------|-------------| | **previous runtime SHA** | Commit previously loaded by stable runtime | | **promoted runtime SHA** | Commit after promotion | | **source branch/PR** | Where the change was reviewed | | **restart/reload method** | How the process was cycled (e.g. supervised restart, client reload) | | **health check proof** | Client-namespace probe success (`gitea_whoami` / namespace health) | | **identity/profile proof** | Expected profile(s) and username(s) after reload | | **workspace/root proof** | Stable checkout path / root matches intended layout | | **mutation capability proof** | Required permissions for the target role present; forbidden ops still forbidden | | **rollback instructions** | How to restore previous SHA and re-verify health | Suggested durable marker: ```text ## MCP STABLE RUNTIME PROMOTION (#615) Status: COMPLETED | ROLLED_BACK | ABORTED Previous-SHA: Promoted-SHA: Source-PR: Source-Branch: Reload-Method: Health-Proof: client_namespace whoami OK / assess_mcp_namespace_health OK Identity-Proof: profile= user= Workspace-Proof: root= Mutation-Proof: allowed_ops include <…>; forbidden include <…> Rollback: checkout ; reload method <…>; re-run health/identity proofs Operator: Timestamp: ``` ### 2.5 Unhealthy stable runtime → stop work If the stable MCP runtime is **unhealthy**, including any of: - client-namespace probes fail - wrong identity / wrong profile - wrong workspace root - missing mutation capability for the intended role - persistent EOF after **client reconnect** - **master parity is stale** (`startup_head` behind on-disk `master` / `restart_required` from the parity gate) then: 1. **Normal PR / review / merge / issue-mutation work must stop immediately.** 2. The session **reports** the unhealthy/stale state (tool error, CTH, or operator handoff) with startup vs current head when known. 3. Do **not** improvise: no LLM process kill/restart, no dev-worktree MCP for production mutations, no env escape hatches, no manual gate bypass. 4. Resume only after: - an **operator** restores the runtime (see §2.6 for routine post-merge parity reload), **or** - a **controlled** promotion/rollback completes with the §2.4 promotion record, **or** - a controller invokes the narrow **bootstrap review path** (#557) when the defect is self-hosted and documented, - **and** the session re-verifies health (and master parity when applicable) before the next mutation. ### 2.6 Routine post-merge master-parity staleness (operator reload, not promotion) **Symptom:** After merges land on `master`, a long-lived stable MCP process still runs the pre-merge `startup_head`. The master-parity gate marks the server **stale** / `restart_required` and **blocks mutations**. **Sanctioned response (authoritative):** | Step | Actor | Action | |------|-------|--------| | 1 | LLM session | Mutations stop immediately when the gate reports stale. | | 2 | LLM session | Report the stale state (startup head, current master head, that operator reload is required). Do not retry mutations. | | 3 | **Operator** | Reload/restart the **stable** control MCP so it loads current `master` (supervised client/daemon reload). | | 4 | LLM session | Resume only after startup/current-head parity is verified (e.g. `gitea_get_runtime_context` / parity assessment shows in parity). | **Not a §2.4 promotion:** Catching the already-designated stable control checkout up to a newly advanced `master` is a **routine operator reload** of the stable runtime. It does **not** require the nine-field promotion ledger. Use §2.4 only when **changing which unpromoted/dev revision** becomes the stable control runtime (new source branch/PR into the stable designation). **Code note:** `master_parity_gate.py` may still say “restart the server” in machine-facing reason strings. That string names the **operator recovery action**, not an LLM self-service instruction. This ADR and the runbooks define the actor split. ## 3. Relationship to other controls | Doc / mechanism | Interaction | |-----------------|-------------| | Namespace health (#543) | Proves IDE client can call tools; does not authorize restart | | EOF recovery | Reconnect only; no process kill | | Daemon import guard (#558) | Mutations require sanctioned daemon; not a bare shell import | | Bootstrap path (#557) | Only controller-authorized exception when live runtime cannot review its own fix | | Allocator / control-plane ADR | Coordination DB is separate; still depends on a healthy MCP surface for Gitea writes | ## 4. Consequences ### Positive - Predictable control plane for concurrent LLMs - Clear operator-only promotion gate with rollback - Aligns session behavior with health/EOF docs already landed ### Costs - LLM sessions must wait when runtime is sick (no DIY restart) - Operators must maintain promotion discipline and dual-runtime config if they use a dev MCP ### Non-goals - Does not ban operator-supervised restarts during incidents - Does not replace CI or code review for MCP changes - Does not authorize editing stable checkout “because tests need a quick fix” ## 5. Implementation follow-ups (optional tooling) These may land in later issues; the **policy binds sessions now**: 1. Session preflight that refuses mutations if workspace root equals a `branches/` feature worktree configured as “dev only.” 2. Explicit `runtime_kind=stable|dev` in MCP config and `gitea_whoami` profile metadata. 3. Promotion checklist script that emits the durable promotion marker fields. **Not optional (issue #615 acceptance criterion 2):** operator guide and runbooks **must** cross-link this ADR (see §6). Cross-links are documentation acceptance, not deferred tooling. ## 6. Acceptance for this ADR 1. Document merged under `docs/architecture/mcp-stable-control-runtime-policy-adr.md`. 2. **Operator guide / runbooks cross-link this ADR** (`docs/wiki/Operator-Guide.md`, `docs/wiki/Runbooks.md`, `docs/llm-workflow-runbooks.md`). 3. Issue #615 references this path. 4. LLM/operator runbooks treat kill/restart/relaunch-from-worktree as **LLM violations**; process restart is **operator-owned**. 5. Unhealthy runtime (including **stale master parity**) stops normal mutation work until operator restore/reload, promotion/rollback, or #557 bootstrap — then re-verify parity before mutating. 6. Routine post-merge parity reload is documented as operator reload (§2.6), not an LLM self-restart and not a full §2.4 promotion. ## 7. Document history | Date | Change | |------|--------| | 2026-07-09 | Initial ADR: stable vs dev runtime, forbidden session actions, promotion proof fields, stop-work rule | | 2026-07-16 | Review 443 remediation (#615 / PR #616): mandatory cross-links; LLM vs operator restart split; routine post-merge parity staleness (§2.6); stale parity in §2.5 unhealthy triggers |