You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/08/29 11:38:20 UTC

(maka) branch main updated: fix(desktop): stabilize local peer recovery (#4146)

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new 8c1586193 fix(desktop): stabilize local peer recovery (#4146)
8c1586193 is described below

commit 8c158619394a2d9e8ed585c5d83e793bc6ad4220
Author: Wang <m4...@gmail.com>
AuthorDate: Sat Aug 29 19:38:15 2026 +0800

    fix(desktop): stabilize local peer recovery (#4146)
---
 .../__tests__/runtime-host-desktop-manager.test.ts | 135 +++++++++++-
 .../runtime-host-local-remote-access.test.ts       | 181 +++++++++++++++-
 apps/desktop/src/main/runtime-host-boot.ts         |   1 +
 .../src/main/runtime-host-desktop-candidate.ts     |   7 +
 .../src/main/runtime-host-desktop-manager.ts       | 148 ++++++++++---
 .../src/main/runtime-host-local-remote-access.ts   | 239 ++++++++++++++++++---
 native/runtime-host-peer/Cargo.toml                |   4 +
 .../cli/src/__tests__/runtime-host-setup.test.ts   |  27 ++-
 packages/cli/src/runtime-host-access-command.ts    |  21 +-
 packages/cli/src/runtime-host-setup-command.ts     |  27 ++-
 .../runtime-host/src/__tests__/peer-mesh.test.ts   |   2 +-
 packages/runtime-host/src/peer-mesh/node.ts        |   5 +-
 12 files changed, 712 insertions(+), 85 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
index d2b9accf0..cbc687b66 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
@@ -23,6 +23,7 @@ import type { BotIncomingMessage } from '@maka/runtime/bots';
 import {
   RuntimeHostOperationError,
   RuntimeHostRequestInterruptedError,
+  type RuntimeHostSpawnedProcess,
 } from '@maka/runtime-host/client';
 import {
   INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
@@ -270,6 +271,33 @@ test('does not retire the local Host twice when an update handoff triggers quit'
   await owner.close();
 });
 
+test('does not block quit after a retired Local Host hands off to an unavailable supervisor', async () => {
+  const current = candidateHarness({ disconnectOnPrepare: true });
+  let starts = 0;
+  let reportFatal!: (error: Error) => void;
+  const fatalReported = new Promise<Error>((resolve) => {
+    reportFatal = resolve;
+  });
+  const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
+    startCandidate: async () => {
+      starts += 1;
+      return starts === 1 ? ready(current.candidate) : incompatibleHost('wait_for_idle_exit');
+    },
+    waitForHostExit: async () => undefined,
+    onFatalError: reportFatal,
+  });
+
+  const handoff = await owner.retireOwnedLocalHost('interrupt_active_work');
+  assert.equal(handoff.kind, 'retired');
+  if (handoff.kind === 'retired') handoff.resume();
+  await fatalReported;
+
+  assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
+    kind: 'not_owned',
+  });
+  await owner.close();
+});
+
 test('coalesces concurrent retirement intents onto one exact Host request', async () => {
   const current = candidateHarness({ disconnectOnPrepare: true });
   let releaseExitWait!: () => void;
@@ -894,8 +922,55 @@ test('keeps reconnecting through transient startup failures until the Desktop ad
   await owner.close();
 });
 
+test('reconciles interrupted managed setup after a Local discovery result', async () => {
+  const managed = candidateHarness({ ownership: 'supervised' });
+  const events: string[] = [];
+  let starts = 0;
+  const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
+    startCandidate: async () => {
+      starts += 1;
+      events.push(`discover:${starts}`);
+      return starts === 1
+        ? { kind: 'failed', reason: 'managed_root_requires_operator' }
+        : ready(managed.candidate);
+    },
+    recoverLocalHost: async () => {
+      events.push('reconcile');
+      return true;
+    },
+  });
+
+  assert.deepEqual(events, ['discover:1', 'reconcile', 'discover:2']);
+  assert.equal(owner.current('local')?.candidate?.hostOwnership, 'supervised');
+  await owner.close();
+});
+
+test('reconciles interrupted managed setup after Local discovery throws', async () => {
+  const managed = candidateHarness({ ownership: 'supervised' });
+  const events: string[] = [];
+  let starts = 0;
+  const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
+    startCandidate: async () => {
+      starts += 1;
+      events.push(`discover:${starts}`);
+      if (starts === 1) throw new Error('managed deployment transition is in progress');
+      return ready(managed.candidate);
+    },
+    recoverLocalHost: async () => {
+      events.push('reconcile');
+      return true;
+    },
+  });
+
+  assert.deepEqual(events, ['discover:1', 'reconcile', 'discover:2']);
+  assert.equal(owner.current('local')?.candidate?.hostOwnership, 'supervised');
+  await owner.close();
+});
+
 test('stops reconnecting when the replacement Host is incompatible', async () => {
-  const first = candidateHarness();
+  const first = candidateHarness({
+    ownedProcess: { pid: 42, exited: new Promise(() => undefined) },
+  });
   let reportFatal!: (error: Error) => void;
   const fatalReported = new Promise<Error>((resolve) => {
     reportFatal = resolve;
@@ -911,6 +986,62 @@ test('stops reconnecting when the replacement Host is incompatible', async () =>
   await first.candidate.close();
   const fatal = await fatalReported;
   assert.match(fatal.message, /older Runtime Host/);
+  await assert.rejects(
+    owner.retireOwnedLocalHost('interrupt_active_work'),
+    (error: unknown) =>
+      error instanceof DesktopLocalHostRetirementError &&
+      error.facts.pid === 42 &&
+      error.cause === fatal,
+  );
+  await owner.close();
+});
+
+test('does not retain manual-stop authority after the owned Host process exits', async () => {
+  const first = candidateHarness({
+    ownedProcess: {
+      pid: 42,
+      exited: Promise.resolve({ code: 0, signal: null, stderr: '', stderrTruncated: false }),
+    },
+  });
+  let reportFatal!: (error: Error) => void;
+  const fatalReported = new Promise<Error>((resolve) => {
+    reportFatal = resolve;
+  });
+  const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
+    startCandidate: async () =>
+      first.closeCalls === 0
+        ? ready(first.candidate)
+        : incompatibleHost('wait_for_idle_exit'),
+    onFatalError: reportFatal,
+  });
+
+  await first.candidate.close();
+  await fatalReported;
+  assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
+    kind: 'not_owned',
+  });
+  await owner.close();
+});
+
+test('does not block quit after a supervised Local Host becomes permanently unavailable', async () => {
+  const first = candidateHarness({ ownership: 'supervised' });
+  let reportFatal!: (error: Error) => void;
+  const fatalReported = new Promise<Error>((resolve) => {
+    reportFatal = resolve;
+  });
+  const owner = await startRuntimeHostDesktopManager({} as DesktopRuntimeHostCandidateStartInput, {
+    startCandidate: async () =>
+      first.closeCalls === 0
+        ? ready(first.candidate)
+        : incompatibleHost('wait_for_idle_exit'),
+    onFatalError: reportFatal,
+  });
+
+  await first.candidate.close();
+  await fatalReported;
+  assert.deepEqual(await owner.retireOwnedLocalHost('interrupt_active_work'), {
+    kind: 'not_owned',
+  });
   await owner.close();
 });
 
