@sparkleideas/swarm 3.0.0-alpha.7

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.
Files changed (65) hide show
  1. package/MIGRATION.md +472 -0
  2. package/README.md +634 -0
  3. package/__tests__/consensus.test.ts +577 -0
  4. package/__tests__/coordinator.test.ts +501 -0
  5. package/__tests__/queen-coordinator.test.ts +1335 -0
  6. package/__tests__/topology.test.ts +621 -0
  7. package/package.json +32 -0
  8. package/src/agent-pool.ts +476 -0
  9. package/src/application/commands/create-task.command.ts +124 -0
  10. package/src/application/commands/spawn-agent.command.ts +122 -0
  11. package/src/application/index.ts +30 -0
  12. package/src/application/services/swarm-application-service.ts +200 -0
  13. package/src/attention-coordinator.ts +1000 -0
  14. package/src/consensus/byzantine.ts +431 -0
  15. package/src/consensus/gossip.ts +513 -0
  16. package/src/consensus/index.ts +267 -0
  17. package/src/consensus/raft.ts +443 -0
  18. package/src/coordination/agent-registry.ts +544 -0
  19. package/src/coordination/index.ts +23 -0
  20. package/src/coordination/swarm-hub.ts +776 -0
  21. package/src/coordination/task-orchestrator.ts +605 -0
  22. package/src/domain/entities/agent.d.ts +151 -0
  23. package/src/domain/entities/agent.d.ts.map +1 -0
  24. package/src/domain/entities/agent.js +280 -0
  25. package/src/domain/entities/agent.js.map +1 -0
  26. package/src/domain/entities/agent.ts +370 -0
  27. package/src/domain/entities/task.d.ts +133 -0
  28. package/src/domain/entities/task.d.ts.map +1 -0
  29. package/src/domain/entities/task.js +261 -0
  30. package/src/domain/entities/task.js.map +1 -0
  31. package/src/domain/entities/task.ts +319 -0
  32. package/src/domain/index.ts +41 -0
  33. package/src/domain/repositories/agent-repository.interface.d.ts +57 -0
  34. package/src/domain/repositories/agent-repository.interface.d.ts.map +1 -0
  35. package/src/domain/repositories/agent-repository.interface.js +9 -0
  36. package/src/domain/repositories/agent-repository.interface.js.map +1 -0
  37. package/src/domain/repositories/agent-repository.interface.ts +69 -0
  38. package/src/domain/repositories/task-repository.interface.d.ts +61 -0
  39. package/src/domain/repositories/task-repository.interface.d.ts.map +1 -0
  40. package/src/domain/repositories/task-repository.interface.js +9 -0
  41. package/src/domain/repositories/task-repository.interface.js.map +1 -0
  42. package/src/domain/repositories/task-repository.interface.ts +75 -0
  43. package/src/domain/services/coordination-service.ts +320 -0
  44. package/src/federation-hub.d.ts +284 -0
  45. package/src/federation-hub.d.ts.map +1 -0
  46. package/src/federation-hub.js +692 -0
  47. package/src/federation-hub.js.map +1 -0
  48. package/src/federation-hub.ts +979 -0
  49. package/src/index.ts +348 -0
  50. package/src/message-bus.ts +607 -0
  51. package/src/queen-coordinator.ts +2025 -0
  52. package/src/shared/events.ts +285 -0
  53. package/src/shared/types.ts +389 -0
  54. package/src/topology-manager.ts +656 -0
  55. package/src/types.ts +545 -0
  56. package/src/unified-coordinator.ts +1844 -0
  57. package/src/workers/index.ts +65 -0
  58. package/src/workers/worker-dispatch.d.ts +234 -0
  59. package/src/workers/worker-dispatch.d.ts.map +1 -0
  60. package/src/workers/worker-dispatch.js +842 -0
  61. package/src/workers/worker-dispatch.js.map +1 -0
  62. package/src/workers/worker-dispatch.ts +1076 -0
  63. package/tmp.json +0 -0
  64. package/tsconfig.json +9 -0
  65. package/vitest.config.ts +20 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * V3 Consensus Engine Factory
