feat(FN-3707): implement mesh auth snapshot replication across core, engine

The merge delivers two major features: mesh auth snapshot synchronization across nodes (FN-3707 steps 2/4/5, including core types, central-core plumbing, peer-exchange updates, and settings/node sync route helpers) and a bundled roadmap plugin with chat UX improvements (FN-3162, FN-3756, FN-3771, FN

Fusion-Task-Id: FN-3707
This commit is contained in:
Fusion
2026-05-08 18:15:15 -07:00
committed by gsxdsm
parent 992771b14b
commit a087aa4e57
14 changed files with 226 additions and 76 deletions

View File

@@ -458,16 +458,29 @@ export const registerMeshRoutes: ApiRouteRegistrar = (ctx) => {
}
});
if (sharedState.authMaterial) {
emitRemoteRouteDiagnostic({
route: "mesh-sync",
message: "Ignoring sharedState.authMaterial; use dedicated auth sync routes (/api/nodes/:id/auth/*)",
nodeId: senderNodeId,
upstreamPath: "/api/mesh/sync",
operationStage: "apply-shared-state-auth-material",
level: "info",
});
}
await applyDomain("auth-material", async () => {
if (!sharedState.authMaterial) return;
validateSnapshotEnvelope(sharedState.authMaterial);
const applied = central.applyAuthMaterialSnapshot(sharedState.authMaterial as Parameters<typeof central.applyAuthMaterialSnapshot>[0]);
const { AuthStorage } = await import("@mariozechner/pi-coding-agent");
const { getFusionAuthPath } = await import("../auth-paths.js");
const authStorage = AuthStorage.create(getFusionAuthPath());
for (const [providerId, credential] of Object.entries(applied.providerAuth)) {
if (credential.type === "api_key" && credential.key) {
authStorage.set(providerId, { type: "api_key", key: credential.key });
continue;
}
if (credential.type === "oauth" && credential.accessToken && credential.refreshToken && typeof credential.expires === "number") {
authStorage.set(providerId, {
type: "oauth",
access: credential.accessToken,
refresh: credential.refreshToken,
expires: credential.expires,
...(credential.accountId ? { accountId: credential.accountId } : {}),
});
}
}
});
// Intentionally do not close this per-request AgentStore wrapper.
// AgentStore uses a process-wide DB cache by rootDir; closing here would
@@ -518,6 +531,11 @@ export const registerMeshRoutes: ApiRouteRegistrar = (ctx) => {
const localGlobal = await store.getGlobalSettingsStore().getSettings();
return central.getProjectSettingsSnapshot(localGlobal);
});
await collectSnapshot("authMaterial", async () => {
const authPathsModule = await import("./register-settings-sync-helpers.js");
const allProviders = await authPathsModule.readStoredAuthProvidersFromDisk();
return central.getAuthMaterialSnapshot(authPathsModule.toProviderAuthEntries(allProviders));
});
await central.close();

View File

