Repository navigation
Complete immediate signed Stable/Developer transitions with rollback-safe shared data #152
Description
Problem
The shared-state channel foundation in #148 can define ReleaseChannel, signed feed selection, and Developer/Stable release artifacts, but it deliberately cannot expose a channel selector that only changes the next feed. The accepted product contract requires an explicit channel choice to replace and restart the installed app immediately, including a signed Developer → Stable transition when the Stable SemVer is lower.
CodexHub currently has no minimal shared-data compatibility declaration, pre-install recovery backup, exact-target pending-transition check, or narrowly scoped downgrade authorization. Enabling the selector before all of those boundaries exist would create a half-enabled and potentially irreversible rollback path.
Accepted outcome
Enable the user-selectable Stable/Developer control only when CodexHub can complete an immediate signed transition safely. Stable → Developer installs the latest complete signed Developer candidate for the current BuildFlavor; Developer → Stable installs the signed supported Stable rollback target even when its SemVer is lower. Both preserve the one shared runtime/data identity and finish with exactly one app/Gateway owner.
Scope and invariants
- Consume the typed shared-state
ReleaseChanneland signedReleaseChannel × BuildFlavorfeed contract from Establish shared-state Stable/Developer channel feeds and release artifacts #148. - Expose Stable and Developer in Settings, show the effective channel/version, and require explicit confirmation that the app will replace/restart and active Codex Tasks may be interrupted.
- Treat selection as an immediate transition. Do not persist a completed channel change merely as a preference for a later update check.
- Fetch and verify the requested channel's signed manifest and reject mismatched channel,
BuildFlavor, platform, target version, URL, signature metadata, or artifact identity before replacement. - Permit a lower version only for an explicit user-selected transition to the signed supported Stable rollback target. Normal checks, Developer transitions, and every other caller keep normal downgrade rejection.
- Use Tauri updater's supported custom version-comparator/
allow_downgradesseam without bypassing signature verification or adding an arbitrary-version installer. - Define the minimal versioned shared-data compatibility metadata required to prove that both the running build and the target Stable reader can use the shared state. Fail closed if either side is unknown or incompatible.
- Keep Developer-written state readable by the currently supported Stable rollback target. Reject an irreversible Developer-only migration before candidate publication.
- Create and verify an atomic recovery backup of the shared state before installation. Preserve that backup and the original data on download, validation, install, restart, or confirmation failure.
- Make pending-transition completion exact-target-aware and channel-aware. A generic
current_version >= target_versioncomparison is not success for a rollback. - Route update installation and channel transitions through Cancel active Gateway work within two seconds on explicit lifecycle stops #143 and Stop CodexHub-managed Gateway when the app fully exits #112: stop admissions, cancel active upstream work, allow no more than two seconds for connection close/diagnostic flush, then force-terminate only the ownership-verified current-session Gateway before replacement/restart.
- Preserve eligible legacy Beta client backups through one bounded, idempotent migration or read-only compatibility fallback. Do not reactivate a legacy Beta home as a second live runtime, silently merge conflicting settings, or delete the legacy source.
- Keep application identifier, installation path, runtime home, settings, Providers/models, history, client backups, ports, and Gateway ownership shared across channels.
- Document that the first pre-channel-capable
0.1.6-dev.Nbuilds require manual installation fromv0.1.5.
Non-goals
- Side-by-side Stable/Developer applications, separate active homes, separate ports, or concurrent Gateways.
- Reintroducing Beta as a product,
BuildFlavor, or runtime owner. - An arbitrary version picker, unsigned installer, generalized downgrade privilege, or custom signature bypass.
- The full revisioned settings patch API or generated cross-language JSON Schema program; this Issue owns only the minimal compatibility metadata required for safe rollback.
- Changing Provider routing, model semantics, Responses/Chat conversion, or diagnostic
BuildFlavorbehavior. - Publishing an incomplete Developer candidate instead of
HOLD.
Acceptance criteria
- The Stable/Developer selector is enabled only with the complete immediate-transition implementation; selecting a channel never leaves only a future-feed preference behind.
- Stable → Developer installs the latest complete signed Developer candidate for the current
BuildFlavorand confirms the exact channel/version after restart. - Developer → Stable immediately installs the manifest-declared signed supported Stable artifact for the current
BuildFlavoreven when Stable has a lower SemVer, then confirms the exact Stable channel/version. - A lower version is rejected unless the request is an explicit user-selected Stable transition and the signed manifest,
BuildFlavor, platform, artifact, compatibility metadata, and supported rollback target all match. - Tampered, unsigned, cross-channel, cross-flavor, wrong-platform, stale-target, and incompatible-data manifests/artifacts fail before replacement with sanitized diagnostics.
- A verified atomic shared-state backup exists before install; injected download/install/restart/confirmation failures preserve the backup and original shared data without creating a second active home.
- Deterministic fixtures prove a Developer-written state remains readable by the supported Stable reader and a simulated irreversible migration is refused before publication.
- Existing eligible legacy Beta client backups remain recoverable without continuing dual active roots or deleting the legacy source.
- The transition uses the approved Cancel active Gateway work within two seconds on explicit lifecycle stops #143 warning/cancellation/two-second ownership-safe shutdown contract and Stop CodexHub-managed Gateway when the app fully exits #112 terminal path.
- Stable and Developer retain one application identifier, installation, runtime home, settings, Providers/models, history, client backups, ports, and Gateway ownership.
- Packaged restart leaves exactly one application/Gateway owner and listener, with exact pending-transition state reconciled.
- Normal/debug remain
BuildFlavors of each channel and cross-flavor artifacts are rejected. - Release documentation states that
v0.1.5users must manually install the early0.1.6-dev.Nbuilds until a channel-capable Stable is available.
Expected hotset
src-tauri/src/app_updates.rs- Focused ReleaseChannel transition/compatibility modules introduced by Establish shared-state Stable/Developer channel feeds and release artifacts #148
src-tauri/src/runtime_paths.rsand config/settings persistence only for compatibility metadata, backups, and legacy Beta backup inputsrc-tauri/src/main.rsplus the Serialize Gateway lifecycle operations and prevent duplicate startup processes #139/Cancel active Gateway work within two seconds on explicit lifecycle stops #143/Stop CodexHub-managed Gateway when the app fully exits #112 lifecycle seams- Frontend Settings/update controls, localization, and UI-contract tests
scripts/e2e-app-update.ps1and packaged rollback fixtures- Bilingual release documentation
Dependencies and relationships
Blocked by #148. That foundation is itself blocked by #112, which consumes #143 after #139.
Related, not duplicate: #135 preserves the legacy Beta-backup reporter evidence but is closed because its dual-active-root premise is superseded. This Issue owns only the bounded compatibility input needed for one shared runtime.
Verification
cd src-tauri; cargo test --lockedcd src-tauri; cargo clippy --locked --all-targets -- -D warningscd frontend; npm run build; npm run test:ui-contractpython -m pytest -qpowershell -File scripts/e2e-app-update.ps1 -Flavor normal -ValidateOnly, extended with Stable/Developer, exact-target, lower-version rollback, compatibility, backup, and failure-injection casespython scripts/report_quality_gates.py(report-only; non-blocking)
Execution contract
Execution-Contract: v2
Verification-Class: strict
Verification-Commands: cd src-tauri; cargo test --locked; cd src-tauri; cargo clippy --locked --all-targets -- -D warnings; cd frontend; npm run build; npm run test:ui-contract; python -m pytest -q; powershell -File scripts/e2e-app-update.ps1 -Flavor normal -ValidateOnly; python scripts/report_quality_gates.py (report-only)
Manual-Evidence: Signed packaged Windows Normal smoke preserving a known shared-state fixture across Stable → Developer → lower-version Stable; verify manifest/signature/channel/flavor/compatibility decisions, backup existence, exact post-restart target confirmation, and exactly one application/Gateway owner and listener after each transition.
Architecture-Decision: resolved
Review-Owner: orchestrator
Activity