/**
 * PendingInboundStore — durable custody store for inbound messages the router
 * cannot deliver right now (Durable Inbound Message Queue spec §1).
 *
 * Spec: docs/specs/durable-inbound-message-queue.md (CMT-1118).
 *
 * Path: `<stateDir>/state/pending-inbound.<sanitizedAgentId>.sqlite`, 0600.
 * Pattern: PendingRelayStore, with the spec's named deviations:
 *   - `synchronous=FULL` — a custody-ack must survive power loss; this store
 *     has no sender-side exit-1 backstop, and write volume is
 *     human-message-rate.
 *   - chmod 0600 happens BEFORE the WAL pragma so the -wal/-shm sidecars
 *     inherit the mode.
 *   - The DB handle is a #private field — no raw-handle export, every
 *     mutation is a named method (spec §1 round-8: the single-writer executor
 *     is a CONTRACT, enforced by encapsulation + a unit test, not convention).
 *
 * Sanitization-collision caveat (inherited from PendingRelayStore): two agent
 * ids that sanitize to the same string would share a file. Same blast radius
 * as the relay store — same-machine, same-user; documented, not defended.
 *
 * The store is data + invariants only. Policy (selection, holds, halt, clock
 * discipline) lives in QueueDrainLoop; the store's methods take explicit
 * `nowIso`/`monoMs` so the policy layer owns time.
 */
export type InboundEntryState = 'queued' | 'claimed' | 'delivered' | 'expired' | 'dropped-overflow';
export interface SenderEnvelope {
    userId?: string | number;
    username?: string;
    firstName?: string;
}
export interface PendingInboundRow {
    enqueue_seq: number;
    session_key: string;
    message_id: string;
    payload: string | null;
    payload_bytes: number;
    sender_envelope: string | null;
    sender_display: string | null;
    topic_metadata: string | null;
    reason: string;
    state: InboundEntryState;
    terminal_reason: string | null;
    enqueued_at: string;
    enqueued_mono: number | null;
    boot_session_id: string | null;
    lease_epoch: string | null;
    first_held_at: string | null;
    first_frozen_at: string | null;
    total_frozen_ms: number;
    frozen_since: string | null;
    attempts: number;
    next_attempt_at: string | null;
    last_error: string | null;
    delivered_unconfirmed: 0 | 1;
    status_history: string;
}
export interface EnqueueInput {
    sessionKey: string;
    messageId: string;
    payload: string;
    senderEnvelope?: SenderEnvelope | null;
    topicMetadata?: unknown;
    reason: string;
    /** Tenure id at enqueue (§3.5) — opaque string from the drain's tenure source. */
    tenure: string | null;
    nowIso: string;
    monoMs: number | null;
    bootSessionId: string | null;
    /** When a pause is durably in effect, new rows are frozen at enqueue (§3.6 round-10). */
    frozenAtEnqueue?: boolean;
}
export interface InboundQueueBounds {
    maxPerSession: number;
    maxTotal: number;
    hardMaxTotal: number;
    maxPayloadBytes: number;
}
export type EnqueueOutcome = {
    result: 'queued';
    seq: number;
    evicted: EvictedRowInfo | null;
} | {
    result: 'already-queued';
    existingState: InboundEntryState;
} | {
    result: 'refused';
    reason: string;
};
/** Loss-report locator for an evicted/expired row — NEVER payload content. */
export interface EvictedRowInfo {
    sessionKey: string;
    messageId: string;
    enqueuedAt: string;
    payloadBytes: number;
    senderDisplay: string | null;
    reason: string;
}
export type ReceiptClass = 'injection' | 'remote';
export interface ReceiptRow {
    session_key: string;
    message_id: string;
    class: ReceiptClass;
    created_at: string;
    injected: 0 | 1;
    reported: 0 | 1;
}
/**
 * Durable counter keys (meta table) — spec §2.4 dry-run counters, §4.5 hold
 * counters, and the observability counters. Well-known keys:
 * wouldEnqueue, wouldHold, wouldRefuse, dryRunErrors, orderingViolations,
 * mirrorDrift, possiblyNotInjected, holdBypassedByAttemptsCap, holdsStarted,
 * holdsRecoveredInPlace, holdsReleasedToFailover:budget-exhausted,
 * holdsReleasedToFailover:flap-forced, holdsReleasedToFailover:maxHeldTotal-refused,
 * budgetOverrunHolds.
 */