@@ -1,5 +1,5 @@
import { readFile as fsReadFile } from "node:fs/promises";
import type { NodeConfig } from "@fusion/core";
import type { AuthMaterialSnapshot, NodeConfig, ProviderAuthEntry } from "@fusion/core";
import { ApiError } from "../api-error.js";
import { getAuthFileCandidates, type StoredAuthProvider } from "../auth-paths.js";
@@ -19,6 +19,37 @@ export async function readStoredAuthProvidersFromDisk(): Promise<Record<string,
return merged;
}
export function toProviderAuthEntries(
providers: Record<string, StoredAuthProvider>,
): Record<string, ProviderAuthEntry> {
const providerAuth: Record<string, ProviderAuthEntry> = {};
for (const [providerId, credential] of Object.entries(providers)) {
if (credential?.type === "api_key" && credential.key) {
providerAuth[providerId] = { type: "api_key", key: credential.key };
continue;
}
if (
credential?.type === "oauth"
&& typeof credential.access === "string"
&& typeof credential.refresh === "string"
&& typeof credential.expires === "number"
) {
providerAuth[providerId] = {
type: "oauth",
accessToken: credential.access,
refreshToken: credential.refresh,
expires: credential.expires,
accountId: credential.accountId,
};
}
}
return providerAuth;
}
export function getProviderNamesFromAuthSnapshot(snapshot: AuthMaterialSnapshot): string[] {
return Object.keys(snapshot.payload.providerAuth ?? {});
}
/**
* Validate node and make an authenticated fetch call to a remote node.
* Returns parsed JSON on success, throws ApiError on failure.

View File

@@ -1,6 +1,6 @@
import { ApiError, badRequest } from "../api-error.js";
import { getFusionAuthPath } from "../auth-paths.js";
import { readStoredAuthProvidersFromDisk } from "./register-settings-sync-helpers.js";
import { readStoredAuthProvidersFromDisk, toProviderAuthEntries } from "./register-settings-sync-helpers.js";
import type { ApiRouteRegistrar } from "./types.js";
export const registerSettingsSyncInboundRoutes: ApiRouteRegistrar = (ctx) => {
@@ -110,12 +110,12 @@ export const registerSettingsSyncInboundRoutes: ApiRouteRegistrar = (ctx) => {
throw new ApiError(401, "Invalid apiKey");
}
const { providers, sourceNodeId, timestamp } = req.body || {};
const { authMaterial, sourceNodeId, timestamp } = req.body || {};
// Validate required fields
if (!providers || typeof providers !== "object") {
if (!authMaterial || typeof authMaterial !== "object") {
await central.close();
throw badRequest("Missing required field: providers");
throw badRequest("Missing required field: authMaterial");
}
if (!sourceNodeId) {
await central.close();
@@ -130,14 +130,23 @@ export const registerSettingsSyncInboundRoutes: ApiRouteRegistrar = (ctx) => {
const { AuthStorage } = await import("@mariozechner/pi-coding-agent");
const authStorage = AuthStorage.create(getFusionAuthPath());
const applyResult = central.applyAuthMaterialSnapshot(authMaterial);
const receivedProviders: string[] = [];
for (const [providerId, credential] of Object.entries(providers)) {
if (typeof credential === "object" && credential !== null) {
const cred = credential as { type: string; key?: string; access?: string; refresh?: string; expires?: number; accountId?: string };
if (cred.type === "api_key" && cred.key) {
authStorage.set(providerId, { type: "api_key", key: cred.key });
receivedProviders.push(providerId);
}
for (const [providerId, credential] of Object.entries(applyResult.providerAuth)) {
if (credential.type === "api_key" && credential.key) {
authStorage.set(providerId, { type: "api_key", key: credential.key });
receivedProviders.push(providerId);
continue;
}
if (credential.type === "oauth" && credential.accessToken && credential.refreshToken && typeof credential.expires === "number") {
authStorage.set(providerId, {
type: "oauth",
access: credential.accessToken,
refresh: credential.refreshToken,
expires: credential.expires,
...(credential.accountId ? { accountId: credential.accountId } : {}),
});
receivedProviders.push(providerId);
}
}
@@ -194,19 +203,12 @@ export const registerSettingsSyncInboundRoutes: ApiRouteRegistrar = (ctx) => {
const localPeerInfo = await central.getLocalPeerInfo();
const allProviders = await readStoredAuthProvidersFromDisk();
// Filter to only API-key-based providers (skip OAuth)
const apiKeyProviders: Record<string, { type: string; key: string }> = {};
for (const [providerId, cred] of Object.entries(allProviders)) {
if (cred.type === "api_key" && cred.key) {
apiKeyProviders[providerId] = { type: "api_key", key: cred.key };
}
}
const authMaterial = central.getAuthMaterialSnapshot(toProviderAuthEntries(allProviders));
await central.close();
res.json({
providers: apiKeyProviders,
authMaterial,
sourceNodeId: localPeerInfo.nodeId,
timestamp: new Date().toISOString(),
});

View File

@@ -2,7 +2,7 @@ import type { ProjectSettings } from "@fusion/core";
import { basename } from "node:path";
import { ApiError, badRequest, notFound } from "../api-error.js";
import { getFusionAuthPath } from "../auth-paths.js";
import { fetchFromRemoteNode, readStoredAuthProvidersFromDisk } from "./register-settings-sync-helpers.js";
import { fetchFromRemoteNode, readStoredAuthProvidersFromDisk, toProviderAuthEntries } from "./register-settings-sync-helpers.js";
import type { ApiRouteRegistrar } from "./types.js";
export const registerSettingsSyncRoutes: ApiRouteRegistrar = (ctx) => {
@@ -338,26 +338,14 @@ export const registerSettingsSyncRoutes: ApiRouteRegistrar = (ctx) => {
const authStorage = AuthStorage.create(getFusionAuthPath());
if (direction === "push") {
// Get OAuth provider IDs to exclude
const oauthProviders = authStorage.getOAuthProviders();
const oauthIds = new Set(oauthProviders.map((p) => p.id));
const allProviders = await readStoredAuthProvidersFromDisk();
// Filter to only API-key-based providers (skip OAuth)
const apiKeyProviders: Record<string, { type: string; key: string }> = {};
for (const [providerId, cred] of Object.entries(allProviders)) {
if (oauthIds.has(providerId)) continue;
if (cred.type === "api_key" && cred.key) {
apiKeyProviders[providerId] = { type: "api_key", key: cred.key };
}
}
const authMaterial = central.getAuthMaterialSnapshot(toProviderAuthEntries(allProviders));
// Send to remote
await fetchFromRemoteNode(node, "/api/settings/auth-receive", {
method: "POST",
body: {
providers: apiKeyProviders,
authMaterial,
sourceNodeId: localPeerInfo.nodeId,
timestamp,
},
@@ -370,7 +358,7 @@ export const registerSettingsSyncRoutes: ApiRouteRegistrar = (ctx) => {
await central.close();
const providerNames = Object.keys(apiKeyProviders);
const providerNames = Object.keys(authMaterial.payload.providerAuth ?? {});
emitAuthSyncAuditLog({
operation: "sync",
direction: "push",
@@ -384,17 +372,30 @@ export const registerSettingsSyncRoutes: ApiRouteRegistrar = (ctx) => {
} else {
// Pull: fetch remote auth and apply locally
const remoteAuth = await fetchFromRemoteNode(node, "/api/settings/auth-export") as {
providers: Record<string, { type: string; key: string }>;
authMaterial: import("@fusion/core").AuthMaterialSnapshot;
sourceNodeId: string;
timestamp: string;
};
const applied = central.applyAuthMaterialSnapshot(remoteAuth.authMaterial);
// Write received credentials to local AuthStorage
const syncedProviders: string[] = [];
for (const [providerId, credential] of Object.entries(remoteAuth.providers || {})) {
for (const [providerId, credential] of Object.entries(applied.providerAuth)) {
if (credential.type === "api_key" && credential.key) {
authStorage.set(providerId, { type: "api_key", key: credential.key });
syncedProviders.push(providerId);
continue;
}
if (credential.type === "oauth" && credential.accessToken && credential.refreshToken && typeof credential.expires === "number") {
authStorage.set(providerId, {
type: "oauth",
access: credential.accessToken,
refresh: credential.refreshToken,
expires: credential.expires,
...(credential.accountId ? { accountId: credential.accountId } : {}),
});
syncedProviders.push(providerId);
}
}