/**
 * DeliveryFailureSentinel — Layer 3 of telegram-delivery-robustness.
 *
 * Spec: docs/specs/telegram-delivery-robustness.md § 4 Layer 3.
 *
 * Reads the SQLite queue created by Layer 2 (`pending-relay-store`),
 * runs the recovery state machine, and is feature-flag-gated default-OFF.
 *
 * State machine (per row):
 *
 *   queued ──claim──► claimed ──/whoami──► (mismatch → retry)
 *                                  │
 *                                  └──tone-gate──► (422 → delivered-tone-gated)
 *                                          │
 *                                          └──/telegram/reply──► (200 → delivered-recovered
 *                                                                  408 → delivered-ambiguous
 *                                                                  5xx/conn-refused → retry)
 *                                          │
 *                                          └──ttl/attempts exhausted ──► escalated
 *
 * The sentinel never overrides the tone-gate authority (§5 signal-vs-
 * authority compliance). On 422 it finalizes the entry as
 * `delivered-tone-gated` and emits the fixed-template meta-notice on the
 * original topic.
 *
 * Lifecycle: `start()` registers the watchdog tick + (when wsManager is
 * provided) an SSE handler for `delivery_failed` events. `stop()` clears
 * timers and detaches from the event stream. `tick()` is exposed for
 * tests so the recovery loop can be driven without waiting for the
 * 5-minute backstop interval.
 *
 * **Default-OFF.** The constructor enforces nothing; the AgentServer
 * gates instantiation on `config.monitoring.deliveryFailureSentinel.enabled`.
 * Layer 1 + Layer 2 ship unconditionally; Layer 3 is opt-in until the
 * canary criteria in spec § 3i are met.
 */
import { EventEmitter } from 'node:events';
import type { PendingRelayStore } from '../messaging/pending-relay-store.js';
import { WhoamiCache } from '../messaging/whoami-cache.js';
import type { MessagingToneGate } from '../core/MessagingToneGate.js';
export interface SentinelConfig {
    /** Backstop watchdog tick interval. Default 5 minutes (spec §3a). */
    watchdogIntervalMs?: number;
    /** Per-entry lease duration. Default 90s (spec §3b). */
    leaseDurationMs?: number;
    /** Per-topic delivery rate cap. Default 30s (spec §3c). */
    perTopicRateMs?: number;
    /** Max parallel cross-topic recoveries. Default 4 (spec §3c). */
    maxConcurrent?: number;
    /** Stampede threshold — entries on same topic before digest fires. Default 5 (spec §3c). */
    stampedeThreshold?: number;
    /**
     * Circuit breaker — N consecutive escalation failures within window
     * trips the breaker. Defaults: N=5, window=1h (spec §3f).
     */
    circuitBreakerCount?: number;
    circuitBreakerWindowMs?: number;
    /**
     * Restore-purge threshold — entries older than this at startup are
     * dropped, not recovered (spec §3h). Default 60 minutes.
     *
     * Was 5 minutes — tuned for a world where restarts are rare. On a
     * frequent-release day (restarts every ~15 min), the one-shot purge ran
     * at EVERY boot and deleted legitimately queued-undelivered messages
     * that simply hadn't gotten a drain window yet (live 2026-06-05: five
     * "restore-purged 1 stale rows" deletions on echo in one day, each a
     * real outbound message — including a milestone report — silently
     * gone). 60 minutes still prevents redelivering genuinely ancient
     * messages after a long outage while surviving restart churn.
     */
    restorePurgeAgeMs?: number;
}
export interface SentinelDeps {
    store: PendingRelayStore;
    /** Path to .instar/config.json — used for whoami-cache mtime invalidation. */
    configPath: string;
    /** Live config accessor — re-read on every tick (spec §3d step 1). */
    readConfig: () => {
        port: number;
        authToken: string;
        agentId: string;
    };
    /** This server's bootId (spec §3b). Required — sentinel exits if absent. */
    bootId: string;
    /** Tone gate authority for §3d step 3. Null = no gate configured. */
    toneGate: MessagingToneGate | null;
    /**
     * Subscribe to in-process `delivery_failed` events. Returns an
     * unsubscribe handle. The wsManager fan-out emits these events; the
     * sentinel reacts in <1s rather than waiting for the 5-min watchdog.
     */
    subscribeFailureEvents?: (listener: (event: {
        delivery_id: string;
        topic_id: number;
        agentId: string;
    }) => void) => () => void;
    /** Override clock (tests). */
    now?: () => number;
    /** Override fetch (tests). Default uses node:http to localhost. */
    postReply?: (port: number, token: string, agentId: string, topicId: number, text: string, deliveryId: string, isSystem?: boolean, 
    /** Serialized queue-row metadata (kind/senderClass/advisoryAck) — the
     *  redrive forwards it whole so a queued automated send is never
     *  mis-kinded (spec outbound-jargon-filepath-gap §2.5). Null on legacy
     *  rows. */
    metadataJson?: string | null) => Promise<{
        status: number;
        body: string;
    }>;
    /** Whoami cache — defaults to a fresh instance with default deps. */
    whoamiCache?: WhoamiCache;
}
export interface SentinelEvents {
    'sentinel:started': [];
    'sentinel:stopped': [];
    'sentinel:tick-complete': [{
        processed: number;
        recovered: number;
        escalated: number;
    }];
    'sentinel:recovered': [{
        delivery_id: string;
        topic_id: number;
    }];
    'sentinel:tone-gated': [{
        delivery_id: string;
        topic_id: number;
        rule?: string;
    }];
    'sentinel:escalated': [{
        delivery_id: string;
        topic_id: number;
        category: string;
    }];
    'sentinel:circuit-breaker-tripped': [{
        consecutiveFailures: number;
    }];
    'sentinel:circuit-breaker-resumed': [];
}
export declare class DeliveryFailureSentinel extends EventEmitter {
    private readonly cfg;
    private readonly deps;
    private watchdog;
    private unsubscribe;
    private running;
    private suspended;
    private escalationFailures;
    private inFlight;
    private lastTopicDelivery;
    /** Templates verified at start(). False disables escalation path. */
    private templatesValid;
    /** Restored on first start(); used to keep restore-purge a one-shot. */
    private restorePurged;
    constructor(deps: SentinelDeps, config?: SentinelConfig);
    /**
     * Start the sentinel. Verifies template integrity, performs a one-shot
     * restore-purge of stale rows, registers the SSE listener (if available),
     * and arms the watchdog tick. Idempotent — calling start() while running
     * is a no-op.
     */
    start(): Promise<void>;
    /** Stop the sentinel, clearing the watchdog and detaching listeners. */
    stop(): Promise<void>;
    /** True iff the sentinel's circuit breaker is currently tripped. */
    isSuspended(): boolean;
    /**
     * Run one recovery tick. Public so tests can drive recovery without
     * waiting for the watchdog interval.
     *
     * Returns aggregate counters useful for assertions.
     */
    tick(): Promise<{
        processed: number;
        recovered: number;
        escalated: number;
    }>;
    private selectClaimable;
    private isLeaseStale;
    private processRow;
    private finalizeToneGated;
    private handlePolicyDecision;
    private escalate;
    private recordEscalationFailure;
    private lastConfigHash;
    private maybeResume;
    private computeConfigAuthHash;
    /** Manual resume — used by `instar sentinel resume` CLI handler. */
    resume(): void;
    private handleStampede;
    private purgeStaleRows;
}
//# sourceMappingURL=delivery-failure-sentinel.d.ts.map