import { type ClusterEnvelope, type ClusterKeyring } from './protocol-envelope.js'; import type { ClusterLogger, ClusterTransport, ClusterTransportDescription } from './types.js'; /** Running totals, surfaced in `cluster status` so drops are visible rather than inferred. */ export interface GroupWireCounters { sent: number; received: number; droppedOtherGroup: number; droppedBadSignature: number; droppedMalformed: number; droppedOldGeneration: number; /** Outbound election datagrams not sent because this machine is in no group. */ droppedNoGroup: number; } export interface GroupWireRouterOptions { readonly inner: ClusterTransport; readonly keyring: ClusterKeyring; readonly logger: ClusterLogger; readonly now: () => number; /** BEACON / ROSTER that verified against the group key. */ readonly onGroupMessage: (envelope: ClusterEnvelope) => void; /** JOIN / REJOIN and their replies, authenticated by the membership layer, not here. */ readonly onOutOfBandMessage: (raw: string, type: string) => void; /** A beacon from a DIFFERENT group. Advertisement only; never acted on. */ readonly onForeignBeacon: (groupId: string, body: Record, version: string) => void; } /** * Routes every inbound datagram to exactly one destination, and wraps every * outbound election datagram. */ export declare class GroupWireRouter { private readonly options; readonly counters: GroupWireCounters; private started; private announcedNoGroup; private electionListener; private seq; constructor(options: GroupWireRouterOptions); /** Start the underlying socket. Idempotent, both tenants may call it. */ ensureStarted(): Promise; stop(): Promise; describe(): ClusterTransportDescription; /** Next per-node sequence number. Shared by both tenants so it never repeats. */ nextSeq(): number; /** Send a datagram the group layer built itself (already a full envelope). */ sendRaw(raw: string): Promise; /** * The transport handed to `ClusterCoordinator`. * * Deliberately a plain object rather than a subclass: the coordinator only * ever calls these four methods, and keeping the seam this narrow is what * lets the election stay entirely unaware of the group layer. */ electionTransport(nodeVersion: string): ClusterTransport; /** * Lift an election message into the group envelope. * * The merged wire format is ONE object carrying the group's fields and the * surface's together: * * { v, groupId, keyGen, surfaceId, type, nodeId, nodeVersion, seq, ts, body, sig } * * so a datagram is answerable for which group it belongs to, which key * generation signed it, AND which surface it is about, all under one * signature. Whatever the election put on the message beyond those fields * rides in `body` untouched, so the message that comes out the far side is * the one that went in. */ private wrapElectionMessage; /** * Rebuild the election's own message from a verified envelope. * * Every lifted field goes back under the name the election gave it, and the * body is restored around them. The reconstruction has to be exact: when * `cluster.secret` is also set, the election re-checks its own signature over * `[v, type, surfaceId, nodeId, nodeVersion, seq, ts]`, and a field that came * back under a different name or a different value would fail every datagram. */ private unwrapElectionMessage; private receive; private countRejection; } //# sourceMappingURL=group-transport.d.ts.map