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