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/09/02 06:37:36 UTC

(maka) branch main updated: fix(runtime-host): retain authenticated owner routes (#4529)

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 cdb293998f fix(runtime-host): retain authenticated owner routes (#4529)
cdb293998f is described below

commit cdb293998f601fc0a65056ede587504df230e541
Author: Wang <m4...@gmail.com>
AuthorDate: Wed Sep 2 14:37:31 2026 +0800

    fix(runtime-host): retain authenticated owner routes (#4529)
    
    Persist authenticated direct-peer route rotations in the existing Owner profile so reconnects and restarts do not fall back to stale connection-code hints. Fence updates by profile incarnation and leave credentials and authority unchanged.
    
    Generated-by: Codex
---
 .../__tests__/runtime-host-profile-service.test.ts | 57 +++++++++++++++++++
 apps/desktop/src/main/runtime-host-boot.ts         | 23 +++++---
 .../src/main/runtime-host-desktop-manager.ts       |  4 +-
 .../src/main/runtime-host-profile-service.ts       | 64 +++++++++++++++++++++-
 .../src/__tests__/runtime-host-cli-context.test.ts |  4 ++
 .../__tests__/runtime-host-profile-command.test.ts |  1 +
 .../src/__tests__/host-profile.test.ts             |  9 +++
 packages/runtime-host/src/client/host-profile.ts   | 41 ++++++++++++++
 8 files changed, 193 insertions(+), 10 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts b/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
index 12a9886d42..c51e8d7ad8 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
@@ -679,6 +679,63 @@ test('classifies connection-code failures without exposing transport errors to t
   );
 });
 
+test('persists authenticated Owner routes imported through a connection code', async () => {
+  const root = await clientRoot();
+  const catalog = createClientRuntimeHostProfileCatalog(root);
+  const startup = await resolveDesktopRuntimeHostStartup(root, { catalog });
+  const staleTransport = {
+    kind: 'libp2p-direct' as const,
+    peerId: '12D3KooWpeer',
+    routeHints: ['/ip4/192.0.2.8/udp/44001/quic-v1'],
+    coordinationRelays: ['/memory/stale-relay'],
+  };
+  const freshEndpoint = {
+    peerId: staleTransport.peerId,
+    routeHints: ['/ip4/198.51.100.9/udp/44002/quic-v1'],
+    coordinationRelays: ['/memory/fresh-relay'],
+  };
+  let observedIncarnation: string | undefined;
+  const service = createDesktopRuntimeHostProfileService({
+    clientDataRoot: root,
+    startup,
+    catalog,
+    states: () => [connectingLocal()],
+    enable: async (target, _sshInteraction, onPeerEndpoint) => {
+      observedIncarnation = target.profileIncarnationId;
+      assert.deepEqual(
+        target.profile.kind === 'remote' ? target.profile.transport : undefined,
+        staleTransport,
+      );
+      assert.ok(onPeerEndpoint);
+      onPeerEndpoint(freshEndpoint);
+    },
+    disable: async () => undefined,
+    setDefault: () => undefined,
+    finalizePairing: async () => undefined,
+  });
+
+  const result = await service.importConnectionCode(
+    encodeRuntimeHostOwnerConnectionCode({
+      name: 'Other computer',
+      rootId: ROOT_ID,
+      transport: staleTransport,
+      credential: 'pending-owner-token',
+    }),
+  );
+
+  assert.equal(result.kind, 'connected');
+  if (result.kind !== 'connected') return;
+  const persisted = await catalog.resolve(result.profileId);
+  assert.equal(persisted.credential, 'pending-owner-token');
+  assert.equal(persisted.profileIncarnationId, observedIncarnation);
+  assert.deepEqual(
+    persisted.profile.kind === 'remote' ? persisted.profile.transport : undefined,
+    { kind: 'libp2p-direct', ...freshEndpoint },
+  );
+  const restarted = await resolveDesktopRuntimeHostStartup(root, { catalog });
+  assert.deepEqual(restarted.remotes, [persisted]);
+});
+
 test("finishes a persisted pairing after Desktop restarts before finalization", async () => {
   const root = await clientRoot();
   const catalog = await stageInterruptedPairing(root);
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 8ac6146168..7a1c7577e6 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -545,7 +545,7 @@ const runtimeHostProfileService = createDesktopRuntimeHostProfileService({
   catalog: runtimeHostProfileCatalog,
   credentialStore: runtimeHostCredentialStore,
   states: () => runtimeHostManager?.entries() ?? [],
-  enable: async (target, sshInteraction) => {
+  enable: async (target, sshInteraction, onPeerEndpoint) => {
     if (target.profile.kind === 'local') {
       throw new Error('A resolved non-local Runtime Host profile is required');
     }
@@ -553,13 +553,20 @@ const runtimeHostProfileService = createDesktopRuntimeHostProfileService({
       throw new Error('A remote Runtime Host profile requires an access credential');
     }
     if (!runtimeHostManager) throw new Error("Runtime Host manager is unavailable");
-    await runtimeHostManager.enable({
-      profile: target.profile,
-      ...(target.credential ? { credential: target.credential } : {}),
-      ...(target.profile.kind === 'remote' && target.profile.transport.kind === "ssh"
-        ? { sshInteraction }
-        : {}),
-    });
+    await runtimeHostManager.enable(
+      {
+        profile: target.profile,
+        ...(target.credential ? { credential: target.credential } : {}),
+        ...(target.profile.kind === 'remote' && target.profile.transport.kind === "ssh"
+          ? { sshInteraction }
+          : {}),
+      },
+      onPeerEndpoint
+        ? (status) => {
+            if (status.peerEndpoint) onPeerEndpoint(status.peerEndpoint);
+          }
+        : undefined,
+    );
   },
   disable: async (profileId) => {
     if (!runtimeHostManager) throw new Error("Runtime Host manager is unavailable");
diff --git a/apps/desktop/src/main/runtime-host-desktop-manager.ts b/apps/desktop/src/main/runtime-host-desktop-manager.ts
index 60dd17f72d..914aeb1b5b 100644
--- a/apps/desktop/src/main/runtime-host-desktop-manager.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-manager.ts
@@ -71,6 +71,7 @@ export interface RuntimeHostDesktopManager {
   unobserveSession(observerId: string): Promise<void>;
   enable(
     profileTarget: DesktopRuntimeHostCandidateStartInput['profileTarget'],
+    onHostStatus?: (status: HostStatusResult) => void,
   ): Promise<void>;
   mountGuest(
     profileTarget: NonNullable<DesktopRuntimeHostCandidateStartInput['profileTarget']>,
@@ -493,13 +494,14 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
 
   async enable(
     profileTarget: DesktopRuntimeHostCandidateStartInput['profileTarget'],
+    onHostStatus?: (status: HostStatusResult) => void,
   ): Promise<void> {
     if (!profileTarget) throw new Error('A non-local Runtime Host profile is required');
     if (isSessionGuestProfile(profileTarget.profile)) {
       throw new Error('Session Guest targets must be mounted instead of enabled as profiles');
     }
     return this.#mutateTarget(profileTarget.profile.id, () =>
-      this.#enable(profileTarget, false),
+      this.#enable(profileTarget, false, undefined, undefined, onHostStatus),
     );
   }
 
diff --git a/apps/desktop/src/main/runtime-host-profile-service.ts b/apps/desktop/src/main/runtime-host-profile-service.ts
index a310141439..b75137439c 100644
--- a/apps/desktop/src/main/runtime-host-profile-service.ts
+++ b/apps/desktop/src/main/runtime-host-profile-service.ts
@@ -40,6 +40,7 @@ import {
   type RuntimeHostProfileCatalog,
 } from "@maka/runtime-host/client";
 import { runtimeHostAccessCredentialFingerprint } from "@maka/runtime-host/operator";
+import type { HostPeerEndpoint } from '@maka/runtime-host/protocol';
 import type { CredentialStore } from "@maka/storage/credential-store";
 import { withFileUpdateLock } from "@maka/storage/file-update-lock";
 import type {
@@ -295,6 +296,7 @@ export function createDesktopRuntimeHostProfileService(input: {
   readonly enable: (
     target: ResolvedRuntimeHostProfile,
     sshInteraction: "terminal" | "batch",
+    onPeerEndpoint?: (endpoint: HostPeerEndpoint) => void,
   ) => Promise<void>;
   readonly disable: (profileId: string) => Promise<void>;
   readonly finalizePairing: (profileId: string) => Promise<void>;
@@ -488,7 +490,9 @@ export function createDesktopRuntimeHostProfileService(input: {
   ): Promise<void> => {
     try {
       const activationTarget = await resolveActivationTarget(target);
-      await input.enable(activationTarget, sshInteraction);
+      const routeObserver = createAuthenticatedPeerRouteObserver(catalog, activationTarget);
+      await input.enable(activationTarget, sshInteraction, routeObserver?.observe);
+      await routeObserver?.flush();
       const current = await catalog.resolve(activationTarget.profile.id).catch(() => undefined);
       if (!current || !sameResolvedRuntimeHostProfileTarget(current, activationTarget)) {
         await input.disable(activationTarget.profile.id);
@@ -1325,6 +1329,64 @@ export function createDesktopRuntimeHostProfileService(input: {
   };
 }
 
+interface AuthenticatedPeerRouteObserver {
+  readonly observe: (endpoint: HostPeerEndpoint) => void;
+  readonly flush: () => Promise<void>;
+}
+
+function createAuthenticatedPeerRouteObserver(
+  catalog: RuntimeHostProfileCatalog,
+  target: ResolvedRuntimeHostProfile,
+): AuthenticatedPeerRouteObserver | undefined {
+  if (
+    target.profile.kind !== 'remote' ||
+    target.profile.transport.kind !== 'libp2p-direct' ||
+    !target.profileIncarnationId
+  ) return undefined;
+  const expectedPeerId = target.profile.transport.peerId;
+  const incarnation = {
+    profile: target.profile,
+    profileIncarnationId: target.profileIncarnationId,
+  } as const;
+  let pending = Promise.resolve();
+  const observe = (endpoint: HostPeerEndpoint): void => {
+    if (
+      endpoint.peerId !== expectedPeerId ||
+      (endpoint.routeHints.length === 0 && endpoint.coordinationRelays.length === 0)
+    ) return;
+    pending = pending
+      .then(async () => {
+        await catalog.updateRemoteProfileIfCurrent(incarnation, (current) => {
+          if (current.transport.kind !== 'libp2p-direct') return current;
+          if (
+            sameStrings(current.transport.routeHints, endpoint.routeHints) &&
+            sameStrings(current.transport.coordinationRelays, endpoint.coordinationRelays)
+          ) return current;
+          return {
+            ...current,
+            transport: {
+              kind: 'libp2p-direct',
+              peerId: expectedPeerId,
+              routeHints: endpoint.routeHints,
+              coordinationRelays: endpoint.coordinationRelays,
+            },
+          };
+        });
+      })
+      .catch((error: unknown) => {
+        console.warn(
+          `[runtime-host] authenticated routes for profile ${target.profile.id} could not be saved:`,
+          asError(error),
+        );
+      });
+  };
+  return { observe, flush: () => pending };
+}
+
+function sameStrings(left: readonly string[], right: readonly string[]): boolean {
+  return left.length === right.length && left.every((value, index) => value === right[index]);
+}
+
 function managedDirectPeerProfileId(sourceProfileId: string): string {
   const digest = createHash('sha256').update(sourceProfileId).digest('hex').slice(0, 32);
   return `direct-${digest}`;
diff --git a/packages/cli/src/__tests__/runtime-host-cli-context.test.ts b/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
index 83fe1dad99..10c6bf6a29 100644
--- a/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
@@ -303,6 +303,9 @@ test('remote CLI profiles pin root identity and resolve credential outside the p
         rebindIfCurrent: async () => {
           throw new Error('unexpected write');
         },
+        updateRemoteProfileIfCurrent: async () => {
+          throw new Error('unexpected write');
+        },
         mutateRemoteProfileIfCurrent: async () => {
           throw new Error('unexpected write');
         },
@@ -573,6 +576,7 @@ function singleRemoteProfileCatalog(profile: RemoteRuntimeHostProfile): RuntimeH
     remove: async () => assert.fail('unexpected write'),
     removeIfCurrent: async () => assert.fail('unexpected write'),
     rebindIfCurrent: async () => assert.fail('unexpected write'),
+    updateRemoteProfileIfCurrent: async () => assert.fail('unexpected write'),
     mutateRemoteProfileIfCurrent: async () => assert.fail('unexpected write'),
     readRemoteProfileIfCurrent: async () => assert.fail('unexpected read'),
   };
diff --git a/packages/cli/src/__tests__/runtime-host-profile-command.test.ts b/packages/cli/src/__tests__/runtime-host-profile-command.test.ts
index bf972eec2d..15fee776b2 100644
--- a/packages/cli/src/__tests__/runtime-host-profile-command.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-profile-command.test.ts
@@ -236,6 +236,7 @@ function createProfileCatalogCapture(): {
     remove: async () => assert.fail('unexpected profile removal'),
     removeIfCurrent: async () => assert.fail('unexpected conditional profile removal'),
     rebindIfCurrent: async () => assert.fail('unexpected conditional profile rebind'),
+    updateRemoteProfileIfCurrent: async () => assert.fail('unexpected conditional update'),
     mutateRemoteProfileIfCurrent: async () => assert.fail('unexpected conditional mutation'),
     readRemoteProfileIfCurrent: async () => assert.fail('unexpected conditional read'),
   };
diff --git a/packages/runtime-host/src/__tests__/host-profile.test.ts b/packages/runtime-host/src/__tests__/host-profile.test.ts
index fcf5fd00fa..0e8c6b05ce 100644
--- a/packages/runtime-host/src/__tests__/host-profile.test.ts
+++ b/packages/runtime-host/src/__tests__/host-profile.test.ts
@@ -632,6 +632,15 @@ describe('Runtime Host profiles', () => {
       false,
     );
     assert.equal(staleMutationRan, false);
+    let staleUpdateRan = false;
+    assert.equal(
+      await catalog.updateRemoteProfileIfCurrent(firstIncarnation, (current) => {
+        staleUpdateRan = true;
+        return current;
+      }),
+      false,
+    );
+    assert.equal(staleUpdateRan, false);
   });
 
   test('pins a direct-peer profile to its PeerId while allowing route discovery to change', () => {
diff --git a/packages/runtime-host/src/client/host-profile.ts b/packages/runtime-host/src/client/host-profile.ts
index 1e77cb64bb..4ac5566570 100644
--- a/packages/runtime-host/src/client/host-profile.ts
+++ b/packages/runtime-host/src/client/host-profile.ts
@@ -225,6 +225,11 @@ export interface RuntimeHostProfileCatalog {
     readonly rebound: boolean;
     readonly document: RuntimeHostProfileDocument;
   }>;
+  /** Update mutable profile metadata while this exact profile lifetime remains current. */
+  updateRemoteProfileIfCurrent(
+    target: RuntimeHostRemoteProfileIncarnation,
+    update: (profile: RemoteRuntimeHostProfile) => RemoteRuntimeHostProfile,
+  ): Promise<boolean>;
   /** Serialize one sidecar mutation with catalog updates while this profile lifetime remains current. */
   mutateRemoteProfileIfCurrent(
     target: RuntimeHostRemoteProfileIncarnation,
@@ -1008,6 +1013,42 @@ class FileRuntimeHostProfileCatalog implements RuntimeHostProfileCatalog {
     });
   }
 
+  updateRemoteProfileIfCurrent(
+    target: RuntimeHostRemoteProfileIncarnation,
+    update: (profile: RemoteRuntimeHostProfile) => RemoteRuntimeHostProfile,
+  ): Promise<boolean> {
+    const expectedProfile = decodeRemoteRuntimeHostProfile(target.profile);
+    const expectedIncarnationId = requireProfileIncarnationId(target.profileIncarnationId);
+    return this.#exclusive(async () => {
+      const current = await this.#readSnapshot();
+      const profile = current.profiles.find(
+        (candidate): candidate is RemoteRuntimeHostProfile =>
+          candidate.id === expectedProfile.id && candidate.kind === 'remote',
+      );
+      if (!profile || !sameRemoteRuntimeHostProfileTarget(profile, expectedProfile)) return false;
+      const credential = await this.credentials.get(profile);
+      if (credential?.profileIncarnationId !== expectedIncarnationId) return false;
+      const value = update(profile);
+      if (value === profile) return true;
+      const updated = decodeRemoteRuntimeHostProfile(value);
+      if (
+        updated.id !== profile.id ||
+        !sameRemoteRuntimeHostProfileTarget(updated, profile) ||
+        runtimeHostProfileAccess(updated) !== runtimeHostProfileAccess(profile)
+      ) {
+        throw new Error('A Runtime Host profile metadata update must retain its connection');
+      }
+      const next = decodeRuntimeHostProfileDocument({
+        schemaVersion: PROFILE_SCHEMA_VERSION,
+        profiles: current.profiles.map((candidate) =>
+          candidate.id === updated.id ? updated : candidate,
+        ),
+      });
+      await writeProfileDocument(this.path, next);
+      return true;
+    });
+  }
+
   mutateRemoteProfileIfCurrent(
     target: RuntimeHostRemoteProfileIncarnation,
     mutation: (profile: RemoteRuntimeHostProfile) => Promise<void>,


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