@@ -1160,6 +1291,7 @@ function candidateHarness(
     disconnectOnPrepare?: boolean;
     activeTasks?: boolean | 'always';
     ownership?: 'owned_ephemeral' | 'supervised' | 'external';
+    ownedProcess?: RuntimeHostSpawnedProcess;
     hostId?: string;
     hostEpoch?: string;
     finalizeFailures?: Error[];
@@ -1184,6 +1316,7 @@ function candidateHarness(
     closed,
     hostOwnership: options.ownership ?? 'owned_ephemeral',
     hostPid: 42,
+    ...(options.ownedProcess ? { ownedProcess: options.ownedProcess } : {}),
     client: {
       hostId: options.hostId ?? 'test-host',
       hostEpoch: options.hostEpoch ?? 'test-host-epoch',
diff --git a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
index 1fbc1844b..1158c7c45 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
@@ -198,7 +198,175 @@ test('revokes the one Local sharing authority without changing peer connectivity
   ]);
 });
 
-test('an interrupted Local Host handoff converges to its exact managed service', async (t) => {
+test('does not persist recoverable setup authority before Desktop ownership commits', async (t) => {
+  const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-ownership-'));
+  t.after(() => rm(base, { recursive: true, force: true }));
+  const clientDataRoot = join(base, 'client');
+  const rootPath = join(clientDataRoot, 'workspaces', 'default');
+  const rootId = 'a'.repeat(64);
+  const lifecyclePath = join(clientDataRoot, 'runtime-host-local-service.json');
+  await mkdir(rootPath, { recursive: true });
+  const handlers = new Map<string, Parameters<Electron.IpcMain['handle']>[1]>();
+  let ownershipChecked = false;
+  let setupCalls = 0;
+  const service = createDesktopLocalRuntimeHostRemoteAccess({
+    ipcMain: {
+      handle: (channel, handler) => { handlers.set(channel, handler); },
+      removeHandler: (channel) => { handlers.delete(channel); },
+    },
+    clientDataRoot,
+    rootPath,
+    rootId,
+    directPeerAvailable: true,
+    manager: () =>
+      ({
+        async retireOwnedLocalHost() {
+          ownershipChecked = true;
+          await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
+          return { kind: 'not_owned' as const };
+        },
+      }) as unknown as RuntimeHostDesktopManager,
+    resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
+    operator: {
+      async runSetup() {
+        setupCalls += 1;
+        throw new Error('setup must not run for an externally managed Host');
+      },
+      async close() {},
+    } as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
+  });
+  t.after(() => service.close());
+
+  const enable = handlers.get('local-runtime-host-remote-access:enable');
+  assert.ok(enable);
+  const enabling = enable({} as Electron.IpcMainInvokeEvent, {
+    allowInterruptActiveTasks: false,
+    coordinationRelays: [],
+  });
+  await assert.rejects(enabling, /already managed outside this Desktop/u);
+  assert.equal(ownershipChecked, true);
+  await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
+  assert.equal(setupCalls, 0);
+});
+
+test('adopts committed managed authority for every pending receipt without replaying setup', async (t) => {
+  const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-prestart-'));
+  t.after(() => rm(base, { recursive: true, force: true }));
+  for (const state of ['handoff', 'setupPending'] as const) {
+    const clientDataRoot = join(base, state);
+    const rootPath = join(clientDataRoot, 'workspaces', 'default');
+    const rootId = 'a'.repeat(64);
+    const deploymentId = '22222222-2222-4222-8222-222222222222';
+    const operatorPath = join(base, 'installed', 'operator');
+    await mkdir(rootPath, { recursive: true });
+    await writeFile(
+      join(clientDataRoot, 'runtime-host-local-service.json'),
+      `${JSON.stringify({
+        schemaVersion: 1,
+        state,
+        rootPath,
+        rootId,
+        coordinationRelays: [],
+        allowInterruptActiveTasks: true,
+      })}\n`,
+    );
+    const service = createDesktopLocalRuntimeHostRemoteAccess({
+      ipcMain: { handle() {}, removeHandler() {} },
+      clientDataRoot,
+      rootPath,
+      rootId,
+      directPeerAvailable: true,
+      manager: () => assert.fail('pre-start reconciliation must not require the Local manager'),
+      resolveManagedDeploymentAuthority: async () => ({
+        kind: 'active',
+        target: {
+          schemaVersion: 1,
+          serviceId: rootId,
+          operatorPath,
+          rootPath,
+          rootId,
+          deploymentId,
+        },
+      }),
+      resolveSetupPackage: async () =>
+        assert.fail('committed authority must not resolve a package'),
+      operator: {
+        async runSetup() {
+          assert.fail('committed authority must not replay setup');
+        },
+        async close() {},
+      } as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
+    });
+    t.after(() => service.close());
+
+    assert.equal(await service.recoverManagedSetup(), true);
+    assert.deepEqual(
+      JSON.parse(await readFile(join(clientDataRoot, 'runtime-host-local-service.json'), 'utf8')),
+      {
+        schemaVersion: 1,
+        state: 'managed',
+        serviceId: rootId,
+        operatorPath,
+        rootPath,
+        rootId,
+        deploymentId,
+      },
+    );
+  }
+});
+
+test('discards a legacy handoff that belongs to an externally managed Host', async (t) => {
+  const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-legacy-external-'));
+  t.after(() => rm(base, { recursive: true, force: true }));
+  const clientDataRoot = join(base, 'client');
+  const rootPath = join(clientDataRoot, 'workspaces', 'default');
+  const rootId = 'a'.repeat(64);
+  const lifecyclePath = join(clientDataRoot, 'runtime-host-local-service.json');
+  await mkdir(rootPath, { recursive: true });
+  await writeFile(
+    lifecyclePath,
+    `${JSON.stringify({
+      schemaVersion: 1,
+      state: 'handoff',
+      rootPath,
+      rootId,
+      coordinationRelays: [],
+      allowInterruptActiveTasks: false,
+    })}\n`,
+  );
+  let setupCalls = 0;
+  const service = createDesktopLocalRuntimeHostRemoteAccess({
+    ipcMain: { handle() {}, removeHandler() {} },
+    clientDataRoot,
+    rootPath,
+    rootId,
+    directPeerAvailable: true,
+    manager: () =>
+      ({
+        async retireOwnedLocalHost() {
+          return { kind: 'not_owned' as const };
+        },
+      }) as unknown as RuntimeHostDesktopManager,
+    resolveManagedDeploymentAuthority: async () => undefined,
+    resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
+    operator: {
+      async runSetup() {
+        setupCalls += 1;
+        throw new Error('setup must not replace an externally managed Host');
+      },
+      async close() {},
+    } as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
+  });
+  t.after(() => service.close());
+
+  assert.equal(await service.recoverManagedSetup(), false);
+  await service.recover();
+
+  assert.equal(setupCalls, 0);
+  await assert.rejects(readFile(lifecyclePath, 'utf8'), { code: 'ENOENT' });
+});
+
+test('interrupted Local Host setup converges to its exact managed service', async (t) => {
   const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-recovery-'));
   t.after(() => rm(base, { recursive: true, force: true }));
   const clientDataRoot = join(base, 'client');
@@ -209,7 +377,7 @@ test('an interrupted Local Host handoff converges to its exact managed service',
     join(clientDataRoot, 'runtime-host-local-service.json'),
     `${JSON.stringify({
       schemaVersion: 1,
-      state: 'handoff',
+      state: 'setupPending',
       rootPath,
       rootId,
       coordinationRelays: [],
@@ -217,6 +385,7 @@ test('an interrupted Local Host handoff converges to its exact managed service',
     })}\n`,
   );
   let setupCalls = 0;
+  let setupQuiesced = false;
   const service = createDesktopLocalRuntimeHostRemoteAccess({
     ipcMain: { handle() {}, removeHandler() {} },
     clientDataRoot,
@@ -229,7 +398,12 @@ test('an interrupted Local Host handoff converges to its exact managed service',
           assert.equal(mode, 'interrupt_active_work');
           return { kind: 'not_owned' as const };
         },
+        async runManagedLocalHostChange(change: () => Promise<unknown>) {
+          setupQuiesced = true;
+          return change();
+        },
       }) as unknown as RuntimeHostDesktopManager,
+    resolveManagedDeploymentAuthority: async () => undefined,
     resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
     operator: {
       async runSetup() {
@@ -253,9 +427,12 @@ test('an interrupted Local Host handoff converges to its exact managed service',
   });
   t.after(() => service.close());
 
+  assert.equal(await service.recoverManagedSetup(), false);
+  assert.equal(setupCalls, 0);
   await service.recover();
 
   assert.equal(setupCalls, 1);
+  assert.equal(setupQuiesced, true);
   assert.equal(
     JSON.parse(await readFile(join(clientDataRoot, 'runtime-host-local-service.json'), 'utf8'))
       .state,
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 0913bbee6..8a73c0be6 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -991,6 +991,7 @@ runtimeHostManager = await startRuntimeHostDesktopManager(
         isDefault: true,
       });
     },
+    recoverLocalHost: (signal) => localRuntimeHostRemoteAccess.recoverManagedSetup(signal),
     onFatalError: (error, target) => {
       if (error instanceof RuntimeHostUpgradeCancelledError) {
         if (target.profile.kind === "local") app.quit();
diff --git a/apps/desktop/src/main/runtime-host-desktop-candidate.ts b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
index 3ecce8214..03ce20e79 100644
--- a/apps/desktop/src/main/runtime-host-desktop-candidate.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
@@ -201,6 +201,7 @@ export interface DesktopRuntimeHostCandidate {
   readonly closed: Promise<void>;
   readonly hostOwnership: DesktopRuntimeHostOwnership;
   readonly hostPid?: number;
+  readonly ownedProcess?: RuntimeHostSpawnedProcess;
   stopSession(sessionId: string): Promise<void>;
   close(): Promise<void>;
 }
@@ -211,6 +212,7 @@ class DesktopRuntimeHostCandidateImpl implements DesktopRuntimeHostCandidate {
   readonly closed: Promise<void>;
   readonly hostOwnership: DesktopRuntimeHostOwnership;
   readonly hostPid: number | undefined;
+  readonly ownedProcess: RuntimeHostSpawnedProcess | undefined;
   readonly #client: DesktopRuntimeHostClient;
   readonly #observer: RuntimeHostSessionObserver;
   readonly #ipc: ScopedIpcMain;
@@ -237,6 +239,7 @@ class DesktopRuntimeHostCandidateImpl implements DesktopRuntimeHostCandidate {
     connectionClosed: Promise<void>;
     hostOwnership: DesktopRuntimeHostOwnership;
     hostPid?: number;
+    ownedProcess?: RuntimeHostSpawnedProcess;
     hasRegisteredCapabilities: () => boolean;
     stopSession: (sessionId: string) => Promise<void>;
   }) {
@@ -255,6 +258,7 @@ class DesktopRuntimeHostCandidateImpl implements DesktopRuntimeHostCandidate {
     this.botIncoming = input.botIncoming;
     this.hostOwnership = input.hostOwnership;
     this.hostPid = input.hostPid;
+    this.ownedProcess = input.ownedProcess;
     this.closed = input.connectionClosed.then(() => this.close());
   }
 
@@ -323,6 +327,7 @@ export async function startDesktopRuntimeHostCandidate(
           : 'supervised',
         "local",
         connection.registration.pid,
+        connection.spawnedProcess,
       ),
     };
   } catch (error) {
@@ -432,6 +437,7 @@ export async function createDesktopRuntimeHostCandidate(
   hostOwnership: DesktopRuntimeHostOwnership,
   targetKind: DesktopRuntimeHostTargetPolicy["kind"],
   hostPid?: number,
+  ownedProcess?: RuntimeHostSpawnedProcess,
 ): Promise<DesktopRuntimeHostCandidate> {
   const target: DesktopRuntimeHostTargetPolicy = {
     kind: targetKind,
@@ -760,6 +766,7 @@ export async function createDesktopRuntimeHostCandidate(
       connectionClosed: connection.closed,
       hostOwnership,
       ...(hostPid === undefined ? {} : { hostPid }),
+      ...(ownedProcess === undefined ? {} : { ownedProcess }),
       hasRegisteredCapabilities: () => capabilitiesRegistered,
       stopSession,
     });
diff --git a/apps/desktop/src/main/runtime-host-desktop-manager.ts b/apps/desktop/src/main/runtime-host-desktop-manager.ts
index c03c6f6af..e50c03e4d 100644
--- a/apps/desktop/src/main/runtime-host-desktop-manager.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-manager.ts
@@ -41,6 +41,7 @@ import {
   type DesktopRuntimeHostCandidate,
   type DesktopRuntimeHostCandidateStartInput,
   type DesktopRuntimeHostCandidateStartResult,
+  type DesktopRuntimeHostOwnership,
 } from './runtime-host-desktop-candidate.js';
 import { RuntimeHostReconnectingIpcMain } from './runtime-host-reconnecting-ipc-main.js';
 import { RuntimeHostSessionObservationRegistry } from './runtime-host-session-observation-registry.js';
@@ -181,9 +182,20 @@ interface DesktopRuntimeHostTargetGeneration {
   hostId?: string;
   lifecycle?: RuntimeHostReconnectLifecycle<DesktopRuntimeHostCandidate>;
   unsubscribeLifecycle?: () => void;
+  lastCandidate?: {
+    readonly hostId: string;
+    readonly hostEpoch: string;
+    readonly ownership: DesktopRuntimeHostOwnership;
+    readonly ownedProcess?: DesktopOwnedProcessEvidence;
+  };
   valid: boolean;
 }
 
+interface DesktopOwnedProcessEvidence {
+  readonly pid: number;
+  state: 'running' | 'exited' | 'unknown';
+}
+
 export async function startRuntimeHostDesktopManager(
   input: DesktopRuntimeHostCandidateStartInput,
   options: {
@@ -198,6 +210,7 @@ export async function startRuntimeHostDesktopManager(
       registration: HostRegistration,
       signal: AbortSignal,
     ) => Promise<void>;
+    recoverLocalHost?: (signal: AbortSignal) => Promise<boolean>;
     reconnectBackoff?: RuntimeHostReconnectBackoff;
     pairingFinalizationTimeoutMs?: number;
     onTargetStateChanged?: (state: RuntimeHostDesktopTargetState) => void;
@@ -213,6 +226,7 @@ export async function startRuntimeHostDesktopManager(
     options.upgradePrompts,
     options.waitForHostExit ?? waitForProcessExit,
     options.waitForHostRetirement ?? waitForProcessRetirement,
+    options.recoverLocalHost,
     options.reconnectBackoff,
     options.pairingFinalizationTimeoutMs ?? DEFAULT_PAIRING_FINALIZATION_TIMEOUT_MS,
     options.onTargetStateChanged,
@@ -252,6 +266,9 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
       registration: HostRegistration,
       signal: AbortSignal,
     ) => Promise<void>,
+    private readonly recoverLocalHost:
+      | ((signal: AbortSignal) => Promise<boolean>)
+      | undefined,
     private readonly reconnectBackoff: RuntimeHostReconnectBackoff | undefined,
     private readonly pairingFinalizationTimeoutMs: number,
     private readonly onTargetStateChanged:
@@ -576,10 +593,20 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
   async #retireOwnedLocalHost(
     mode: RuntimeHostRetirementMode,
   ): Promise<DesktopLocalHostRetirement> {
-    const lifecycle = this.#requireLifecycle(
-      this.#requireTarget(LOCAL_RUNTIME_HOST_PROFILE.id),
-    );
-    const quiescence = await lifecycle.quiesce();
+    const target = this.#requireTarget(LOCAL_RUNTIME_HOST_PROFILE.id);
+    const lifecycle = this.#requireLifecycle(target);
+    const unavailable = this.#unavailableLocalHostRetirement(target);
+    if (unavailable) return unavailable;
+    let quiescence: Awaited<
+      ReturnType<RuntimeHostReconnectLifecycle<DesktopRuntimeHostCandidate>['quiesce']>
+    >;
+    try {
+      quiescence = await lifecycle.quiesce();
+    } catch (error) {
+      const terminal = this.#unavailableLocalHostRetirement(target, error);
+      if (terminal) return terminal;
+      throw error;
+    }
     let hostPid = quiescence.current.hostPid;
     let launchBarrierPaused = false;
     const resume = () => {
@@ -610,6 +637,7 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
         return result;
       }
       await this.waitForHostExit(result.pid);
+      target.lastCandidate = undefined;
       return this.#completeLocalHostRetirement(resume);
     } catch (error) {
       resume();
@@ -626,6 +654,28 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
     }
   }
 
+  #unavailableLocalHostRetirement(
+    target: DesktopRuntimeHostTargetGeneration,
+    cause: unknown = target.state.readiness === 'unavailable' ? target.state.error : undefined,
+  ): DesktopLocalHostRetirement | undefined {
+    if (target.state.readiness !== 'unavailable') return undefined;
+    const last = target.lastCandidate;
+    if (!last || last.ownership !== 'owned_ephemeral') return { kind: 'not_owned' };
+    if (last.ownedProcess?.state === 'exited') return { kind: 'not_owned' };
+    throw new DesktopLocalHostRetirementError(
+      {
+        hostId: last.hostId,
+        hostEpoch: last.hostEpoch,
+        lifecycleMode: 'ephemeral',
+        rootPath: this.#baseInput.rootPath,
+        ...(last.ownedProcess?.state === 'running'
+          ? { pid: last.ownedProcess.pid }
+          : {}),
+      },
+      { cause: cause instanceof Error ? cause : new Error(String(cause)) },
+    );
+  }
+
   #completeLocalHostRetirement(
     resume: () => void,
   ): Extract<DesktopLocalHostRetirement, { kind: 'retired' }> {
@@ -719,30 +769,64 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
     sshInteraction: RuntimeHostSshInteraction | undefined,
   ): Promise<DesktopRuntimeHostCandidate> {
     let takeoverHostEpoch: string | undefined;
+    let localRecoveryAttempted = false;
     const inheritedExit = target.input.onExit;
+    const tryRecoverLocalHost = async (): Promise<boolean> => {
+      if (target.input.profileTarget || localRecoveryAttempted || !this.recoverLocalHost) {
+        return false;
+      }
+      localRecoveryAttempted = true;
+      return this.recoverLocalHost(signal);
+    };
     while (true) {
-      const result = await this.startCandidate(
-        {
-          ...target.input,
-          onExit: (details) => this.#reportCandidateExit(inheritedExit, details),
-          ...(target.input.profileTarget
-            ? {
-                profileTarget: {
-                  ...target.input.profileTarget,
-                  ...(sshInteraction === undefined ? {} : { sshInteraction }),
-                },
-              }
-            : {}),
-          ipcMain: this.#ipcMain.createTarget(target.epoch),
-          isTargetActive: () => this.#ipcMain.isActive(target.epoch),
-          isTargetValid: () => target.valid,
-          signal,
-          ...(takeoverHostEpoch === undefined ? {} : { takeoverHostEpoch }),
-        },
-        target.observations,
-      );
+      let result: DesktopRuntimeHostCandidateStartResult;
+      try {
+        result = await this.startCandidate(
+          {
+            ...target.input,
+            onExit: (details) => this.#reportCandidateExit(inheritedExit, details),
+            ...(target.input.profileTarget
+              ? {
+                  profileTarget: {
+                    ...target.input.profileTarget,
+                    ...(sshInteraction === undefined ? {} : { sshInteraction }),
+                  },
+                }
+              : {}),
+            ipcMain: this.#ipcMain.createTarget(target.epoch),
+            isTargetActive: () => this.#ipcMain.isActive(target.epoch),
+            isTargetValid: () => target.valid,
+            signal,
+            ...(takeoverHostEpoch === undefined ? {} : { takeoverHostEpoch }),
+          },
+          target.observations,
+        );
+      } catch (error) {
+        signal.throwIfAborted();
+        if (await tryRecoverLocalHost()) continue;
+        throw error;
+      }
       if (result.kind === 'ready') {
         target.hostId = result.candidate.client.hostId;
+        const previous = target.lastCandidate;
+        const retainedOwnedProcess =
+          previous?.hostId === result.candidate.client.hostId &&
+          previous.hostEpoch === result.candidate.client.hostEpoch &&
+          previous.ownership === 'owned_ephemeral' &&
+          result.candidate.hostOwnership === 'owned_ephemeral' &&
+          previous.ownedProcess?.pid === result.candidate.hostPid
+            ? previous.ownedProcess
+            : undefined;
+        target.lastCandidate = {
+          hostId: result.candidate.client.hostId,
+          hostEpoch: result.candidate.client.hostEpoch,
+          ownership: result.candidate.hostOwnership,
+          ...(result.candidate.ownedProcess
+            ? { ownedProcess: trackOwnedProcess(result.candidate.ownedProcess) }
+            : retainedOwnedProcess
+              ? { ownedProcess: retainedOwnedProcess }
+              : {}),
+        };
         return result.candidate;
       }
       if (result.kind === 'upgrade_required' && result.restartable) {
@@ -775,6 +859,7 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
         await this.waitForHostRetirement(result.registration, signal);
         continue;
       }
+      if (await tryRecoverLocalHost()) continue;
       throw runtimeHostStartupError(result.reason, result.diagnostic);
     }
   }
@@ -975,6 +1060,21 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
   }
 }
 
+function trackOwnedProcess(
+  process: NonNullable<DesktopRuntimeHostCandidate['ownedProcess']>,
+): DesktopOwnedProcessEvidence {
+  const evidence: DesktopOwnedProcessEvidence = { pid: process.pid, state: 'running' };
+  void process.exited.then(
+    () => {
+      evidence.state = 'exited';
+    },
+    () => {
+      evidence.state = 'unknown';
+    },
+  );
+  return evidence;
+}
+
 function pairingFinalizeRetry(error: unknown): boolean {
   // Finalization is idempotent for the current credential, so both a known
   // non-dispatch and an unknown outcome converge on the replacement connection.
diff --git a/apps/desktop/src/main/runtime-host-local-remote-access.ts b/apps/desktop/src/main/runtime-host-local-remote-access.ts
index d19b3803b..ab6659d08 100644
--- a/apps/desktop/src/main/runtime-host-local-remote-access.ts
+++ b/apps/desktop/src/main/runtime-host-local-remote-access.ts
@@ -26,6 +26,7 @@ import {
   consumeAccessCredentialDelivery,
   encodeRuntimeHostOwnerConnectionCode,
 } from '@maka/runtime-host/client';
+import { resolveRuntimeHostManagedDeploymentAuthority } from '@maka/runtime-host/operator';
 import { REMOTE_OWNER_OPERATION_GRANTS } from '@maka/runtime-host/protocol';
 import type {
   DesktopLocalRuntimeHostRemoteAccessEnableResult,
@@ -54,7 +55,18 @@ interface LocalServiceTarget extends DesktopRuntimeHostLocalServiceTarget {
   readonly deploymentId: string;
 }
 
-interface LocalServiceHandoff {
+interface LocalServiceSetupPending {
+  readonly schemaVersion: 1;
+  /** Persisted only after the Desktop-owned Host has retired. */
+  readonly state: 'setupPending';
+  readonly rootPath: string;
+  readonly rootId: string;
+  readonly coordinationRelays: readonly string[];
+  readonly allowInterruptActiveTasks: boolean;
+}
+
+/** Schema-v1 setup intent written by Desktop releases before ownership was established. */
+interface LocalServiceLegacyHandoff {
   readonly schemaVersion: 1;
   readonly state: 'handoff';
   readonly rootPath: string;
@@ -67,6 +79,10 @@ interface LocalServiceManaged extends LocalServiceTarget {
   readonly state: 'managed';
 }
 
+type LocalManagedDeploymentAuthority =
+  | { readonly kind: 'active'; readonly target: LocalServiceTarget }
+  | { readonly kind: 'transition' };
+
 interface LocalServicePeerChanging extends LocalServiceTarget {
   readonly state: 'peerChanging';
   readonly peerEnabled: boolean;
@@ -80,7 +96,8 @@ interface LocalServiceUninstalling extends LocalServiceTarget {
 }
 
 type LocalServiceLifecycle =
-  | LocalServiceHandoff
+  | LocalServiceLegacyHandoff
+  | LocalServiceSetupPending
   | LocalServiceManaged
   | LocalServicePeerChanging
   | LocalServiceUninstalling;
@@ -110,7 +127,14 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     signal?: AbortSignal,
   ) => DesktopRuntimeHostSetupPackage | Promise<DesktopRuntimeHostSetupPackage>;
   readonly operator: DesktopRuntimeHostLocalOperator;
-}): { recover(): Promise<void>; close(): Promise<void> } {
+  readonly resolveManagedDeploymentAuthority?: (
+    rootId: string,
+  ) => Promise<LocalManagedDeploymentAuthority | undefined>;
+}): {
+  recoverManagedSetup(signal?: AbortSignal): Promise<boolean>;
+  recover(): Promise<void>;
+  close(): Promise<void>;
+} {
   const lifecyclePath = join(input.clientDataRoot, LIFECYCLE_FILE);
   const closing = new AbortController();
   let mutation = Promise.resolve();
@@ -122,6 +146,41 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     );
     return result;
   };
+  const resolveManagedDeploymentAuthority =
+    input.resolveManagedDeploymentAuthority ??
+    (async (rootId: string): Promise<LocalManagedDeploymentAuthority | undefined> => {
+      const authority = await resolveRuntimeHostManagedDeploymentAuthority(rootId);
+      if (!authority) return undefined;
+      if (authority.record.state !== 'active') return { kind: 'transition' };
+      return {
+        kind: 'active',
+        target: requireServiceTarget(
+          {
+            schemaVersion: 1,
+            serviceId: authority.record.root.id,
+            operatorPath: join(authority.record.deploymentRoot, 'operator'),
+            rootPath: authority.record.root.path,
+            rootId: authority.record.root.id,
+            deploymentId: authority.record.deploymentId,
+          },
+          input.rootPath,
+        ),
+      };
+    });
+
+  const adoptCommittedSetup = async (
+    setup: LocalServiceSetupPending,
+  ): Promise<
+    | { readonly kind: 'absent' | 'transition' }
+    | { readonly kind: 'managed'; readonly managed: LocalServiceManaged }
+  > => {
+    const authority = await resolveManagedDeploymentAuthority(setup.rootId);
+    if (!authority) return { kind: 'absent' };
+    if (authority.kind === 'transition') return authority;
+    const managed = managedLifecycle(authority.target);
+    await writeDocument(lifecyclePath, managed);
+    return { kind: 'managed', managed };
+  };
 
   const getSnapshot = (): Promise<DesktopLocalRuntimeHostRemoteAccessSnapshot> =>
     serialize(async () => {
@@ -133,7 +192,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           return {
             state: 'unavailable',
             message:
-              lifecycle.state === 'handoff'
+              lifecycle.state === 'setupPending' || lifecycle.state === 'handoff'
                 ? 'Local Runtime Host setup is being recovered'
                 : lifecycle.state === 'peerChanging'
                   ? 'Local Runtime Host remote access is being recovered'
@@ -170,10 +229,23 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
         lifecycle = managedLifecycle(lifecycle);
       }
       if (lifecycle?.state === 'handoff') {
-        const recovered = await finishHandoff(lifecycle, true);
+        const recovered = await recoverLegacyHandoff(lifecycle);
         if (recovered.kind === 'active_tasks') return recovered;
+        if (recovered.kind === 'external') {
+          throw new Error('The Local Runtime Host is already managed outside this Desktop');
+        }
         lifecycle = recovered.managed;
       }
+      if (lifecycle?.state === 'setupPending') {
+        const committed = await adoptCommittedSetup(lifecycle);
+        if (committed.kind === 'managed') {
+          lifecycle = committed.managed;
+        } else {
+          const recovered = await finishSetup(lifecycle, 'recovery');
+          if (recovered.kind === 'active_tasks') return recovered;
+          lifecycle = recovered.managed;
+        }
+      }
       if (lifecycle?.state === 'managed') {
         const manager = requireManager(input.manager);
         const previousHostEpoch = manager.current('local')?.candidate?.client.hostEpoch;
@@ -199,16 +271,15 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
         );
       }
 
-      const handoff: LocalServiceHandoff = {
+      const setup: LocalServiceSetupPending = {
         schemaVersion: 1,
-        state: 'handoff',
+        state: 'setupPending',
         rootPath: input.rootPath,
         rootId: input.rootId,
         coordinationRelays: request.coordinationRelays,
         allowInterruptActiveTasks: request.allowInterruptActiveTasks,
       };
-      await writeDocument(lifecyclePath, handoff);
-      const completed = await finishHandoff(handoff, false);
+      const completed = await finishSetup(setup, 'request');
       if (completed.kind === 'active_tasks') return completed;
       return enabledResult(
         encodeRuntimeHostOwnerConnectionCode({
@@ -220,9 +291,9 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
       );
     });
 
-  const finishHandoff = async (
-    handoff: LocalServiceHandoff,
-    allowAlreadyManaged: boolean,
+  const finishSetup = async (
+    setup: LocalServiceSetupPending,
+    origin: 'request' | 'recovery',
   ): Promise<
     | { readonly kind: 'active_tasks' }
     | {
@@ -235,38 +306,60 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     const setupPackage = await input.resolveSetupPackage(closing.signal);
     const manager = requireManager(input.manager);
     const retirement = await manager.retireOwnedLocalHost(
-      handoff.allowInterruptActiveTasks ? 'interrupt_active_work' : 'refuse_active_work',
+      setup.allowInterruptActiveTasks ? 'interrupt_active_work' : 'refuse_active_work',
     );
     if (retirement.kind === 'active_tasks') {
-      if (!allowAlreadyManaged) await removeDocument(lifecyclePath);
       return { kind: 'active_tasks' };
     }
-    if (retirement.kind === 'not_owned' && !allowAlreadyManaged) {
-      await removeDocument(lifecyclePath);
+    if (retirement.kind === 'not_owned' && origin === 'request') {
       throw new Error('The Local Runtime Host is already managed outside this Desktop');
     }
+    try {
+      if (origin === 'request') await writeDocument(lifecyclePath, setup);
+      const reconcile = () => reconcileSetup(setup, setupPackage);
+      // Recovery may find that the operator already owns the root. Its setup
+      // can still restart that Host, so keep Desktop reconnect quiesced across
+      // the entire reconciliation just like every other managed-service change.
+      return retirement.kind === 'not_owned'
+        ? await manager.runManagedLocalHostChange(reconcile)
+        : await reconcile();
+    } finally {
+      if (retirement.kind === 'retired') retirement.resume();
+    }
+  };
+
+  const reconcileSetup = async (
+    setup: LocalServiceSetupPending,
+    setupPackage: DesktopRuntimeHostSetupPackage,
+    signal: AbortSignal = closing.signal,
+  ): Promise<{
+    readonly kind: 'complete';
+    readonly managed: LocalServiceManaged;
+    readonly peer: LocalPeerDescriptor;
+    readonly credential: string;
+  }> => {
     let target: LocalServiceTarget | undefined;
     try {
       const complete = await input.operator.runSetup(
         {
           setupPackage,
           clientDataRoot: input.clientDataRoot,
-          rootPath: handoff.rootPath,
+          rootPath: setup.rootPath,
           principalId: LOCAL_REMOTE_ACCESS_PRINCIPAL_ID,
-          coordinationRelays: handoff.coordinationRelays,
+          coordinationRelays: setup.coordinationRelays,
           expectedTarget: {
-            serviceId: handoff.rootId,
-            rootPath: handoff.rootPath,
-            rootId: handoff.rootId,
+            serviceId: setup.rootId,
+            rootPath: setup.rootPath,
+            rootId: setup.rootId,
           },
-          signal: closing.signal,
+          signal,
         },
         () => undefined,
       );
       if (
-        complete.serviceId !== handoff.rootId ||
-        complete.rootPath !== handoff.rootPath ||
-        complete.rootId !== handoff.rootId ||
+        complete.serviceId !== setup.rootId ||
+        complete.rootPath !== setup.rootPath ||
+        complete.rootId !== setup.rootId ||
         !DEPLOYMENT_ID_PATTERN.test(complete.deploymentId) ||
         !complete.directPeer
       ) {
@@ -281,13 +374,10 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           rootId: complete.rootId,
           deploymentId: complete.deploymentId,
         },
-        handoff.rootPath,
+        setup.rootPath,
       );
       const peer = requireEnabledPeer({ state: 'enabled', ...complete.directPeer });
-      const managed: LocalServiceManaged = {
-        ...target,
-        state: 'managed',
-      };
+      const managed: LocalServiceManaged = { ...target, state: 'managed' };
       await writeDocument(lifecyclePath, managed);
       return { kind: 'complete', managed, peer, credential: complete.credential };
     } catch (error) {
@@ -302,6 +392,49 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
         );
       }
       throw error;
+    }
+  };
+
+  const recoverLegacyHandoff = async (
+    legacy: LocalServiceLegacyHandoff,
+  ): Promise<
+    | { readonly kind: 'active_tasks' }
+    | { readonly kind: 'external' }
+    | { readonly kind: 'complete'; readonly managed: LocalServiceManaged }
+  > => {
+    const pending = pendingSetup(legacy);
+    const authority = await adoptCommittedSetup(pending);
+    if (authority.kind === 'managed') {
+      return { kind: 'complete', managed: authority.managed };
+    }
+    if (authority.kind === 'transition') {
+      await writeDocument(lifecyclePath, pending);
+      return finishSetup(pending, 'recovery');
+    }
+
+    const manager = requireManager(input.manager);
+    const retirement = await manager.retireOwnedLocalHost(
+      legacy.allowInterruptActiveTasks ? 'interrupt_active_work' : 'refuse_active_work',
+    );
+    if (retirement.kind === 'active_tasks') return { kind: 'active_tasks' };
+    if (retirement.kind === 'not_owned') {
+      const raced = await adoptCommittedSetup(pending);
+      if (raced.kind === 'managed') {
+        return { kind: 'complete', managed: raced.managed };
+      }
+      if (raced.kind === 'absent') {
+        await removeDocument(lifecyclePath);
+        return { kind: 'external' };
+      }
+    }
+
+    try {
+      await writeDocument(lifecyclePath, pending);
+      const setupPackage = await input.resolveSetupPackage(closing.signal);
+      const reconcile = () => reconcileSetup(pending, setupPackage);
+      return retirement.kind === 'not_owned'
+        ? await manager.runManagedLocalHostChange(reconcile)
+        : await reconcile();
     } finally {
       if (retirement.kind === 'retired') retirement.resume();
     }
@@ -486,13 +619,38 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
   input.ipcMain.handle(channels[5], (_event, value: unknown) => uninstall(value));
 
   return {
+    recoverManagedSetup: async (signal) => {
+      if (!supported(input.directPeerAvailable)) return false;
+      const operationSignal = signal ? AbortSignal.any([signal, closing.signal]) : closing.signal;
+      operationSignal.throwIfAborted();
+      const observed = await readLifecycle(lifecyclePath, input.rootPath, input.rootId);
+      if (observed?.state !== 'setupPending' && observed?.state !== 'handoff') return false;
+      return serialize(async () => {
+        operationSignal.throwIfAborted();
+        const pending = await readLifecycle(lifecyclePath, input.rootPath, input.rootId);
+        if (pending?.state !== 'setupPending' && pending?.state !== 'handoff') return false;
+        const committed = pendingSetup(pending);
+        const authority = await adoptCommittedSetup(committed);
+        if (authority.kind === 'absent') return false;
+        if (authority.kind === 'managed') return true;
+        if (pending.state === 'handoff') await writeDocument(lifecyclePath, committed);
+        const setupPackage = await input.resolveSetupPackage(operationSignal);
+        await reconcileSetup(committed, setupPackage, operationSignal);
+        return true;
+      });
+    },
     recover: () =>
       serialize(async () => {
         if (!supported(input.directPeerAvailable)) return;
         const lifecycle = await readLifecycle(lifecyclePath, input.rootPath, input.rootId);
         if (!lifecycle) return;
         if (lifecycle.state === 'handoff') {
-          await finishHandoff(lifecycle, true);
+          await recoverLegacyHandoff(lifecycle);
+          return;
+        }
+        if (lifecycle.state === 'setupPending') {
+          const committed = await adoptCommittedSetup(lifecycle);
+          if (committed.kind !== 'managed') await finishSetup(lifecycle, 'recovery');
           return;
         }
         if (lifecycle.state === 'uninstalling') {
@@ -694,6 +852,19 @@ function requireManaged(lifecycle: LocalServiceLifecycle | undefined): LocalServ
   return lifecycle;
 }
 
+function pendingSetup(
+  intent: LocalServiceSetupPending | LocalServiceLegacyHandoff,
+): LocalServiceSetupPending {
+  return {
+    schemaVersion: 1,
+    state: 'setupPending',
+    rootPath: intent.rootPath,
+    rootId: intent.rootId,
+    coordinationRelays: intent.coordinationRelays,
+    allowInterruptActiveTasks: intent.allowInterruptActiveTasks,
+  };
+}
+
 function managedLifecycle(intent: LocalServiceTarget): LocalServiceManaged {
   return {
     schemaVersion: 1,
@@ -726,7 +897,7 @@ async function readLifecycle(
   ) {
     throw new Error('Local Runtime Host service lifecycle is invalid');
   }
-  if (value.state === 'handoff') {
+  if (value.state === 'setupPending' || value.state === 'handoff') {
     assertExactKeys(value, [
       'schemaVersion',
       'state',
@@ -738,11 +909,11 @@ async function readLifecycle(
     if (
       typeof value.allowInterruptActiveTasks !== 'boolean'
     ) {
-      throw new Error('Local Runtime Host handoff intent is invalid');
+      throw new Error('Local Runtime Host setup intent is invalid');
     }
     return {
       schemaVersion: 1,
-      state: 'handoff',
+      state: value.state,
       rootPath,
       rootId,
       coordinationRelays: requireAddresses(value.coordinationRelays),
diff --git a/native/runtime-host-peer/Cargo.toml b/native/runtime-host-peer/Cargo.toml
index 01e367934..35c4f35a3 100644
--- a/native/runtime-host-peer/Cargo.toml
+++ b/native/runtime-host-peer/Cargo.toml
@@ -49,3 +49,7 @@ tokio = { version = "1.53", features = ["fs", "io-util", "rt-multi-thread", "syn
 
 [build-dependencies]
 napi-build = "2.4"
+
+[profile.release]
+# build.mjs strips the copied addon once; Cargo stripping every intermediate artifact is redundant.
+strip = "none"
diff --git a/packages/cli/src/__tests__/runtime-host-setup.test.ts b/packages/cli/src/__tests__/runtime-host-setup.test.ts
index 6c6f1f4e8..0854089df 100644
--- a/packages/cli/src/__tests__/runtime-host-setup.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-setup.test.ts
@@ -59,6 +59,7 @@ import {
   resolveRuntimeHostManagedDeploymentRoot,
 } from '../runtime-host-managed-deployment.js';
 import { runRuntimeHostSetupCli } from '../runtime-host-setup-command.js';
+import { RuntimeHostAccessUnavailableError } from '../runtime-host-access-command.js';
 import { manageRuntimeHostManagedLifecycle } from '../runtime-host-managed-lifecycle-manager.js';
 import {
   resolveRuntimeHostLifecycleProvider,
@@ -86,6 +87,7 @@ test('on-demand setup installs one exact deployment without a service backend',
   const outputs: string[] = [];
   let rootId = '';
   let projectedOperatorDeploymentRoot = '';
+  let pairingAttempts = 0;
   t.after(async () => {
     if (previousDataHome === undefined) delete process.env.XDG_DATA_HOME;
     else process.env.XDG_DATA_HOME = previousDataHome;
@@ -166,16 +168,20 @@ test('on-demand setup installs one exact deployment without a service backend',
       projectedOperatorDeploymentRoot = desired?.deploymentRoot ?? '';
     },
     verifyOperator: async () => undefined,
-    replaceCredential: async () => ({
-      rootId,
-      credential: 'secret-token',
-      credentialId: 'credential-1',
-      principalKind: 'remote_owner' as const,
-      principalId: 'desktop:client-1',
-      operationGrants: [] as const,
-      canPublishClientCapabilities: false,
-      canUseHostPaths: false,
-    }),
+    replaceCredential: async () => {
+      pairingAttempts += 1;
+      if (pairingAttempts === 1) throw new RuntimeHostAccessUnavailableError('unavailable');
+      return {
+        rootId,
+        credential: 'secret-token',
+        credentialId: 'credential-1',
+        principalKind: 'remote_owner' as const,
+        principalId: 'desktop:client-1',
+        operationGrants: [] as const,
+        canPublishClientCapabilities: false,
+        canUseHostPaths: false,
+      };
+    },
     verifyCredential: async ({ endpoint, rootId: expectedRootId }) => {
       assert.equal(endpoint, 'ws://127.0.0.1:43210/runtime-host');
       assert.equal(expectedRootId, rootId);
@@ -183,6 +189,7 @@ test('on-demand setup installs one exact deployment without a service backend',
     writeOutput: (value) => outputs.push(value),
   } satisfies NonNullable<Parameters<typeof runRuntimeHostSetupCli>[1]>;
   assert.equal(await runRuntimeHostSetupCli(options, overrides), 0);
+  assert.equal(pairingAttempts, 2);
   const complete = outputs
     .map(decodeRuntimeHostSetupFrame)
     .find((frame) => frame?.kind === 'complete');
diff --git a/packages/cli/src/runtime-host-access-command.ts b/packages/cli/src/runtime-host-access-command.ts
index 40f4bd207..47311f16a 100644
--- a/packages/cli/src/runtime-host-access-command.ts
+++ b/packages/cli/src/runtime-host-access-command.ts
@@ -45,6 +45,16 @@ const PROTOCOL = {
   max: RUNTIME_HOST_PROTOCOL_VERSION,
 } as const;
 
+export class RuntimeHostAccessUnavailableError extends Error {
+  constructor(
+    readonly reason: string,
+    options?: ErrorOptions,
+  ) {
+    super(`Runtime Host service is not available (${reason})`, options);
+    this.name = 'RuntimeHostAccessUnavailableError';
+  }
+}
+
 export interface RuntimeHostAccessIssueOptions {
   readonly rootPath: string;
   readonly expectedRootId?: string;
@@ -350,12 +360,13 @@ export async function revokeRuntimeHostAccessCredential(
 }
 
 async function connectLocalOwner(rootPath: string, expectedRootId?: string) {
-  const result = await connectExistingRuntimeHost({
-    rootPath,
-    protocol: PROTOCOL,
-  });
+  const result = await connectExistingRuntimeHost({ rootPath, protocol: PROTOCOL }).catch(
+    (error: unknown) => {
+      throw new RuntimeHostAccessUnavailableError('connection_failed', { cause: error });
+    },
+  );
   if (result.kind !== 'connected') {
-    throw new Error(`Runtime Host service is not available (${result.kind})`);
+    throw new RuntimeHostAccessUnavailableError(result.kind);
   }
   if (expectedRootId && result.connection.rootId !== expectedRootId) {
     await result.connection.close();
diff --git a/packages/cli/src/runtime-host-setup-command.ts b/packages/cli/src/runtime-host-setup-command.ts
index 275213356..5a933d938 100644
--- a/packages/cli/src/runtime-host-setup-command.ts
+++ b/packages/cli/src/runtime-host-setup-command.ts
@@ -22,6 +22,7 @@ import { homedir } from 'node:os';
 import { join, resolve } from 'node:path';
 import { isDeepStrictEqual } from 'node:util';
 import { truncateUtf8 } from '@maka/core/diagnostic-log';
+import { generalizedErrorMessage } from '@maka/core/redaction';
 import {
   activateRuntimeHostManagedDeployment,
   connectRemoteRuntimeHost,
@@ -47,6 +48,7 @@ import {
   prepareRuntimeHostAccessCredential,
   replaceRuntimeHostAccessCredential,
   revokeRuntimeHostAccessCredential,
+  RuntimeHostAccessUnavailableError,
   type RuntimeHostAccessPreset,
 } from './runtime-host-access-command.js';
 import {
@@ -118,6 +120,8 @@ import {
 import { activateRuntimeHostManagedDeploymentWithReconciliation } from './runtime-host-activation-command.js';
 
 const SETUP_LOCK_TIMEOUT_MS = 5 * 60_000;
+const PAIRING_AVAILABILITY_TIMEOUT_MS = 10_000;
+const PAIRING_AVAILABILITY_POLL_MS = 100;
 
 export interface RuntimeHostSetupCliOptions {
   readonly json: boolean;
@@ -1075,20 +1079,35 @@ async function pairAndVerifyRuntimeHostSetup(
     const pairCredential = options.deferPairingCommit
       ? deps.prepareCredential
       : deps.replaceCredential;
-    paired = await pairCredential({
+    const credentialInput = {
       rootPath: target.rootPath,
-      principalKind: 'remote_owner',
+      principalKind: 'remote_owner' as const,
       principalId: options.principalId,
       operationGrants: [],
       canPublishClientCapabilities: false,
       canUseHostPaths: false,
       preset: options.preset,
       ...(options.bindPairingToClient ? { bindClientInstance: true } : {}),
-    });
+    };
+    const deadline = Date.now() + PAIRING_AVAILABILITY_TIMEOUT_MS;
+    while (true) {
+      try {
+        paired = await pairCredential(credentialInput);
+        break;
+      } catch (error) {
+        if (!(error instanceof RuntimeHostAccessUnavailableError) || Date.now() >= deadline) {
+          throw error;
+        }
+        await new Promise<void>((resolveWait) =>
+          setTimeout(resolveWait, Math.min(PAIRING_AVAILABILITY_POLL_MS, deadline - Date.now())),
+        );
+      }
+    }
   } catch (error) {
+    const reason = generalizedErrorMessage(error, 'Runtime Host access service is unavailable');
     throw new RuntimeHostSetupError(
       'pairing_failed',
-      'Runtime Host could not pair the requested Client identity',
+      `Runtime Host could not pair the requested Client identity: ${reason}`,
       { cause: error },
     );
   }
diff --git a/packages/runtime-host/src/__tests__/peer-mesh.test.ts b/packages/runtime-host/src/__tests__/peer-mesh.test.ts
index 78c598fe6..8ee98365f 100644
--- a/packages/runtime-host/src/__tests__/peer-mesh.test.ts
+++ b/packages/runtime-host/src/__tests__/peer-mesh.test.ts
@@ -233,7 +233,7 @@ test('closed Mesh records do not permanently consume membership capacity', async
       if (index === 0) assert.equal(await hasActivePeerMeshMembership(root, 'peer-a'), false);
     }
     assert.equal((await node.create()).roster.roster.closed, false);
-    assert.equal(node.status().length, 16);
+    assert.equal(node.status().length, 1);
   } finally {
     await node.close();
     await peer.close();
diff --git a/packages/runtime-host/src/peer-mesh/node.ts b/packages/runtime-host/src/peer-mesh/node.ts
index edbbdcbe5..4aa43b614 100644
--- a/packages/runtime-host/src/peer-mesh/node.ts
+++ b/packages/runtime-host/src/peer-mesh/node.ts
@@ -233,10 +233,7 @@ class PeerMeshNodeImpl implements PeerMeshNode {
     return Object.freeze(
       this.#store
         .read()
-        .meshes.filter(
-          (state) =>
-            state.role === 'authority' || state.roster.roster.members.includes(identity.peerId),
-        )
+        .meshes.filter((state) => isActiveMembership(state, identity.peerId))
         .map((state) => peerMeshStatus(state, identity, this.#store.read().routes, this.#now())),
     );
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org