export type CounterKey = string;
export declare function resolvePendingInboundPath(stateDir: string, agentId: string): string;
export declare class PendingInboundStore {
    #private;
    private constructor();
    /**
     * Open (or create) the store. Throws on an unopenable file — the caller
     * (QueueDrainLoop boot sweep) owns the §5.3 quarantine path; the store
     * never quarantines itself.
     */
    static open(agentId: string, stateDir: string): PendingInboundStore;
    pathOnDisk(): string;
    close(): void;
    /**
     * Take custody of a message. One transaction: existence check + bounds +
     * (possible per-session eviction) + insert (spec §1 Transactions/Bounds).
     *
     * Tri-state semantics (spec §1):
     *  - existing row non-terminal or `delivered` → `already-queued` (custody
     *    re-affirmed — correct dedupe);
     *  - existing row `expired`/`dropped-overflow` → `refused` (the prior
     *    instance was loss-reported; re-affirming custody against a row that
     *    will never dispatch would be silent loss);
     *  - oversize payload → `refused`;
     *  - `hardMaxTotal` reached → `refused` (even carve-out appends);
     *  - `maxTotal` reached and the session has nothing queued → `refused`
     *    (FIRST entries refused; sessions already queued may append);
     *  - `maxPerSession` reached → evict that session's oldest `queued` row
     *    (never `claimed`) as `dropped-overflow`, returned for loss-reporting.
     *
     * Storage failures (ENOSPC etc.) THROW — the caller maps a throw to
     * `refused` → fall-through (the fail-safe direction, spec §1 round-5).
     * Enqueue must be the caller's last fallible step before returning its
     * outcome (no-throw-after-commit invariant, §2.2).
     */
    enqueue(input: EnqueueInput, bounds: InboundQueueBounds): EnqueueOutcome;
    /** Point read for the custody-aware route-throw catch (§2.2): does a
     *  committed NON-TERMINAL row exist for THIS message? Indexed PK lookup.
     *  Throws propagate — the caller fails OPEN to fall-through on a read
     *  error (a bounded duplicate window, §5-enumerated). */
    hasNonTerminalRow(sessionKey: string, messageId: string): boolean;
    getRow(seq: number): PendingInboundRow | null;
    getRowByCanonicalId(sessionKey: string, messageId: string): PendingInboundRow | null;
    /**
     * Head-only per-session selection (§3.2): for each session whose LOWEST
     * non-terminal seq is `queued`, due (`next_attempt_at` null or <= now), and
     * not frozen, return that head row. Successors inherit the head's schedule
     * by construction (they are simply never selected while the head exists).
     * A session whose lowest non-terminal row is `claimed` is in flight → skip.
     */
    selectEligibleHeads(nowIso: string, limit: number): PendingInboundRow[];
    /** Queued rows with a hold clock running — the boot-rebuild source for the
     *  in-memory held-set cache (§3.2: the first_held_at column is AUTHORITATIVE). */
    listHeldRows(): PendingInboundRow[];
    /** Targeted deadline write for the §6 sleep-shift (queued rows only). */
    setNextAttempt(seq: number, nextAttemptAtIso: string | null): void;
    /** Scoped event-trigger reset (§3.2): make a session's queued rows due now. */
    resetNextAttempt(sessionKey: string): number;
    /** All non-terminal rows (boot sweep, §5.3). */
    listNonTerminal(): PendingInboundRow[];
    /** Sessions with operator-stop terminal rows still in retention — the boot
     *  sweep's PIS-veto scan (§3.4, STOP-scoped only). */
    listOperatorStopSessions(): string[];
    /** Per-session non-terminal counts + min seq — the mirror rebuild/reconcile
     *  source (§2.3). GROUP BY over ≤hardMaxTotal rows. */
    sessionCounts(): Array<{
        session_key: string;
        count: number;
        min_seq: number;
    }>;
    counts(): {
        queued: number;
        claimed: number;
        held: number;
        frozen: number;
        delivered24h: number;
        deliveredUnconfirmed24h: number;
        expired24h: number;
        droppedOverflow24h: number;
        oldestQueuedAt: string | null;
    };
    /** Atomic claim: queued → claimed iff still queued. Returns the claimed row
     *  or null (lost race / no longer queued). */
    claim(seq: number, nowIso: string): PendingInboundRow | null;
    /**
     * Generic transition with expected-prior-state assert (spec §1): mismatch is
     * a LOGGED NO-OP (returns false), never a throw. Terminal transitions null
     * payload/envelope/metadata in the same transaction (payload hygiene) while
     * the locator (timestamps, ids, sender_display, payload_bytes) survives
     * (MUST 11).
     */
    transition(seq: number, expectedPrior: InboundEntryState, to: InboundEntryState, opts: {
        nowIso: string;
        terminalReason?: string;
        deliveredUnconfirmed?: boolean;
        lastError?: string | null;
        attempts?: number;
        nextAttemptAt?: string | null;
    }): boolean;
    /** Release a claimed row back to queued with backoff (failed attempt, §3.3).
     *  When a pause is in effect the caller passes `freeze:true` — the row is
     *  frozen at release (§3.6 round-9). */
    release(seq: number, opts: {
        nowIso: string;
        attempts: number;
        nextAttemptAt: string | null;
        lastError?: string | null;
        freeze?: boolean;
    }): boolean;
    /** Mark a queued head held (first_held_at set once; §4.3 cumulative clock). */
    markHeld(seq: number, nowIso: string): void;
    /**
     * The CONDITIONAL receipt commit (§3.6): writes the injection-class receipt
     * iff the queue row is still `claimed` — one transaction, so a stop's
     * claimed→expired transition makes a late receipt write fail atomically
     * (which aborts the inject). Returns true when the receipt committed.
     */
    writeReceiptIfClaimed(seq: number, sessionKey: string, messageId: string, nowIso: string): boolean;
    hasReceipt(sessionKey: string, messageId: string, cls?: ReceiptClass): boolean;
    /** Remote path (§3.4): receive-side receipt, keyed on the canonical id. */
    recordRemoteReceipt(sessionKey: string, messageId: string, nowIso: string): boolean;
    /** Flip the `injected` marker after the local inject completes (§3.4 remote
     *  path round-8 — makes peer-crash-between-receipt-and-inject boot-detectable). */
    markReceiptInjected(sessionKey: string, messageId: string, cls: ReceiptClass): void;
    markReceiptReported(sessionKey: string, messageId: string, cls: ReceiptClass): void;
    /** Unflipped (never-injected), unreported REMOTE receipts — the boot sweep's
     *  window-6 detection surface (§3.4 remote path). REMOTE-class only: local
     *  injection-class receipts never flip a marker — their loss variant is
     *  carried by the row's `delivered_unconfirmed` flag instead. */
    findUnflippedUnreportedReceipts(): ReceiptRow[];
    /**
     * Receipt pruning (§3.4, §3.2 backstop duty): receipts older than the cutoff
     * are pruned — but an UNFLIPPED receipt is never silently pruned: the rows
     * about to be dropped while injected=0 AND reported=0 are RETURNED so the
     * caller reports them first (report once, then prune — retention is never
     * extended; spec round-10). The prune itself runs after the caller's report
     * via `confirmPruneReceipts`.
     */
    listPrunableReceipts(cutoffIso: string): {
        silent: number;
        needsReport: ReceiptRow[];
    };
    confirmPruneReceipts(cutoffIso: string): number;
    /** Delete a just-committed receipt (deliberate in-process un-commit — only
     *  legal while the caller KNOWS no inject ran; used by halt skip paths). */
    deleteReceipt(sessionKey: string, messageId: string, cls: ReceiptClass): void;
    /**
     * Tenure source of truth (§3.5): the queue maintains its OWN
     * acquisition-generation counter, persisted here, bumped iff the lease ref
     * tip observed at this machine's claim names a holder ≠ self. Unchanged on
     * renewals and same-holder re-acquire.
     *
     * Returns the current tenure id (`<selfMachineId>#<generation>`).
     */
    observeLeaseClaim(selfMachineId: string, tipHolderAtClaim: string | null): string;
    currentTenure(selfMachineId: string): string | null;
    /** The bare generation number (capacity-heartbeat field, §5.1). */
    acquisitionGeneration(): number | null;
    /** Durable pause flag — restart mid-pause stays paused. */
    setPaused(paused: boolean, nowIso: string): void;
    isPaused(): boolean;
    /** Freeze all QUEUED rows (pause scope pin, §3.6 round-9 — claimed rows
     *  complete normally and freeze at release if they fail). */
    freezeQueuedRows(nowIso: string): number;
    /**
     * Resume: fold each live frozen span into `total_frozen_ms`, clear
     * `frozen_since`, shift `next_attempt_at` deadlines by the frozen span
     * (the §6 sleep-shift pattern), and return rows whose CUMULATIVE frozen
     * time now exceeds `pauseMaxMs` — the caller terminals those as
     * `pause-expired` (the cumulative cap, §3.6 round-7/8).
     */
    resumeFrozenRows(nowIso: string, pauseMaxMs: number): {
        resumed: number;
        overCap: PendingInboundRow[];
    };
    /** Live cumulative frozen span for the cap check while still paused
     *  (`total_frozen_ms + (now − frozen_since)`, §1 columns round-8). */
    liveFrozenMs(row: PendingInboundRow, nowIso: string): number;
    /** Rows whose TTL has expired (frozen rows excluded — TTL accounting pauses
     *  while frozen, §3.6). Caller terminals + loss-reports them. */
    listTtlExpired(nowIso: string, entryTtlMs: number): PendingInboundRow[];
    /** Prune terminal rows past retention (payload already nulled at terminal). */
    pruneTerminal(cutoffIso: string): number;
    incrementCounter(key: CounterKey, by?: number): void;
    getCounter(key: CounterKey): number;
    /** File mode check support — path only; the test stats the file itself. */
    countAll(): number;
}
/** Sanitized last_error (spec §1): error class + length-capped message; the
 *  caller must never pass payload-bearing text — this cap is the backstop. */
export declare function sanitizeError(err: unknown): string | null;
export declare const PENDING_INBOUND_TERMINAL_STATES: InboundEntryState[];
export declare const PENDING_INBOUND_NON_TERMINAL_STATES: InboundEntryState[];
//# sourceMappingURL=PendingInboundStore.d.ts.map