/**
 * PendingPullLedger — P2.2 of multi-machine coherence: the durable record of
 * working-set pulls that could not complete because the producer was
 * offline / unreachable / revoked / mid-run. The EXO case, solved: the
 * request survives restarts and re-fires the moment the peer returns.
 *
 * Spec: docs/specs/WORKING-SET-HANDOFF-SPEC.md §3.4.
 *
 * SINGLE-WRITER DISCIPLINE (the topic-flood-#3 lesson, applied at birth):
 * SIX mutators can overlap on one tick (onAccepted scheduler, reappearance
 * re-arm, run-stopped re-arm, attempt/breaker update, reflex route, TTL
 * sweep). ALL mutations route through ONE serialized `mutate(fn)` funnel —
 * an in-process async queue, so read-modify-write never interleaves — with
 * temp-file + atomic-rename persistence.
 *
 * PARSE-FAILURE POSTURE (the flood's second root): a corrupt/unparseable
 * ledger is NEVER read as "no pending pulls" — the file is quarantined
 * aside (`.corrupt-<ts>`), ONE notice fires through the injected
 * `onCorrupt` seam ("pending-pull ledger unreadable — stranded-recovery
 * records may be lost"), and a fresh ledger starts.
 *
 * Store path: `state/coherence-journal/pending-pulls.json` — registered in
 * the State-Coherence Registry (machine-local; the records describe THIS
 * machine's outstanding fetches).
 */
export declare const PENDING_PULLS_FILENAME = "pending-pulls.json";
export declare const DEFAULT_PENDING_PULL_TTL_DAYS = 7;
/** Per-record attempt cap — the (peer,topic,epoch) breaker (§3.4). */
export declare const DEFAULT_ATTEMPT_CAP = 6;
export type PendingPullReason = 'peer-offline' | 'peer-unreachable' | 'peer-revoked' | 'live-source' | 'busy-exhausted';
export interface PendingPullRecord {
    topic: number;
    epoch: number;
    nominee: string;
    reason: PendingPullReason;
    createdAt: string;
    attempts: number;
    lastAttemptAt: string | null;
}
export interface PendingPullLedgerConfig {
    /** Absolute path to the agent's `.instar/` directory. */
    stateDir: string;
    ttlDays?: number;
    attemptCap?: number;
    now?: () => Date;
    /**
     * Fired ONCE per corrupt-quarantine episode (agent-health lane upstream).
     * Never throws into the ledger.
     */
    onCorrupt?: (quarantinedPath: string) => void;
    /**
     * Fired once per record on TTL expiry — "topic T's working set on
     * <machine> was never recovered" (§3.4). Never throws into the ledger.
     */
    onExpired?: (record: PendingPullRecord) => void;
    logger?: (msg: string) => void;
}
export declare class PendingPullLedger {
    private readonly file;
    private readonly ttlMs;
    private readonly attemptCap;
    private readonly now;
    private readonly onCorrupt?;
    private readonly onExpired?;
    private readonly logger;
    /** The serialized mutate() funnel — read-modify-write never interleaves. */
    private queue;
    /** In-memory truth between mutations (loaded once, kept current). */
    private records;
    private corruptNotified;
    constructor(config: PendingPullLedgerConfig);
    /**
     * File (or refresh) a pending pull. Idempotent on (topic, epoch, nominee):
     * an existing record keeps its createdAt/attempts; only `reason` refreshes.
     */
    file_(rec: {
        topic: number;
        epoch: number;
        nominee: string;
        reason: PendingPullReason;
    }): Promise<void>;
    /**
     * Record a GENUINE failed attempt (offline / unreachable / refused /
     * verify-failed). `busy` responses MUST NOT come through here — busy is
     * retry-without-penalty (§3.2); a throttled drain must never exhaust the
     * very records it exists to recover.
     */
    recordAttempt(topic: number, epoch: number, nominee: string): Promise<void>;
    /** A completed pull clears its record. */
    clear(topic: number, epoch: number, nominee: string): Promise<void>;
    /**
     * Supersession (§3.4): a newer epoch for the topic clears ALL records for
     * that topic with `epoch < newEpoch`, across all nominees — a partial clear
     * must never strand a sibling record.
     */
    supersede(topic: number, newEpoch: number): Promise<void>;
    /**
     * TTL sweep: expire records older than ttlDays — each surfaced ONCE via
     * onExpired ("never recovered"), then removed. Run on a slow cadence by the
     * caller (it is one of the six mutators, so it rides the funnel too).
     * Returns the expired records.
     */
    sweepExpired(): Promise<PendingPullRecord[]>;
    /**
     * The records eligible to re-fire for a returning peer — the staggered
     * drain reads this (most-recent-epoch-first; the drain itself enforces
     * rearmConcurrency). Records at/over the attempt cap are excluded (the
     * breaker; a NEW epoch files a NEW record, so an old exhausted breaker
     * never suppresses a fresh, warranted pull).
     */
    pendingForPeer(nominee: string): Promise<PendingPullRecord[]>;
    /** All records for a topic (the run-stopped re-arm + reflex route read this). */
    pendingForTopic(topic: number): Promise<PendingPullRecord[]>;
    /** Every record, including breaker-exhausted ones (observability). */
    all(): Promise<PendingPullRecord[]>;
    /**
     * Serialize a read-modify-write. The fn receives the current records array
     * (mutable) and returns the next one; persistence is temp-file + atomic
     * rename. Errors persist nothing and propagate to THIS caller only — the
     * queue itself never wedges.
     */
    private mutate;
    private read;
    /** Load from memory or disk. Corrupt disk → quarantine + notice + fresh. */
    private readUnqueued;
    private quarantineCorrupt;
    private persist;
}
//# sourceMappingURL=PendingPullLedger.d.ts.map