@codemem/server 0.46.0-beta.2 → 0.46.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/sync-status.d.ts +7 -0
- package/dist/application/sync-status.d.ts.map +1 -1
- package/dist/index.js +64 -15
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +46 -46
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
- package/static/index.html +1 -1
|
@@ -34,6 +34,13 @@ export interface SyncStatusInput {
|
|
|
34
34
|
getSyncRuntimeStatus?: () => SyncRuntimeStatus | null;
|
|
35
35
|
operations: SyncStatusOperations;
|
|
36
36
|
}
|
|
37
|
+
interface PeerStatusReadModel {
|
|
38
|
+
items: Record<string, unknown>[];
|
|
39
|
+
byId: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export declare function safeDaemonIssueCode(state: string, lastError: string | null | undefined): "coordinator_timeout" | "coordinator_error" | null;
|
|
42
|
+
export declare function latestFailedPeerIds(store: MemoryStore, activePeerIds: Set<string>, isRecentIso: (value: unknown) => boolean): Set<string>;
|
|
43
|
+
export declare function degradedDaemonState(peers: PeerStatusReadModel, failedPeerIds: Set<string>): string;
|
|
37
44
|
export declare function buildSyncStatusResponse(input: SyncStatusInput): Promise<Record<string, unknown>>;
|
|
38
45
|
export {};
|
|
39
46
|
//# sourceMappingURL=sync-status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-status.d.ts","sourceRoot":"","sources":["../../src/application/sync-status.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,yBAAyB,EAEzB,2BAA2B,EAE3B,KAAK,gCAAgC,EAErC,mBAAmB,EACnB,KAAK,WAAW,EAGhB,MAAM,eAAe,CAAC;AAKvB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-status.d.ts","sourceRoot":"","sources":["../../src/application/sync-status.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4151,6 +4151,14 @@ async function reconcileRecipientPolicyProjectsOperation(store, options) {
|
|
|
4151
4151
|
}
|
|
4152
4152
|
//#endregion
|
|
4153
4153
|
//#region src/application/sync-status.ts
|
|
4154
|
+
var SAFE_RUNTIME_DETAILS = {
|
|
4155
|
+
starting: "Running initial sync in background",
|
|
4156
|
+
stopping: "Stopping sync daemon",
|
|
4157
|
+
error: "Background sync failed. Open diagnostics for details.",
|
|
4158
|
+
rebootstrapping: "Restoring sync baseline",
|
|
4159
|
+
needs_attention: "Sync needs attention",
|
|
4160
|
+
disabled: "Sync is off"
|
|
4161
|
+
};
|
|
4154
4162
|
function traceSync(label, operation) {
|
|
4155
4163
|
if (process.env.CODEMEM_TRACE_SYNC_STATUS !== "1") return operation();
|
|
4156
4164
|
const startedAt = Date.now();
|
|
@@ -4215,6 +4223,10 @@ function initialDaemonState(config, rows, daemonRunning) {
|
|
|
4215
4223
|
if (!daemonRunning) return "stopped";
|
|
4216
4224
|
return "ok";
|
|
4217
4225
|
}
|
|
4226
|
+
function safeDaemonIssueCode(state, lastError) {
|
|
4227
|
+
if (state !== "error" || !lastError?.includes("coordinator enrollment maintenance failed")) return null;
|
|
4228
|
+
return lastError.includes(":request_timeout") || lastError.endsWith(" (coordinator_timeout)") ? "coordinator_timeout" : "coordinator_error";
|
|
4229
|
+
}
|
|
4218
4230
|
function retentionPayload(config, rows, retainedFloor) {
|
|
4219
4231
|
const state = rows.retention;
|
|
4220
4232
|
return {
|
|
@@ -4282,8 +4294,9 @@ function readBaseStatus(input) {
|
|
|
4282
4294
|
state = runtime.phase;
|
|
4283
4295
|
statusPayload.daemon_state = state;
|
|
4284
4296
|
statusPayload.daemon_running = runtime.phase === "starting" || running;
|
|
4285
|
-
statusPayload.daemon_detail = runtime.detail ?? detail;
|
|
4297
|
+
statusPayload.daemon_detail = input.showDiagnostics ? runtime.detail ?? detail : SAFE_RUNTIME_DETAILS[runtime.phase] ?? null;
|
|
4286
4298
|
}
|
|
4299
|
+
statusPayload.daemon_issue_code = safeDaemonIssueCode(state, runtime?.phase === "error" ? null : rows.daemon?.last_error);
|
|
4287
4300
|
return {
|
|
4288
4301
|
config,
|
|
4289
4302
|
localDeviceId: rows.device?.device_id ?? null,
|
|
@@ -4340,16 +4353,35 @@ function readAttemptStatus(input) {
|
|
|
4340
4353
|
latestError: String(rows[0]?.error || "").trim()
|
|
4341
4354
|
};
|
|
4342
4355
|
}
|
|
4356
|
+
function latestFailedPeerIds(store, activePeerIds, isRecentIso) {
|
|
4357
|
+
const cutoff = (/* @__PURE__ */ new Date(Date.now() - 6e5)).toISOString();
|
|
4358
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4359
|
+
return traceSync("recentPeerFailures", () => {
|
|
4360
|
+
const rows = store.db.prepare(`SELECT peer_device_id, ok, error, finished_at
|
|
4361
|
+
FROM sync_attempts
|
|
4362
|
+
WHERE (CASE WHEN finished_at IS NULL THEN started_at ELSE finished_at END) BETWEEN ? AND ?
|
|
4363
|
+
ORDER BY (CASE WHEN finished_at IS NULL THEN started_at ELSE finished_at END) DESC, id DESC`).iterate(cutoff, now);
|
|
4364
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4365
|
+
const failed = /* @__PURE__ */ new Set();
|
|
4366
|
+
for (const row of rows) {
|
|
4367
|
+
if (!activePeerIds.has(row.peer_device_id) || seen.has(row.peer_device_id)) continue;
|
|
4368
|
+
if (!isRecentIso(row.finished_at)) continue;
|
|
4369
|
+
seen.add(row.peer_device_id);
|
|
4370
|
+
if (!row.ok && row.error) failed.add(row.peer_device_id);
|
|
4371
|
+
}
|
|
4372
|
+
return failed;
|
|
4373
|
+
});
|
|
4374
|
+
}
|
|
4343
4375
|
function peerStates(peers) {
|
|
4344
4376
|
return new Set(peers.items.map((peer) => String(peer.status?.peer_state ?? "")));
|
|
4345
4377
|
}
|
|
4346
4378
|
function allPeersOffline(peers) {
|
|
4347
4379
|
return peers.items.length > 0 && peers.items.every((peer) => String(peer.status?.peer_state ?? "") === "offline");
|
|
4348
4380
|
}
|
|
4349
|
-
function degradedDaemonState(
|
|
4381
|
+
function degradedDaemonState(peers, failedPeerIds) {
|
|
4350
4382
|
const states = peerStates(peers);
|
|
4351
4383
|
const allOffline = allPeersOffline(peers);
|
|
4352
|
-
if (
|
|
4384
|
+
if (failedPeerIds.size > 0) {
|
|
4353
4385
|
if (states.has("online") || states.has("degraded")) return "degraded";
|
|
4354
4386
|
if (allOffline) return "offline-peers";
|
|
4355
4387
|
if (peers.items.length > 0) return "stale";
|
|
@@ -4359,14 +4391,14 @@ function degradedDaemonState(input, peers, attempts) {
|
|
|
4359
4391
|
if (peers.items.length > 0 && !states.has("online")) return "stale";
|
|
4360
4392
|
return "ok";
|
|
4361
4393
|
}
|
|
4362
|
-
function applyDerivedDaemonState(
|
|
4394
|
+
function applyDerivedDaemonState(base, peers, attempts, failedPeerIds, statusBlock) {
|
|
4363
4395
|
let state = base.daemonState;
|
|
4364
4396
|
if (state === "ok" && attempts.latestError.startsWith("needs_attention:")) {
|
|
4365
4397
|
state = "needs_attention";
|
|
4366
4398
|
const detail = attempts.latestError.replace(/^needs_attention:/, "");
|
|
4367
4399
|
base.statusPayload.daemon_detail = detail;
|
|
4368
4400
|
statusBlock.daemon_detail = detail;
|
|
4369
|
-
} else if (state === "ok") state = degradedDaemonState(
|
|
4401
|
+
} else if (state === "ok") state = degradedDaemonState(peers, failedPeerIds);
|
|
4370
4402
|
base.statusPayload.daemon_state = state;
|
|
4371
4403
|
statusBlock.daemon_state = state;
|
|
4372
4404
|
}
|
|
@@ -4384,6 +4416,12 @@ async function buildSyncStatusResponse(input) {
|
|
|
4384
4416
|
const coordinator = traceSync("coordinator", () => input.operations.redactCoordinatorStatus(coordinatorSnapshot, input.showDiagnostics));
|
|
4385
4417
|
const peers = readPeerStatus(input, base.localDeviceId);
|
|
4386
4418
|
const attempts = readAttemptStatus(input);
|
|
4419
|
+
const activePeerIds = new Set(peers.items.map((peer) => String(peer.peer_device_id ?? "")));
|
|
4420
|
+
const failedPeerIds = latestFailedPeerIds(input.store, activePeerIds, input.operations.isRecentIso);
|
|
4421
|
+
for (const peer of peers.items) {
|
|
4422
|
+
const status = peer.status;
|
|
4423
|
+
status.recent_failed_attempt = failedPeerIds.has(String(peer.peer_device_id ?? ""));
|
|
4424
|
+
}
|
|
4387
4425
|
const statusBlock = {
|
|
4388
4426
|
...base.statusPayload,
|
|
4389
4427
|
background_maintenance: input.operations.summarizeMaintenanceJobs(listMaintenanceJobs(input.store.db), input.showDiagnostics),
|
|
@@ -4397,7 +4435,7 @@ async function buildSyncStatusResponse(input) {
|
|
|
4397
4435
|
const sharingReview = traceSync("sharingReview", () => input.store.sharingReviewSummary(input.project));
|
|
4398
4436
|
const recipientPolicyReconciliation = traceSync("recipientPolicyReconciliation", () => input.operations.listRecipientPolicyReconciliationStatus(input.store));
|
|
4399
4437
|
const joinRequests = await readJoinRequests(input, base.config);
|
|
4400
|
-
applyDerivedDaemonState(
|
|
4438
|
+
applyDerivedDaemonState(base, peers, attempts, failedPeerIds, statusBlock);
|
|
4401
4439
|
const response = {
|
|
4402
4440
|
...base.statusPayload,
|
|
4403
4441
|
status: statusBlock,
|
|
@@ -7429,6 +7467,21 @@ function recipientPolicyPresenceCapabilityExpiresAt(enrollment, observedAt) {
|
|
|
7429
7467
|
function recipientPolicyEnrollmentValueIsValid(value) {
|
|
7430
7468
|
return typeof value === "string" && value.length > 0 && value === value.trim();
|
|
7431
7469
|
}
|
|
7470
|
+
function recipientPolicyPresenceCapabilityExpiries(enrollments, observedAt) {
|
|
7471
|
+
return new Map(enrollments.map((enrollment) => [enrollment.device_id, recipientPolicyPresenceCapabilityExpiresAt(enrollment, observedAt)]));
|
|
7472
|
+
}
|
|
7473
|
+
function recipientPolicySnapshotUnavailable(stage, error) {
|
|
7474
|
+
console.warn(`[sync] recipient policy coordinator snapshot failed: stage=${stage} code=${coordinatorEnrollmentFailureCode(error)}`);
|
|
7475
|
+
throw new Error("recipient_policy_snapshot_not_fresh");
|
|
7476
|
+
}
|
|
7477
|
+
async function readRecipientPolicyCoordinatorSnapshot(read) {
|
|
7478
|
+
for (let attempt = 0;; attempt++) try {
|
|
7479
|
+
return await read();
|
|
7480
|
+
} catch (error) {
|
|
7481
|
+
if (!(error instanceof RemoteCoordinatorRequestError) || error.status !== 429 || attempt >= 2) throw error;
|
|
7482
|
+
await new Promise((resolve) => setTimeout(resolve, (attempt + 1) * 1e3));
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7432
7485
|
function createRecipientPolicyReconcilerEffects(store, options = {}) {
|
|
7433
7486
|
const config = options.config ?? readCoordinatorSyncConfig();
|
|
7434
7487
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -7470,15 +7523,13 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
|
|
|
7470
7523
|
now,
|
|
7471
7524
|
snapshot: async ({ scopeId }) => {
|
|
7472
7525
|
const targetOptions = coordinatorOptions(scopeId);
|
|
7473
|
-
const snapshotMemberships = (await coordinatorListScopeMembershipsAction({
|
|
7526
|
+
const snapshotMemberships = (await readRecipientPolicyCoordinatorSnapshot(() => coordinatorListScopeMembershipsAction({
|
|
7474
7527
|
groupId: targetOptions.groupId,
|
|
7475
7528
|
scopeId,
|
|
7476
7529
|
includeRevoked: true,
|
|
7477
7530
|
remoteUrl: targetOptions.remoteUrl,
|
|
7478
7531
|
adminSecret: targetOptions.adminSecret
|
|
7479
|
-
}).catch(() => {
|
|
7480
|
-
throw new Error("recipient_policy_snapshot_not_fresh");
|
|
7481
|
-
})).map((membership) => {
|
|
7532
|
+
})).catch((error) => recipientPolicySnapshotUnavailable("scope_memberships", error))).map((membership) => {
|
|
7482
7533
|
if (typeof membership.device_id !== "string" || membership.status !== "active" && membership.status !== "revoked") throw new Error("recipient_policy_snapshot_invalid");
|
|
7483
7534
|
const status = membership.status;
|
|
7484
7535
|
return {
|
|
@@ -7499,15 +7550,13 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
|
|
|
7499
7550
|
listBoundaryEnrollments: async ({ scopeId }) => {
|
|
7500
7551
|
const targetOptions = coordinatorOptions(scopeId);
|
|
7501
7552
|
const observedAt = now();
|
|
7502
|
-
const enrollments = await listDevices({
|
|
7553
|
+
const enrollments = await readRecipientPolicyCoordinatorSnapshot(() => listDevices({
|
|
7503
7554
|
groupId: targetOptions.groupId,
|
|
7504
7555
|
includeDisabled: true,
|
|
7505
7556
|
remoteUrl: targetOptions.remoteUrl,
|
|
7506
7557
|
adminSecret: targetOptions.adminSecret
|
|
7507
|
-
}).catch(() =>
|
|
7508
|
-
|
|
7509
|
-
});
|
|
7510
|
-
const presenceCapabilityExpiries = new Map(enrollments.map((enrollment) => [enrollment.device_id, recipientPolicyPresenceCapabilityExpiresAt(enrollment, observedAt)]));
|
|
7558
|
+
})).catch((error) => recipientPolicySnapshotUnavailable("device_enrollments", error));
|
|
7559
|
+
const presenceCapabilityExpiries = recipientPolicyPresenceCapabilityExpiries(enrollments, observedAt);
|
|
7511
7560
|
const mapped = enrollments.map((enrollment) => {
|
|
7512
7561
|
if (enrollment.group_id !== targetOptions.groupId || !recipientPolicyEnrollmentValueIsValid(enrollment.device_id) || !recipientPolicyEnrollmentValueIsValid(enrollment.public_key) || !recipientPolicyEnrollmentValueIsValid(enrollment.fingerprint) || fingerprintPublicKey(enrollment.public_key) !== enrollment.fingerprint || enrollment.enabled !== 0 && enrollment.enabled !== 1 || enrollment.identity_id !== null && !recipientPolicyEnrollmentValueIsValid(enrollment.identity_id)) throw new Error("recipient_policy_snapshot_invalid");
|
|
7513
7562
|
return {
|