/**
 * LiveTailSource — the HOLDER-side flush producer (spec §8 G3b). Tracks, per
 * active topic, what conversation content has already been streamed to the
 * standby, and emits the NEW suffix as a monotonic-sequence flush via the
 * live-tail transport. The standby's LiveTailBuffer appends each flush's content
 * contiguously (applies only lastAppliedSeq+1), so the source MUST send deltas,
 * not the full tail — this class owns that delta accounting.
 *
 * Two drivers:
 *   - cadence (liveTailPushRateMs): pushTick() flushes any topic with new content,
 *     keeping the standby's persisted copy fresh to within liveTailMaxStalenessMs.
 *   - handoff: flushTopic()/flushAll() force a flush and return the resulting per-
 *     topic sequence numbers so HandoffSentinel can build the manifest the incoming
 *     machine must echo.
 *
 * EVENT-LOOP DISCIPLINE (the 2026-06-05 Laptop-meltdown fix): building a topic's
 * content is the expensive step — the provider may serialize hundreds of history
 * entries. Doing that for EVERY known topic on EVERY tick blocked the server's
 * event loop for seconds at a stretch, which made our mesh RPC timestamps go
 * stale, which made the standby reject flushes, which caused hot retries — a
 * self-amplifying storm. Three guards now bound the work:
 *
 *   1. VERSION GATE — when the deps provide getTopicVersion (a cheap, monotonic
 *      per-topic message counter), a topic whose version is unchanged since its
 *      last successful flush is skipped WITHOUT building its content. Idle topics
 *      cost one Map lookup per tick instead of a full history serialization.
 *   2. FAILURE BACKOFF — a topic whose flush was rejected/unreachable is not
 *      retried every tick. Consecutive failures back off exponentially (base
 *      doubling, capped), so a rejecting peer (clock-skew 403s, auth break) is
 *      never hammered with full-tail resends at tick rate.
 *   3. CONTENT CAP — a single flush's content is capped (maxFlushBytes); an
 *      oversized delta/full-resend sends only the freshest suffix. The standby's
 *      buffer caps per-topic bytes anyway (LiveTailBuffer.maxBytesPerTopic), so
 *      an unbounded send is pure cost with no retention benefit.
 *
 * The handoff path passes { force: true }, which bypasses the version gate and
 * the backoff window (a handoff is a deliberate one-shot that must try NOW) —
 * but still sends nothing when content is genuinely unchanged.
 *
 * The content provider (getTopicContent) and the transport are injected, so this
 * is unit-testable without a network or a live message store, and so the same
 * producer works for any channel (Telegram, Slack) — the source is channel-
 * agnostic; only the content provider differs.
 */
export interface LiveTailSourceDeps {
    /** The holder's full current tail content for a topic (caller resolves it). */
    getTopicContent: (topic: string) => string;
    /** Topics with live conversation worth streaming to the standby. */
    activeTopics: () => string[];
    /** The wire — redacts + encrypts + posts to the standby (HttpLiveTailTransport). */
    transport: {
        broadcast: (flush: {
            topic: string;
            seq: number;
            content: string;
        }) => Promise<boolean>;
    };
    /**
     * Cheap, monotonic per-topic content version (e.g. a message counter bumped on
     * every logged message). When provided, an unchanged version skips the topic
     * without calling getTopicContent — the event-loop guard that keeps idle
     * topics at O(1) per tick. Omitted → every tick builds every topic's content
     * (the pre-fix behavior; fine for tests and tiny installs).
     */
    getTopicVersion?: (topic: string) => number;
    /**
     * Cap on a single flush's content size, in UTF-16 chars (≈ bytes for our
     * ASCII-heavy logs; heavily non-ASCII content can be up to ~3× larger on the
     * wire — acceptable: the standby buffer enforces its own independent byte
     * cap, this is purely a sender-side cost bound). Default 256 KiB.
     */
    maxFlushBytes?: number;
    /** First-failure retry delay; doubles per consecutive failure. Default 5s. */
    failureBackoffBaseMs?: number;
    /** Backoff ceiling. Default 5 min. */
    failureBackoffMaxMs?: number;
    /**
     * Eternal Sentinel condition 4 ("No Unbounded Loops" / P19): the backoff
     * keeps retrying a failing topic forever (correct — the standby copy should
     * converge whenever the peer recovers), but a topic whose flushes have been
     * failing past staleSignalAfterMs must SAY SO once per episode instead of
     * going quietly stale. Wired to DegradationReporter in server.ts; omitted →
     * silent (tests / channels without a reporter).
     */
    reportStaleStandby?: (info: {
        topic: string;
        failingForMs: number;
        consecutiveFailures: number;
    }) => void;
    /** Sustained-failure threshold for the one-per-episode stale signal. Default 30 min. */
    staleSignalAfterMs?: number;
    now?: () => number;
    logger?: (msg: string) => void;
}
export interface FlushOutcome {
    topic: string;
    /** The per-topic sequence of the flush that was sent (or the current high-water if none). */
    seq: number;
    /** Whether new content was actually flushed (false = nothing new). */
    flushed: boolean;
}
export interface FlushOpts {
    /**
     * Bypass the version gate and the failure-backoff window (handoff path — a
     * deliberate one-shot that must attempt NOW). Unchanged content still sends
     * nothing.
     */
    force?: boolean;
}
export declare class LiveTailSource {
    private readonly d;
    /** Per-topic content already streamed (the prefix the standby has). */
    private streamed;
    /** Per-topic monotonic sequence (matches the standby's applied seq). */
    private seq;
    /** Per-topic provider version at the last successful flush / confirmed no-op. */
    private lastSeenVersion;
    /** Per-topic consecutive broadcast failures (drives the backoff). */
    private failures;
    /** Per-topic earliest next attempt (ms epoch) while backing off. */
    private nextAttemptAt;
    /** Per-topic failure-episode start (ms epoch; absent = healthy). */
    private failingSince;
    /** Episode-keyed one-shot latch for the stale-standby signal (value = failingSince it fired for). */
    private staleSignaledFor;
    constructor(deps: LiveTailSourceDeps);
    private log;
    private now;
    /** Current high-water sequence for a topic (for the handoff manifest). */
    currentSeq(topic: string): number;
    /**
     * Flush the NEW suffix of a topic's content (the delta since the last flush).
     * If the content shrank or diverged from what we streamed (e.g. a history
     * rewrite), we resend the whole content as a fresh delta. No new content → no
     * flush, no sequence bump (so duplicate ticks don't inflate the standby's seq).
     */
    flushTopic(topic: string, opts?: FlushOpts): Promise<FlushOutcome>;
    /** Flush every active topic; returns each topic's outcome (used by the handoff). */
    flushAll(opts?: FlushOpts): Promise<FlushOutcome[]>;
    /** The cadence driver — flush whatever has new content. Returns count actually flushed. */
    pushTick(opts?: FlushOpts): Promise<number>;
}
//# sourceMappingURL=LiveTailSource.d.ts.map