claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
141 lines • 7.19 kB
TypeScript
/**
* ADR-095 G2 — pluggable transport for hive-mind consensus protocols.
*
* The raft/byzantine/gossip consensus implementations historically used a
* local `EventEmitter` for *everything* — both observability events
* ("leader.elected", "consensus.achieved") AND inter-node messages
* (append-entries, vote requests, pre-prepare/prepare/commit). The latter
* never actually crossed a process or node boundary: a node "sent" a
* message by `emit`ting it locally and synthesizing the peer's reply
* inline. That's the single-process limitation #G2 names.
*
* This module separates the inter-node-message dimension behind a
* `ConsensusTransport` interface. Two implementations:
*
* - `LocalTransport` — an in-process registry. Multiple consensus
* instances in the same Node process share a registry and deliver
* messages to each other synchronously. Matches the current
* single-process behavior; the default so nothing breaks.
* - `FederationTransport` (separate file, ADR-104 wire) — serializes
* ConsensusMessages into federation envelopes, signs them with the
* node's Ed25519 key, sends over WS via agentic-flow/transport/loader,
* and dispatches inbound envelopes with signature verification.
*
* Observability events stay on the consensus class's own EventEmitter —
* this is purely the messaging layer.
*
* No new dependencies: Ed25519 signing uses Node's built-in `crypto`
* (`generateKeyPairSync('ed25519')` + `sign`/`verify` with `null` algorithm,
* which is correct for Ed25519).
*/
/** A message exchanged between consensus nodes. */
export interface ConsensusMessage {
/** Protocol message type — e.g. 'append-entries', 'request-vote', 'pre-prepare', 'prepare', 'commit', 'gossip', 'gossip-ack'. */
readonly type: string;
/** Sender node id. */
readonly from: string;
/** Recipient node id. Omit for broadcast. */
readonly to?: string;
/** Protocol payload (term, log entries, vote, digest, …). */
readonly payload: unknown;
/** Raft term, when applicable. Lets the transport drop stale-term messages cheaply. */
readonly term?: number;
/** PBFT view number, when applicable. */
readonly viewNumber?: number;
/** Monotonic per-sender sequence number — replay defense. */
readonly seq?: number;
/** Ed25519 signature (base64) over `canonicalizeForSigning(msg)`. */
readonly signature?: string;
}
/**
* A reply to a `send()`. Protocols use this for the request-response legs
* (request-vote → vote-response, append-entries → append-entries-response).
* Broadcasts don't get replies; responses arrive via `onMessage`.
*/
export type ConsensusReply = ConsensusMessage | null;
export type ConsensusMessageHandler = (msg: ConsensusMessage) => Promise<ConsensusReply | void> | ConsensusReply | void;
export interface ConsensusTransport {
/** This node's id (the one consensus protocols use as `from`). */
readonly nodeId: string;
/**
* Send a message to a specific peer. Resolves with the peer's reply
* (or `null` if the peer ack'd without a reply), rejects on timeout or
* unreachable peer. `timeoutMs` defaults to the transport's configured value.
*/
send(to: string, msg: Omit<ConsensusMessage, 'from'>, timeoutMs?: number): Promise<ConsensusReply>;
/** Broadcast to all currently-reachable peers. Resolves once dispatched; replies (if any) arrive via onMessage. */
broadcast(msg: Omit<ConsensusMessage, 'from'>): Promise<void>;
/** Register the inbound-message handler. Calling again replaces the previous handler. */
onMessage(handler: ConsensusMessageHandler): void;
/** Currently-reachable peer node ids (excludes self). */
peers(): readonly string[];
/** Tear down. After close(), send/broadcast reject. */
close(): Promise<void>;
}
export interface NodeKeyPair {
/** Ed25519 private key in PKCS8 PEM. */
readonly privateKeyPem: string;
/** Ed25519 public key in SPKI PEM. */
readonly publicKeyPem: string;
}
/** Generate a fresh Ed25519 keypair for a consensus node. */
export declare function generateNodeKeyPair(): NodeKeyPair;
/**
* Canonical byte string for signing. Deterministic across hosts: deep-sorted-key
* JSON of the message's content fields (everything except `signature`).
*/
export declare function canonicalizeForSigning(msg: Omit<ConsensusMessage, 'signature'>): Buffer;
/** Stable digest of a message's content — handy for dedup and logging. */
export declare function messageDigest(msg: Omit<ConsensusMessage, 'signature'>): string;
/** Sign a message with an Ed25519 private key (PEM). Returns base64 signature. */
export declare function signMessage(msg: Omit<ConsensusMessage, 'signature'>, privateKeyPem: string): string;
/**
* Verify a signed message against a peer's Ed25519 public key (PEM).
* Returns true iff the signature is present and valid over the message's
* content fields. Fail-closed: a missing signature returns false.
*/
export declare function verifyMessage(msg: ConsensusMessage, publicKeyPem: string): boolean;
/**
* Shared registry of LocalTransport instances. Multiple consensus nodes in
* the same process register here; send/broadcast deliver to peers' handlers.
* Use a fresh registry per test to keep tests isolated.
*/
export declare class LocalTransportRegistry {
private readonly nodes;
register(t: LocalTransport): void;
unregister(nodeId: string): void;
get(nodeId: string): LocalTransport | undefined;
peerIds(exclude: string): string[];
}
/** Process-wide default registry. Tests should pass their own. */
export declare const defaultLocalRegistry: LocalTransportRegistry;
export interface LocalTransportOptions {
readonly registry?: LocalTransportRegistry;
readonly defaultTimeoutMs?: number;
/** Optional Ed25519 keypair — when set, outbound messages are signed and inbound are verified against the sender's pubkey (resolved via `resolvePeerPublicKey`). */
readonly keyPair?: NodeKeyPair;
/** Map a peer nodeId → its Ed25519 public key PEM. Required if `keyPair` is set and you want verification. */
readonly resolvePeerPublicKey?: (nodeId: string) => string | undefined;
}
export declare class LocalTransport implements ConsensusTransport {
readonly nodeId: string;
private readonly registry;
private readonly defaultTimeoutMs;
private readonly keyPair?;
private readonly resolvePeerPublicKey?;
private handler;
private closed;
private seqCounter;
/** Per-sender last-seen seq for replay defense (only used when signed). */
private readonly lastSeenSeq;
constructor(nodeId: string, opts?: LocalTransportOptions);
onMessage(handler: ConsensusMessageHandler): void;
peers(): readonly string[];
private stamp;
/** Deliver an inbound message to a target's handler, with optional sig + replay checks. */
private deliver;
send(to: string, msg: Omit<ConsensusMessage, 'from'>, timeoutMs?: number): Promise<ConsensusReply>;
broadcast(msg: Omit<ConsensusMessage, 'from'>): Promise<void>;
close(): Promise<void>;
}
//# sourceMappingURL=transport.d.ts.map