a142725a8a
Mirror of the 2026-05-05 Wear-OS fix: port the canonical client sync spec (project_client_sync_semantics.md) into the Rust desktop client. Before this change, server_sync::run_loop had a `UploadOutcome::Terminal` branch that deleted the m4a + sidecar pair on any non-2xx + non-5xx response (401, 413, 415, parse drift, audio read failures) and never called mark_synced — so the marker stayed `synced_to_server=false` as a non-removable ghost in the desktop UI. Same shape as the Wear-OS bug. Changes: - `UploadOutcome` reduced to two variants (`Succeeded` | `Transient`). The Terminal arm in `run_loop` is gone; `delete_pair` only fires from the success path. - `post_upload` now classifies every failure mode as `Transient`: 4xx incl. 401, parse-ack drift on 200, file-read errors, multipart build errors, and network errors. Backoff retries forever. - New `CaseStore::cleanup_orphaned_unsynced(pending_case_ids, cutoff)`: removes markers that are unsynced AND have no waiting upload pair AND are older than a small grace window. Self-heals pre-spec ghosts. - `run_startup_cleanup` gains a third sweep that calls `cleanup_orphaned_unsynced` with `DEFAULT_ORPHAN_MARKER_GRACE = 5 min`, matching the Wear-OS StartupCleanup contract. - Tests inverted/added: `post_upload_transient_on_401`, `post_upload_transient_on_2xx_unparseable_body`, `former_terminal_401_keeps_pair_and_retries`, four new `cleanup_orphaned_unsynced_*` cases, plus `sweeps_orphan_unsynced_marker` and `keeps_unsynced_marker_with_pending_pair` integration tests. Followup (out of scope): `UploadEvent::Failed.will_retry` can now only be `true` and is vestigial. Removing it touches every Failed-event consumer in app.rs/UI; left for a separate cleanup commit.