/** * surface-registry.ts, which inbound surfaces THIS node can actually serve. * * The registry is the answer to the question the ruling turns on: a node must * never win an election for something it cannot serve. Winning a surface with * no credential for it, or with that surface switched off locally, starves the * node that could have served it, the loser stands down, the winner reads * nothing, and the topic goes unread by anybody on the network. * * So membership is not a configuration list. A surface is in here only because * a working consumer for it was registered: the composition root resolved the * credential, found the surface enabled, built something that can start, and * handed it over. A node with no inbound surfaces registers nothing here, * joins no elections, and claims nothing. * * (Not to be confused with `channels/surface-registry.ts`, which is about * message routing between channel surfaces. This one is only about who on the * LAN is allowed to contest which inbound consumer.) */ import { type ClusterSurfaceKey } from './surface-id.js'; import type { ClusterConsumerGate, ClusterConsumerStartContext, ClusterLogger } from './types.js'; /** One servable surface and every consumer registered against it. */ export interface RegisteredClusterSurface { readonly surfaceId: string; readonly key: ClusterSurfaceKey; /** Digest-derived label, safe for logs. */ readonly label: string; readonly gates: readonly ClusterConsumerGate[]; } export declare class ClusterSurfaceRegistry { private readonly logger; private readonly surfaces; /** * Surfaces whose last consumer has been unregistered but whose election has * not finished standing down yet. * * They must stay reachable for exactly one more `stopSurface`. Leaving with * a RESIGN means asserting to the whole network that this node has stopped * reading, and if the gate were already unreachable there would be nothing * to stop, so the assertion would be false and the successor would start * against a consumer still running. They are dropped by `forget` once the * election has actually stopped. */ private readonly retiring; private readonly listeners; constructor(logger: ClusterLogger); /** * Add a consumer. Returns an unregister function. * * Several gates may share one surface, the ntfy stream and a diagnostic * tap on the same topic, say. They start in registration order and stop in * the reverse, so a consumer another depends on is up first and down last. */ register(gate: ClusterConsumerGate): () => void; /** Called whenever the servable set changes, with the surface that moved. */ onChange(listener: (surfaceId: string) => void): () => void; /** True when this node has a working consumer for the surface right now. */ canServe(surfaceId: string): boolean; get(surfaceId: string): RegisteredClusterSurface | undefined; /** Every servable surface, in a stable order. */ list(): RegisteredClusterSurface[]; get size(): number; /** Start every consumer for one surface, in registration order. */ startSurface(surfaceId: string, context: ClusterConsumerStartContext): Promise; /** * Stop every consumer for one surface, newest first, and do not resolve * until they have all settled. The RESIGN that follows this is a claim that * consumption has genuinely ceased, so it must not be sent a moment early. */ stopSurface(surfaceId: string, reason: string): Promise; /** Drop a retired surface once its election has finished standing down. */ forget(surfaceId: string): void; private notify; } //# sourceMappingURL=surface-registry.d.ts.map