# ServerFS MCP v0.22.1 Maintenance Release Plan Status: **released as stable v0.22.1**; see the [release checklist](docs/v0.22.1-release-checklist-2026-10-23.md) for tag, GitHub Release, Windows wheel workflow, and asset digest evidence. Baseline: current clean `main` after v0.22.0 Scope: Linux Agent Bridge installer compatibility, macOS native restart reliability, and maintainer safety guidance ## 1. Objective v0.22.1 is a narrow maintenance release for two defects observed during real v0.22.0 upgrades and one operational safety gap exposed by the macOS recovery procedure. Success means: 1. Linux Agent Bridge installation no longer depends on the host's ambient `python3` version after the release virtual environment has been created; helper execution uses the staged Bridge environment that already satisfies `agent_bridge/pyproject.toml` (`Python >=3.12`). 2. macOS `serverfs service restart` does not report success until the requested managed services have completed the stop/re-register/readiness closure; an enabled Agent Bridge must additionally pass its existing RPC health probe. 3. Restart failures fail closed instead of being converted into a superficially successful lifecycle result. 4. Maintainer guidance explicitly forbids using persistent/keepalive `launchctl submit` jobs as one-shot delayed maintenance handoffs and requires bounded one-shot mechanisms plus post-operation cleanup verification. 5. Linux regression tests and source-level macOS lifecycle tests pass on the VPS; real `SMAppService`/launchd acceptance is performed separately on the supported Mac before release. 6. When Tunnel+Bridge restart is requested, a launchd-owned one-shot child completes the strict restart and health closure independently of the Bridge-owned caller; final evidence is written only after verified cleanup. ## 2. Non-goals - No MCP tool/schema changes. - No Agent Bridge RPC/provider/runtime behavior changes. - No system Python replacement, PATH persistence, root operation, or host-wide Python configuration change. - No redesign of the macOS `SMAppService`/LaunchAgent architecture. - No new generic launchd helper framework. - No Windows lifecycle changes. - No website/product-copy expansion unless implementation changes create a release-facing behavior statement that must be updated. - No commit, push, tag, GitHub Release, or deployment as part of implementation unless separately requested. ## 3. Fix A — Linux Agent Bridge installer Python selection ### Problem `agent_bridge/pyproject.toml` requires Python `>=3.12`, and `uv sync --frozen` creates the staged release virtual environment accordingly. The installer then invokes `deployment/agent-bridge/render_config.py` with ambient `python3`, which can still be Python 3.11 and fail before activation. This was observed on the VPS where system `python3` was 3.11.15 while Python 3.12.13 was already available. ### Implementation - After the release environment is created, execute Python deployment helpers that require the Bridge code/runtime contract with `"$RELEASE_DIR/.venv/bin/python"`, not ambient `python3`. - Keep the installer entirely current-user scoped and preserve existing transactional rollback behavior. - Avoid adding a separate `python3.12` lookup or permanently modifying PATH; the release venv is the canonical interpreter after `uv sync` succeeds. - Make the installer's post-install validation guidance use the compatible interpreter path where appropriate so the documented next step does not reintroduce the same ambient-Python hazard. ### Regression tests - Lock the installer contract so config rendering uses the staged release interpreter. - Add a subprocess regression proving installation can proceed when ambient `python3` is absent/incompatible while the staged venv interpreter is usable. - Preserve existing non-root, current-user, transactional and `--no-start` tests. Acceptance: - `bash -n deployment/agent-bridge/*.sh` passes. - `uv run pytest tests/test_agent_deployment.py` passes. ## 4. Fix B — macOS `service restart` stop/readiness/health closure ### Problem Current macOS `restart()` is effectively `stop(); start()`. `stop()` waits for unregister completion and known managed-process exit, but `start()` registers services and returns immediately. Therefore restart can report success before replacement processes are active, and before an enabled Agent Bridge has passed the health probe already used during installation. ### Implementation - Keep normal lifecycle on `SMAppService` + app-associated per-user LaunchAgents. - Refactor only as much as necessary to share a narrow "register and verify" lifecycle primitive between ordinary start/restart and existing install activation where semantics match. - For every service represented in the installed manifest: - registration must return `enabled`; `requires-approval` and unexpected states fail closed with an operator-facing error; - wait for the managed process to become active with the existing bounded `_wait_managed_job()` mechanism; - when the service is Agent Bridge, run existing bounded `_wait_bridge_health()` after the process is active. - `restart()` must finish the full stop phase before registration begins, preserving the existing `_unregister_and_wait()` process-exit contract. - Do not treat an Agent Bridge absent from the manifest as a restart failure. - Do not copy install-only legacy migration or rollback semantics into ordinary restart. - `restart_service(service)` should provide the same readiness/health guarantees for the selected service. ### Regression tests Add source-level Darwin lifecycle tests covering: - restart ordering: unregister/wait/old PID exit precedes re-register/readiness; - Tunnel waits for managed-process readiness; - enabled Agent Bridge waits for managed process and then RPC health; - Bridge-absent manifest does not create a false failure; - `requires-approval`, missing process/readiness timeout, or Bridge health failure propagates as failure rather than success; - selected-service restart follows the same readiness/health contract. Acceptance on VPS: - focused Darwin lifecycle tests pass where platform-independent mocked contracts are collectible; - root Ruff/format checks relevant to changed Python source pass. Required final macOS acceptance before release: - build/validate the ServiceManagement companion; - run the supported-Mac positive-path lifecycle acceptance for the changed self-restart boundaries: Bridge-only hand-over and Tunnel+Agent-Bridge hand-over; - verify old managed PIDs are replaced where applicable, Bridge health is usable after reconnect, no duplicate managed/unmanaged Tunnel tree remains, and final hand-over success is published only after temporary job/plist cleanup; - keep the unchanged Tunnel-only synchronous branch under deterministic regression coverage rather than modifying an active Bridge-enabled installation solely to manufacture a Tunnel-only manifest; - cover readiness/health failure propagation deterministically; do not intentionally destabilize the active management path solely for live fault injection when the same fail-closed contract is already exercised by source-level tests. Linux/VPS evidence must not be presented as real macOS `SMAppService` acceptance. ## 5. Fix C — macOS temporary launchd maintenance safety rule ### Problem A one-shot delayed maintenance handoff was mistakenly implemented with a `launchctl submit` job that had keepalive behavior and repeatedly executed `serverfs service restart`. The product's normal lifecycle code does not use this mechanism; this is a maintainer-operation safety gap. ### Documentation changes - Add a durable macOS invariant: normal maintenance must not use persistent/keepalive `launchctl submit` jobs as one-shot delayed actions. - In maintainer workflow guidance, require bounded one-shot mechanisms for temporary handoff work and explicit verification that any temporary launchd job/artifact is gone after completion. - Preserve the rule that direct operator/maintainer `launchctl` mutation is not an ordinary service-ownership path; project-owned launchd mutation is limited to the bounded one-shot migration/self-restart hand-over implemented by ServerFS. - Keep `AGENTS.md` concise; add only a short repository-wide pointer/rule if needed so delegated maintainers see the hazard without duplicating the detailed procedure. Acceptance: - Maintainer docs are internally consistent with the existing macOS platform invariant. - No product code is added solely to support temporary `launchctl submit` jobs. ## 6. Fix D — Tunnel+Bridge restart handover ### Live acceptance evidence During supported-Mac v0.22.1 live acceptance, `serverfs service restart` with the managed Agent Bridge registered interrupted its Bridge-owned caller while unregistering the Bridge. The CLI never completed its synchronous restart/readiness/Bridge-health closure. Tunnel-only restart remains synchronous and is not implicated. ### Implementation contract - Reuse the existing `KeepAlive=false`, `RunAtLoad=false` launchd handover job for Tunnel+Bridge restart. Its arguments are only `serverfs service restart --_handover-child`; it reads the installed private manifest and never guesses config or env paths. - Apply the same handover to `serverfs agent-bridge restart` after any requested policy/config synchronization has finished. That child restarts only the Agent Bridge; it does not restart the Tunnel. Its working directory is the product-owned service-manager directory. - The child skips handover scheduling, performs the existing strict unregister/register/readiness/Bridge-health closure, and records a private pending result (`complete=false`). A failed operation also records its failure before cleanup. - An independent detached cleanup process waits for the operation child, boots out the temporary job, verifies `launchctl print` fails and removes/verifies the temporary plist. Only then may it publish the final result (`complete=true`). Cleanup failure produces final failure evidence while preserving the operation failure. Failure to start cleanup must never produce success. - Tunnel-only restart remains direct and synchronous. The temporary job remains one-shot; no `launchctl submit`, `nohup`, root operation, repeating job or recursive handover is introduced. - Central child finalization schedules cleanup on every normal child exit, including validation/configuration early returns. - Native `serverfs service status` exposes the bounded final handover evidence and whether the temporary job/plist remain; common status projection stays unchanged. ### Regression tests Cover Bridge restart scheduling without config/env argv, one-shot plist flags, child closure and pending evidence, Tunnel-only direct restart, early-failure cleanup scheduling, operation-failure preservation, final-evidence ordering, and launchd/plist cleanup verification. ### Acceptance boundary VPS mocked/source-level tests prove orchestration contracts only. Repeat live acceptance on the supported Mac for the changed Bridge-only and Tunnel+Bridge hand-over paths; verify final result is complete only after the temporary launchd job and plist are absent. Keep the unchanged Tunnel-only synchronous branch under deterministic regression coverage unless a Tunnel-only installation is naturally available. Do not claim the interrupted acceptance as a pass. ## 7. Implementation order 1. Freeze this plan and confirm clean baseline. 2. Implement Fix A with focused regression tests; run the Linux installer gates. 3. Implement Fix B with mocked/source-level lifecycle regression tests; run focused Python gates on the VPS. 4. Implement Fix C in maintainer docs/AGENTS with the smallest wording necessary. 5. Run the combined affected validation set and `git diff --check`. 6. Review final diff for scope creep and unresolved dirty state. 7. Hand off only the real native acceptance portion to the Mac environment. 8. Implement Fix D after the newly observed live-acceptance interruption; rerun focused source-level checks, then repeat the Tunnel+Bridge native acceptance before release. Each phase stops on a failing gate. Diagnose the demonstrated failure before proceeding; do not weaken tests or invariants to obtain green status. ## 8. Validation gates During implementation use focused tests first. Before declaring the VPS portion complete, run the smallest complete affected set: ```bash bash -n deployment/agent-bridge/*.sh uv run pytest tests/test_agent_deployment.py uv run pytest tests/test_darwin_service_lifecycle.py tests/test_darwin_service.py uv run ruff check deployment/agent-bridge tests/test_agent_deployment.py src/serverfs_mcp/darwin_service.py tests/test_darwin_service_lifecycle.py tests/test_darwin_service.py uv run ruff format --check deployment/agent-bridge tests/test_agent_deployment.py src/serverfs_mcp/darwin_service.py tests/test_darwin_service_lifecycle.py tests/test_darwin_service.py git diff --check ``` Because the change crosses base deployment/native lifecycle boundaries, run the broader root gate if focused checks are green and practical in the current VPS environment: ```bash uv sync --frozen uv run ruff check . uv run ruff format --check . uv run pytest ``` For the Agent Bridge package itself, run its package-local gate only if files under `agent_bridge/` are changed; this plan does not currently require such a change. For the final supported-Mac gate, follow `docs/maintainers/workflow.md`, including: ```bash bash native/macos/service_manager/build.sh /tmp/serverfs-service-manager-check uv build --wheel --out-dir /tmp/serverfs-wheel-check ``` and live current-user `SMAppService` lifecycle acceptance. ## 9. VPS implementation evidence Implementation and Linux/VPS validation completed on 2026-10-23 with the following measured evidence: - Fix A: `bash -n deployment/agent-bridge/*.sh` — PASS. - Fix A: `uv run pytest tests/test_agent_deployment.py` — **79 passed**. - Fix B source-level lifecycle tests: `uv run pytest tests/test_darwin_service_lifecycle.py tests/test_darwin_service.py` — **11 passed, 21 skipped**; the 21 existing Darwin-only tests are intentionally skipped on Linux. - Root dependency sync: `uv sync --frozen` — PASS; 37 packages checked and no new repository changes introduced. - Root lint: `uv run ruff check .` — PASS. - Root format check: `uv run ruff format --check .` — PASS; **452 files already formatted**. - Root test suite: `uv run pytest` — **1747 passed, 317 skipped, 1 warning** in 65.54s. The warning is an existing Starlette test-client deprecation warning involving `anyio.abc.BlockingPortal`, not a v0.22.1 failure. - `git diff --check` — PASS. - Final working-tree review found exactly seven v0.22.1 files and no staged or unrelated dirty state: `dev_plan_v0.22.1.md`, `deployment/agent-bridge/install.sh`, `tests/test_agent_deployment.py`, `src/serverfs_mcp/darwin_service.py`, `tests/test_darwin_service_lifecycle.py`, `docs/maintainers/invariants.md`, and `docs/maintainers/workflow.md`. These results were the pre-Fix-D Linux/VPS evidence. The supported-Mac implementation and acceptance evidence is recorded below. ## 10. Supported-Mac implementation and acceptance evidence Fix D was implemented and validated on the supported Apple Silicon/macOS 27 environment on 2026-10-23. Source/build gates: - macOS ServiceManagement companion build and signature validation — PASS. - `uv build --wheel --out-dir /tmp/serverfs-wheel-check` — PASS; produced `serverfs_mcp-0.22.0-py3-none-any.whl` for source validation without changing release metadata. - Final Darwin-focused suite (`test_darwin_lifecycle.py`, `test_darwin_service.py`, `test_darwin_service_lifecycle.py`, `test_darwin_cli.py`) — **65 passed**. - Root `uv run ruff check .` — PASS. - Root `uv run ruff format --check .` — PASS; **452 files already formatted**. - Root `uv run pytest` after repairing the macOS test-isolation boundary — **1566 passed, 395 skipped, 1 warning** in 65.18s. - `git diff --check` — PASS. The first full-root pytest attempt exposed a test-isolation defect: two legacy `agent-bridge restart` tests still mocked the old inline `restart_service()` boundary, so the new parent path could schedule a real hand-over on the developer Mac. `tests/test_darwin_lifecycle.py` was updated to mock `schedule_handover()` and explicitly fail if the parent attempts an inline Bridge restart. The subsequent full-root suite reached 100% without another Bridge shutdown. Live hand-over evidence: - A Bridge-only hand-over completed with native status evidence `complete=true`, `ok=true`, `job_loaded=false`, `plist_exists=false`, detail `agent-bridge restarted`; the Bridge was usable again through a new Agent task after replacement. - Before the explicit Tunnel+Bridge restart, managed PIDs were Tunnel **70792** and Agent Bridge **77811**. - `serverfs service restart` intentionally interrupted its Bridge-owned caller while the launchd-owned one-shot child continued the operation. - After reconnection, managed PIDs were Tunnel **79300** and Agent Bridge **79297**; both registration states were `enabled`, `unmanaged_tunnel_pids=[]`, and a new Agent task executed successfully. - Final native hand-over evidence was `complete=true`, `ok=true`, detail `restarted`, `job_loaded=false`, `plist_exists=false`. The hand-over introduced no additional repository files. This proves the positive-path self-restart hand-over and verified cleanup on the supported Mac. Deliberately withholding live Bridge readiness/health was not performed because that would intentionally destabilize the active management path; deterministic regression tests cover readiness/health failure propagation and cleanup-failure evidence instead. ## 11. Completion contract The VPS implementation phase is complete only when: - every changed line is traceable to Fix A, B, C, or D; - focused tests and applicable lint/format/shell checks pass; - final diff contains no unrelated cleanup; - documentation explicitly distinguishes VPS source/test evidence from unexecuted Mac native acceptance; - the remaining Mac acceptance steps are concrete and reproducible. The implementation and supported-Mac acceptance described in this plan preceded publication. The v0.22.1 release was subsequently published; exact release evidence is recorded in the release checklist.