import type { MailboxPeerMeta } from "../core/mailbox-store.js";
import type { CrossSessionHoldCause, CrossSessionInboundVerdict } from "./cross-session-judge.js";
import { type PeerDeliveryReceiptState } from "./peer-notices.js";
/** The `cause` vocabulary of a `peer.inbound_disposition{disposition:"held"}` notice: the parity
 *  judge's causes plus the drain-stage gate's TRANSIENT `rate_limited` (a bucket-wait, not a review
 *  hold — it never enters the held queue; the drain stops at that seq and re-judges next pass). */
export type PeerInboundHoldCause = CrossSessionHoldCause | "rate_limited";
export interface PeerHeldEntry {
    /** The recipient box seq of the held record (its identity for the review face). */
    readonly seq: number;
    /** The recipient's box handle. */
    readonly box: string;
    /** The stored body (`[summary] message`, the canonical stored form). */
    readonly content: string;
    /** The sender's display name as the record carried it (display only). */
    readonly from?: string;
    readonly sentAt: number;
    readonly hopChain?: readonly string[];
    /** The typed record — reply routing (`fromSession`) and the attestation the re-judgment reads. */
    readonly peerMeta?: MailboxPeerMeta;
    readonly heldAt: number;
    /** The cause last announced for this entry (re-judgment may move it). */
    cause: CrossSessionHoldCause;
    /** Approved by the review face while no run of the session was live: delivered at the next flush
     *  (a re-judgment that now refuses still wins — CC `l0e`'s approve arm re-checks policy). */
    released?: boolean;
}
export type PeerHeldSettlement = "expired" | "delivered" | "refused" | "denied" | "dropped";
export type PeerHeldSettleReason = "evicted" | "deadline" | "shutdown" | "rejudge" | "approved" | "denied" | "cancelled" | "inject_failed" | "superseded";
/** The clock/timer seam (tests inject a virtual one). Handles are opaque. */
export interface PeerClock {
    now(): number;
    setTimeout(fn: () => void, ms: number): unknown;
    clearTimeout(handle: unknown): void;
}
export declare const realPeerClock: PeerClock;
/** The live seats a bound run gives the queue. `inject` is the ONLY member that is run-scoped (a run's
 *  own injection lane); the others survive the run (the receipt route rides the mailbox, the judge
 *  reads the deployment's settings getter) and are what a headless deadline settles through. */
