Route every mode transition through one locked helper so the staged
managed version cannot outlive the decision that created it. Both
download-only transitions kept pendingVersion, letting Install put on
the old enforced release after management disabled updates or the
platform fell back to download-only.
The helper also bumps a generation counter. handleUpdate and NotifyUI
copy the mode under the mutex but publish and install after releasing
it, so a reset landing in that window could still emit an enforced
notification. Both now recheck the generation right before the side
effect and drop the stale work.
Reset on every connection attempt instead of once per run: the backoff
loop reconnects without re-entering run, and since disconnects no longer
force download-only, a fetch in the disconnected gap republished the
enforced notification before the new network map arrived.
A missing AutoUpdateSettings now logs that it defaults to download-only
rather than claiming auto-update is disabled, which contradicted the
notifications that mode still emits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The update manager started in download-only mode and every engine stop
reset it there again, kicking the update loop with the cached latest
version. On a managed peer this published a "New version available"
notification on each disconnect, session expiry or logout, and once more
in the window between daemon start and the first network map. Users under
enforced updates then installed the release by hand.
Replace the boolean with a three-state mode. The manager starts undecided
and publishes nothing, from the fetcher or from NotifyUI, until the network
map picks download-only or managed. A new connection lifecycle resets to
undecided; disconnects leave the last decision untouched. A missing
AutoUpdateSettings now means download-only instead of a no-op, and on
platforms without an installer SetVersion falls back to download-only so
they keep notifying.
Auto-update logic moved out of the UI into a dedicated updatemanager.Manager service that runs in the connection layer. The
UI no longer polls or checks for updates independently.
The update manager supports three modes driven by the management server's auto-update policy:
No policy set by mgm: checks GitHub for the latest version and notifies the user (previous behavior, now centralized)
mgm enforces update: the "About" menu triggers installation directly instead of just downloading the file — user still initiates the action
mgm forces update: installation proceeds automatically without user interaction
updateManager lifecycle is now owned by daemon, giving the daemon server direct control via a new TriggerUpdate RPC
Introduces EngineServices struct to group external service dependencies passed to NewEngine, reducing its argument count from 11 to 4