feat(update): complete Phase 4 fleet UX and canary rollouts (#91277) - #93042
feat(update): complete Phase 4 fleet UX and canary rollouts (#91277)#93042andrexibiza wants to merge 49 commits into
Conversation
Diff too large for automated batch review (122 changed files, +30802/-1558) — recommending manual human review. |
Exact-current acceptance receipt — 2026-08-23 14:27 CDTLive PR head is now Current exact-head workflow truth:
So the current blocker is independent review authority only, not an observed code/test regression. Please review by the architecture boundaries rather than treating 122 files as 122 unrelated units: deployment classification/refusal; install-scoped lock + correlation identity; rollout checkpoint/apply/quiesce/restart/verify/rollback; runtime ownership/restart settlement; Desktop↔gateway route/profile publication; and native-platform witnesses. No new parallel updater/fleet PR should be opened for findings inside those boundaries; route each finding back to this owner or the existing Phase 3 companion #92545. I will not claim this head merge-ready until independent review authority is present. |
|
Full wave analysis in progress |
f6b34a0 to
1193e77
Compare
Current-head acceptance gate — 2026-08-23 21:53 CDTLive repository truth has moved again. The PR is open/non-draft at exact head The body still carries execution receipts and 5×2×3 adjudication for older immutable heads. Those remain historical evidence only. On current head I re-read the Region-4 executable-path blocker against the live head. It is still present:
Required invariant: no candidate interpreter may execute until the selected venv root and executable path have passed the same no-symlink/no-reparse provenance validation used by the rollout authority. The fix should reuse one canonical validator rather than invent a second partial check. Add adversarial witnesses for a POSIX symlinked venv and a Windows junction/reparse-point venv, and prove the probe is never invoked on rejection. After that repair, rebuild/rebase against current main, run fresh exact-head CI/Nix/Docker, and re-adjudicate the surviving R1/R2/R3/R5 findings against the new SHA rather than inheriting either the old rejection or the old green execution receipts. This remains the highest-leverage Phase-4 closure gate. |
Draft transition — semantic gates, not CI color, are authoritativeI have returned this PR to draft. The attached 5×2×3 contracts on the prior exact head The closure target stays this existing Phase 4 object rather than spawning parallel PRs. The blocking classes are:
Implementation receipt — R1 Windows profile-root authorityThe branch has now advanced to exact head That commit closes the specific R1 profile-casing defect by making the Rust install-root classifier accept ASCII case variants of the This is not a re-certification of Region 1 or of the PR. There are currently no hosted workflow runs attached to Do not mark ready for review until the remaining exact-head findings are repaired or explicitly disproven, followed by fresh exact-head CI/Docker/Nix and a new independent 5×2×3 adjudication. Phase 3 #92545 remains a separate upstream refusal contract; Phase 4 must compose with it rather than bypass or absorb it. |
Ship the Phase 4 desktop fleet-management surface as one reviewable commit on current main. - add gateway fleet inventory, lifecycle, policy, telemetry, and rollout primitives - expose desktop fleet controls and routing-aware management UI - preserve canonical repository CI workflows - include native, integration, and adversarial regression coverage Supersedes the development commit chain for NousResearch#93042 so every commit in the PR is independently testable at its exact SHA.
Signed-off-by: Axl Ibiza, MBA <andrexibiza@gmail.com>
(cherry picked from commit ef58a2194990d0f04696395840648d78b3f295e0)
(cherry picked from commit f2f8b16c0f7596f2f1870ea0b628dceca811e909)
Integrate the clean upstream prefix after removing the PR-local workflow changes. The next upstream commit, 6a6e16f, is the first semantic collision and will be resolved separately.
|
Current-head topology correction (2026-08-26 14:57 CDT): the live delivery head is now |
Landing-edge topology guard — 2026-08-26 17:53 CDTThe live publication object is now There is now a concrete landing-edge semantic collision, not merely branch age. After this PR's current head was minted, #95859 merged on main as I have moved this PR back to draft to make that topology explicit. No CI/Docker/Nix receipt from The next valid publication object is one semantic composition onto then-current |
The Desktop can drive a full update of a REMOTE SSH instance: claim the connection (ManagedConnectionUpdateGate pauses dials/mutations while the update owns it), terminate the owned backend with an identity re-proof at the signal boundary (terminateOwnedDashboardForUpdate — argv+creation time re-read in the same remote shell that signals), run the updater under an update-in-progress marker/mutex on the remote install root, bootstrap the new backend, and fence its publication so a rollback cannot leak a half-published serve (fenceManagedSshBootstrapPublication + waitForManagedSshBootstrapFence barrier). Extraction notes (campaign #91277 Phase 4; rollout/canary engine stays behind per sequencing): - managed-ssh-update.ts + test: clean cherry-pick (new files). - windows-remote-lifecycle.ts + test: applied as-is (zero main drift). - remote-lifecycle.ts: PR hunks stitched AROUND current main's #95532 skew guards and #91668 SIGKILL-escalating cleanupStale, which are PRESERVED verbatim — the PR's python identity-re-proof termination is wired for the managed-update path only; connect's stale replacement keeps main's proven kill. One PR test assertion re-pinned accordingly. - One PR test fixed: floating coordinator.start() promise whose rejection IS the contract under test now has an explicit handler (vitest flagged it as an unhandled rejection). tsc clean; 131/131 across the three touched electron suites. main.ts wiring (IPC + deps bag) follows as a separate commit.
Re-implements the #93042 main.ts wiring against current main (post-#94724 drift), making the extracted managed-ssh-update engine reachable: - ManagedConnectionUpdateGate instance + owner-only recovery journal at DESKTOP_MANAGED_SSH_RECOVERY_PATH (read/write/persist/mark/clear with strict record validation). - IPC: hermes:connections:update-managed (requestManagedSshUpdate with correlation-id claim + in-flight dedupe); update-all's ssh rows now route through the transactional drain/update/restore lifecycle instead of POSTing the remote backend updater. - Gate enforcement at every dial/mutate seam: bootstrapSshConnectionInner (pre-dial + publication fence with exact-serve rollback via rollbackSshBootstrapResult), resolveRemoteBackend, ensureRegistryBackend, saveRegistryConnection dial-field edits, connections:remove, and primary-routing mutations (set-primary, set-launch-mode, connection-config save/apply, profile:set) via assertCanMutateManagedPrimaryRouting. - Scope capture/drain/restore drivers: captureManagedSshScopes (pool + primary discovery, bootstrap fence join), drainManagedSshScope (exact identity-re-proof termination, no-kill forward recovery), ensureManagedSshBackend(AtKey)/restoreManagedPrimarySshBackend restores, openManagedSshUpdateTransport for serve-less connections. - Startup recovery (resumeManagedSshRecoveries before createWindow) and before-quit join of in-flight update/recovery operations BEFORE the SSH coordinator is sealed, so restore dials are not refused during quit. - Extended sshConnections state (spawnNonce/creationTime(Ns)/startedAt/ hermesPath/hermesHome/pythonPath/remoteProfile/registryConnectionId/ primaryRegistryScope) so drain can prove the exact serve it owns; bootstrap coordinator entries carry metadata for the update fence; persistSshConnectionToken mirrors tokens per managedSshTokenPersistencePlan. - preload/global.d.ts: connections.updateManaged + DesktopManagedConnectionUpdateResult/Receipt types. Renderer UI (fleet-updates store, about-settings, system.ts ProfileScope plumbing, i18n) intentionally NOT wired — it belongs to the deferred fleet rollout UI and follows separately. Wiring re-implemented against current main; design from #93042 by @andrexibiza tsc -p apps/desktop clean; electron project 1924/1924 passed (133 files, incl. 131/131 across the three engine suites); eslint clean on touched files.
|
Current-main topology correction after the latest extraction pass:
I have moved this PR back to draft because the merge object no longer matches the architectural ownership claimed by the old publication body. The surviving unique scope is now the canary/rollout composition over the shipped update engine + current UI/settlement contracts, not the 166-file historical carrier. Landing condition: materialize that remaining scope onto current main with the smallest collision-free FILE-LIST, preserve extracted contributor credit, then obtain fresh CI/Docker/Nix and rollout-specific evidence on the exact final head. Historical green and extracted child receipts do not transfer to |
…Research#93042 The Desktop can drive a full update of a REMOTE SSH instance: claim the connection (ManagedConnectionUpdateGate pauses dials/mutations while the update owns it), terminate the owned backend with an identity re-proof at the signal boundary (terminateOwnedDashboardForUpdate — argv+creation time re-read in the same remote shell that signals), run the updater under an update-in-progress marker/mutex on the remote install root, bootstrap the new backend, and fence its publication so a rollback cannot leak a half-published serve (fenceManagedSshBootstrapPublication + waitForManagedSshBootstrapFence barrier). Extraction notes (campaign NousResearch#91277 Phase 4; rollout/canary engine stays behind per sequencing): - managed-ssh-update.ts + test: clean cherry-pick (new files). - windows-remote-lifecycle.ts + test: applied as-is (zero main drift). - remote-lifecycle.ts: PR hunks stitched AROUND current main's NousResearch#95532 skew guards and NousResearch#91668 SIGKILL-escalating cleanupStale, which are PRESERVED verbatim — the PR's python identity-re-proof termination is wired for the managed-update path only; connect's stale replacement keeps main's proven kill. One PR test assertion re-pinned accordingly. - One PR test fixed: floating coordinator.start() promise whose rejection IS the contract under test now has an explicit handler (vitest flagged it as an unhandled rejection). tsc clean; 131/131 across the three touched electron suites. main.ts wiring (IPC + deps bag) follows as a separate commit.
Re-implements the NousResearch#93042 main.ts wiring against current main (post-NousResearch#94724 drift), making the extracted managed-ssh-update engine reachable: - ManagedConnectionUpdateGate instance + owner-only recovery journal at DESKTOP_MANAGED_SSH_RECOVERY_PATH (read/write/persist/mark/clear with strict record validation). - IPC: hermes:connections:update-managed (requestManagedSshUpdate with correlation-id claim + in-flight dedupe); update-all's ssh rows now route through the transactional drain/update/restore lifecycle instead of POSTing the remote backend updater. - Gate enforcement at every dial/mutate seam: bootstrapSshConnectionInner (pre-dial + publication fence with exact-serve rollback via rollbackSshBootstrapResult), resolveRemoteBackend, ensureRegistryBackend, saveRegistryConnection dial-field edits, connections:remove, and primary-routing mutations (set-primary, set-launch-mode, connection-config save/apply, profile:set) via assertCanMutateManagedPrimaryRouting. - Scope capture/drain/restore drivers: captureManagedSshScopes (pool + primary discovery, bootstrap fence join), drainManagedSshScope (exact identity-re-proof termination, no-kill forward recovery), ensureManagedSshBackend(AtKey)/restoreManagedPrimarySshBackend restores, openManagedSshUpdateTransport for serve-less connections. - Startup recovery (resumeManagedSshRecoveries before createWindow) and before-quit join of in-flight update/recovery operations BEFORE the SSH coordinator is sealed, so restore dials are not refused during quit. - Extended sshConnections state (spawnNonce/creationTime(Ns)/startedAt/ hermesPath/hermesHome/pythonPath/remoteProfile/registryConnectionId/ primaryRegistryScope) so drain can prove the exact serve it owns; bootstrap coordinator entries carry metadata for the update fence; persistSshConnectionToken mirrors tokens per managedSshTokenPersistencePlan. - preload/global.d.ts: connections.updateManaged + DesktopManagedConnectionUpdateResult/Receipt types. Renderer UI (fleet-updates store, about-settings, system.ts ProfileScope plumbing, i18n) intentionally NOT wired — it belongs to the deferred fleet rollout UI and follows separately. Wiring re-implemented against current main; design from NousResearch#93042 by @andrexibiza tsc -p apps/desktop clean; electron project 1924/1924 passed (133 files, incl. 131/131 across the three engine suites); eslint clean on touched files.
Slim renderer UI for the managed SSH remote update engine (#95942), adapted from #93042's renderer unit with the deferred canary/rollout scope stripped. Adds a per-connection store (idle/updating/terminal states, receipt, managed-update-in-progress busy envelope) and a 'Managed updates' section on the Gateways settings page with an Update button, progress line, and correlated receipt per registered Desktop-managed SSH connection. Fails closed when the Electron main lacks connections.updateManaged.
|
Implemented the topology cleanup before adding another receipt: the PR body is now reconciled in place to live head The merge target is now stated narrowly: one current-main canary/rollout composition over the shipped managed-update engine/UI/settlement contracts, smallest collision-free FILE-LIST, preserved contributor lineage, then fresh exact-final-head CI/Docker/Nix + rollout/platform witnesses. No second fleet/update carrier. |
|
Current-main update interlock, 2026-08-27 09:55 CDT: Two fresh reports land on seams that this historical Phase-4 carrier used to own, but they should not widen this PR back into a general updater/remote-lifecycle carrier.
These are current-product defects in the already-extracted update/lifecycle surface. The remaining #93042 object should consume their eventual current-main resolutions; it should not reimplement either fix inside the 166-file carrier. The shared architectural rule is narrower and useful for the canary/rollout remainder: qualify the candidate owner before applying fail-closed authority semantics, and bind shell/runtime assumptions to the actual execution environment. A rollout must not promote on a path that is only valid under bash or that treats unrelated host state as update authority. Landing boundary remains unchanged: rematerialize only canary/rollout composition on then-current main, preserve whatever exact fixes settle #96367/#96360, and certify the final composed object. Historical Phase-4 receipts do not certify those newer runtime surfaces. |
|
Topology reconciliation implemented in the PR body at live head The important correction is that #96282 is no longer an open readiness dependency: #96311 shipped the source-announcement half. #96280 remains the distinct launcher/grandchild realization boundary, and #96315 is independent incident evidence that source announcement is not equivalent to generation settlement. That narrows this carrier further. The only defensible surviving product scope is canary/rollout composition over current-main update, receipt, recovery, restart, and readiness contracts. The 166-file carrier should not land by conflict resolution; the next valid object is a current-main, collision-free rematerialization of that unique remainder with exact-final-head proof. |
…Research#93042 The Desktop can drive a full update of a REMOTE SSH instance: claim the connection (ManagedConnectionUpdateGate pauses dials/mutations while the update owns it), terminate the owned backend with an identity re-proof at the signal boundary (terminateOwnedDashboardForUpdate — argv+creation time re-read in the same remote shell that signals), run the updater under an update-in-progress marker/mutex on the remote install root, bootstrap the new backend, and fence its publication so a rollback cannot leak a half-published serve (fenceManagedSshBootstrapPublication + waitForManagedSshBootstrapFence barrier). Extraction notes (campaign NousResearch#91277 Phase 4; rollout/canary engine stays behind per sequencing): - managed-ssh-update.ts + test: clean cherry-pick (new files). - windows-remote-lifecycle.ts + test: applied as-is (zero main drift). - remote-lifecycle.ts: PR hunks stitched AROUND current main's NousResearch#95532 skew guards and NousResearch#91668 SIGKILL-escalating cleanupStale, which are PRESERVED verbatim — the PR's python identity-re-proof termination is wired for the managed-update path only; connect's stale replacement keeps main's proven kill. One PR test assertion re-pinned accordingly. - One PR test fixed: floating coordinator.start() promise whose rejection IS the contract under test now has an explicit handler (vitest flagged it as an unhandled rejection). tsc clean; 131/131 across the three touched electron suites. main.ts wiring (IPC + deps bag) follows as a separate commit.
Re-implements the NousResearch#93042 main.ts wiring against current main (post-NousResearch#94724 drift), making the extracted managed-ssh-update engine reachable: - ManagedConnectionUpdateGate instance + owner-only recovery journal at DESKTOP_MANAGED_SSH_RECOVERY_PATH (read/write/persist/mark/clear with strict record validation). - IPC: hermes:connections:update-managed (requestManagedSshUpdate with correlation-id claim + in-flight dedupe); update-all's ssh rows now route through the transactional drain/update/restore lifecycle instead of POSTing the remote backend updater. - Gate enforcement at every dial/mutate seam: bootstrapSshConnectionInner (pre-dial + publication fence with exact-serve rollback via rollbackSshBootstrapResult), resolveRemoteBackend, ensureRegistryBackend, saveRegistryConnection dial-field edits, connections:remove, and primary-routing mutations (set-primary, set-launch-mode, connection-config save/apply, profile:set) via assertCanMutateManagedPrimaryRouting. - Scope capture/drain/restore drivers: captureManagedSshScopes (pool + primary discovery, bootstrap fence join), drainManagedSshScope (exact identity-re-proof termination, no-kill forward recovery), ensureManagedSshBackend(AtKey)/restoreManagedPrimarySshBackend restores, openManagedSshUpdateTransport for serve-less connections. - Startup recovery (resumeManagedSshRecoveries before createWindow) and before-quit join of in-flight update/recovery operations BEFORE the SSH coordinator is sealed, so restore dials are not refused during quit. - Extended sshConnections state (spawnNonce/creationTime(Ns)/startedAt/ hermesPath/hermesHome/pythonPath/remoteProfile/registryConnectionId/ primaryRegistryScope) so drain can prove the exact serve it owns; bootstrap coordinator entries carry metadata for the update fence; persistSshConnectionToken mirrors tokens per managedSshTokenPersistencePlan. - preload/global.d.ts: connections.updateManaged + DesktopManagedConnectionUpdateResult/Receipt types. Renderer UI (fleet-updates store, about-settings, system.ts ProfileScope plumbing, i18n) intentionally NOT wired — it belongs to the deferred fleet rollout UI and follows separately. Wiring re-implemented against current main; design from NousResearch#93042 by @andrexibiza tsc -p apps/desktop clean; electron project 1924/1924 passed (133 files, incl. 131/131 across the three engine suites); eslint clean on touched files.
Slim renderer UI for the managed SSH remote update engine (NousResearch#95942), adapted from NousResearch#93042's renderer unit with the deferred canary/rollout scope stripped. Adds a per-connection store (idle/updating/terminal states, receipt, managed-update-in-progress busy envelope) and a 'Managed updates' section on the Gateways settings page with an Update button, progress line, and correlated receipt per registered Desktop-managed SSH connection. Fails closed when the Electron main lacks connections.updateManaged.
|
Reconciled the rollout carrier against newer current-main mutation contracts. #96440’s user-file preservation and #95531’s holder-safe DB restore now sit inside the rollout settlement boundary; the adjacent #96597 data-loss class makes the other side explicit: failure cleanup may delete only operation-owned staging, never a pre-existing durable destination. The 166-file carrier remains provenance. The only valid landing shape is the narrow canary/rollout graph consuming shipped updater/UI/readiness/restore semantics. |
Phase 4: fleet UX, canary rollouts, and safe cross-runtime updates
Implements the still-unique rollout/canary remainder of #91277. This PR intentionally does not close #91277.
Current source of truth — 2026-08-27 14:22 CDT
48c11b0511c2bb18788b01a2c94d345b6daffe86.What current main already owns
This carrier is no longer the owner of the full Phase-4 shape.
READY/BACKEND_PORT_IN_USE.state.dbrestoration: destructive replacement fails closed under foreign holders and current restore semantics must not be overwritten by an older rollout rollback path.#96280 remains a distinct Windows realization defect: a
uvlauncher can own Electron's process/pipe while the serving interpreter is a grandchild. #96315 independently shows the observer side: a backend can be alive while a generation-bound waiter still loses. Neither permits rollout promotion based on elapsed time, process existence, or attachment to one stdout owner.Surviving unique scope: the canary/rollout transaction over already-shipped update, UI/receipt, settlement, restart, restore, destination-preservation, and readiness contracts. The 166-file historical carrier is not the merge candidate.
Rollout transaction law
A fleet rollout is one settlement graph:
checkpoint → quiesce → apply → realize → verify → promote | rollback → reconcileEach edge is bound to the exact connection/profile/install target, transaction generation, and current realization. A parent may not promote if a child is failed, timed out, stale, receipt-mismatched, or indeterminate.
The destination/rollback side is now equally explicit:
The newly reported #96597 Desktop download data-loss class is not rollout implementation scope, but it is the other side of this exact invariant: opening the durable destination before settlement and unlinking it on failure converts an indeterminate transfer into destructive mutation. Rollout code must not reproduce that shape at install/state/package sinks.
Landing contract
The next valid product object must:
main, with the smallest collision-free FILE-LIST;failed,timeout,unknown,stale_generation, orreceipt_mismatch;Until those gates exist on one current-main product object, this PR remains draft.
Phase-4 invariants retained from the historical carrier
/updatethrough the same authoritative transaction with identity-bound confirmation and exact-correlation terminal receipts.Provenance and coordination
69ed873961900b99b241bf85b3cb80d6d31219ec.1425a8ba8c863b5177bd5dba6a3261f599369a4c.Historical exact-object receipts for earlier publication objects remain provenance only and are not evidence for
48c11b…or a future recomposition.Topology rule
Do not open another fleet/update carrier to obtain evidence or salvage already-shipped seams. Current-main product ownership is the source of truth: subtract what main owns, compose the unique rollout/canary remainder once, and certify that exact final object.
Disposition: #93042 is the provenance owner of Phase-4 rollout/canary semantics, but its 166-file head is intentionally draft. The next valid object is a narrow current-main rollout transaction that consumes, rather than replaces, shipped update/settlement/readiness/restore/destination-safety contracts.