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