--- phase: 02-lifecycle-command-safety plan: 01 type: execute wave: 1 depends_on: [] files_modified: - tauri-app/src-tauri/src/runtime/mod.rs - tauri-app/src-tauri/src/runtime/state.rs - tauri-app/src-tauri/src/runtime/types.rs autonomous: true requirements: [CTRL-01, CTRL-03] must_haves: truths: - The runtime backend distinguishes active lifecycle operations from idle snapshots so overlapping start and stop requests do not silently race. - The runtime backend returns explicit conflict or completion metadata that frontend consumers can turn into trustworthy lifecycle feedback. - Restart-impacting save rules remain aligned with the actual backend fields that require a local runtime restart. artifacts: - path: tauri-app/src-tauri/src/runtime/mod.rs provides: lifecycle command guards, structured command results, and start/stop flow behavior - path: tauri-app/src-tauri/src/runtime/state.rs provides: authoritative runtime snapshot state and process-refresh rules for in-flight lifecycle actions - path: tauri-app/src-tauri/src/runtime/types.rs provides: serialized lifecycle metadata that the frontend can consume without guessing command outcomes key_links: - from: tauri-app/src-tauri/src/runtime/mod.rs to: tauri-app/src-tauri/src/runtime/types.rs via: serialized lifecycle command fields added to the runtime snapshot pattern: RuntimeSnapshot|RuntimeStatus|last_error - from: tauri-app/src-tauri/src/runtime/mod.rs to: tauri-app/src-tauri/src/runtime/state.rs via: shared guard rules for starting, stopping, and process refresh transitions pattern: start|stop|refresh_process_state --- Create the backend lifecycle contract that Phase 2 can build on before frontend state and UI changes begin. Purpose: make start, stop, and conflicting lifecycle requests deterministic at the Tauri runtime boundary instead of relying on a single frontend `busy` boolean. Output: runtime snapshot metadata and guard behavior that explicitly describe in-flight operations, blocked conflicts, and lifecycle completion outcomes. @$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md @$HOME/.config/opencode/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/REQUIREMENTS.md @.planning/phases/02-lifecycle-command-safety/02-RESEARCH.md @tauri-app/src-tauri/src/runtime/mod.rs @tauri-app/src-tauri/src/runtime/state.rs @tauri-app/src-tauri/src/runtime/types.rs From `tauri-app/src-tauri/src/runtime/types.rs`: ```rust pub enum RuntimeStatus { Stopped, Starting, Ready, Degraded, Stopping, Error, } pub struct RuntimeSnapshot { pub status: RuntimeStatus, pub ready: bool, pub process_running: bool, pub resolved_url: String, pub binary_path: Option, pub last_error: Option, pub config: RuntimeConfig, } ``` From `tauri-app/src-tauri/src/runtime/mod.rs`: ```rust pub fn save_runtime_config_command(...) -> Result; pub fn start_runtime(...) -> Result; pub fn stop_runtime(...) -> Result; ``` Task 1: Add explicit lifecycle command metadata to the runtime snapshot tauri-app/src-tauri/src/runtime/types.rs, tauri-app/src-tauri/src/runtime/state.rs - tauri-app/src-tauri/src/runtime/types.rs - tauri-app/src-tauri/src/runtime/state.rs - tauri-app/src/features/runtime/types.ts - .planning/phases/02-lifecycle-command-safety/02-RESEARCH.md - Test 1: the serialized runtime snapshot exposes the currently active lifecycle action and the last completed lifecycle result. - Test 2: snapshots still report existing status, readiness, process_running, and error fields without breaking current callers. Extend `tauri-app/src-tauri/src/runtime/types.rs` and `tauri-app/src-tauri/src/runtime/state.rs` with concrete lifecycle metadata. Add a serialized enum or string union for the active command with the values `idle`, `save`, `start`, `stop`, and `save_and_start`. Add a serialized result payload or equivalent snapshot fields that record the last command name, a terminal outcome of `succeeded`, `blocked`, or `failed`, and a human-readable message. Keep the existing `RuntimeSnapshot`, `RuntimeStatus`, and `RuntimeErrorInfo` fields intact; add new fields instead of replacing old ones so brownfield frontend callers continue compiling. - `tauri-app/src-tauri/src/runtime/types.rs` defines serialized lifecycle command metadata alongside `RuntimeSnapshot`. - `tauri-app/src-tauri/src/runtime/state.rs` stores enough lifecycle metadata to build snapshots with active-command and last-result information. - Existing snapshot fields `status`, `ready`, `process_running`, `resolved_url`, `binary_path`, `last_error`, and `config` remain present in `RuntimeSnapshot`. cargo test -p tauri-app runtime:: The runtime snapshot becomes an explicit transport for lifecycle state instead of forcing the frontend to infer command outcomes from generic status changes. Task 2: Guard conflicting lifecycle commands and preserve restart-impacting save rules tauri-app/src-tauri/src/runtime/mod.rs, tauri-app/src-tauri/src/runtime/state.rs - tauri-app/src-tauri/src/runtime/mod.rs - tauri-app/src-tauri/src/runtime/state.rs - tauri-app/src-tauri/src/runtime/process.rs - .planning/phases/02-lifecycle-command-safety/02-RESEARCH.md - Test 1: a second `start_runtime` request while a start or stop command is active returns a blocked lifecycle result instead of mutating process state. - Test 2: `stop_runtime` while no local process is running returns a blocked or no-op lifecycle result with a stable message. - Test 3: saving a running local config that changes `connection_mode`, `model_path`, `addr`, `gpu_layers`, or `ctx_size` still returns `restart_required` guidance. Refactor `tauri-app/src-tauri/src/runtime/mod.rs` to centralize lifecycle command guards before mutating process state. Add explicit conflict handling for these cases: `start_runtime` while `status` is `Starting` or `Stopping`, `start_runtime` while a managed child already exists, `stop_runtime` while `status` is `Starting` or `Stopping`, and `stop_runtime` when no managed child exists. For each blocked case, populate the new lifecycle result metadata with `blocked` and a concrete message such as `Local API start already in progress.`, `Stop the local API before starting it again.`, or `Local API is already stopped.`. Preserve the existing restart-impacting save guard for `connection_mode`, `model_path`, `addr`, `gpu_layers`, and `ctx_size`, and route it through the same structured lifecycle result metadata so the frontend receives one consistent contract. - `tauri-app/src-tauri/src/runtime/mod.rs` contains explicit guard branches for duplicate or conflicting start/stop requests. - Blocked lifecycle cases return concrete user-facing messages containing `already in progress`, `already running`, or `already stopped`. - The restart-impacting field list in `requires_runtime_restart` remains limited to `connection_mode`, `model_path`, `addr`, `gpu_layers`, and `ctx_size` unless tests are updated to prove a different list. cargo test -p tauri-app runtime:: The backend becomes the authoritative source for lifecycle conflict prevention and restart-required guidance. - `cargo test -p tauri-app runtime::` - Start and stop requests have explicit blocked-state outcomes instead of silent no-ops. - Runtime snapshots expose active and completed lifecycle command information. - Restart-required saves still map to the existing backend-impact field list. After completion, create `.planning/phases/02-lifecycle-command-safety/02-01-SUMMARY.md`