/** * A channel that dies has to REACH the owner, not sit in a field. * * Making the reported state honest fixes the answer to a question, but nobody * asks that question. The lived failure was not "the status endpoint said the * wrong thing", it was that the owner sent a Telegram message, got nothing * back, and had no way to find out why without going and looking. A truthful * `dead` that nothing reads is the same silence with better bookkeeping. * * So this sweeps the registry, notices the transitions, and hands each one to * an announcer. Three properties it is built around: * * 1. It never announces over the channel that died. The dead surface is named * in the alert so the announcer can exclude it; announcing a dead Telegram * over Telegram is how this class of bug hides. * 2. It announces recoveries too. An owner who was told a channel was dead is * owed the other half of that sentence, or the next alert is one they have * learned to ignore. * 3. It repeats while a channel stays dead, on a long interval. A one-shot * alert that arrives while nobody is looking has not told anyone anything. */ import type { ChannelHealthState, ChannelStatusSnapshot, ChannelSurface } from './types.js'; /** What the owner is told, and enough structure to route it sensibly. */ export interface ChannelHealthAlert { readonly kind: 'failed' | 'recovered' | 'still-failing'; readonly surface: ChannelSurface; readonly label: string; readonly state: ChannelHealthState; readonly previousState: ChannelHealthState | null; /** Why, in the surface's own words, the supervisor's named reason. */ readonly reason: string; /** A complete sentence fit to send as-is. */ readonly message: string; /** How long it has been in this state, when known. */ readonly failingSinceMs?: number | undefined; } export interface ChannelHealthWatcherDeps { /** The registry sweep; the same snapshots every other surface reads. */ readonly listStatus: () => Promise; /** * Deliver the alert to the owner over some channel OTHER than * `alert.surface`. Absent means nothing is wired, which this class refuses to * treat as normal, see `start()`. */ readonly announce?: ((alert: ChannelHealthAlert) => Promise | void) | undefined; /** How often to sweep. */ readonly intervalMs?: number | undefined; /** How long a channel must stay failing before it is announced again. */ readonly repeatMs?: number | undefined; /** Test seam. */ readonly now?: (() => number) | undefined; } export declare class ChannelHealthWatcher { private readonly deps; private readonly tracked; private timer; private sweeping; constructor(deps: ChannelHealthWatcherDeps); /** * Begin sweeping. * * A watcher with no announcer says so at WARN and keeps running: the state it * records is still worth having, but an embedder must not be able to believe * the owner is being told when nothing is wired to tell them. That belief is * precisely what shipped. */ start(): void; stop(): void; /** One pass. Public so a caller can force a check without waiting a cycle. */ sweep(): Promise; private sweepOnce; private decide; private emit; } //# sourceMappingURL=health-watcher.d.ts.map