[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,908 @@
import test from "node:test";
import assert from "node:assert/strict";
import { EncryptionService } from "../EncryptionService.ts";
import {
clearProviderMergeStateImpl,
commitRemoteInspectionImpl,
} from "./authMethods.ts";
import {
selectConvergentSyncToProviderResult,
syncToProviderImpl,
uploadToProviderImpl,
} from "./providerSyncMethods.ts";
import {
clearSyncBaseImpl,
loadSyncSnapshotsImpl,
saveSyncBaseImpl,
syncAllProvidersImpl,
} from "./syncAllStorageMethods.ts";
import type {
CloudProvider,
SyncedFile,
SyncPayload,
SyncResult,
} from "../../../domain/sync.ts";
import { setConvergentSyncLocalConfig } from "../convergentSyncConfig.ts";
function payload(hostId: string): SyncPayload {
return payloadWithHosts([hostId]);
}
function payloadWithHosts(hostIds: string[]): SyncPayload {
return {
hosts: hostIds.map((hostId) => ({
id: hostId,
label: hostId,
hostname: `${hostId}.example.com`,
port: 22,
username: "root",
tags: [],
os: "linux",
})),
keys: [],
identities: [],
proxyProfiles: [],
snippets: [],
customGroups: [],
snippetPackages: [],
portForwardingRules: [],
groupConfigs: [],
settings: undefined,
syncedAt: 0,
};
}
function remoteFile(provider: CloudProvider, version: number, updatedAt: number): SyncedFile {
return {
meta: {
version,
updatedAt,
deviceId: `${provider}-device`,
deviceName: provider,
appVersion: "0.0.0",
iv: "",
salt: "",
algorithm: "AES-256-GCM",
kdf: "PBKDF2",
kdfIterations: 1,
},
payload: provider,
};
}
test("provider identity changes clear v1 base, v2 baseline, and remote anchor together", () => {
const removed: string[] = [];
const manager = {
syncBaseKey: (provider: CloudProvider) => `base:${provider}`,
convergentProviderBaselineKey: (provider: CloudProvider) => `convergent:${provider}`,
removeFromStorage: (key: string) => removed.push(key),
clearSyncAnchor: (provider: CloudProvider) => removed.push(`anchor:${provider}`),
};
clearProviderMergeStateImpl.call(manager, "github");
assert.deepEqual(removed, ["base:github", "convergent:github", "anchor:github"]);
});
test("clearing all merge bases also removes every convergent provider baseline", () => {
const removed = new Set<string>();
const providers: CloudProvider[] = ["github", "google", "onedrive", "webdav", "s3"];
const manager = {
removeFromStorage: (key: string) => removed.add(key),
syncBaseKey: (provider?: CloudProvider) => `base:${provider ?? "default"}`,
syncSnapshotsKey: (provider?: CloudProvider) => `snapshots:${provider ?? "default"}`,
convergentProviderBaselineKey: (provider: CloudProvider) => `convergent:${provider}`,
clearSyncAnchor: () => {},
};
clearSyncBaseImpl.call(manager);
for (const provider of providers) {
assert.equal(removed.has(`convergent:${provider}`), true);
}
});
test("syncAllProviders uses the newest cloud payload without merging other remotes when cloud wins", async () => {
const originalDecryptPayload = EncryptionService.decryptPayload;
const originalEncryptPayload = EncryptionService.encryptPayload;
const githubRemote = remoteFile("github", 3, 300);
const googleRemote = remoteFile("google", 2, 200);
const githubPayload = payload("github-winner");
const localPayload = payload("local");
const uploaded: Array<{ provider: CloudProvider; payload: SyncPayload }> = [];
const committed: CloudProvider[] = [];
EncryptionService.decryptPayload = async (file: SyncedFile) => {
if (file === githubRemote) return githubPayload;
return payload("google-loser");
};
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => ({
...remoteFile("github", 4, 400),
payload: JSON.stringify(outgoing),
});
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
google: { enabled: true, connected: true, status: "connected" },
onedrive: { enabled: false, connected: false, status: "disconnected" },
webdav: { enabled: false, connected: false, status: "disconnected" },
s3: { enabled: false, connected: false, status: "disconnected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "preferCloud",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async (provider: CloudProvider) => ({ provider }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async (provider: CloudProvider) => ({
conflict: true,
remoteFile: provider === "github" ? githubRemote : googleRemote,
}),
loadSyncBase: async () => payload("base"),
commitRemoteInspection: async (provider: CloudProvider) => {
committed.push(provider);
},
uploadToProvider: async (provider: CloudProvider, _adapter: unknown, _file: SyncedFile, outgoing: SyncPayload) => {
uploaded.push({ provider, payload: outgoing });
return { success: true, provider, action: "upload" as const, version: 4 };
},
exitBlockedState: () => {},
notifyStateChange: () => {},
};
const results = await syncAllProvidersImpl.call(manager, localPayload);
assert.equal(results.get("github")?.action, "download");
assert.deepEqual(results.get("github")?.mergedPayload, githubPayload);
assert.equal(results.get("github")?.remoteFile, githubRemote);
assert.equal(uploaded.length, 1);
assert.equal(uploaded[0].provider, "google");
assert.equal(uploaded[0].payload.hosts[0]?.id, "github-winner");
assert.equal(uploaded[0].payload.syncMeta?.schemaVersion, 1);
assert.deepEqual(committed, []);
} finally {
EncryptionService.decryptPayload = originalDecryptPayload;
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("syncToProvider uses the checked remote as metadata base when no stored base exists", async () => {
const originalDecryptPayload = EncryptionService.decryptPayload;
const originalEncryptPayload = EncryptionService.encryptPayload;
const checkedRemote = remoteFile("github", 3, 300);
const remotePayload = payloadWithHosts(["kept", "deleted-on-local"]);
const localPayload = payload("kept");
let uploadedPayload: SyncPayload | undefined;
EncryptionService.decryptPayload = async (file: SyncedFile) => {
assert.equal(file, checkedRemote);
return remotePayload;
};
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => ({
...remoteFile("github", 4, 400),
payload: JSON.stringify(outgoing),
});
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
providerDecryptSeq: { github: 0 },
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async () => ({ provider: "github" }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: false, remoteFile: checkedRemote }),
loadSyncBase: async () => null,
uploadToProvider: async (provider: CloudProvider, _adapter: unknown, _file: SyncedFile, outgoing: SyncPayload) => {
uploadedPayload = outgoing;
return { success: true, provider, action: "upload" as const, version: 4 };
},
exitBlockedState: () => {},
};
const result = await syncToProviderImpl.call(manager, "github", localPayload);
assert.equal(result.success, true);
assert.deepEqual(uploadedPayload?.syncMeta?.deletions, [{
entityType: "hosts",
id: "deleted-on-local",
deletedAt: uploadedPayload?.syncMeta?.generatedAt,
deviceId: "local-device",
}]);
} finally {
EncryptionService.decryptPayload = originalDecryptPayload;
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("syncToProvider refuses to downgrade a checked convergent remote", async () => {
const checkedRemote = remoteFile("github", 3, 300);
checkedRemote.meta.syncSchemaVersion = 2;
let encrypted = false;
const originalEncryptPayload = EncryptionService.encryptPayload;
EncryptionService.encryptPayload = async () => {
encrypted = true;
return checkedRemote;
};
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: { github: { status: "connected" } },
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async () => ({ provider: "github" }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: false, remoteFile: checkedRemote }),
addSyncHistoryEntry: () => {},
};
const result = await syncToProviderImpl.call(manager, "github", payload("local"));
assert.equal(result.success, false);
assert.equal(encrypted, false);
assert.match(result.error ?? "", /Enable or migrate convergent sync/);
} finally {
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("syncToProvider aborts an upload when the master key changes after encryption", async () => {
const originalEncryptPayload = EncryptionService.encryptPayload;
const localPayload = payload("local");
let generation = 0;
let uploaded = false;
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => {
generation += 1;
return {
...remoteFile("github", 2, 200),
payload: JSON.stringify(outgoing),
};
};
try {
const manager = {
masterPassword: "old-master-password",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getSyncSecurityGeneration: () => 0,
assertSyncSecurityGeneration: (expected: number) => {
if (generation !== expected) {
throw new Error("Sync cancelled because master key changed");
}
},
getConnectedAdapter: async () => ({ provider: "github" }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: false }),
loadSyncBase: async () => null,
uploadToProvider: async (provider: CloudProvider) => {
uploaded = true;
return { success: true, provider, action: "upload" as const, version: 2 };
},
exitBlockedState: () => {},
addSyncHistoryEntry: () => {},
};
const result = await syncToProviderImpl.call(manager, "github", localPayload);
assert.equal(uploaded, false);
assert.equal(result.success, false);
assert.match(result.error ?? "", /master key changed/);
} finally {
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("uploadToProvider skips local commits when the master key changes during upload", async () => {
let generation = 0;
let savedAnchor = false;
let savedBase = false;
let savedProvider = false;
const file = remoteFile("github", 2, 200);
const manager = {
providerDecryptSeq: { github: 0 },
state: {
providers: {
github: { enabled: true, connected: true, status: "syncing" },
},
lastError: null,
syncState: "SYNCING",
localVersion: 1,
localUpdatedAt: 100,
remoteVersion: 1,
remoteUpdatedAt: 100,
deviceName: "Local",
},
assertSyncSecurityGeneration: (expected: number) => {
if (generation !== expected) {
throw new Error("Sync cancelled because master key changed");
}
},
saveSyncConfig: () => {},
saveSyncBase: async () => {
savedBase = true;
},
saveSyncAnchor: async () => {
savedAnchor = true;
},
saveProviderConnection: async () => {
savedProvider = true;
},
notifyStateChange: () => {},
addSyncHistoryEntry: () => {},
updateProviderStatus: () => {},
emit: () => {},
};
const adapter = {
upload: async () => {
generation += 1;
return "resource-id";
},
};
const result = await uploadToProviderImpl.call(
manager,
"github",
adapter,
file,
payload("local"),
0,
);
assert.equal(result.success, false);
assert.match(result.error ?? "", /master key changed/);
assert.equal(savedBase, false);
assert.equal(savedAnchor, false);
assert.equal(savedProvider, false);
});
test("syncAllProviders uses the checked remote as metadata base when provider base is missing", async () => {
const originalDecryptPayload = EncryptionService.decryptPayload;
const originalEncryptPayload = EncryptionService.encryptPayload;
const checkedRemote = remoteFile("github", 3, 300);
const remotePayload = payloadWithHosts(["kept", "deleted-on-local"]);
const localPayload = payload("kept");
let uploadedPayload: SyncPayload | undefined;
EncryptionService.decryptPayload = async (file: SyncedFile) => {
assert.equal(file, checkedRemote);
return remotePayload;
};
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => ({
...remoteFile("github", 4, 400),
payload: JSON.stringify(outgoing),
});
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
google: { enabled: false, connected: false, status: "disconnected" },
onedrive: { enabled: false, connected: false, status: "disconnected" },
webdav: { enabled: false, connected: false, status: "disconnected" },
s3: { enabled: false, connected: false, status: "disconnected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async () => ({ provider: "github" }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: false, remoteFile: checkedRemote }),
loadSyncBase: async () => null,
uploadToProvider: async (provider: CloudProvider, _adapter: unknown, _file: SyncedFile, outgoing: SyncPayload) => {
uploadedPayload = outgoing;
return { success: true, provider, action: "upload" as const, version: 4 };
},
exitBlockedState: () => {},
notifyStateChange: () => {},
};
const results = await syncAllProvidersImpl.call(manager, localPayload);
assert.equal(results.get("github")?.success, true);
assert.deepEqual(uploadedPayload?.syncMeta?.deletions, [{
entityType: "hosts",
id: "deleted-on-local",
deletedAt: uploadedPayload?.syncMeta?.generatedAt,
deviceId: "local-device",
}]);
} finally {
EncryptionService.decryptPayload = originalDecryptPayload;
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("commitRemoteInspection saves the comparison base before advancing the remote anchor", async () => {
const calls: string[] = [];
const file = remoteFile("github", 5, 500);
const incoming = payload("cloud");
const manager = {
providerDecryptSeq: { github: 0 },
state: {
providers: {
github: { resourceId: "old", lastSync: 0, lastSyncVersion: 0 },
},
localVersion: 0,
localUpdatedAt: 0,
remoteVersion: 0,
remoteUpdatedAt: 0,
},
getConnectedAdapter: async () => ({ resourceId: "remote-resource" }),
saveSyncConfig: () => calls.push("config"),
saveSyncBase: async () => calls.push("base"),
saveSyncAnchor: async () => calls.push("anchor"),
saveProviderConnection: async () => calls.push("connection"),
addSyncHistoryEntry: () => calls.push("history"),
notifyStateChange: () => calls.push("notify"),
};
await commitRemoteInspectionImpl.call(manager, "github", file, incoming, {
recordDownload: true,
});
assert.deepEqual(calls, ["base", "config", "anchor", "connection", "history", "notify"]);
});
test("commitRemoteInspection does not advance the remote anchor when saving the base fails", async () => {
const calls: string[] = [];
const manager = {
providerDecryptSeq: { github: 0 },
state: {
providers: {
github: { resourceId: "remote-resource", lastSync: 0, lastSyncVersion: 0 },
},
localVersion: 0,
localUpdatedAt: 0,
remoteVersion: 0,
remoteUpdatedAt: 0,
},
getConnectedAdapter: async () => ({ resourceId: "remote-resource" }),
saveSyncConfig: () => calls.push("config"),
saveSyncBase: async () => {
calls.push("base");
throw new Error("base failed");
},
saveSyncAnchor: async () => calls.push("anchor"),
saveProviderConnection: async () => calls.push("connection"),
addSyncHistoryEntry: () => calls.push("history"),
notifyStateChange: () => calls.push("notify"),
};
await assert.rejects(
() => commitRemoteInspectionImpl.call(manager, "github", remoteFile("github", 5, 500), payload("cloud")),
/base failed/,
);
assert.deepEqual(calls, ["base"]);
});
test("saveSyncBase reports storage failures so callers do not advance anchors", async () => {
const originalWarn = console.warn;
const manager = {
state: {
unlockedKey: {
derivedKey: await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
),
},
},
syncBaseKey: () => "sync-base",
saveToStorage: () => {
throw new Error("storage full");
},
};
console.warn = () => {};
try {
await assert.rejects(
() => saveSyncBaseImpl.call(manager, payload("cloud"), "github"),
/storage full/,
);
} finally {
console.warn = originalWarn;
}
});
test("saveSyncBase reports a missing local encryption key", async () => {
const manager = {
state: { unlockedKey: null },
syncBaseKey: () => "sync-base",
saveToStorage: () => {},
};
await assert.rejects(
() => saveSyncBaseImpl.call(manager, payload("cloud"), "github"),
/Sync base encryption key is unavailable/,
);
});
test("saveSyncBase keeps a bounded encrypted snapshot history before replacing the base", async () => {
const stored = new Map<string, string>();
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
const manager = {
state: { unlockedKey: { derivedKey: key } },
syncBaseKey: (provider?: CloudProvider) => `base-${provider ?? "default"}`,
syncSnapshotsKey: (provider?: CloudProvider) => `snapshots-${provider ?? "default"}`,
saveToStorage: (storageKey: string, value: string) => stored.set(storageKey, value),
loadFromStorage: (storageKey: string) => stored.get(storageKey),
};
await saveSyncBaseImpl.call(manager, payload("base-0"), "github");
for (let i = 1; i <= 7; i += 1) {
await saveSyncBaseImpl.call(manager, payload(`base-${i}`), "github");
}
const snapshots = await loadSyncSnapshotsImpl.call(manager, "github");
assert.equal(snapshots.length, 5);
assert.deepEqual(
snapshots.map((snapshot) => snapshot.payload.hosts[0]?.id),
["base-6", "base-5", "base-4", "base-3", "base-2"],
);
});
test("syncAllProviders builds provider-specific sync metadata from each provider base", async () => {
const originalEncryptPayload = EncryptionService.encryptPayload;
const uploaded: Array<{ provider: CloudProvider; payload: SyncPayload }> = [];
const baseByProvider = {
github: payload("shared"),
google: payload("deleted-on-local"),
} as Partial<Record<CloudProvider, SyncPayload>>;
const localPayload = payload("shared");
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => ({
...remoteFile("github", 4, 400),
payload: JSON.stringify(outgoing),
});
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
google: { enabled: true, connected: true, status: "connected" },
onedrive: { enabled: false, connected: false, status: "disconnected" },
webdav: { enabled: false, connected: false, status: "disconnected" },
s3: { enabled: false, connected: false, status: "disconnected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async (provider: CloudProvider) => ({ provider }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: false, remoteFile: null }),
loadSyncBase: async (provider: CloudProvider) => baseByProvider[provider] ?? null,
uploadToProvider: async (provider: CloudProvider, _adapter: unknown, _file: SyncedFile, outgoing: SyncPayload) => {
uploaded.push({ provider, payload: outgoing });
return { success: true, provider, action: "upload" as const, version: 4 };
},
exitBlockedState: () => {},
notifyStateChange: () => {},
};
await syncAllProvidersImpl.call(manager, localPayload);
assert.equal(uploaded.length, 2);
assert.deepEqual(uploaded.find((entry) => entry.provider === "github")?.payload.syncMeta?.deletions, []);
assert.deepEqual(uploaded.find((entry) => entry.provider === "google")?.payload.syncMeta?.deletions, [{
entityType: "hosts",
id: "deleted-on-local",
deletedAt: uploaded.find((entry) => entry.provider === "google")?.payload.syncMeta?.generatedAt,
deviceId: "local-device",
}]);
} finally {
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("syncAllProviders upload-local override overwrites remote without decrypting when password differs", async () => {
const originalDecryptPayload = EncryptionService.decryptPayload;
const originalEncryptPayload = EncryptionService.encryptPayload;
const checkedRemote = remoteFile("github", 5, 500);
const localPayload = payload("local-after-reinstall");
const uploaded: Array<{ provider: CloudProvider; payload: SyncPayload }> = [];
const encryptBaseVersions: number[] = [];
let decryptCalls = 0;
EncryptionService.decryptPayload = async () => {
decryptCalls += 1;
throw new Error("OperationError: unable to authenticate data");
};
EncryptionService.encryptPayload = async (
outgoing: SyncPayload,
_password: string,
_deviceId: string,
_deviceName: string,
_appVersion: string,
existingVersion?: number,
) => {
encryptBaseVersions.push(existingVersion ?? 0);
return {
...remoteFile("github", (existingVersion ?? 0) + 1, 600),
payload: JSON.stringify(outgoing),
};
};
try {
const manager = {
masterPassword: "new-master-password",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
google: { enabled: false, connected: false, status: "disconnected" },
onedrive: { enabled: false, connected: false, status: "disconnected" },
webdav: { enabled: false, connected: false, status: "disconnected" },
s3: { enabled: false, connected: false, status: "disconnected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async (provider: CloudProvider) => ({ provider }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: true, remoteFile: checkedRemote }),
loadSyncBase: async () => null,
uploadToProvider: async (provider: CloudProvider, _adapter: unknown, _file: SyncedFile, outgoing: SyncPayload) => {
uploaded.push({ provider, payload: outgoing });
return { success: true, provider, action: "upload" as const, version: 6 };
},
exitBlockedState: () => {},
notifyStateChange: () => {},
};
const conflicted = await syncAllProvidersImpl.call(manager, localPayload);
assert.equal(conflicted.get("github")?.success, false);
assert.equal(conflicted.get("github")?.conflictDetected, true);
assert.equal(conflicted.get("github")?.error, undefined);
assert.equal(uploaded.length, 0);
const forced = await syncAllProvidersImpl.call(manager, localPayload, {
conflictActionOverride: "upload-local",
overrideShrink: true,
});
assert.equal(forced.get("github")?.success, true);
assert.equal(forced.get("github")?.action, "upload");
assert.equal(uploaded.length, 1);
assert.equal(uploaded[0]?.payload.hosts[0]?.id, "local-after-reinstall");
// Keep-local under smartMerge must base on the conflicting remote version
// (v5 → encrypted as v6), matching single-provider upload-local.
assert.deepEqual(encryptBaseVersions, [5]);
// Shrink-guard may attempt decrypt and ignore failure; merge path must not run.
assert.ok(decryptCalls >= 1);
} finally {
EncryptionService.decryptPayload = originalDecryptPayload;
EncryptionService.encryptPayload = originalEncryptPayload;
}
});
test("an initialized but paused v2 replica cannot fall through to legacy provider writes", async () => {
const originalStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
const values = new Map<string, string>();
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
value: {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => values.set(key, value),
removeItem: (key: string) => values.delete(key),
},
});
try {
setConvergentSyncLocalConfig({ enabled: false, initialized: true });
let adapterRequested = false;
const manager = {
state: {
providers: {
github: { provider: "github", status: "connected" },
},
},
getConnectedAdapter: async () => {
adapterRequested = true;
throw new Error("legacy path must not run");
},
};
const all = await syncAllProvidersImpl.call(manager, payload("local"));
const one = await syncToProviderImpl.call(manager, "github", payload("local"));
assert.equal(all.get("github")?.success, false);
assert.match(all.get("github")?.error ?? "", /paused/i);
assert.equal(one.success, false);
assert.match(one.error ?? "", /paused/i);
assert.equal(adapterRequested, false);
} finally {
if (originalStorage) Object.defineProperty(globalThis, "localStorage", originalStorage);
else Reflect.deleteProperty(globalThis, "localStorage");
}
});
test("syncToProvider preserves a merged payload discovered by a non-target provider", () => {
const mergedPayload = payload("remote-merged");
const results = new Map<CloudProvider, SyncResult>([
["github", {
success: false,
provider: "github",
action: "none",
error: "github unavailable",
}],
["google", {
success: true,
provider: "google",
action: "merge",
mergedPayload,
}],
]);
const selected = selectConvergentSyncToProviderResult("github", results);
assert.equal(selected.success, false);
assert.equal(selected.provider, "github");
assert.equal(selected.error, "github unavailable");
assert.equal(selected.mergedPayload, mergedPayload);
assert.equal(selected.remoteFile, undefined);
});
test("syncAllProviders smart-merge strips device-bound enc:v1 secrets before upload", async () => {
const originalDecryptPayload = EncryptionService.decryptPayload;
const originalEncryptPayload = EncryptionService.encryptPayload;
const completeBlob = Buffer.alloc(31, 0);
Buffer.from("v10", "utf8").copy(completeBlob, 0);
const ENC = `enc:v1:${completeBlob.toString("base64")}`;
const checkedRemote = remoteFile("github", 5, 500);
const localPayload = {
...payload("shared"),
hosts: [{
...payload("shared").hosts[0]!,
password: "kept-secret",
}],
};
const remotePoisoned: SyncPayload = {
...payload("shared"),
hosts: [
{
...payload("shared").hosts[0]!,
label: "remote-label",
password: ENC,
},
],
};
const uploaded: SyncPayload[] = [];
EncryptionService.decryptPayload = async () => remotePoisoned;
EncryptionService.encryptPayload = async (outgoing: SyncPayload) => ({
...remoteFile("github", 6, 600),
payload: JSON.stringify(outgoing),
});
try {
const manager = {
masterPassword: "pw",
adapters: new Map(),
state: {
securityState: "UNLOCKED",
providers: {
github: { enabled: true, connected: true, status: "connected" },
google: { enabled: false, connected: false, status: "disconnected" },
onedrive: { enabled: false, connected: false, status: "disconnected" },
webdav: { enabled: false, connected: false, status: "disconnected" },
s3: { enabled: false, connected: false, status: "disconnected" },
},
lastError: null,
syncState: "IDLE",
syncStrategy: "smartMerge",
localVersion: 1,
deviceId: "local-device",
deviceName: "Local",
},
getConnectedAdapter: async (provider: CloudProvider) => ({ provider }),
updateProviderStatus: () => {},
emit: () => {},
checkProviderConflict: async () => ({ conflict: true, remoteFile: checkedRemote }),
loadSyncBase: async () => ({
...payload("shared"),
hosts: [{
...payload("shared").hosts[0]!,
password: "kept-secret",
}],
}),
uploadToProvider: async (
_provider: CloudProvider,
_adapter: unknown,
_file: SyncedFile,
outgoing: SyncPayload,
) => {
uploaded.push(outgoing);
return { success: true, provider: "github" as const, action: "upload" as const, version: 6 };
},
exitBlockedState: () => {},
notifyStateChange: () => {},
};
const results = await syncAllProvidersImpl.call(manager, localPayload);
assert.equal(results.get("github")?.success, true);
assert.equal(uploaded.length, 1);
const sharedHost = uploaded[0]?.hosts.find((host) => host.id === "shared");
assert.ok(sharedHost);
// Local/base usable secret must survive; remote non-secret edits can still apply.
assert.equal(sharedHost?.password, "kept-secret");
assert.equal(sharedHost?.label, "remote-label");
} finally {
EncryptionService.decryptPayload = originalDecryptPayload;
EncryptionService.encryptPayload = originalEncryptPayload;
}
});