/** * facade-cluster.ts, leadership gating for the daemon's INBOUND consumers, * one gate per surface. * * Several things start listening when the daemon comes up: the Telegram * ingress supervisor, a subscription per ntfy topic, the Slack Socket Mode * connection, the Discord Gateway. Each is registered here as its OWN gate on * the cluster coordinator instead of being started directly, so on a network * where this install runs more than once exactly one node reads each of them, * and different nodes may read different ones. * * That per-surface split is the whole point. The previous shape registered one * gate for "the provider runtime" covering Slack, Discord and every ntfy topic * together, which meant a node either read all of them or none. A laptop with * a bot token and a desktop with only a topic could not divide the work, and * whichever won took surfaces it had no credential for. * * A gate is registered only for a surface this node can ACTUALLY serve: the * surface is enabled in config and its credential resolves. That is what stops * a node winning an election for something it cannot read, which would starve * the node that could. * * What is deliberately NOT gated: outbound delivery, sessions, the control * plane, the HTTP listener, watchers, triggers, and every other daemon * subsystem. A standby node is a fully functional daemon that simply is not the * one reading a particular inbox, it can still send, still serve the web UI, * still run agents. Gating anything else would turn "we are not the reader" * into "we are degraded", which is not what leadership means here. * * A host that gates inbound consumers of its own passes a coordinator in * (`DaemonConfig.clusterCoordinator`). It MUST be reused rather than * supplemented: two coordinators in one process would be two nodes in the * group, and one would gate consumers the other owns. The goodvibes-tui daemon * does exactly this so its own inbox poller rides the same leadership. */ import { ClusterCoordinator } from '../cluster/index.js'; import type { DaemonConfig } from './types.js'; import type { DaemonFacadeCollaborators, ResolvedDaemonFacadeRuntime } from './facade-types.js'; export { ClusterCoordinator } from '../cluster/index.js'; /** * Build (or adopt) the coordinator, and tell it how to find out what this node * can serve. * * The surfaces are registered through `onPrepare` rather than here, because * deciding whether a surface is servable means RESOLVING its credential and * this function is synchronous. `onPrepare` runs that work at `start()`, ahead * of the first boot probe, so every servable surface is in its own first * election and no unresolvable one is contested at all. */ export declare function buildDaemonClusterCoordinator(config: DaemonConfig, runtime: ResolvedDaemonFacadeRuntime, collaborators: DaemonFacadeCollaborators): ClusterCoordinator; /** Register a gate for every inbound surface this node can actually serve. */ export declare function registerDaemonClusterSurfaces(coordinator: ClusterCoordinator, runtime: ResolvedDaemonFacadeRuntime, collaborators: DaemonFacadeCollaborators): Promise; //# sourceMappingURL=facade-cluster.d.ts.map