brainy/.recovery-workspace/dist-backup-20250910-141917/distributed/coordinator.js
David Snelling 8ff382ca3b chore: recovery checkpoint - v3.0 API successfully recovered
CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB

Recovery Status:
- Successfully recovered brainy.ts from compiled JavaScript
- All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.)
- Neural subsystem intact (562KB embedded patterns, NLP working)
- Augmentation pipeline operational (20+ augmentations)
- HNSW clustering system complete
- Triple Intelligence compiled (needs constructor fix)
- Test suite validates functionality

Changes preserved:
- 898 files with changes from last 3 days
- 144,475 insertions
- All augmentation improvements
- All test coverage enhancements
- Complete v3.0 feature set

This is a LOCAL checkpoint only - contains recovered work after corruption incident.
Created backup in .backups/brainy-full-20250910-151314.tar.gz

Branch: recovery-checkpoint-20250910-151433
Date: Wed Sep 10 03:18:04 PM PDT 2025
2025-09-10 15:18:04 -07:00

548 lines
No EOL
17 KiB
JavaScript

/**
* Distributed Coordinator for Brainy 3.0
* Provides leader election, consensus, and coordination for distributed instances
*/
import { EventEmitter } from 'events';
import { createHash } from 'crypto';
/**
* Distributed Coordinator implementing Raft-like consensus
*/
export class DistributedCoordinator extends EventEmitter {
constructor(config = {}) {
super();
this.nodes = new Map();
this.isRunning = false;
this.votesReceived = new Set();
this.currentTerm = 0;
this.lastLogIndex = -1;
this.lastLogTerm = 0;
this.logEntries = [];
this.transport = null; // For migration proposals
this.pendingMigrations = new Map();
this.committedMigrations = new Set();
// Generate node ID if not provided
this.nodeId = config.nodeId || this.generateNodeId();
// Configuration
this.heartbeatInterval = config.heartbeatInterval || 1000;
this.electionTimeout = config.electionTimeout || 5000;
// Initialize consensus state
this.consensusState = {
term: 0,
votedFor: null,
leader: null,
state: 'follower'
};
// Register this node
this.nodes.set(this.nodeId, {
id: this.nodeId,
address: config.address || 'localhost',
port: config.port || 3000,
lastSeen: Date.now(),
status: 'active',
state: 'follower',
lastHeartbeat: Date.now()
});
// Register other nodes if provided
if (config.nodes) {
this.registerNodes(config.nodes);
}
}
/**
* Start the coordinator
*/
async start(networkTransport) {
if (this.isRunning)
return;
this.isRunning = true;
this.networkTransport = networkTransport;
// Setup network message handlers if transport is provided
if (this.networkTransport) {
this.setupNetworkHandlers();
}
this.emit('started', { nodeId: this.nodeId });
// Start as follower
this.becomeFollower();
}
/**
* Setup network message handlers
*/
setupNetworkHandlers() {
if (!this.networkTransport)
return;
// Register message handlers directly on the messageHandlers map
const handlers = this.networkTransport.messageHandlers;
// Handle vote requests
handlers.set('requestVote', async (msg) => {
const response = await this.handleVoteRequest(msg);
// Send response back
if (this.networkTransport) {
await this.networkTransport.sendToNode(msg.from, 'voteResponse', response);
}
return response;
});
// Handle vote responses
handlers.set('voteResponse', async (msg) => {
this.handleVoteResponse(msg);
});
// Handle heartbeats/append entries
handlers.set('appendEntries', async (msg) => {
const response = await this.handleAppendEntries(msg);
// Send response back
if (this.networkTransport) {
await this.networkTransport.sendToNode(msg.from, 'appendResponse', response);
}
return response;
});
// Handle append responses
handlers.set('appendResponse', async (msg) => {
this.handleAppendResponse(msg);
});
}
/**
* Stop the coordinator
*/
async stop() {
if (!this.isRunning)
return;
this.isRunning = false;
if (this.electionTimer) {
clearTimeout(this.electionTimer);
this.electionTimer = undefined;
}
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = undefined;
}
this.emit('stopped');
}
/**
* Register additional nodes
*/
registerNodes(nodes) {
for (const node of nodes) {
const [address, port] = node.split(':');
const nodeId = this.generateNodeId(node);
if (!this.nodes.has(nodeId)) {
this.nodes.set(nodeId, {
id: nodeId,
address,
port: parseInt(port || '3000'),
lastSeen: Date.now(),
status: 'active',
state: 'follower'
});
}
}
}
/**
* Become a follower
*/
becomeFollower() {
this.consensusState.state = 'follower';
const node = this.nodes.get(this.nodeId);
if (node) {
node.state = 'follower';
}
this.resetElectionTimeout();
this.emit('stateChange', 'follower');
}
/**
* Become a candidate and start election
*/
async becomeCandidate() {
this.consensusState.state = 'candidate';
this.currentTerm++;
this.consensusState.term = this.currentTerm;
this.consensusState.votedFor = this.nodeId;
this.votesReceived = new Set([this.nodeId]);
const node = this.nodes.get(this.nodeId);
if (node) {
node.state = 'candidate';
}
this.emit('stateChange', 'candidate');
// Request votes from all other nodes
await this.requestVotes();
// Reset election timeout
this.resetElectionTimeout();
}
/**
* Become the leader
*/
becomeLeader() {
this.consensusState.state = 'leader';
this.consensusState.leader = this.nodeId;
const node = this.nodes.get(this.nodeId);
if (node) {
node.state = 'leader';
}
// Stop election timer as leader
if (this.electionTimer) {
clearTimeout(this.electionTimer);
this.electionTimer = undefined;
}
this.emit('stateChange', 'leader');
this.emit('leaderElected', this.nodeId);
// Start sending heartbeats
this.startHeartbeat();
}
/**
* Request votes from all nodes
*/
async requestVotes() {
if (!this.networkTransport) {
// Simulate vote for testing
this.checkVoteMajority();
return;
}
const voteRequest = {
type: 'requestVote',
term: this.currentTerm,
candidateId: this.nodeId,
lastLogIndex: this.getLastLogIndex(),
lastLogTerm: this.getLastLogTerm()
};
// Send vote requests to all other nodes
for (const [nodeId] of this.nodes) {
if (nodeId !== this.nodeId) {
try {
await this.networkTransport.sendToNode(nodeId, 'requestVote', voteRequest);
}
catch (err) {
console.error(`Failed to request vote from ${nodeId}:`, err);
}
}
}
}
/**
* Handle vote request from another node
*/
async handleVoteRequest(msg) {
const { term, candidateId, lastLogIndex, lastLogTerm } = msg.data;
// Update term if necessary
if (term > this.currentTerm) {
this.currentTerm = term;
this.consensusState.term = term;
this.consensusState.votedFor = null;
this.becomeFollower();
}
// Grant vote if conditions are met
let voteGranted = false;
if (term >= this.currentTerm &&
(!this.consensusState.votedFor || this.consensusState.votedFor === candidateId) &&
this.isLogUpToDate(lastLogIndex, lastLogTerm)) {
this.consensusState.votedFor = candidateId;
voteGranted = true;
this.resetElectionTimeout();
}
return {
type: 'voteResponse',
term: this.currentTerm,
voteGranted
};
}
/**
* Handle vote response
*/
handleVoteResponse(msg) {
const { term, voteGranted } = msg.data;
// Ignore old responses
if (term < this.currentTerm)
return;
// Update term if necessary
if (term > this.currentTerm) {
this.currentTerm = term;
this.consensusState.term = term;
this.becomeFollower();
return;
}
// Count vote if granted
if (voteGranted && this.consensusState.state === 'candidate') {
this.votesReceived.add(msg.from);
this.checkVoteMajority();
}
}
/**
* Check if we have majority votes
*/
checkVoteMajority() {
const majority = Math.floor(this.nodes.size / 2) + 1;
if (this.votesReceived.size >= majority) {
this.becomeLeader();
}
}
/**
* Handle append entries (heartbeat) from leader
*/
async handleAppendEntries(msg) {
const { term, leaderId, prevLogIndex, prevLogTerm, entries, leaderCommit } = msg.data;
// Update term if necessary
if (term > this.currentTerm) {
this.currentTerm = term;
this.consensusState.term = term;
this.consensusState.votedFor = null;
this.becomeFollower();
}
// Reset election timeout when receiving valid heartbeat
if (term >= this.currentTerm) {
this.consensusState.leader = leaderId;
this.resetElectionTimeout();
// Update leader node's last heartbeat
const leaderNode = this.nodes.get(leaderId);
if (leaderNode) {
leaderNode.lastHeartbeat = Date.now();
leaderNode.lastSeen = Date.now();
}
}
// Check log consistency
let success = false;
if (term >= this.currentTerm) {
if (this.checkLogConsistency(prevLogIndex, prevLogTerm)) {
// Append new entries if any
if (entries && entries.length > 0) {
this.appendLogEntries(prevLogIndex, entries);
}
success = true;
}
}
return {
type: 'appendResponse',
term: this.currentTerm,
success
};
}
/**
* Handle append response from follower
*/
handleAppendResponse(msg) {
const { term, success } = msg.data;
// Update term if necessary
if (term > this.currentTerm) {
this.currentTerm = term;
this.consensusState.term = term;
this.becomeFollower();
}
// Process successful append
if (success && this.consensusState.state === 'leader') {
// Update follower's match index
// In a real implementation, this would track replication progress
}
}
/**
* Send heartbeat to followers
*/
async sendHeartbeat() {
if (!this.networkTransport) {
// Fallback for testing
await new Promise(resolve => setTimeout(resolve, 10));
return;
}
const heartbeat = {
type: 'appendEntries',
term: this.currentTerm,
leaderId: this.nodeId,
prevLogIndex: this.getLastLogIndex(),
prevLogTerm: this.getLastLogTerm(),
entries: [],
leaderCommit: 0
};
// Send heartbeat to all followers
for (const [nodeId] of this.nodes) {
if (nodeId !== this.nodeId) {
try {
await this.networkTransport.sendToNode(nodeId, 'appendEntries', heartbeat);
}
catch (err) {
// Node might be down, mark as suspected
const node = this.nodes.get(nodeId);
if (node) {
node.status = 'suspected';
}
}
}
}
}
/**
* Start heartbeat timer as leader
*/
startHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
}
this.heartbeatTimer = setInterval(() => {
this.sendHeartbeat();
}, this.heartbeatInterval);
// Send immediate heartbeat
this.sendHeartbeat();
}
/**
* Reset election timeout
*/
resetElectionTimeout() {
if (this.electionTimer) {
clearTimeout(this.electionTimer);
}
// Randomize timeout to prevent split votes
const timeout = this.electionTimeout + Math.random() * this.electionTimeout;
this.electionTimer = setTimeout(() => {
if (this.consensusState.state === 'follower') {
this.becomeCandidate();
}
}, timeout);
}
/**
* Check if log is up to date
*/
isLogUpToDate(lastLogIndex, lastLogTerm) {
const myLastLogTerm = this.getLastLogTerm();
const myLastLogIndex = this.getLastLogIndex();
if (lastLogTerm > myLastLogTerm)
return true;
if (lastLogTerm < myLastLogTerm)
return false;
return lastLogIndex >= myLastLogIndex;
}
/**
* Check log consistency
*/
checkLogConsistency(prevLogIndex, prevLogTerm) {
if (prevLogIndex === -1)
return true;
if (prevLogIndex >= this.logEntries.length)
return false;
const entry = this.logEntries[prevLogIndex];
return entry && entry.term === prevLogTerm;
}
/**
* Append log entries
*/
appendLogEntries(prevLogIndex, entries) {
// Remove conflicting entries
this.logEntries = this.logEntries.slice(0, prevLogIndex + 1);
// Append new entries
for (const entry of entries) {
this.logEntries.push({
term: entry.term,
index: this.logEntries.length,
data: entry.data
});
}
this.lastLogIndex = this.logEntries.length - 1;
if (this.lastLogIndex >= 0) {
this.lastLogTerm = this.logEntries[this.lastLogIndex].term;
}
}
/**
* Get last log index
*/
getLastLogIndex() {
return this.lastLogIndex;
}
/**
* Get last log term
*/
getLastLogTerm() {
return this.lastLogTerm;
}
/**
* Generate a unique node ID
*/
generateNodeId(seed) {
const source = seed || `${process.pid}-${Date.now()}-${Math.random()}`;
return createHash('sha256').update(source).digest('hex').substring(0, 16);
}
/**
* Get current leader
*/
getLeader() {
return this.consensusState.leader;
}
/**
* Propose a shard migration to the cluster
*/
async proposeMigration(migration) {
if (!this.isLeader()) {
throw new Error('Only leader can propose migrations');
}
// Broadcast migration proposal to all nodes
const message = {
type: 'migration-proposal',
migration,
timestamp: Date.now()
};
await this.transport.broadcast('migration', message);
// Store migration as pending
this.pendingMigrations.set(migration.migrationId, {
...migration,
status: 'pending'
});
}
/**
* Get migration status
*/
async getMigrationStatus(migrationId) {
const migration = this.pendingMigrations.get(migrationId);
if (!migration) {
// Check if it was committed
return this.committedMigrations.has(migrationId) ? 'committed' : 'rejected';
}
return migration.status || 'pending';
}
/**
* Check if this node is the leader
*/
isLeader() {
return this.consensusState.state === 'leader';
}
/**
* Get all nodes in the cluster
*/
getNodes() {
return Array.from(this.nodes.values());
}
/**
* Get cluster health status
*/
getHealth() {
const now = Date.now();
const activeNodes = Array.from(this.nodes.values()).filter(node => now - node.lastSeen < this.electionTimeout).length;
return {
healthy: this.consensusState.leader !== null && activeNodes > this.nodes.size / 2,
leader: this.consensusState.leader,
nodes: this.nodes.size,
activeNodes
};
}
/**
* Propose a command to the cluster
*/
async proposeCommand(command) {
if (this.consensusState.state !== 'leader') {
throw new Error(`Not the leader. Current leader: ${this.consensusState.leader}`);
}
// Add to log
const entry = {
term: this.currentTerm,
index: this.logEntries.length,
data: command
};
this.logEntries.push(entry);
this.lastLogIndex = entry.index;
this.lastLogTerm = entry.term;
// Replicate to followers
await this.sendHeartbeat();
this.emit('commandProposed', command);
}
/**
* Get current state
*/
getState() {
return { ...this.consensusState };
}
}
/**
* Create a coordinator instance
*/
export function createCoordinator(config) {
return new DistributedCoordinator(config);
}
//# sourceMappingURL=coordinator.js.map