/** The kinds of inbound surface that can be elected over. */ export type ClusterSurfaceKind = 'telegram' | 'ntfy' | 'slack' | 'discord' | 'inbox' | 'custom'; /** * A surface's LOCAL identity. Never serialized onto the wire, only its digest * is. `discriminator` is whatever distinguishes one surface of this kind from * another on the same network: a bot id, a server-and-topic pair, an account. */ export interface ClusterSurfaceKey { readonly kind: ClusterSurfaceKind; readonly discriminator: string; } /** * The exact string that gets hashed. * * Every node must derive the same digest for the same surface without talking * to anyone, so normalization happens HERE and nowhere else: trimmed, and * lowercased for the kind only. The discriminator's case is preserved because * an ntfy topic is case-sensitive and folding it would merge two real surfaces * into one election. */ export declare function canonicalSurfaceKey(key: ClusterSurfaceKey): string; /** The digest that goes on the wire as `surfaceId`. */ export declare function surfaceIdFor(key: ClusterSurfaceKey): string; /** True when `value` is a well-formed surface digest and nothing else. */ export declare function isSurfaceId(value: unknown): value is string; /** * A short, human-readable label for local logs and this host's `/status`. * * Deliberately NOT the full discriminator: a log line wants enough to tell two * topics apart, and log files get pasted into issues. The kind is spelled out * and the discriminator is reduced to its first eight digest characters. */ export declare function surfaceLabel(key: ClusterSurfaceKey): string; /** * A Telegram bot's public numeric id, taken from its token. * * A bot token is `:`. Only the id half ever reaches this module, * and even that is hashed before it is sent, the secret half is never read, * never logged, and never hashed. */ export declare function telegramBotIdFromToken(token: string): string; /** The surface for one Telegram bot. */ export declare function telegramSurface(botIdOrToken: string): ClusterSurfaceKey; /** * The surface for ONE ntfy topic on one server. * * Server and topic together, because the same topic name on two different ntfy * servers is two unrelated surfaces, and because a node configured against a * self-hosted server must not stand down for a node reading ntfy.sh. */ export declare function ntfySurface(baseUrl: string, topic: string): ClusterSurfaceKey; /** The surface for one inbox provider account (Slack, Discord, an IMAP box). */ export declare function inboxSurface(providerId: string): ClusterSurfaceKey; /** The surface for a single-instance provider runtime (Slack, Discord sockets). */ export declare function providerSurface(kind: ClusterSurfaceKind, discriminator: string): ClusterSurfaceKey; /** * The spread tiebreak: a stable pseudo-random ordering of nodes, DIFFERENT for * every surface. * * Using the nodeId alone as the final tiebreak, as the whole-node design did, * hands every surface that reaches the tiebreak to the same node, which is the * exact concentration this ranking exists to avoid. Mixing the surface into * the hash makes each surface prefer a different node, so a two-node cluster * with three surfaces splits them instead of stacking them. * * It is a hash, not randomness: every node computes the same value for the * same pair, forever, with no coordination. */ export declare function stableSurfaceHash(nodeId: string, surfaceId: string): string; //# sourceMappingURL=surface-id.d.ts.map