/** * holdings.ts, who currently holds what, derived from traffic alone. * * Spread ranking needs a number the whole cluster agrees on: how many surfaces * each node is already responsible for. There is no field on the wire that * carries it, and there deliberately isn't one, a node that ADVERTISED its own * load could advertise a convenient number, and every node would then be * ranking on a different node's self-report. * * It does not need one. Every node hears every datagram in the group: the * transport is multicast with loopback on, so a node that does not serve * Telegram still RECEIVES the Telegram heartbeats and can count them. Holdings * are therefore observed, not announced, each node counts, from the same * datagram stream, which node last claimed each surface. Two nodes that have * seen the same traffic compute the same numbers, including for themselves. * * Two separate tables, with different lifetimes: * * holders , surfaceId -> the node heard heartbeating it. Expires at the * master timeout, because a holder that stopped heartbeating is * exactly what "no longer holds it" looks like. * * candidates, surfaceId -> nodes that have shown they can serve it, by * sending ANY datagram for it. Expires much later: a standby is * quiet by design, and forgetting it would make an overloaded * holder believe it has nobody to hand a surface to. */ export interface ClusterHoldingsOptions { /** How long a holder stays believed after its last heartbeat. */ readonly holderTtlMs: number; /** How long a node stays a believed candidate after its last datagram. */ readonly candidateTtlMs: number; } /** One observed holding, for `/status`. */ export interface ClusterHoldingRecord { readonly surfaceId: string; readonly nodeId: string; } export declare class ClusterHoldingsLedger { private readonly options; private readonly holders; private readonly candidates; constructor(options: ClusterHoldingsOptions); /** `nodeId` is heartbeating `surfaceId`, it holds it as of `mono`. */ noteHolder(surfaceId: string, nodeId: string, mono: number): void; /** * `nodeId` said it is no longer holding `surfaceId` (a RESIGN, or our own * ordered stop). Only clears the entry when that node is the believed holder *, a stale farewell from a node that already lost the surface must not * erase the successor. * * The node stays a CANDIDATE: resigning is not the same as being unable to * serve it, and a node that yields for balance is precisely the node we may * want to hand it back to later. */ noteReleased(surfaceId: string, nodeId: string): void; /** `nodeId` spoke about `surfaceId`, so it can serve it. */ noteCandidate(surfaceId: string, nodeId: string, mono: number): void; /** The believed holder of `surfaceId`, or null when it has gone stale. */ holderOf(surfaceId: string, mono: number): string | null; /** * How many surfaces `nodeId` currently holds, the spread ranking's second * tier, computed identically for self and for every peer. */ holdingsOf(nodeId: string, mono: number): number; /** Which surfaces `nodeId` currently holds. */ surfacesHeldBy(nodeId: string, mono: number): string[]; /** * Nodes believed able to serve `surfaceId`, excluding `exceptNodeId`. * * `withinMs` narrows the answer to nodes heard from recently, and the yield * decision passes it deliberately. The table's own TTL is generous so a * standby that misses a beat is not forgotten; handing a working surface to * a node that may have died two beats ago needs a tighter bar than that, or * a machine that is switched off drags a surface offline for a full timeout * before its old holder takes it back. */ candidatesFor(surfaceId: string, mono: number, exceptNodeId?: string, withinMs?: number): string[]; /** * Drop everything known about a node. * * Used when the local view has to be rebuilt from scratch, after a host * suspend, the holdings table describes a network that moved on without us, * and acting on it would rank against nodes that may no longer exist. */ forgetAll(): void; /** Every live holding, for the `cluster` section of `/status`. */ snapshot(mono: number): ClusterHoldingRecord[]; } //# sourceMappingURL=holdings.d.ts.map