@affectively/aeon 1.0.0

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.
@@ -0,0 +1,1874 @@
1
+ 'use strict';
2
+
3
+ var eventemitter3 = require('eventemitter3');
4
+
5
+ // src/distributed/SyncCoordinator.ts
6
+
7
+ // src/utils/logger.ts
8
+ var consoleLogger = {
9
+ debug: (...args) => {
10
+ console.debug("[AEON:DEBUG]", ...args);
11
+ },
12
+ info: (...args) => {
13
+ console.info("[AEON:INFO]", ...args);
14
+ },
15
+ warn: (...args) => {
16
+ console.warn("[AEON:WARN]", ...args);
17
+ },
18
+ error: (...args) => {
19
+ console.error("[AEON:ERROR]", ...args);
20
+ }
21
+ };
22
+ var currentLogger = consoleLogger;
23
+ function getLogger() {
24
+ return currentLogger;
25
+ }
26
+ var logger = {
27
+ debug: (...args) => getLogger().debug(...args),
28
+ info: (...args) => getLogger().info(...args),
29
+ warn: (...args) => getLogger().warn(...args),
30
+ error: (...args) => getLogger().error(...args)
31
+ };
32
+
33
+ // src/distributed/SyncCoordinator.ts
34
+ var SyncCoordinator = class extends eventemitter3.EventEmitter {
35
+ nodes = /* @__PURE__ */ new Map();
36
+ sessions = /* @__PURE__ */ new Map();
37
+ syncEvents = [];
38
+ nodeHeartbeats = /* @__PURE__ */ new Map();
39
+ heartbeatInterval = null;
40
+ // Crypto support
41
+ cryptoProvider = null;
42
+ nodesByDID = /* @__PURE__ */ new Map();
43
+ // DID -> nodeId
44
+ constructor() {
45
+ super();
46
+ }
47
+ /**
48
+ * Configure cryptographic provider for authenticated sync
49
+ */
50
+ configureCrypto(provider) {
51
+ this.cryptoProvider = provider;
52
+ logger.debug("[SyncCoordinator] Crypto configured", {
53
+ initialized: provider.isInitialized()
54
+ });
55
+ }
56
+ /**
57
+ * Check if crypto is configured
58
+ */
59
+ isCryptoEnabled() {
60
+ return this.cryptoProvider !== null && this.cryptoProvider.isInitialized();
61
+ }
62
+ /**
63
+ * Register a node with DID-based identity
64
+ */
65
+ async registerAuthenticatedNode(nodeInfo) {
66
+ const node = {
67
+ ...nodeInfo
68
+ };
69
+ this.nodes.set(node.id, node);
70
+ this.nodeHeartbeats.set(node.id, Date.now());
71
+ this.nodesByDID.set(nodeInfo.did, node.id);
72
+ if (this.cryptoProvider) {
73
+ await this.cryptoProvider.registerRemoteNode({
74
+ id: node.id,
75
+ did: nodeInfo.did,
76
+ publicSigningKey: nodeInfo.publicSigningKey,
77
+ publicEncryptionKey: nodeInfo.publicEncryptionKey
78
+ });
79
+ }
80
+ const event = {
81
+ type: "node-joined",
82
+ nodeId: node.id,
83
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
84
+ data: { did: nodeInfo.did, authenticated: true }
85
+ };
86
+ this.syncEvents.push(event);
87
+ this.emit("node-joined", node);
88
+ logger.debug("[SyncCoordinator] Authenticated node registered", {
89
+ nodeId: node.id,
90
+ did: nodeInfo.did,
91
+ version: node.version
92
+ });
93
+ return node;
94
+ }
95
+ /**
96
+ * Get node by DID
97
+ */
98
+ getNodeByDID(did) {
99
+ const nodeId = this.nodesByDID.get(did);
100
+ if (!nodeId) return void 0;
101
+ return this.nodes.get(nodeId);
102
+ }
103
+ /**
104
+ * Get all authenticated nodes (nodes with DIDs)
105
+ */
106
+ getAuthenticatedNodes() {
107
+ return Array.from(this.nodes.values()).filter((n) => n.did);
108
+ }
109
+ /**
110
+ * Create an authenticated sync session with UCAN-based authorization
111
+ */
112
+ async createAuthenticatedSyncSession(initiatorDID, participantDIDs, options) {
113
+ const initiatorNodeId = this.nodesByDID.get(initiatorDID);
114
+ if (!initiatorNodeId) {
115
+ throw new Error(`Initiator node with DID ${initiatorDID} not found`);
116
+ }
117
+ const participantIds = [];
118
+ for (const did of participantDIDs) {
119
+ const nodeId = this.nodesByDID.get(did);
120
+ if (nodeId) {
121
+ participantIds.push(nodeId);
122
+ }
123
+ }
124
+ let sessionToken;
125
+ if (this.cryptoProvider && this.cryptoProvider.isInitialized()) {
126
+ const capabilities = (options?.requiredCapabilities || ["aeon:sync:read", "aeon:sync:write"]).map((cap) => ({ can: cap, with: "*" }));
127
+ if (participantDIDs.length > 0) {
128
+ sessionToken = await this.cryptoProvider.createUCAN(
129
+ participantDIDs[0],
130
+ capabilities,
131
+ { expirationSeconds: 3600 }
132
+ // 1 hour
133
+ );
134
+ }
135
+ }
136
+ const session = {
137
+ id: `sync-${Date.now()}-${Math.random().toString(36).slice(2)}`,
138
+ initiatorId: initiatorNodeId,
139
+ participantIds,
140
+ status: "pending",
141
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
142
+ itemsSynced: 0,
143
+ itemsFailed: 0,
144
+ conflictsDetected: 0,
145
+ initiatorDID,
146
+ participantDIDs,
147
+ encryptionMode: options?.encryptionMode || "none",
148
+ requiredCapabilities: options?.requiredCapabilities,
149
+ sessionToken
150
+ };
151
+ this.sessions.set(session.id, session);
152
+ const event = {
153
+ type: "sync-started",
154
+ sessionId: session.id,
155
+ nodeId: initiatorNodeId,
156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
157
+ data: {
158
+ authenticated: true,
159
+ initiatorDID,
160
+ participantCount: participantDIDs.length,
161
+ encryptionMode: session.encryptionMode
162
+ }
163
+ };
164
+ this.syncEvents.push(event);
165
+ this.emit("sync-started", session);
166
+ logger.debug("[SyncCoordinator] Authenticated sync session created", {
167
+ sessionId: session.id,
168
+ initiatorDID,
169
+ participants: participantDIDs.length,
170
+ encryptionMode: session.encryptionMode
171
+ });
172
+ return session;
173
+ }
174
+ /**
175
+ * Verify a node's UCAN capabilities for a session
176
+ */
177
+ async verifyNodeCapabilities(sessionId, nodeDID, token) {
178
+ if (!this.cryptoProvider) {
179
+ return { authorized: true };
180
+ }
181
+ const session = this.sessions.get(sessionId);
182
+ if (!session) {
183
+ return { authorized: false, error: `Session ${sessionId} not found` };
184
+ }
185
+ const result = await this.cryptoProvider.verifyUCAN(token, {
186
+ requiredCapabilities: session.requiredCapabilities?.map((cap) => ({
187
+ can: cap,
188
+ with: "*"
189
+ }))
190
+ });
191
+ if (!result.authorized) {
192
+ logger.warn("[SyncCoordinator] Node capability verification failed", {
193
+ sessionId,
194
+ nodeDID,
195
+ error: result.error
196
+ });
197
+ }
198
+ return result;
199
+ }
200
+ /**
201
+ * Register a node in the cluster
202
+ */
203
+ registerNode(node) {
204
+ this.nodes.set(node.id, node);
205
+ this.nodeHeartbeats.set(node.id, Date.now());
206
+ const event = {
207
+ type: "node-joined",
208
+ nodeId: node.id,
209
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
210
+ };
211
+ this.syncEvents.push(event);
212
+ this.emit("node-joined", node);
213
+ logger.debug("[SyncCoordinator] Node registered", {
214
+ nodeId: node.id,
215
+ address: node.address,
216
+ version: node.version
217
+ });
218
+ }
219
+ /**
220
+ * Deregister a node from the cluster
221
+ */
222
+ deregisterNode(nodeId) {
223
+ const node = this.nodes.get(nodeId);
224
+ if (!node) {
225
+ throw new Error(`Node ${nodeId} not found`);
226
+ }
227
+ this.nodes.delete(nodeId);
228
+ this.nodeHeartbeats.delete(nodeId);
229
+ const event = {
230
+ type: "node-left",
231
+ nodeId,
232
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
233
+ };
234
+ this.syncEvents.push(event);
235
+ this.emit("node-left", node);
236
+ logger.debug("[SyncCoordinator] Node deregistered", { nodeId });
237
+ }
238
+ /**
239
+ * Create a new sync session
240
+ */
241
+ createSyncSession(initiatorId, participantIds) {
242
+ const node = this.nodes.get(initiatorId);
243
+ if (!node) {
244
+ throw new Error(`Initiator node ${initiatorId} not found`);
245
+ }
246
+ const session = {
247
+ id: `sync-${Date.now()}-${Math.random().toString(36).slice(2)}`,
248
+ initiatorId,
249
+ participantIds,
250
+ status: "pending",
251
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
252
+ itemsSynced: 0,
253
+ itemsFailed: 0,
254
+ conflictsDetected: 0
255
+ };
256
+ this.sessions.set(session.id, session);
257
+ const event = {
258
+ type: "sync-started",
259
+ sessionId: session.id,
260
+ nodeId: initiatorId,
261
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
262
+ };
263
+ this.syncEvents.push(event);
264
+ this.emit("sync-started", session);
265
+ logger.debug("[SyncCoordinator] Sync session created", {
266
+ sessionId: session.id,
267
+ initiator: initiatorId,
268
+ participants: participantIds.length
269
+ });
270
+ return session;
271
+ }
272
+ /**
273
+ * Update sync session
274
+ */
275
+ updateSyncSession(sessionId, updates) {
276
+ const session = this.sessions.get(sessionId);
277
+ if (!session) {
278
+ throw new Error(`Session ${sessionId} not found`);
279
+ }
280
+ Object.assign(session, updates);
281
+ if (updates.status === "completed" || updates.status === "failed") {
282
+ session.endTime = (/* @__PURE__ */ new Date()).toISOString();
283
+ const event = {
284
+ type: "sync-completed",
285
+ sessionId,
286
+ nodeId: session.initiatorId,
287
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
288
+ data: { status: updates.status, itemsSynced: session.itemsSynced }
289
+ };
290
+ this.syncEvents.push(event);
291
+ this.emit("sync-completed", session);
292
+ }
293
+ logger.debug("[SyncCoordinator] Sync session updated", {
294
+ sessionId,
295
+ status: session.status,
296
+ itemsSynced: session.itemsSynced
297
+ });
298
+ }
299
+ /**
300
+ * Record a conflict during sync
301
+ */
302
+ recordConflict(sessionId, nodeId, conflictData) {
303
+ const session = this.sessions.get(sessionId);
304
+ if (session) {
305
+ session.conflictsDetected++;
306
+ const event = {
307
+ type: "conflict-detected",
308
+ sessionId,
309
+ nodeId,
310
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
311
+ data: conflictData
312
+ };
313
+ this.syncEvents.push(event);
314
+ this.emit("conflict-detected", { session, nodeId, conflictData });
315
+ logger.debug("[SyncCoordinator] Conflict recorded", {
316
+ sessionId,
317
+ nodeId,
318
+ totalConflicts: session.conflictsDetected
319
+ });
320
+ }
321
+ }
322
+ /**
323
+ * Update node status
324
+ */
325
+ updateNodeStatus(nodeId, status) {
326
+ const node = this.nodes.get(nodeId);
327
+ if (!node) {
328
+ throw new Error(`Node ${nodeId} not found`);
329
+ }
330
+ node.status = status;
331
+ this.nodeHeartbeats.set(nodeId, Date.now());
332
+ logger.debug("[SyncCoordinator] Node status updated", {
333
+ nodeId,
334
+ status
335
+ });
336
+ }
337
+ /**
338
+ * Record heartbeat from node
339
+ */
340
+ recordHeartbeat(nodeId) {
341
+ const node = this.nodes.get(nodeId);
342
+ if (!node) {
343
+ return;
344
+ }
345
+ node.lastHeartbeat = (/* @__PURE__ */ new Date()).toISOString();
346
+ this.nodeHeartbeats.set(nodeId, Date.now());
347
+ }
348
+ /**
349
+ * Get all nodes
350
+ */
351
+ getNodes() {
352
+ return Array.from(this.nodes.values());
353
+ }
354
+ /**
355
+ * Get node by ID
356
+ */
357
+ getNode(nodeId) {
358
+ return this.nodes.get(nodeId);
359
+ }
360
+ /**
361
+ * Get online nodes
362
+ */
363
+ getOnlineNodes() {
364
+ return Array.from(this.nodes.values()).filter((n) => n.status === "online");
365
+ }
366
+ /**
367
+ * Get nodes by capability
368
+ */
369
+ getNodesByCapability(capability) {
370
+ return Array.from(this.nodes.values()).filter(
371
+ (n) => n.capabilities.includes(capability)
372
+ );
373
+ }
374
+ /**
375
+ * Get sync session
376
+ */
377
+ getSyncSession(sessionId) {
378
+ return this.sessions.get(sessionId);
379
+ }
380
+ /**
381
+ * Get all sync sessions
382
+ */
383
+ getAllSyncSessions() {
384
+ return Array.from(this.sessions.values());
385
+ }
386
+ /**
387
+ * Get active sync sessions
388
+ */
389
+ getActiveSyncSessions() {
390
+ return Array.from(this.sessions.values()).filter((s) => s.status === "active");
391
+ }
392
+ /**
393
+ * Get sessions for a node
394
+ */
395
+ getSessionsForNode(nodeId) {
396
+ return Array.from(this.sessions.values()).filter(
397
+ (s) => s.initiatorId === nodeId || s.participantIds.includes(nodeId)
398
+ );
399
+ }
400
+ /**
401
+ * Get sync statistics
402
+ */
403
+ getStatistics() {
404
+ const sessions = Array.from(this.sessions.values());
405
+ const completed = sessions.filter((s) => s.status === "completed").length;
406
+ const failed = sessions.filter((s) => s.status === "failed").length;
407
+ const active = sessions.filter((s) => s.status === "active").length;
408
+ const totalItemsSynced = sessions.reduce((sum, s) => sum + s.itemsSynced, 0);
409
+ const totalConflicts = sessions.reduce((sum, s) => sum + s.conflictsDetected, 0);
410
+ return {
411
+ totalNodes: this.nodes.size,
412
+ onlineNodes: this.getOnlineNodes().length,
413
+ offlineNodes: this.nodes.size - this.getOnlineNodes().length,
414
+ totalSessions: sessions.length,
415
+ activeSessions: active,
416
+ completedSessions: completed,
417
+ failedSessions: failed,
418
+ successRate: sessions.length > 0 ? completed / sessions.length * 100 : 0,
419
+ totalItemsSynced,
420
+ totalConflicts,
421
+ averageConflictsPerSession: sessions.length > 0 ? totalConflicts / sessions.length : 0
422
+ };
423
+ }
424
+ /**
425
+ * Get sync events
426
+ */
427
+ getSyncEvents(limit) {
428
+ const events = [...this.syncEvents];
429
+ if (limit) {
430
+ return events.slice(-limit);
431
+ }
432
+ return events;
433
+ }
434
+ /**
435
+ * Get sync events for session
436
+ */
437
+ getSessionEvents(sessionId) {
438
+ return this.syncEvents.filter((e) => e.sessionId === sessionId);
439
+ }
440
+ /**
441
+ * Check node health
442
+ */
443
+ getNodeHealth() {
444
+ const health = {};
445
+ for (const [nodeId, lastHeartbeat] of this.nodeHeartbeats) {
446
+ const now = Date.now();
447
+ const downtime = now - lastHeartbeat;
448
+ const isHealthy = downtime < 3e4;
449
+ health[nodeId] = {
450
+ isHealthy,
451
+ downtime
452
+ };
453
+ }
454
+ return health;
455
+ }
456
+ /**
457
+ * Start heartbeat monitoring
458
+ */
459
+ startHeartbeatMonitoring(interval = 5e3) {
460
+ if (this.heartbeatInterval) {
461
+ return;
462
+ }
463
+ this.heartbeatInterval = setInterval(() => {
464
+ const health = this.getNodeHealth();
465
+ for (const [nodeId, { isHealthy }] of Object.entries(health)) {
466
+ const node = this.nodes.get(nodeId);
467
+ if (!node) {
468
+ continue;
469
+ }
470
+ const newStatus = isHealthy ? "online" : "offline";
471
+ if (node.status !== newStatus) {
472
+ this.updateNodeStatus(nodeId, newStatus);
473
+ }
474
+ }
475
+ }, interval);
476
+ logger.debug("[SyncCoordinator] Heartbeat monitoring started", { interval });
477
+ }
478
+ /**
479
+ * Stop heartbeat monitoring
480
+ */
481
+ stopHeartbeatMonitoring() {
482
+ if (this.heartbeatInterval) {
483
+ clearInterval(this.heartbeatInterval);
484
+ this.heartbeatInterval = null;
485
+ logger.debug("[SyncCoordinator] Heartbeat monitoring stopped");
486
+ }
487
+ }
488
+ /**
489
+ * Clear all state (for testing)
490
+ */
491
+ clear() {
492
+ this.nodes.clear();
493
+ this.sessions.clear();
494
+ this.syncEvents = [];
495
+ this.nodeHeartbeats.clear();
496
+ this.nodesByDID.clear();
497
+ this.cryptoProvider = null;
498
+ this.stopHeartbeatMonitoring();
499
+ }
500
+ /**
501
+ * Get the crypto provider (for advanced usage)
502
+ */
503
+ getCryptoProvider() {
504
+ return this.cryptoProvider;
505
+ }
506
+ };
507
+
508
+ // src/distributed/ReplicationManager.ts
509
+ var ReplicationManager = class {
510
+ replicas = /* @__PURE__ */ new Map();
511
+ policies = /* @__PURE__ */ new Map();
512
+ replicationEvents = [];
513
+ syncStatus = /* @__PURE__ */ new Map();
514
+ // Crypto support
515
+ cryptoProvider = null;
516
+ replicasByDID = /* @__PURE__ */ new Map();
517
+ // DID -> replicaId
518
+ /**
519
+ * Configure cryptographic provider for encrypted replication
520
+ */
521
+ configureCrypto(provider) {
522
+ this.cryptoProvider = provider;
523
+ logger.debug("[ReplicationManager] Crypto configured", {
524
+ initialized: provider.isInitialized()
525
+ });
526
+ }
527
+ /**
528
+ * Check if crypto is configured
529
+ */
530
+ isCryptoEnabled() {
531
+ return this.cryptoProvider !== null && this.cryptoProvider.isInitialized();
532
+ }
533
+ /**
534
+ * Register an authenticated replica with DID
535
+ */
536
+ async registerAuthenticatedReplica(replica, encrypted = false) {
537
+ const authenticatedReplica = {
538
+ ...replica,
539
+ encrypted
540
+ };
541
+ this.replicas.set(replica.id, authenticatedReplica);
542
+ this.replicasByDID.set(replica.did, replica.id);
543
+ if (!this.syncStatus.has(replica.nodeId)) {
544
+ this.syncStatus.set(replica.nodeId, { synced: 0, failed: 0 });
545
+ }
546
+ if (this.cryptoProvider && replica.publicSigningKey) {
547
+ await this.cryptoProvider.registerRemoteNode({
548
+ id: replica.nodeId,
549
+ did: replica.did,
550
+ publicSigningKey: replica.publicSigningKey,
551
+ publicEncryptionKey: replica.publicEncryptionKey
552
+ });
553
+ }
554
+ const event = {
555
+ type: "replica-added",
556
+ replicaId: replica.id,
557
+ nodeId: replica.nodeId,
558
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
559
+ details: { did: replica.did, encrypted, authenticated: true }
560
+ };
561
+ this.replicationEvents.push(event);
562
+ logger.debug("[ReplicationManager] Authenticated replica registered", {
563
+ replicaId: replica.id,
564
+ did: replica.did,
565
+ encrypted
566
+ });
567
+ return authenticatedReplica;
568
+ }
569
+ /**
570
+ * Get replica by DID
571
+ */
572
+ getReplicaByDID(did) {
573
+ const replicaId = this.replicasByDID.get(did);
574
+ if (!replicaId) return void 0;
575
+ return this.replicas.get(replicaId);
576
+ }
577
+ /**
578
+ * Get all encrypted replicas
579
+ */
580
+ getEncryptedReplicas() {
581
+ return Array.from(this.replicas.values()).filter((r) => r.encrypted);
582
+ }
583
+ /**
584
+ * Encrypt data for replication to a specific replica
585
+ */
586
+ async encryptForReplica(data, targetReplicaDID) {
587
+ if (!this.cryptoProvider || !this.cryptoProvider.isInitialized()) {
588
+ throw new Error("Crypto provider not initialized");
589
+ }
590
+ const dataBytes = new TextEncoder().encode(JSON.stringify(data));
591
+ const encrypted = await this.cryptoProvider.encrypt(dataBytes, targetReplicaDID);
592
+ const localDID = this.cryptoProvider.getLocalDID();
593
+ return {
594
+ ct: encrypted.ct,
595
+ iv: encrypted.iv,
596
+ tag: encrypted.tag,
597
+ epk: encrypted.epk,
598
+ senderDID: localDID || void 0,
599
+ targetDID: targetReplicaDID,
600
+ encryptedAt: encrypted.encryptedAt
601
+ };
602
+ }
603
+ /**
604
+ * Decrypt data received from replication
605
+ */
606
+ async decryptReplicationData(encrypted) {
607
+ if (!this.cryptoProvider || !this.cryptoProvider.isInitialized()) {
608
+ throw new Error("Crypto provider not initialized");
609
+ }
610
+ const decrypted = await this.cryptoProvider.decrypt(
611
+ {
612
+ alg: "ECIES-P256",
613
+ ct: encrypted.ct,
614
+ iv: encrypted.iv,
615
+ tag: encrypted.tag,
616
+ epk: encrypted.epk
617
+ },
618
+ encrypted.senderDID
619
+ );
620
+ return JSON.parse(new TextDecoder().decode(decrypted));
621
+ }
622
+ /**
623
+ * Create an encrypted replication policy
624
+ */
625
+ createEncryptedPolicy(name, replicationFactor, consistencyLevel, encryptionMode, options) {
626
+ const policy = {
627
+ id: `policy-${Date.now()}-${Math.random().toString(36).slice(2)}`,
628
+ name,
629
+ replicationFactor,
630
+ consistencyLevel,
631
+ syncInterval: options?.syncInterval || 1e3,
632
+ maxReplicationLag: options?.maxReplicationLag || 1e4,
633
+ encryptionMode,
634
+ requiredCapabilities: options?.requiredCapabilities
635
+ };
636
+ this.policies.set(policy.id, policy);
637
+ logger.debug("[ReplicationManager] Encrypted policy created", {
638
+ policyId: policy.id,
639
+ name,
640
+ replicationFactor,
641
+ encryptionMode
642
+ });
643
+ return policy;
644
+ }
645
+ /**
646
+ * Verify a replica's capabilities via UCAN
647
+ */
648
+ async verifyReplicaCapabilities(replicaDID, token, policyId) {
649
+ if (!this.cryptoProvider) {
650
+ return { authorized: true };
651
+ }
652
+ const policy = policyId ? this.policies.get(policyId) : void 0;
653
+ const result = await this.cryptoProvider.verifyUCAN(token, {
654
+ requiredCapabilities: policy?.requiredCapabilities?.map((cap) => ({
655
+ can: cap,
656
+ with: "*"
657
+ }))
658
+ });
659
+ if (!result.authorized) {
660
+ logger.warn("[ReplicationManager] Replica capability verification failed", {
661
+ replicaDID,
662
+ error: result.error
663
+ });
664
+ }
665
+ return result;
666
+ }
667
+ /**
668
+ * Register a replica
669
+ */
670
+ registerReplica(replica) {
671
+ this.replicas.set(replica.id, replica);
672
+ if (!this.syncStatus.has(replica.nodeId)) {
673
+ this.syncStatus.set(replica.nodeId, { synced: 0, failed: 0 });
674
+ }
675
+ const event = {
676
+ type: "replica-added",
677
+ replicaId: replica.id,
678
+ nodeId: replica.nodeId,
679
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
680
+ };
681
+ this.replicationEvents.push(event);
682
+ logger.debug("[ReplicationManager] Replica registered", {
683
+ replicaId: replica.id,
684
+ nodeId: replica.nodeId,
685
+ status: replica.status
686
+ });
687
+ }
688
+ /**
689
+ * Remove a replica
690
+ */
691
+ removeReplica(replicaId) {
692
+ const replica = this.replicas.get(replicaId);
693
+ if (!replica) {
694
+ throw new Error(`Replica ${replicaId} not found`);
695
+ }
696
+ this.replicas.delete(replicaId);
697
+ const event = {
698
+ type: "replica-removed",
699
+ replicaId,
700
+ nodeId: replica.nodeId,
701
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
702
+ };
703
+ this.replicationEvents.push(event);
704
+ logger.debug("[ReplicationManager] Replica removed", { replicaId });
705
+ }
706
+ /**
707
+ * Create a replication policy
708
+ */
709
+ createPolicy(name, replicationFactor, consistencyLevel, syncInterval = 1e3, maxReplicationLag = 1e4) {
710
+ const policy = {
711
+ id: `policy-${Date.now()}-${Math.random().toString(36).slice(2)}`,
712
+ name,
713
+ replicationFactor,
714
+ consistencyLevel,
715
+ syncInterval,
716
+ maxReplicationLag
717
+ };
718
+ this.policies.set(policy.id, policy);
719
+ logger.debug("[ReplicationManager] Policy created", {
720
+ policyId: policy.id,
721
+ name,
722
+ replicationFactor,
723
+ consistencyLevel
724
+ });
725
+ return policy;
726
+ }
727
+ /**
728
+ * Update replica status
729
+ */
730
+ updateReplicaStatus(replicaId, status, lagBytes = 0, lagMillis = 0) {
731
+ const replica = this.replicas.get(replicaId);
732
+ if (!replica) {
733
+ throw new Error(`Replica ${replicaId} not found`);
734
+ }
735
+ replica.status = status;
736
+ replica.lagBytes = lagBytes;
737
+ replica.lagMillis = lagMillis;
738
+ replica.lastSyncTime = (/* @__PURE__ */ new Date()).toISOString();
739
+ const event = {
740
+ type: status === "syncing" ? "replica-synced" : "sync-failed",
741
+ replicaId,
742
+ nodeId: replica.nodeId,
743
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
744
+ details: { status, lagBytes, lagMillis }
745
+ };
746
+ this.replicationEvents.push(event);
747
+ const syncStatus = this.syncStatus.get(replica.nodeId);
748
+ if (syncStatus) {
749
+ if (status === "syncing" || status === "secondary") {
750
+ syncStatus.synced++;
751
+ } else if (status === "failed") {
752
+ syncStatus.failed++;
753
+ }
754
+ }
755
+ logger.debug("[ReplicationManager] Replica status updated", {
756
+ replicaId,
757
+ status,
758
+ lagBytes,
759
+ lagMillis
760
+ });
761
+ }
762
+ /**
763
+ * Get replicas for node
764
+ */
765
+ getReplicasForNode(nodeId) {
766
+ return Array.from(this.replicas.values()).filter((r) => r.nodeId === nodeId);
767
+ }
768
+ /**
769
+ * Get healthy replicas
770
+ */
771
+ getHealthyReplicas() {
772
+ return Array.from(this.replicas.values()).filter(
773
+ (r) => r.status === "secondary" || r.status === "primary"
774
+ );
775
+ }
776
+ /**
777
+ * Get syncing replicas
778
+ */
779
+ getSyncingReplicas() {
780
+ return Array.from(this.replicas.values()).filter((r) => r.status === "syncing");
781
+ }
782
+ /**
783
+ * Get failed replicas
784
+ */
785
+ getFailedReplicas() {
786
+ return Array.from(this.replicas.values()).filter((r) => r.status === "failed");
787
+ }
788
+ /**
789
+ * Check replication health for policy
790
+ */
791
+ checkReplicationHealth(policyId) {
792
+ const policy = this.policies.get(policyId);
793
+ if (!policy) {
794
+ throw new Error(`Policy ${policyId} not found`);
795
+ }
796
+ const healthy = this.getHealthyReplicas();
797
+ const maxLag = Math.max(0, ...healthy.map((r) => r.lagMillis));
798
+ return {
799
+ healthy: healthy.length >= policy.replicationFactor && maxLag <= policy.maxReplicationLag,
800
+ replicasInPolicy: policy.replicationFactor,
801
+ healthyReplicas: healthy.length,
802
+ replicationLag: maxLag
803
+ };
804
+ }
805
+ /**
806
+ * Get consistency level
807
+ */
808
+ getConsistencyLevel(policyId) {
809
+ const policy = this.policies.get(policyId);
810
+ if (!policy) {
811
+ return "eventual";
812
+ }
813
+ return policy.consistencyLevel;
814
+ }
815
+ /**
816
+ * Get replica
817
+ */
818
+ getReplica(replicaId) {
819
+ return this.replicas.get(replicaId);
820
+ }
821
+ /**
822
+ * Get all replicas
823
+ */
824
+ getAllReplicas() {
825
+ return Array.from(this.replicas.values());
826
+ }
827
+ /**
828
+ * Get policy
829
+ */
830
+ getPolicy(policyId) {
831
+ return this.policies.get(policyId);
832
+ }
833
+ /**
834
+ * Get all policies
835
+ */
836
+ getAllPolicies() {
837
+ return Array.from(this.policies.values());
838
+ }
839
+ /**
840
+ * Get replication statistics
841
+ */
842
+ getStatistics() {
843
+ const healthy = this.getHealthyReplicas().length;
844
+ const syncing = this.getSyncingReplicas().length;
845
+ const failed = this.getFailedReplicas().length;
846
+ const total = this.replicas.size;
847
+ const replicationLags = Array.from(this.replicas.values()).map((r) => r.lagMillis);
848
+ const avgLag = replicationLags.length > 0 ? replicationLags.reduce((a, b) => a + b) / replicationLags.length : 0;
849
+ const maxLag = replicationLags.length > 0 ? Math.max(...replicationLags) : 0;
850
+ return {
851
+ totalReplicas: total,
852
+ healthyReplicas: healthy,
853
+ syncingReplicas: syncing,
854
+ failedReplicas: failed,
855
+ healthiness: total > 0 ? healthy / total * 100 : 0,
856
+ averageReplicationLagMs: avgLag,
857
+ maxReplicationLagMs: maxLag,
858
+ totalPolicies: this.policies.size
859
+ };
860
+ }
861
+ /**
862
+ * Get replication events
863
+ */
864
+ getReplicationEvents(limit) {
865
+ const events = [...this.replicationEvents];
866
+ if (limit) {
867
+ return events.slice(-limit);
868
+ }
869
+ return events;
870
+ }
871
+ /**
872
+ * Get sync status for node
873
+ */
874
+ getSyncStatus(nodeId) {
875
+ return this.syncStatus.get(nodeId) || { synced: 0, failed: 0 };
876
+ }
877
+ /**
878
+ * Get replication lag distribution
879
+ */
880
+ getReplicationLagDistribution() {
881
+ const distribution = {
882
+ "0-100ms": 0,
883
+ "100-500ms": 0,
884
+ "500-1000ms": 0,
885
+ "1000+ms": 0
886
+ };
887
+ for (const replica of this.replicas.values()) {
888
+ if (replica.lagMillis <= 100) {
889
+ distribution["0-100ms"]++;
890
+ } else if (replica.lagMillis <= 500) {
891
+ distribution["100-500ms"]++;
892
+ } else if (replica.lagMillis <= 1e3) {
893
+ distribution["500-1000ms"]++;
894
+ } else {
895
+ distribution["1000+ms"]++;
896
+ }
897
+ }
898
+ return distribution;
899
+ }
900
+ /**
901
+ * Check if can satisfy consistency level
902
+ */
903
+ canSatisfyConsistency(policyId, _requiredAcks) {
904
+ const policy = this.policies.get(policyId);
905
+ if (!policy) {
906
+ return false;
907
+ }
908
+ const healthyCount = this.getHealthyReplicas().length;
909
+ switch (policy.consistencyLevel) {
910
+ case "eventual":
911
+ return true;
912
+ // Always achievable
913
+ case "read-after-write":
914
+ return healthyCount >= 1;
915
+ case "strong":
916
+ return healthyCount >= policy.replicationFactor;
917
+ default:
918
+ return false;
919
+ }
920
+ }
921
+ /**
922
+ * Clear all state (for testing)
923
+ */
924
+ clear() {
925
+ this.replicas.clear();
926
+ this.policies.clear();
927
+ this.replicationEvents = [];
928
+ this.syncStatus.clear();
929
+ this.replicasByDID.clear();
930
+ this.cryptoProvider = null;
931
+ }
932
+ /**
933
+ * Get the crypto provider (for advanced usage)
934
+ */
935
+ getCryptoProvider() {
936
+ return this.cryptoProvider;
937
+ }
938
+ };
939
+
940
+ // src/distributed/SyncProtocol.ts
941
+ var SyncProtocol = class {
942
+ version = "1.0.0";
943
+ messageQueue = [];
944
+ messageMap = /* @__PURE__ */ new Map();
945
+ handshakes = /* @__PURE__ */ new Map();
946
+ protocolErrors = [];
947
+ messageCounter = 0;
948
+ // Crypto support
949
+ cryptoProvider = null;
950
+ cryptoConfig = null;
951
+ /**
952
+ * Configure cryptographic provider for authenticated/encrypted messages
953
+ */
954
+ configureCrypto(provider, config) {
955
+ this.cryptoProvider = provider;
956
+ this.cryptoConfig = {
957
+ encryptionMode: config?.encryptionMode ?? "none",
958
+ requireSignatures: config?.requireSignatures ?? false,
959
+ requireCapabilities: config?.requireCapabilities ?? false,
960
+ requiredCapabilities: config?.requiredCapabilities
961
+ };
962
+ logger.debug("[SyncProtocol] Crypto configured", {
963
+ encryptionMode: this.cryptoConfig.encryptionMode,
964
+ requireSignatures: this.cryptoConfig.requireSignatures,
965
+ requireCapabilities: this.cryptoConfig.requireCapabilities
966
+ });
967
+ }
968
+ /**
969
+ * Check if crypto is configured
970
+ */
971
+ isCryptoEnabled() {
972
+ return this.cryptoProvider !== null && this.cryptoProvider.isInitialized();
973
+ }
974
+ /**
975
+ * Get crypto configuration
976
+ */
977
+ getCryptoConfig() {
978
+ return this.cryptoConfig ? { ...this.cryptoConfig } : null;
979
+ }
980
+ /**
981
+ * Get protocol version
982
+ */
983
+ getVersion() {
984
+ return this.version;
985
+ }
986
+ /**
987
+ * Create authenticated handshake message with DID and keys
988
+ */
989
+ async createAuthenticatedHandshake(capabilities, targetDID) {
990
+ if (!this.cryptoProvider || !this.cryptoProvider.isInitialized()) {
991
+ throw new Error("Crypto provider not initialized");
992
+ }
993
+ const localDID = this.cryptoProvider.getLocalDID();
994
+ if (!localDID) {
995
+ throw new Error("Local DID not available");
996
+ }
997
+ const publicInfo = await this.cryptoProvider.exportPublicIdentity();
998
+ if (!publicInfo) {
999
+ throw new Error("Cannot export public identity");
1000
+ }
1001
+ let ucan;
1002
+ if (targetDID && this.cryptoConfig?.requireCapabilities) {
1003
+ const caps = this.cryptoConfig.requiredCapabilities || [
1004
+ { can: "aeon:sync:read", with: "*" },
1005
+ { can: "aeon:sync:write", with: "*" }
1006
+ ];
1007
+ ucan = await this.cryptoProvider.createUCAN(targetDID, caps);
1008
+ }
1009
+ const handshakePayload = {
1010
+ protocolVersion: this.version,
1011
+ nodeId: localDID,
1012
+ capabilities,
1013
+ state: "initiating",
1014
+ did: localDID,
1015
+ publicSigningKey: publicInfo.publicSigningKey,
1016
+ publicEncryptionKey: publicInfo.publicEncryptionKey,
1017
+ ucan
1018
+ };
1019
+ const message = {
1020
+ type: "handshake",
1021
+ version: this.version,
1022
+ sender: localDID,
1023
+ receiver: targetDID || "",
1024
+ messageId: this.generateMessageId(),
1025
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1026
+ payload: handshakePayload
1027
+ };
1028
+ if (this.cryptoConfig?.requireSignatures) {
1029
+ const signed = await this.cryptoProvider.signData(handshakePayload);
1030
+ message.auth = {
1031
+ senderDID: localDID,
1032
+ receiverDID: targetDID,
1033
+ signature: signed.signature
1034
+ };
1035
+ }
1036
+ this.messageMap.set(message.messageId, message);
1037
+ this.messageQueue.push(message);
1038
+ logger.debug("[SyncProtocol] Authenticated handshake created", {
1039
+ messageId: message.messageId,
1040
+ did: localDID,
1041
+ capabilities: capabilities.length,
1042
+ hasUCAN: !!ucan
1043
+ });
1044
+ return message;
1045
+ }
1046
+ /**
1047
+ * Verify and process an authenticated handshake
1048
+ */
1049
+ async verifyAuthenticatedHandshake(message) {
1050
+ if (message.type !== "handshake") {
1051
+ return { valid: false, error: "Message is not a handshake" };
1052
+ }
1053
+ const handshake = message.payload;
1054
+ if (!this.cryptoProvider || !this.cryptoConfig) {
1055
+ this.handshakes.set(message.sender, handshake);
1056
+ return { valid: true, handshake };
1057
+ }
1058
+ if (handshake.did && handshake.publicSigningKey) {
1059
+ await this.cryptoProvider.registerRemoteNode({
1060
+ id: handshake.nodeId,
1061
+ did: handshake.did,
1062
+ publicSigningKey: handshake.publicSigningKey,
1063
+ publicEncryptionKey: handshake.publicEncryptionKey
1064
+ });
1065
+ }
1066
+ if (this.cryptoConfig.requireSignatures && message.auth?.signature) {
1067
+ const signed = {
1068
+ payload: handshake,
1069
+ signature: message.auth.signature,
1070
+ signer: message.auth.senderDID || message.sender,
1071
+ algorithm: "ES256",
1072
+ signedAt: Date.now()
1073
+ };
1074
+ const isValid = await this.cryptoProvider.verifySignedData(signed);
1075
+ if (!isValid) {
1076
+ logger.warn("[SyncProtocol] Handshake signature verification failed", {
1077
+ messageId: message.messageId,
1078
+ sender: message.sender
1079
+ });
1080
+ return { valid: false, error: "Invalid signature" };
1081
+ }
1082
+ }
1083
+ if (this.cryptoConfig.requireCapabilities && handshake.ucan) {
1084
+ const localDID = this.cryptoProvider.getLocalDID();
1085
+ const result = await this.cryptoProvider.verifyUCAN(handshake.ucan, {
1086
+ expectedAudience: localDID || void 0,
1087
+ requiredCapabilities: this.cryptoConfig.requiredCapabilities
1088
+ });
1089
+ if (!result.authorized) {
1090
+ logger.warn("[SyncProtocol] Handshake UCAN verification failed", {
1091
+ messageId: message.messageId,
1092
+ error: result.error
1093
+ });
1094
+ return { valid: false, error: result.error || "Unauthorized" };
1095
+ }
1096
+ }
1097
+ this.handshakes.set(message.sender, handshake);
1098
+ logger.debug("[SyncProtocol] Authenticated handshake verified", {
1099
+ messageId: message.messageId,
1100
+ did: handshake.did
1101
+ });
1102
+ return { valid: true, handshake };
1103
+ }
1104
+ /**
1105
+ * Sign and optionally encrypt a message payload
1106
+ */
1107
+ async signMessage(message, payload, encrypt = false) {
1108
+ if (!this.cryptoProvider || !this.cryptoProvider.isInitialized()) {
1109
+ throw new Error("Crypto provider not initialized");
1110
+ }
1111
+ const localDID = this.cryptoProvider.getLocalDID();
1112
+ const signed = await this.cryptoProvider.signData(payload);
1113
+ message.auth = {
1114
+ senderDID: localDID || void 0,
1115
+ receiverDID: message.receiver || void 0,
1116
+ signature: signed.signature,
1117
+ encrypted: false
1118
+ };
1119
+ if (encrypt && message.receiver && this.cryptoConfig?.encryptionMode !== "none") {
1120
+ const payloadBytes = new TextEncoder().encode(JSON.stringify(payload));
1121
+ const encrypted = await this.cryptoProvider.encrypt(payloadBytes, message.receiver);
1122
+ message.payload = encrypted;
1123
+ message.auth.encrypted = true;
1124
+ logger.debug("[SyncProtocol] Message encrypted", {
1125
+ messageId: message.messageId,
1126
+ recipient: message.receiver
1127
+ });
1128
+ } else {
1129
+ message.payload = payload;
1130
+ }
1131
+ return message;
1132
+ }
1133
+ /**
1134
+ * Verify signature and optionally decrypt a message
1135
+ */
1136
+ async verifyMessage(message) {
1137
+ if (!this.cryptoProvider || !message.auth) {
1138
+ return { valid: true, payload: message.payload };
1139
+ }
1140
+ let payload = message.payload;
1141
+ if (message.auth.encrypted && message.payload) {
1142
+ try {
1143
+ const encrypted = message.payload;
1144
+ const decrypted = await this.cryptoProvider.decrypt(
1145
+ encrypted,
1146
+ message.auth.senderDID
1147
+ );
1148
+ payload = JSON.parse(new TextDecoder().decode(decrypted));
1149
+ logger.debug("[SyncProtocol] Message decrypted", {
1150
+ messageId: message.messageId
1151
+ });
1152
+ } catch (error) {
1153
+ return {
1154
+ valid: false,
1155
+ error: `Decryption failed: ${error instanceof Error ? error.message : String(error)}`
1156
+ };
1157
+ }
1158
+ }
1159
+ if (message.auth.signature && message.auth.senderDID) {
1160
+ const signed = {
1161
+ payload,
1162
+ signature: message.auth.signature,
1163
+ signer: message.auth.senderDID,
1164
+ algorithm: "ES256",
1165
+ signedAt: Date.now()
1166
+ };
1167
+ const isValid = await this.cryptoProvider.verifySignedData(signed);
1168
+ if (!isValid) {
1169
+ return { valid: false, error: "Invalid signature" };
1170
+ }
1171
+ }
1172
+ return { valid: true, payload };
1173
+ }
1174
+ /**
1175
+ * Create handshake message
1176
+ */
1177
+ createHandshakeMessage(nodeId, capabilities) {
1178
+ const message = {
1179
+ type: "handshake",
1180
+ version: this.version,
1181
+ sender: nodeId,
1182
+ receiver: "",
1183
+ messageId: this.generateMessageId(),
1184
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1185
+ payload: {
1186
+ protocolVersion: this.version,
1187
+ nodeId,
1188
+ capabilities,
1189
+ state: "initiating"
1190
+ }
1191
+ };
1192
+ this.messageMap.set(message.messageId, message);
1193
+ this.messageQueue.push(message);
1194
+ logger.debug("[SyncProtocol] Handshake message created", {
1195
+ messageId: message.messageId,
1196
+ nodeId,
1197
+ capabilities: capabilities.length
1198
+ });
1199
+ return message;
1200
+ }
1201
+ /**
1202
+ * Create sync request message
1203
+ */
1204
+ createSyncRequestMessage(sender, receiver, sessionId, fromVersion, toVersion, filter) {
1205
+ const message = {
1206
+ type: "sync-request",
1207
+ version: this.version,
1208
+ sender,
1209
+ receiver,
1210
+ messageId: this.generateMessageId(),
1211
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1212
+ payload: {
1213
+ sessionId,
1214
+ fromVersion,
1215
+ toVersion,
1216
+ filter
1217
+ }
1218
+ };
1219
+ this.messageMap.set(message.messageId, message);
1220
+ this.messageQueue.push(message);
1221
+ logger.debug("[SyncProtocol] Sync request created", {
1222
+ messageId: message.messageId,
1223
+ sessionId,
1224
+ fromVersion,
1225
+ toVersion
1226
+ });
1227
+ return message;
1228
+ }
1229
+ /**
1230
+ * Create sync response message
1231
+ */
1232
+ createSyncResponseMessage(sender, receiver, sessionId, fromVersion, toVersion, data, hasMore = false, offset = 0) {
1233
+ const message = {
1234
+ type: "sync-response",
1235
+ version: this.version,
1236
+ sender,
1237
+ receiver,
1238
+ messageId: this.generateMessageId(),
1239
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1240
+ payload: {
1241
+ sessionId,
1242
+ fromVersion,
1243
+ toVersion,
1244
+ data,
1245
+ hasMore,
1246
+ offset
1247
+ }
1248
+ };
1249
+ this.messageMap.set(message.messageId, message);
1250
+ this.messageQueue.push(message);
1251
+ logger.debug("[SyncProtocol] Sync response created", {
1252
+ messageId: message.messageId,
1253
+ sessionId,
1254
+ itemCount: data.length,
1255
+ hasMore
1256
+ });
1257
+ return message;
1258
+ }
1259
+ /**
1260
+ * Create acknowledgement message
1261
+ */
1262
+ createAckMessage(sender, receiver, messageId) {
1263
+ const message = {
1264
+ type: "ack",
1265
+ version: this.version,
1266
+ sender,
1267
+ receiver,
1268
+ messageId: this.generateMessageId(),
1269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1270
+ payload: { acknowledgedMessageId: messageId }
1271
+ };
1272
+ this.messageMap.set(message.messageId, message);
1273
+ this.messageQueue.push(message);
1274
+ return message;
1275
+ }
1276
+ /**
1277
+ * Create error message
1278
+ */
1279
+ createErrorMessage(sender, receiver, error, relatedMessageId) {
1280
+ const message = {
1281
+ type: "error",
1282
+ version: this.version,
1283
+ sender,
1284
+ receiver,
1285
+ messageId: this.generateMessageId(),
1286
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1287
+ payload: {
1288
+ error,
1289
+ relatedMessageId
1290
+ }
1291
+ };
1292
+ this.messageMap.set(message.messageId, message);
1293
+ this.messageQueue.push(message);
1294
+ this.protocolErrors.push({
1295
+ error,
1296
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1297
+ });
1298
+ logger.error("[SyncProtocol] Error message created", {
1299
+ messageId: message.messageId,
1300
+ errorCode: error.code,
1301
+ recoverable: error.recoverable
1302
+ });
1303
+ return message;
1304
+ }
1305
+ /**
1306
+ * Validate message
1307
+ */
1308
+ validateMessage(message) {
1309
+ const errors = [];
1310
+ if (!message.type) {
1311
+ errors.push("Message type is required");
1312
+ }
1313
+ if (!message.sender) {
1314
+ errors.push("Sender is required");
1315
+ }
1316
+ if (!message.messageId) {
1317
+ errors.push("Message ID is required");
1318
+ }
1319
+ if (!message.timestamp) {
1320
+ errors.push("Timestamp is required");
1321
+ }
1322
+ try {
1323
+ new Date(message.timestamp);
1324
+ } catch {
1325
+ errors.push("Invalid timestamp format");
1326
+ }
1327
+ return {
1328
+ valid: errors.length === 0,
1329
+ errors
1330
+ };
1331
+ }
1332
+ /**
1333
+ * Serialize message
1334
+ */
1335
+ serializeMessage(message) {
1336
+ try {
1337
+ return JSON.stringify(message);
1338
+ } catch (error) {
1339
+ logger.error("[SyncProtocol] Message serialization failed", {
1340
+ messageId: message.messageId,
1341
+ error: error instanceof Error ? error.message : String(error)
1342
+ });
1343
+ throw new Error(`Failed to serialize message: ${error instanceof Error ? error.message : String(error)}`);
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Deserialize message
1348
+ */
1349
+ deserializeMessage(data) {
1350
+ try {
1351
+ const message = JSON.parse(data);
1352
+ const validation = this.validateMessage(message);
1353
+ if (!validation.valid) {
1354
+ throw new Error(`Invalid message: ${validation.errors.join(", ")}`);
1355
+ }
1356
+ return message;
1357
+ } catch (error) {
1358
+ logger.error("[SyncProtocol] Message deserialization failed", {
1359
+ error: error instanceof Error ? error.message : String(error)
1360
+ });
1361
+ throw new Error(`Failed to deserialize message: ${error instanceof Error ? error.message : String(error)}`);
1362
+ }
1363
+ }
1364
+ /**
1365
+ * Process handshake
1366
+ */
1367
+ processHandshake(message) {
1368
+ if (message.type !== "handshake") {
1369
+ throw new Error("Message is not a handshake");
1370
+ }
1371
+ const handshake = message.payload;
1372
+ const nodeId = message.sender;
1373
+ this.handshakes.set(nodeId, handshake);
1374
+ logger.debug("[SyncProtocol] Handshake processed", {
1375
+ nodeId,
1376
+ protocolVersion: handshake.protocolVersion,
1377
+ capabilities: handshake.capabilities.length
1378
+ });
1379
+ return handshake;
1380
+ }
1381
+ /**
1382
+ * Get message
1383
+ */
1384
+ getMessage(messageId) {
1385
+ return this.messageMap.get(messageId);
1386
+ }
1387
+ /**
1388
+ * Get all messages
1389
+ */
1390
+ getAllMessages() {
1391
+ return [...this.messageQueue];
1392
+ }
1393
+ /**
1394
+ * Get messages by type
1395
+ */
1396
+ getMessagesByType(type) {
1397
+ return this.messageQueue.filter((m) => m.type === type);
1398
+ }
1399
+ /**
1400
+ * Get messages from sender
1401
+ */
1402
+ getMessagesFromSender(sender) {
1403
+ return this.messageQueue.filter((m) => m.sender === sender);
1404
+ }
1405
+ /**
1406
+ * Get pending messages
1407
+ */
1408
+ getPendingMessages(receiver) {
1409
+ return this.messageQueue.filter((m) => m.receiver === receiver);
1410
+ }
1411
+ /**
1412
+ * Get handshakes
1413
+ */
1414
+ getHandshakes() {
1415
+ return new Map(this.handshakes);
1416
+ }
1417
+ /**
1418
+ * Get protocol statistics
1419
+ */
1420
+ getStatistics() {
1421
+ const messagesByType = {};
1422
+ for (const message of this.messageQueue) {
1423
+ messagesByType[message.type] = (messagesByType[message.type] || 0) + 1;
1424
+ }
1425
+ const errorCount = this.protocolErrors.length;
1426
+ const recoverableErrors = this.protocolErrors.filter((e) => e.error.recoverable).length;
1427
+ return {
1428
+ totalMessages: this.messageQueue.length,
1429
+ messagesByType,
1430
+ totalHandshakes: this.handshakes.size,
1431
+ totalErrors: errorCount,
1432
+ recoverableErrors,
1433
+ unrecoverableErrors: errorCount - recoverableErrors
1434
+ };
1435
+ }
1436
+ /**
1437
+ * Get protocol errors
1438
+ */
1439
+ getErrors() {
1440
+ return [...this.protocolErrors];
1441
+ }
1442
+ /**
1443
+ * Generate message ID
1444
+ */
1445
+ generateMessageId() {
1446
+ this.messageCounter++;
1447
+ return `msg-${Date.now()}-${this.messageCounter}`;
1448
+ }
1449
+ /**
1450
+ * Clear all state (for testing)
1451
+ */
1452
+ clear() {
1453
+ this.messageQueue = [];
1454
+ this.messageMap.clear();
1455
+ this.handshakes.clear();
1456
+ this.protocolErrors = [];
1457
+ this.messageCounter = 0;
1458
+ this.cryptoProvider = null;
1459
+ this.cryptoConfig = null;
1460
+ }
1461
+ /**
1462
+ * Get the crypto provider (for advanced usage)
1463
+ */
1464
+ getCryptoProvider() {
1465
+ return this.cryptoProvider;
1466
+ }
1467
+ };
1468
+
1469
+ // src/distributed/StateReconciler.ts
1470
+ var StateReconciler = class {
1471
+ stateVersions = /* @__PURE__ */ new Map();
1472
+ reconciliationHistory = [];
1473
+ cryptoProvider = null;
1474
+ requireSignedVersions = false;
1475
+ /**
1476
+ * Configure cryptographic provider for signed state versions
1477
+ */
1478
+ configureCrypto(provider, requireSigned = false) {
1479
+ this.cryptoProvider = provider;
1480
+ this.requireSignedVersions = requireSigned;
1481
+ logger.debug("[StateReconciler] Crypto configured", {
1482
+ initialized: provider.isInitialized(),
1483
+ requireSigned
1484
+ });
1485
+ }
1486
+ /**
1487
+ * Check if crypto is configured
1488
+ */
1489
+ isCryptoEnabled() {
1490
+ return this.cryptoProvider !== null && this.cryptoProvider.isInitialized();
1491
+ }
1492
+ /**
1493
+ * Record a signed state version with cryptographic verification
1494
+ */
1495
+ async recordSignedStateVersion(key, version, data) {
1496
+ if (!this.cryptoProvider || !this.cryptoProvider.isInitialized()) {
1497
+ throw new Error("Crypto provider not initialized");
1498
+ }
1499
+ const localDID = this.cryptoProvider.getLocalDID();
1500
+ if (!localDID) {
1501
+ throw new Error("Local DID not available");
1502
+ }
1503
+ const dataBytes = new TextEncoder().encode(JSON.stringify(data));
1504
+ const hashBytes = await this.cryptoProvider.hash(dataBytes);
1505
+ const hash = Array.from(hashBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1506
+ const versionData = { version, data, hash };
1507
+ const signed = await this.cryptoProvider.signData(versionData);
1508
+ const stateVersion = {
1509
+ version,
1510
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1511
+ nodeId: localDID,
1512
+ hash,
1513
+ data,
1514
+ signerDID: localDID,
1515
+ signature: signed.signature,
1516
+ signedAt: signed.signedAt
1517
+ };
1518
+ if (!this.stateVersions.has(key)) {
1519
+ this.stateVersions.set(key, []);
1520
+ }
1521
+ this.stateVersions.get(key).push(stateVersion);
1522
+ logger.debug("[StateReconciler] Signed state version recorded", {
1523
+ key,
1524
+ version,
1525
+ signerDID: localDID,
1526
+ hash: hash.slice(0, 16) + "..."
1527
+ });
1528
+ return stateVersion;
1529
+ }
1530
+ /**
1531
+ * Verify a state version's signature
1532
+ */
1533
+ async verifyStateVersion(version) {
1534
+ if (!version.signature || !version.signerDID) {
1535
+ if (this.requireSignedVersions) {
1536
+ return { valid: false, error: "Signature required but not present" };
1537
+ }
1538
+ const dataBytes = new TextEncoder().encode(JSON.stringify(version.data));
1539
+ if (this.cryptoProvider) {
1540
+ const hashBytes = await this.cryptoProvider.hash(dataBytes);
1541
+ const computedHash = Array.from(hashBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1542
+ if (computedHash !== version.hash) {
1543
+ return { valid: false, error: "Hash mismatch" };
1544
+ }
1545
+ }
1546
+ return { valid: true };
1547
+ }
1548
+ if (!this.cryptoProvider) {
1549
+ return { valid: false, error: "Crypto provider not configured" };
1550
+ }
1551
+ const versionData = {
1552
+ version: version.version,
1553
+ data: version.data,
1554
+ hash: version.hash
1555
+ };
1556
+ const signed = {
1557
+ payload: versionData,
1558
+ signature: version.signature,
1559
+ signer: version.signerDID,
1560
+ algorithm: "ES256",
1561
+ signedAt: version.signedAt || Date.now()
1562
+ };
1563
+ const isValid = await this.cryptoProvider.verifySignedData(signed);
1564
+ if (!isValid) {
1565
+ return { valid: false, error: "Invalid signature" };
1566
+ }
1567
+ return { valid: true };
1568
+ }
1569
+ /**
1570
+ * Reconcile with verification - only accept verified versions
1571
+ */
1572
+ async reconcileWithVerification(key, strategy = "last-write-wins") {
1573
+ const versions = this.stateVersions.get(key) || [];
1574
+ const verifiedVersions = [];
1575
+ const verificationErrors = [];
1576
+ for (const version of versions) {
1577
+ const result2 = await this.verifyStateVersion(version);
1578
+ if (result2.valid) {
1579
+ verifiedVersions.push(version);
1580
+ } else {
1581
+ verificationErrors.push(
1582
+ `Version ${version.version} from ${version.nodeId}: ${result2.error}`
1583
+ );
1584
+ logger.warn("[StateReconciler] Version verification failed", {
1585
+ version: version.version,
1586
+ nodeId: version.nodeId,
1587
+ error: result2.error
1588
+ });
1589
+ }
1590
+ }
1591
+ if (verifiedVersions.length === 0) {
1592
+ return {
1593
+ success: false,
1594
+ mergedState: null,
1595
+ conflictsResolved: 0,
1596
+ strategy,
1597
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1598
+ verificationErrors
1599
+ };
1600
+ }
1601
+ let result;
1602
+ switch (strategy) {
1603
+ case "last-write-wins":
1604
+ result = this.reconcileLastWriteWins(verifiedVersions);
1605
+ break;
1606
+ case "vector-clock":
1607
+ result = this.reconcileVectorClock(verifiedVersions);
1608
+ break;
1609
+ case "majority-vote":
1610
+ result = this.reconcileMajorityVote(verifiedVersions);
1611
+ break;
1612
+ default:
1613
+ result = this.reconcileLastWriteWins(verifiedVersions);
1614
+ }
1615
+ return { ...result, verificationErrors };
1616
+ }
1617
+ /**
1618
+ * Record a state version
1619
+ */
1620
+ recordStateVersion(key, version, timestamp, nodeId, hash, data) {
1621
+ if (!this.stateVersions.has(key)) {
1622
+ this.stateVersions.set(key, []);
1623
+ }
1624
+ const versions = this.stateVersions.get(key);
1625
+ versions.push({
1626
+ version,
1627
+ timestamp,
1628
+ nodeId,
1629
+ hash,
1630
+ data
1631
+ });
1632
+ logger.debug("[StateReconciler] State version recorded", {
1633
+ key,
1634
+ version,
1635
+ nodeId,
1636
+ hash
1637
+ });
1638
+ }
1639
+ /**
1640
+ * Detect conflicts in state versions
1641
+ */
1642
+ detectConflicts(key) {
1643
+ const versions = this.stateVersions.get(key);
1644
+ if (!versions || versions.length <= 1) {
1645
+ return false;
1646
+ }
1647
+ const hashes = new Set(versions.map((v) => v.hash));
1648
+ return hashes.size > 1;
1649
+ }
1650
+ /**
1651
+ * Compare two states and generate diff
1652
+ */
1653
+ compareStates(state1, state2) {
1654
+ const diff = {
1655
+ added: {},
1656
+ modified: {},
1657
+ removed: [],
1658
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1659
+ };
1660
+ for (const [key, value] of Object.entries(state2)) {
1661
+ if (!(key in state1)) {
1662
+ diff.added[key] = value;
1663
+ } else if (JSON.stringify(state1[key]) !== JSON.stringify(value)) {
1664
+ diff.modified[key] = { old: state1[key], new: value };
1665
+ }
1666
+ }
1667
+ for (const key of Object.keys(state1)) {
1668
+ if (!(key in state2)) {
1669
+ diff.removed.push(key);
1670
+ }
1671
+ }
1672
+ return diff;
1673
+ }
1674
+ /**
1675
+ * Reconcile states using last-write-wins strategy
1676
+ */
1677
+ reconcileLastWriteWins(versions) {
1678
+ if (versions.length === 0) {
1679
+ throw new Error("No versions to reconcile");
1680
+ }
1681
+ const sorted = [...versions].sort(
1682
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
1683
+ );
1684
+ const latest = sorted[0];
1685
+ const conflictsResolved = versions.length - 1;
1686
+ const result = {
1687
+ success: true,
1688
+ mergedState: latest.data,
1689
+ conflictsResolved,
1690
+ strategy: "last-write-wins",
1691
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1692
+ };
1693
+ this.reconciliationHistory.push(result);
1694
+ logger.debug("[StateReconciler] State reconciled (last-write-wins)", {
1695
+ winnerNode: latest.nodeId,
1696
+ conflictsResolved
1697
+ });
1698
+ return result;
1699
+ }
1700
+ /**
1701
+ * Reconcile states using vector clock strategy
1702
+ */
1703
+ reconcileVectorClock(versions) {
1704
+ if (versions.length === 0) {
1705
+ throw new Error("No versions to reconcile");
1706
+ }
1707
+ const sorted = [...versions].sort(
1708
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
1709
+ );
1710
+ const latest = sorted[0];
1711
+ let conflictsResolved = 0;
1712
+ for (const v of versions) {
1713
+ const timeDiff = Math.abs(
1714
+ new Date(v.timestamp).getTime() - new Date(latest.timestamp).getTime()
1715
+ );
1716
+ if (timeDiff > 100) {
1717
+ conflictsResolved++;
1718
+ }
1719
+ }
1720
+ const result = {
1721
+ success: true,
1722
+ mergedState: latest.data,
1723
+ conflictsResolved,
1724
+ strategy: "vector-clock",
1725
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1726
+ };
1727
+ this.reconciliationHistory.push(result);
1728
+ logger.debug("[StateReconciler] State reconciled (vector-clock)", {
1729
+ winnerVersion: latest.version,
1730
+ conflictsResolved
1731
+ });
1732
+ return result;
1733
+ }
1734
+ /**
1735
+ * Reconcile states using majority vote strategy
1736
+ */
1737
+ reconcileMajorityVote(versions) {
1738
+ if (versions.length === 0) {
1739
+ throw new Error("No versions to reconcile");
1740
+ }
1741
+ const hashGroups = /* @__PURE__ */ new Map();
1742
+ for (const version of versions) {
1743
+ if (!hashGroups.has(version.hash)) {
1744
+ hashGroups.set(version.hash, []);
1745
+ }
1746
+ hashGroups.get(version.hash).push(version);
1747
+ }
1748
+ let majorityVersion = null;
1749
+ let maxCount = 0;
1750
+ for (const [, versionGroup] of hashGroups) {
1751
+ if (versionGroup.length > maxCount) {
1752
+ maxCount = versionGroup.length;
1753
+ majorityVersion = versionGroup[0];
1754
+ }
1755
+ }
1756
+ if (!majorityVersion) {
1757
+ majorityVersion = versions[0];
1758
+ }
1759
+ const conflictsResolved = versions.length - maxCount;
1760
+ const result = {
1761
+ success: true,
1762
+ mergedState: majorityVersion.data,
1763
+ conflictsResolved,
1764
+ strategy: "majority-vote",
1765
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1766
+ };
1767
+ this.reconciliationHistory.push(result);
1768
+ logger.debug("[StateReconciler] State reconciled (majority-vote)", {
1769
+ majorityCount: maxCount,
1770
+ conflictsResolved
1771
+ });
1772
+ return result;
1773
+ }
1774
+ /**
1775
+ * Merge multiple states
1776
+ */
1777
+ mergeStates(states) {
1778
+ if (states.length === 0) {
1779
+ return {};
1780
+ }
1781
+ if (states.length === 1) {
1782
+ return states[0];
1783
+ }
1784
+ const merged = {};
1785
+ for (const state of states) {
1786
+ if (typeof state === "object" && state !== null) {
1787
+ Object.assign(merged, state);
1788
+ }
1789
+ }
1790
+ return merged;
1791
+ }
1792
+ /**
1793
+ * Validate state after reconciliation
1794
+ */
1795
+ validateState(state) {
1796
+ const errors = [];
1797
+ if (state === null) {
1798
+ errors.push("State is null");
1799
+ } else if (state === void 0) {
1800
+ errors.push("State is undefined");
1801
+ } else if (typeof state !== "object") {
1802
+ errors.push("State is not an object");
1803
+ }
1804
+ return {
1805
+ valid: errors.length === 0,
1806
+ errors
1807
+ };
1808
+ }
1809
+ /**
1810
+ * Get state versions for a key
1811
+ */
1812
+ getStateVersions(key) {
1813
+ return this.stateVersions.get(key) || [];
1814
+ }
1815
+ /**
1816
+ * Get all state versions
1817
+ */
1818
+ getAllStateVersions() {
1819
+ const result = {};
1820
+ for (const [key, versions] of this.stateVersions) {
1821
+ result[key] = [...versions];
1822
+ }
1823
+ return result;
1824
+ }
1825
+ /**
1826
+ * Get reconciliation history
1827
+ */
1828
+ getReconciliationHistory() {
1829
+ return [...this.reconciliationHistory];
1830
+ }
1831
+ /**
1832
+ * Get reconciliation statistics
1833
+ */
1834
+ getStatistics() {
1835
+ const resolvedConflicts = this.reconciliationHistory.reduce(
1836
+ (sum, r) => sum + r.conflictsResolved,
1837
+ 0
1838
+ );
1839
+ const strategyUsage = {};
1840
+ for (const result of this.reconciliationHistory) {
1841
+ strategyUsage[result.strategy] = (strategyUsage[result.strategy] || 0) + 1;
1842
+ }
1843
+ return {
1844
+ totalReconciliations: this.reconciliationHistory.length,
1845
+ successfulReconciliations: this.reconciliationHistory.filter((r) => r.success).length,
1846
+ totalConflictsResolved: resolvedConflicts,
1847
+ averageConflictsPerReconciliation: this.reconciliationHistory.length > 0 ? resolvedConflicts / this.reconciliationHistory.length : 0,
1848
+ strategyUsage,
1849
+ trackedKeys: this.stateVersions.size
1850
+ };
1851
+ }
1852
+ /**
1853
+ * Clear all state (for testing)
1854
+ */
1855
+ clear() {
1856
+ this.stateVersions.clear();
1857
+ this.reconciliationHistory = [];
1858
+ this.cryptoProvider = null;
1859
+ this.requireSignedVersions = false;
1860
+ }
1861
+ /**
1862
+ * Get the crypto provider (for advanced usage)
1863
+ */
1864
+ getCryptoProvider() {
1865
+ return this.cryptoProvider;
1866
+ }
1867
+ };
1868
+
1869
+ exports.ReplicationManager = ReplicationManager;
1870
+ exports.StateReconciler = StateReconciler;
1871
+ exports.SyncCoordinator = SyncCoordinator;
1872
+ exports.SyncProtocol = SyncProtocol;
1873
+ //# sourceMappingURL=index.cjs.map
1874
+ //# sourceMappingURL=index.cjs.map