A stable launcher owns the runtime selected by systemd or launchd. It is the only runtime writer of durable service state. Server children request updates over inherited IPC; they never rewrite their service definition or select their own replacement. Local service commands may replace the launcher and state while the service is stopped. Foreground CLI processes do not self-update.
Exact-version installs keep restarts independent of npm cache eviction or a moving release tag. Installation and preflight happen in staging before publishing an immutable runtime. Preflight checks the launcher protocol because a target that needs new rollback guarantees cannot safely run under an older launcher. Upgrading that launcher requires a local service update.
The launcher durably records the pending update before acknowledging it, then stops the old child and starts the target as a trial. Service-state writes use same-directory replacement with file and directory fsync. Invalid state stops startup rather than guessing which runtime to boot.
The trial must finish migrations, acquire dependencies, bind HTTP, and park every
long-running root at the activation gate before reporting prepared. The launcher
then commits the target version durably and replies committed. Only then may the
child release its gates, accept commands, and publish ready. Keep fallible startup
acquisitions before this boundary. A listener alone does not prove the runtime is
ready to commit.
A failed or timed-out trial returns to the old version. After commit, the target is authoritative and the service manager's ordinary restart policy applies.
After the old child exits, the launcher snapshots SQLite's main file, WAL, and shared-memory file. This makes trial migrations reversible without down migrations. The snapshot is made once per update and survives launcher restarts; replacing it during a retry could capture changes from the failed trial.
Rollback stops the trial before restoring. A durable restore marker makes an interrupted restore finish before either version boots. Keep the snapshot until commit, or until both restoration and the terminal rollback state are durable. Attachments and other files outside SQLite are outside this rollback boundary.
An accepted update is still pending. Clients correlate the launcher's update ID with the ready event after reconnecting, then check the outcome and target version. A reconnect alone cannot distinguish successful replacement from rollback. Older servers without an update ID retain version-only correlation.
Desktop updates have a separate two-phase handoff because installing the app stops its bundled backend. Preparation returns a token while the connection is alive; the client commits that token only after receiving it. Otherwise backend shutdown could lose the only successful RPC result. The client must then observe the prepared version after reconnecting. If installation fails, desktop restarts the stopped backends and replays the failure for the same token.