export interface PeerHeldQueueSink {
    /** Re-judge an entry against the recipient's CURRENT settings and mode class (CC `M`). */
    judge: (entry: PeerHeldEntry) => CrossSessionInboundVerdict;
    /** Deliver a released entry into the live run's model context. Absent ⇒ no run is live. Throws ⇒
     *  the lane refused the frame (settled `dropped`, receipt sent). Returns `"deferred"` ⇒ the run is
     *  live but this pass's delivery budget is spent: the entry stays released and goes at the next
     *  pass's flush (the same wait a run-less approval takes). */
    inject?: (entry: PeerHeldEntry) => void | "deferred";
    /** The receipt to the sender (the binding routes it into the sender's box). */
    receipt: (entry: PeerHeldEntry, state: PeerDeliveryReceiptState) => void;
    /** Announce an entry entering the queue (or its cause changing on re-judgment). */
    onHeld?: (entry: PeerHeldEntry, heldCount: number, cause: CrossSessionHoldCause, reannounce: boolean) => void;
    /** Announce an entry leaving the queue. */
    onSettled?: (entry: PeerHeldEntry, settlement: PeerHeldSettlement, reason: PeerHeldSettleReason, heldCount: number) => void;
}
export interface PeerHeldQueueConfig {
    sink: PeerHeldQueueSink;
    /** The resolved `dialogExpiry`: ms before a REVIEW-cause hold expires; `null` = never. */
    dialogExpiryMs: number | null;
}
export type PeerHeldSettleOutcome = "gone" | "delivered" | "released_pending" | "dropped" | "dropped_by_policy" | "denied" | "expired";
export interface PeerHeldQueue {
    /** The registry key (scope + box). */
    readonly key: string;
    /** Bind (or re-bind) the live seats. Idempotent per run; a later bind replaces an earlier one. */
    configure(cfg: PeerHeldQueueConfig): void;
    /** The bound run ended: approvals now park as `released`; deadlines and receipts keep working. */
    detachLeg(): void;
    /** `true` once {@link configure} ran at least once (the queue can route receipts). */
    configured(): boolean;
    /** Park a message (CC `v` hold arm): evict-oldest when full, arm the deadline for a review cause,
     *  announce, receipt `held`. */
    hold(entry: Omit<PeerHeldEntry, "heldAt" | "released">): void;
    /** Re-judge every entry against current settings (CC `D`): released ⇒ delivered (or parked
     *  `released` with no live run), refused ⇒ refused with a receipt, still held ⇒ re-announced only
     *  when the cause moved. */
    rejudge(): {
        released: number;
        refused: number;
        held: number;
    };
    /**
     * #1036 — the box is this queue's durable seat, so an entry whose record is no longer IN the box was
     * settled by whoever held the lease meanwhile (a takeover acked it, delivered or refused). Called by a
     * drain right after it claims the lease, with the seqs the claimed snapshot holds (a claim marks EVERY
     * unacked record, so absence is absence): such entries leave the queue as `expired` / `superseded`,
     * with no receipt (the owner that settled them sent one) and no decision recorded (there is no record
     * left to decide about). Returns how many were retired.
     */
    reconcile(present: ReadonlySet<number>): number;
    /** The review face (CC `l0e`): approve re-judges then delivers (or parks `released` with no live
     *  run), deny/cancel drop with the matching receipt. `"gone"` when nothing is held under that seq. */
    settle(seq: number, decision: "approve" | "deny" | "cancel"): PeerHeldSettleOutcome;
    list(): readonly PeerHeldEntry[];
    size(): number;
    /** CC `HBe` — a graceful shutdown settles everything still held as `expired` with receipts. */
    shutdown(): void;
    /**
     * #1036 — the TERMINAL DECISIONS this queue has taken about records that are still parked in
     * the box, seq → the settlement word. Since a held record now stays in its durable box (the box is
     * this queue's durable seat), a decision taken BETWEEN runs — a human's deny, a deadline, an
     * eviction — has to survive the run whose ledger recorded it, or the session's next run re-judges
     * the record from scratch and can deliver a message a person already refused. A decision is kept
     * only until a drain that holds the lease acks past it ({@link PeerHeldQueue.forgetDecisions}); it
     * is process-level, like the queue itself, and a crash loses it exactly as it loses the queue.
     */
    decisions(): ReadonlyMap<number, PeerHeldSettlement>;
    /** #1036 — a lease-owning drain acked these seqs out of the box: their decisions are spent. */
    forgetDecisions(seqs: Iterable<number>): void;
}
/** #1036 — how many terminal decisions about still-parked records one box's queue remembers
 *  (see {@link PeerHeldQueue.decisions}). Several times the queue's own cap: the queue bounds what is
 *  LIVE, this bounds what has been decided and not yet observed as consumed. */
export declare const PEER_HELD_DECISION_MEMORY: number;
export declare function createPeerHeldQueue(key: string, opts?: {
    cap?: number;
    clock?: PeerClock;
}): PeerHeldQueue;
/** A session's held-queue key: (scope, the session's box) — and, for a queue of ANOTHER box that session
 *  drains (its scope's principal inbox), that box's handle as a third member: the approval face addresses
 *  entries by seq, and seqs are minted per box, so one queue never mixes two boxes. */
export declare function peerHeldQueueKey(scope: string, sessionId: string, box?: string): string;
/** The held queue of a session (created on first use) — of its own box, or of `box` (the principal inbox it
 *  also drains). `clock` is honored only at creation. */
export declare function peerHeldQueueFor(scope: string, sessionId: string, opts?: {
    cap?: number;
    clock?: PeerClock;
    box?: string;
}): PeerHeldQueue;
/** The held queue of a session if one exists in this process (a host review face reads through this
 *  without creating state for sessions that never held anything); `box` as in {@link peerHeldQueueFor}. */
export declare function peerHeldQueueIfAny(scope: string, sessionId: string, box?: string): PeerHeldQueue | undefined;
/** Test seam: drop every queue (timers cleared, NO receipts — the crash form). Never called by production code. */
export declare function resetPeerHeldQueuesForTests(): void;