3
+ * Unified interface for different consensus algorithms
4
+ */
5
+
6
+ import { EventEmitter } from 'events';
7
+ import {
8
+ ConsensusAlgorithm,
9
+ ConsensusConfig,
10
+ ConsensusProposal,
11
+ ConsensusVote,
12
+ ConsensusResult,
13
+ IConsensusEngine,
14
+ SWARM_CONSTANTS,
15
+ } from '../types.js';
16
+ import { RaftConsensus, createRaftConsensus, RaftConfig } from './raft.js';
17
+ import { ByzantineConsensus, createByzantineConsensus, ByzantineConfig } from './byzantine.js';
18
+ import { GossipConsensus, createGossipConsensus, GossipConfig } from './gossip.js';
19
+
20
+ export { RaftConsensus, ByzantineConsensus, GossipConsensus };
21
+ export type { RaftConfig, ByzantineConfig, GossipConfig };
22
+
23
+ type ConsensusImplementation = RaftConsensus | ByzantineConsensus | GossipConsensus;
24
+
25
+ export class ConsensusEngine extends EventEmitter implements IConsensusEngine {
26
+ private config: ConsensusConfig;
27
+ private nodeId: string;
28
+ private implementation?: ConsensusImplementation;
29
+ private proposals: Map<string, ConsensusProposal> = new Map();
30
+
31
+ constructor(nodeId: string, config: Partial<ConsensusConfig> = {}) {
32
+ super();
33
+ this.nodeId = nodeId;
34
+ this.config = {
35
+ algorithm: config.algorithm ?? 'raft',
36
+ threshold: config.threshold ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_THRESHOLD,
37
+ timeoutMs: config.timeoutMs ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_TIMEOUT_MS,
38
+ maxRounds: config.maxRounds ?? 10,
39
+ requireQuorum: config.requireQuorum ?? true,
40
+ };
41
+ }
42
+
43
+ async initialize(config?: ConsensusConfig): Promise<void> {
44
+ if (config) {
45
+ this.config = { ...this.config, ...config };
46
+ }
47
+
48
+ // Create implementation based on algorithm
49
+ switch (this.config.algorithm) {
50
+ case 'raft':
51
+ this.implementation = createRaftConsensus(this.nodeId, {
52
+ threshold: this.config.threshold,
53
+ timeoutMs: this.config.timeoutMs,
54
+ maxRounds: this.config.maxRounds,
55
+ requireQuorum: this.config.requireQuorum,
56
+ });
57
+ break;
58
+
59
+ case 'byzantine':
60
+ this.implementation = createByzantineConsensus(this.nodeId, {
61
+ threshold: this.config.threshold,
62
+ timeoutMs: this.config.timeoutMs,
63
+ maxRounds: this.config.maxRounds,
64
+ requireQuorum: this.config.requireQuorum,
65
+ });
66
+ break;
67
+
68
+ case 'gossip':
69
+ this.implementation = createGossipConsensus(this.nodeId, {
70
+ threshold: this.config.threshold,
71
+ timeoutMs: this.config.timeoutMs,
72
+ maxRounds: this.config.maxRounds,
73
+ requireQuorum: this.config.requireQuorum,
74
+ });
75
+ break;
76
+
77
+ case 'paxos':
78
+ // Fall back to Raft for Paxos (similar guarantees)
79
+ this.implementation = createRaftConsensus(this.nodeId, {
80
+ threshold: this.config.threshold,
81
+ timeoutMs: this.config.timeoutMs,
82
+ maxRounds: this.config.maxRounds,
83
+ requireQuorum: this.config.requireQuorum,
84
+ });
85
+ break;
86
+
87
+ default:
88
+ throw new Error(`Unknown consensus algorithm: ${this.config.algorithm}`);
89
+ }
90
+
91
+ await this.implementation.initialize();
92
+
93
+ // Forward events
94
+ this.implementation.on('consensus.achieved', (data) => {
95
+ this.emit('consensus.achieved', data);
96
+ });
97
+
98
+ this.implementation.on('leader.elected', (data) => {
99
+ this.emit('leader.elected', data);
100
+ });
101
+
102
+ this.emit('initialized', {
103
+ nodeId: this.nodeId,
104
+ algorithm: this.config.algorithm
105
+ });
106
+ }
107
+
108
+ async shutdown(): Promise<void> {
109
+ if (this.implementation) {
110
+ await this.implementation.shutdown();
111
+ }
112
+ this.emit('shutdown');
113
+ }
114
+
115
+ addNode(nodeId: string, options?: { isPrimary?: boolean }): void {
116
+ if (!this.implementation) {
117
+ throw new Error('Consensus engine not initialized');
118
+ }
119
+
120
+ if (this.implementation instanceof RaftConsensus) {
121
+ this.implementation.addPeer(nodeId);
122
+ } else if (this.implementation instanceof ByzantineConsensus) {
123
+ this.implementation.addNode(nodeId, options?.isPrimary);
124
+ } else if (this.implementation instanceof GossipConsensus) {
125
+ this.implementation.addNode(nodeId);
126
+ }
127
+ }
128
+
129
+ removeNode(nodeId: string): void {
130
+ if (!this.implementation) {
131
+ return;
132
+ }
133
+
134
+ if (this.implementation instanceof RaftConsensus) {
135
+ this.implementation.removePeer(nodeId);
136
+ } else if (this.implementation instanceof ByzantineConsensus) {
137
+ this.implementation.removeNode(nodeId);
138
+ } else if (this.implementation instanceof GossipConsensus) {
139
+ this.implementation.removeNode(nodeId);
140
+ }
141
+ }
142
+
143
+ async propose(value: unknown, proposerId?: string): Promise<ConsensusProposal> {
144
+ if (!this.implementation) {
145
+ throw new Error('Consensus engine not initialized');
146
+ }
147
+
148
+ const proposal = await this.implementation.propose(value);
149
+ this.proposals.set(proposal.id, proposal);
150
+ return proposal;
151
+ }
152
+
153
+ async vote(proposalId: string, vote: ConsensusVote): Promise<void> {
154
+ if (!this.implementation) {
155
+ throw new Error('Consensus engine not initialized');
156
+ }
157
+
158
+ await this.implementation.vote(proposalId, vote);
159
+ }
160
+
161
+ getProposal(proposalId: string): ConsensusProposal | undefined {
162
+ return this.proposals.get(proposalId);
163
+ }
164
+
165
+ async awaitConsensus(proposalId: string): Promise<ConsensusResult> {
166
+ if (!this.implementation) {
167
+ throw new Error('Consensus engine not initialized');
168
+ }
169
+
170
+ return this.implementation.awaitConsensus(proposalId);
171
+ }
172
+
173
+ getActiveProposals(): ConsensusProposal[] {
174
+ return Array.from(this.proposals.values()).filter(
175
+ p => p.status === 'pending'
176
+ );
177
+ }
178
+
179
+ // Algorithm-specific queries
180
+ isLeader(): boolean {
181
+ if (this.implementation instanceof RaftConsensus) {
182
+ return this.implementation.isLeader();
183
+ }
184
+ if (this.implementation instanceof ByzantineConsensus) {
185
+ return this.implementation.isPrimary();
186
+ }
187
+ return false; // Gossip has no leader
188
+ }
189
+
190
+ getLeaderId(): string | undefined {
191
+ if (this.implementation instanceof RaftConsensus) {
192
+ return this.implementation.getLeaderId();
193
+ }
194
+ return undefined;
195
+ }
196
+
197
+ getAlgorithm(): ConsensusAlgorithm {
198
+ return this.config.algorithm;
199
+ }
200
+
201
+ getConfig(): ConsensusConfig {
202
+ return { ...this.config };
203
+ }
204
+
205
+ // Metrics
206
+ getStats(): {
207
+ algorithm: ConsensusAlgorithm;
208
+ totalProposals: number;
209
+ pendingProposals: number;
210
+ acceptedProposals: number;
211
+ rejectedProposals: number;
212
+ expiredProposals: number;
213
+ } {
214
+ const proposals = Array.from(this.proposals.values());
215
+
216
+ return {
217
+ algorithm: this.config.algorithm,
218
+ totalProposals: proposals.length,
219
+ pendingProposals: proposals.filter(p => p.status === 'pending').length,
220
+ acceptedProposals: proposals.filter(p => p.status === 'accepted').length,
221
+ rejectedProposals: proposals.filter(p => p.status === 'rejected').length,
222
+ expiredProposals: proposals.filter(p => p.status === 'expired').length,
223
+ };
224
+ }
225
+ }
226
+
227
+ // Factory function
228
+ export function createConsensusEngine(
229
+ nodeId: string,
230
+ algorithm: ConsensusAlgorithm = 'raft',
231
+ config?: Partial<ConsensusConfig>
232
+ ): ConsensusEngine {
233
+ return new ConsensusEngine(nodeId, { ...config, algorithm });
234
+ }
235
+
236
+ // Helper to select optimal algorithm based on requirements
237
+ export function selectOptimalAlgorithm(requirements: {
238
+ faultTolerance: 'crash' | 'byzantine';
239
+ consistency: 'strong' | 'eventual';
240
+ networkScale: 'small' | 'medium' | 'large';
241
+ latencyPriority: 'low' | 'medium' | 'high';
242
+ }): ConsensusAlgorithm {
243
+ const { faultTolerance, consistency, networkScale, latencyPriority } = requirements;
244
+
245
+ // Byzantine fault tolerance required
246
+ if (faultTolerance === 'byzantine') {
247
+ return 'byzantine';
248
+ }
249
+
250
+ // Eventual consistency acceptable and large scale
251
+ if (consistency === 'eventual' && networkScale === 'large') {
252
+ return 'gossip';
253
+ }
254
+
255
+ // Low latency priority with medium scale
256
+ if (latencyPriority === 'high' && networkScale !== 'large') {
257
+ return 'raft';
258
+ }
259
+
260
+ // Strong consistency with small/medium scale
261
+ if (consistency === 'strong') {
262
+ return 'raft';
263
+ }
264
+
265
+ // Default to Raft
266
+ return 'raft';
267
+ }
@@ -0,0 +1,443 @@
1
+ /**
2
+ * V3 Raft Consensus Implementation
3
+ * Leader election and log replication for distributed coordination
4
+ */
5
+
6
+ import { EventEmitter } from 'events';
7
+ import {
8
+ ConsensusProposal,
9
+ ConsensusVote,
10
+ ConsensusResult,
11
+ ConsensusConfig,
12
+ SWARM_CONSTANTS,
13
+ } from '../types.js';
14
+
15
+ export type RaftState = 'follower' | 'candidate' | 'leader';
16
+
17
+ export interface RaftNode {
18
+ id: string;
19
+ state: RaftState;
20
+ currentTerm: number;
21
+ votedFor?: string;
22
+ log: RaftLogEntry[];
23
+ commitIndex: number;
24
+ lastApplied: number;
25
+ }
26
+
27
+ export interface RaftLogEntry {
28
+ term: number;
29
+ index: number;
30
+ command: unknown;
31
+ timestamp: Date;
32
+ }
33
+
34
+ export interface RaftConfig extends Partial<ConsensusConfig> {
35
+ electionTimeoutMinMs?: number;
36
+ electionTimeoutMaxMs?: number;
37
+ heartbeatIntervalMs?: number;
38
+ }
39
+
40
+ export class RaftConsensus extends EventEmitter {
41
+ private config: RaftConfig;
42
+ private node: RaftNode;
43
+ private peers: Map<string, RaftNode> = new Map();
44
+ private proposals: Map<string, ConsensusProposal> = new Map();
45
+ private electionTimeout?: NodeJS.Timeout;
46
+ private heartbeatInterval?: NodeJS.Timeout;
47
+ private proposalCounter: number = 0;
48
+
49
+ constructor(nodeId: string, config: RaftConfig = {}) {
50
+ super();
51
+ this.config = {
52
+ threshold: config.threshold ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_THRESHOLD,
53
+ timeoutMs: config.timeoutMs ?? SWARM_CONSTANTS.DEFAULT_CONSENSUS_TIMEOUT_MS,
54
+ maxRounds: config.maxRounds ?? 10,
55
+ requireQuorum: config.requireQuorum ?? true,
56
+ electionTimeoutMinMs: config.electionTimeoutMinMs ?? 150,
57
+ electionTimeoutMaxMs: config.electionTimeoutMaxMs ?? 300,
58
+ heartbeatIntervalMs: config.heartbeatIntervalMs ?? 50,
59
+ };
60
+
61
+ this.node = {
62
+ id: nodeId,
63
+ state: 'follower',
64
+ currentTerm: 0,
65
+ log: [],
66
+ commitIndex: 0,
67
+ lastApplied: 0,
68
+ };
69
+ }
70
+
71
+ async initialize(): Promise<void> {
72
+ this.resetElectionTimeout();
73
+ this.emit('initialized', { nodeId: this.node.id });
74
+ }
75
+
76
+ async shutdown(): Promise<void> {
77
+ if (this.electionTimeout) {
78
+ clearTimeout(this.electionTimeout);
79
+ }
80
+ if (this.heartbeatInterval) {
81
+ clearInterval(this.heartbeatInterval);
82
+ }
83
+ this.emit('shutdown');
84
+ }
85
+
86
+ addPeer(peerId: string): void {
87
+ this.peers.set(peerId, {
88
+ id: peerId,
89
+ state: 'follower',
90
+ currentTerm: 0,
91
+ log: [],
92
+ commitIndex: 0,
93
+ lastApplied: 0,
94
+ });
95
+ }
96
+
97
+ removePeer(peerId: string): void {
98
+ this.peers.delete(peerId);
99
+ }
100
+
101
+ async propose(value: unknown): Promise<ConsensusProposal> {
102
+ if (this.node.state !== 'leader') {
103
+ throw new Error('Only leader can propose values');
104
+ }
105
+
106
+ this.proposalCounter++;
107
+ const proposalId = `raft_${this.node.id}_${this.proposalCounter}`;
108
+
109
+ const proposal: ConsensusProposal = {
110
+ id: proposalId,
111
+ proposerId: this.node.id,
112
+ value,
113
+ term: this.node.currentTerm,
114
+ timestamp: new Date(),
115
+ votes: new Map(),
116
+ status: 'pending',
117
+ };
118
+
119
+ // Add to local log
120
+ const logEntry: RaftLogEntry = {
121
+ term: this.node.currentTerm,
122
+ index: this.node.log.length + 1,
123
+ command: { proposalId, value },
124
+ timestamp: new Date(),
125
+ };
126
+ this.node.log.push(logEntry);
127
+
128
+ this.proposals.set(proposalId, proposal);
129
+
130
+ // Leader votes for itself
131
+ proposal.votes.set(this.node.id, {
132
+ voterId: this.node.id,
133
+ approve: true,
134
+ confidence: 1.0,
135
+ timestamp: new Date(),
136
+ });
137
+
138
+ // Replicate to followers
139
+ await this.replicateToFollowers(logEntry);
140
+
141
+ return proposal;
142
+ }
143
+
144
+ async vote(proposalId: string, vote: ConsensusVote): Promise<void> {
145
+ const proposal = this.proposals.get(proposalId);
146
+ if (!proposal) {
147
+ throw new Error(`Proposal ${proposalId} not found`);
148
+ }
149
+
150
+ if (proposal.status !== 'pending') {
151
+ return;
152
+ }
153
+
154
+ proposal.votes.set(vote.voterId, vote);
155
+
156
+ // Check if we have consensus
157
+ await this.checkConsensus(proposalId);
158
+ }
159
+
160
+ async awaitConsensus(proposalId: string): Promise<ConsensusResult> {
161
+ const startTime = Date.now();
162
+
163
+ return new Promise((resolve, reject) => {
164
+ const checkInterval = setInterval(() => {
165
+ const proposal = this.proposals.get(proposalId);
166
+ if (!proposal) {
167
+ clearInterval(checkInterval);
168
+ reject(new Error(`Proposal ${proposalId} not found`));
169
+ return;
170
+ }
171
+
172
+ if (proposal.status !== 'pending') {
173
+ clearInterval(checkInterval);
174
+ resolve(this.createResult(proposal, Date.now() - startTime));
175
+ return;
176
+ }
177
+
178
+ if (Date.now() - startTime > (this.config.timeoutMs ?? 30000)) {
179
+ clearInterval(checkInterval);
180
+ proposal.status = 'expired';
181
+ resolve(this.createResult(proposal, Date.now() - startTime));
182
+ }
183
+ }, 10);
184
+ });
185
+ }
186
+
187
+ getState(): RaftState {
188
+ return this.node.state;
189
+ }
190
+
191
+ getTerm(): number {
192
+ return this.node.currentTerm;
193
+ }
194
+
195
+ isLeader(): boolean {
196
+ return this.node.state === 'leader';
197
+ }
198
+
199
+ getLeaderId(): string | undefined {
200
+ if (this.node.state === 'leader') {
201
+ return this.node.id;
202
+ }
203
+ return this.node.votedFor;
204
+ }
205
+
206
+ // ===== PRIVATE METHODS =====
207
+
208
+ private resetElectionTimeout(): void {
209
+ if (this.electionTimeout) {
210
+ clearTimeout(this.electionTimeout);
211
+ }
212
+
213
+ const timeout = this.randomElectionTimeout();
214
+ this.electionTimeout = setTimeout(() => {
215
+ this.startElection();
216
+ }, timeout);
217
+ }
218
+
219
+ private randomElectionTimeout(): number {
220
+ const min = this.config.electionTimeoutMinMs ?? 150;
221
+ const max = this.config.electionTimeoutMaxMs ?? 300;
222
+ return Math.floor(Math.random() * (max - min + 1)) + min;
223
+ }
224
+
225
+ private async startElection(): Promise<void> {
226
+ this.node.state = 'candidate';
227
+ this.node.currentTerm++;
228
+ this.node.votedFor = this.node.id;
229
+
230
+ this.emit('election.started', {
231
+ term: this.node.currentTerm,
232
+ candidateId: this.node.id
233
+ });
234
+
235
+ // Vote for self
236
+ let votesReceived = 1;
237
+ const votesNeeded = Math.floor((this.peers.size + 1) / 2) + 1;
238
+
239
+ // Request votes from peers
240
+ for (const [peerId, peer] of this.peers) {
241
+ const granted = await this.requestVote(peerId);
242
+ if (granted) {
243
+ votesReceived++;
244
+ }
245
+
246
+ if (votesReceived >= votesNeeded) {
247
+ this.becomeLeader();
248
+ return;
249
+ }
250
+ }
251
+
252
+ // Election failed, reset to follower
253
+ this.node.state = 'follower';
254
+ this.resetElectionTimeout();
255
+ }
256
+
257
+ private async requestVote(peerId: string): Promise<boolean> {
258
+ const peer = this.peers.get(peerId);
259
+ if (!peer) return false;
260
+
261
+ // Local vote request - uses in-process peer state
262
+ // Grant vote if candidate's term is higher
263
+ if (this.node.currentTerm > peer.currentTerm) {
264
+ peer.votedFor = this.node.id;
265
+ peer.currentTerm = this.node.currentTerm;
266
+ return true;
267
+ }
268
+
269
+ return false;
270
+ }
271
+
272
+ private becomeLeader(): void {
273
+ this.node.state = 'leader';
274
+
275
+ if (this.electionTimeout) {
276
+ clearTimeout(this.electionTimeout);
277
+ }
278
+
279
+ // Start sending heartbeats
280
+ this.heartbeatInterval = setInterval(() => {
281
+ this.sendHeartbeats();
282
+ }, this.config.heartbeatIntervalMs ?? 50);
283
+
284
+ this.emit('leader.elected', {
285
+ term: this.node.currentTerm,
286
+ leaderId: this.node.id
287
+ });
288
+ }
289
+
290
+ private async sendHeartbeats(): Promise<void> {
291
+ for (const [peerId, peer] of this.peers) {
292
+ await this.appendEntries(peerId, []);
293
+ }
294
+ }
295
+
296
+ private async appendEntries(peerId: string, entries: RaftLogEntry[]): Promise<boolean> {
297
+ const peer = this.peers.get(peerId);
298
+ if (!peer) return false;
299
+
300
+ // AppendEntries - local peer state update
301
+ if (this.node.currentTerm >= peer.currentTerm) {
302
+ peer.currentTerm = this.node.currentTerm;
303
+ peer.state = 'follower';
304
+ peer.log.push(...entries);
305
+ return true;
306
+ }
307
+
308
+ return false;
309
+ }
310
+
311
+ private async replicateToFollowers(entry: RaftLogEntry): Promise<void> {
312
+ const replicationPromises = Array.from(this.peers.keys()).map(
313
+ peerId => this.appendEntries(peerId, [entry])
314
+ );
315
+
316
+ const results = await Promise.allSettled(replicationPromises);
317
+ const successCount = results.filter(
318
+ r => r.status === 'fulfilled' && r.value
319
+ ).length;
320
+
321
+ // Check if majority replicated
322
+ const majority = Math.floor((this.peers.size + 1) / 2) + 1;
323
+ if (successCount + 1 >= majority) {
324
+ this.node.commitIndex = entry.index;
325
+ this.emit('log.committed', { index: entry.index });
326
+ }
327
+ }
328
+
329
+ private async checkConsensus(proposalId: string): Promise<void> {
330
+ const proposal = this.proposals.get(proposalId);
331
+ if (!proposal || proposal.status !== 'pending') {
332
+ return;
333
+ }
334
+
335
+ const totalVoters = this.peers.size + 1;
336
+ const votesReceived = proposal.votes.size;
337
+ const approvingVotes = Array.from(proposal.votes.values()).filter(
338
+ v => v.approve
339
+ ).length;
340
+
341
+ const threshold = this.config.threshold ?? 0.66;
342
+ const quorum = Math.floor(totalVoters * threshold);
343
+
344
+ if (approvingVotes >= quorum) {
345
+ proposal.status = 'accepted';
346
+ this.emit('consensus.achieved', { proposalId, approved: true });
347
+ } else if (votesReceived - approvingVotes > totalVoters - quorum) {
348
+ proposal.status = 'rejected';
349
+ this.emit('consensus.achieved', { proposalId, approved: false });
350
+ }
351
+ }
352
+
353
+ private createResult(proposal: ConsensusProposal, durationMs: number): ConsensusResult {
354
+ const totalVoters = this.peers.size + 1;
355
+ const approvingVotes = Array.from(proposal.votes.values()).filter(
356
+ v => v.approve
357
+ ).length;
358
+
359
+ return {
360
+ proposalId: proposal.id,
361
+ approved: proposal.status === 'accepted',
362
+ approvalRate: proposal.votes.size > 0
363
+ ? approvingVotes / proposal.votes.size
364
+ : 0,
365
+ participationRate: proposal.votes.size / totalVoters,
366
+ finalValue: proposal.value,
367
+ rounds: 1,
368
+ durationMs,
369
+ };
370
+ }
371
+
372
+ // Handle vote request from another candidate
373
+ handleVoteRequest(
374
+ candidateId: string,
375
+ term: number,
376
+ lastLogIndex: number,
377
+ lastLogTerm: number
378
+ ): boolean {
379
+ if (term < this.node.currentTerm) {
380
+ return false;
381
+ }
382
+
383
+ if (term > this.node.currentTerm) {
384
+ this.node.currentTerm = term;
385
+ this.node.state = 'follower';
386
+ this.node.votedFor = undefined;
387
+ }
388
+
389
+ if (this.node.votedFor === undefined || this.node.votedFor === candidateId) {
390
+ // Check log is at least as up-to-date
391
+ const lastEntry = this.node.log[this.node.log.length - 1];
392
+ const myLastTerm = lastEntry?.term ?? 0;
393
+ const myLastIndex = lastEntry?.index ?? 0;
394
+
395
+ if (lastLogTerm > myLastTerm ||
396
+ (lastLogTerm === myLastTerm && lastLogIndex >= myLastIndex)) {
397
+ this.node.votedFor = candidateId;
398
+ this.resetElectionTimeout();
399
+ return true;
400
+ }
401
+ }
402
+
403
+ return false;
404
+ }
405
+
406
+ // Handle append entries from leader
407
+ handleAppendEntries(
408
+ leaderId: string,
409
+ term: number,
410
+ entries: RaftLogEntry[],
411
+ leaderCommit: number
412
+ ): boolean {
413
+ if (term < this.node.currentTerm) {
414
+ return false;
415
+ }
416
+
417
+ this.resetElectionTimeout();
418
+
419
+ if (term > this.node.currentTerm) {
420
+ this.node.currentTerm = term;
421
+ this.node.state = 'follower';
422
+ }
423
+
424
+ this.node.votedFor = leaderId;
425
+
426
+ // Append entries
427
+ this.node.log.push(...entries);
428
+
429
+ // Update commit index
430
+ if (leaderCommit > this.node.commitIndex) {
431
+ this.node.commitIndex = Math.min(
432
+ leaderCommit,
433
+ this.node.log.length
434
+ );
435
+ }
436
+
437
+ return true;
438
+ }
439
+ }
440
+
441
+ export function createRaftConsensus(nodeId: string, config?: RaftConfig): RaftConsensus {
442
+ return new RaftConsensus(nodeId, config);
443
+ }