/** * conflict-policy.ts, what to do about a Telegram 409, decided as pure data. * * ── The failure this exists to prevent ──────────────────────────────────── * * Inbound Telegram went permanently dead on a live machine. Polling stopped * and stayed stopped until a human restarted the daemon; every message in * between was unread, with nothing but a log line to say so. * * Telegram answers `getUpdates` with 409 in two unrelated situations, a * registered webhook, and another process long-polling the same token, and * they used to be told apart by matching the error description against * "terminated by other getUpdates", with "webhook" as the fallback for * everything else. So a 409 whose description was missing, reworded, or * replaced by an intermediary's own error body was read as a webhook * conflict, and the webhook branch gave up after three attempts. A string * that has to be exhaustive in order to be safe is a guess, not a * classification. * * The machine's own evidence settled which case it actually was: * `getWebhookInfo` reported no webhook, the logs contained no `setWebhook` * call, and `deleteWebhook` ran three times without the 409 ever clearing. * **A conflict that survives a successful deleteWebhook is not a webhook * conflict.** * * ── The rules ───────────────────────────────────────────────────────────── * * 1. `getWebhookInfo` is the authority. The description corroborates and * enriches what a person reads; it never decides on its own. * 2. `deleteWebhook` is idempotent and harmless, so it is attempted whenever a * webhook is even plausible rather than only when it is proven. * 3. **Nothing here is ever terminal.** A stuck webhook can be removed by a * person at any moment; a competing consumer is frequently transient, a * test daemon, a second checkout, a stale process. Both must recover on * their own, so the caller keeps polling either way. * 4. Crossing the escalation threshold changes only how LOUDLY it is said. * * Kept pure so every branch is provable without a socket, a fake server, or a * clock. */ /** How many conflicts to absorb quietly before escalating to an error. */ export declare const CONFLICT_ESCALATION_ATTEMPTS = 3; /** What the supervisor should do about one 409. */ export type TelegramConflictAction = { /** A webhook is registered, or plausibly is. Clear it and poll again. */ readonly kind: 'clear-webhook'; /** True once this has gone on long enough that a person needs telling. */ readonly escalate: boolean; /** Populated when escalating: what is wrong and what to do about it. */ readonly reason: string | null; /** The url Telegram reported, when it reported one. */ readonly webhookUrl: string | null; } | { /** Another process holds this token. Report it, back off, poll again. */ readonly kind: 'competing-consumer'; readonly escalate: boolean; /** Always populated: a coordinator is told on every occurrence. */ readonly reason: string; /** Whether a webhook was implicated first and ruled out by evidence. */ readonly ruledOutWebhook: boolean; }; export interface TelegramConflictInput { /** Telegram's `description`, or a synthesized `HTTP nnn` when it sent none. */ readonly description: string; /** The url `getWebhookInfo` reported, or null when it reported none. */ readonly webhookUrl: string | null; /** `cluster.enabled`, whether there is an election to defer to. */ readonly clustered: boolean; /** 1-based count of consecutive conflicts. */ readonly attempt: number; } /** * Decide what one 409 means and what to do about it. * * Never returns "give up", there is no such action, deliberately. See rule 3. */ export declare function classifyTelegramConflict(input: TelegramConflictInput): TelegramConflictAction; //# sourceMappingURL=conflict-policy.d.ts.map