import { type MailboxAckResult, type MailboxAppendMessage, type MailboxLease, type MailboxStore } from "../../core/mailbox-store.js";
import { type CorruptReadInfo } from "./corrupt-read-sink.js";
/** The waiter's refusal when a box lock stays held by a LIVE process past `lockWaitMs`. Carried as
 *  `.code` on a plain Error (consumers branch on the string); the message names the lock and the holder. */
export declare const MAILBOX_BOX_LOCK_TIMEOUT_CODE = "mailbox.box_lock_timeout";
/** The holder's refusal when its lock was found to have changed hands at release — the critical
 *  section may have overlapped another process's; its effects are reported as NOT trustworthy. */
export declare const MAILBOX_BOX_LOCK_COMPROMISED_CODE = "mailbox.box_lock_compromised";
/**
 * Acquire the cross-process BOX LOCK (design/385 §1.2⑥ ring 1) at `lockPath` — `<box>.jsonl.lock`.
 * Resolves with a release function once held; rejects with `mailbox.box_lock_timeout` when the lock is
 * not obtainable within `waitMs` — a LIVE holder keeps it, or it is unreadable/unreclaimable and needs
 * an operator (the message names which). Exported at module level for the real-process acceptance
 * run (a fixture that holds the lock and dies / stays alive); not part of the package surface.
 *
 * Reclaim protocol (a holder that DIED — the pid recorded in the lock no longer exists):
 *  - ONE reclaimer per stale lock instance: a reclaimer first publishes a GATE named after the stale
 *    lock's own nonce (`<lock>.reclaim.<nonce>`, `O_EXCL`); only the gate winner touches the lock, so
 *    no second reclaimer can remove the stale file underneath the winner and let a fresh live lock
 *    appear at the path in between — the interleaving that would otherwise let the winner move a
 *    live holder's lock aside. Losers back off like any waiter.
 *  - the winner renames the stale file aside, re-reads the moved file and confirms it is exactly the
 *    (pid, nonce) it judged (belt: a mismatch is moved back untouched), unlinks it, then removes the
 *    gate. A gate or an aside file older than {@link BOX_LOCK_RECLAIM_GRACE_MS} is the residue of a
 *    reclaimer that died mid-reclaim and is cleared by the next one.
 *  - a lock whose recorded pid is ALIVE is never reclaimed. Liveness is `kill(pid, 0)`: the processes
 *    that share a box must share the host's pid namespace (a container that mounts the same volume
 *    from another pid namespace cannot see the holder and would judge every lock dead). A dead
 *    holder's pid reused by an unrelated long-lived process reads as alive; the waiter then times out
 *    LOUDLY naming the pid, and the lock file is removed by hand — the accepted failure, chosen over
 *    an age rule that would reclaim a live-but-slow holder.
 *  - the holder verifies its nonce at release; a lock that changed hands underneath it surfaces as
 *    `mailbox.box_lock_compromised`, never as a silent success.
 */
export declare function acquireMailboxBoxLock(lockPath: string, waitMs: number): Promise<() => void>;
export interface FileMailboxStoreOptions {
    /** When false, ledger appends are NOT fsync'd. Default true. */
    fsync?: boolean;
    /** design/385 — how long an operation waits for a box lock a LIVE process holds before failing with
     *  `mailbox.box_lock_timeout`. Default 10 000 ms. Must be a finite number > 0 (anything else refuses
     *  at construction — a bad knob never becomes a silent default). */
    lockWaitMs?: number;
    /** Compact a box's event log into a snapshot-form log once it exceeds this many events. Default 500. */
    compactEvery?: number;
    /** Disclosure seat (review-510 family; server pickup receipt named this store's gap): a corrupt
     *  INTERIOR record dropped by the JSONL replay is disclosed here instead of silently reading as
     *  "less mail". The torn tail stays silent by design — that is the append-log's documented
     *  crash-recovery shape, not corruption. Swallow-guarded; absent = the old silent tolerance. */
    onCorruptRead?: (info: CorruptReadInfo) => void;
}
export declare class FileMailboxStore implements MailboxStore {
    /** design/385 — the whole cross-process set (box lock + fresh re-read + descriptor reopen +
     *  compaction under the lock) is in place and accepted by `mailboxCrossProcessContract`. */
    readonly crossProcessSafe = true;
    private readonly dir;
    private readonly tmpDir;
    private readonly fsyncEnabled;
    private readonly compactEvery;
    private readonly lockWaitMs;
    /** Paths THIS instance has touched → the EXACT BoxState it took a ref on (server[1523] 对等复审
     *  发现1: after a drop/reap deletes the shared box, another instance's stale close() must not
     *  decrement a REBUILT box's refs — identity is checked, not just the path). */
    private readonly touched;
    /** The swallow-guarded corrupt-read seat (undefined = the old silent tolerance). */
    private readonly discloseCorrupt;
    constructor(root: string, opts?: FileMailboxStoreOptions);
    /**
     * RB-67 (2026-07-25, red probe): the on-disk name is CASE-FOLDED, so the in-memory key and the physical
     * file can never disagree. Before this, `AgentOne` and `agentone` produced two DIFFERENT shared-state
     * keys but the SAME file on a case-insensitive filesystem (macOS/Windows default) — one recipient read
     * the other's messages after a restart. Folding is also the semantically correct answer, not just the
     * safe one: the addressing layer's `normalizeAgentName` (defined in core/task-registry-shared.ts and
     * re-exported by task-registry.ts) already lowercases, so
     * two case-variant handles ARE the same agent; this makes the storage layer say so explicitly instead of
     * depending on an upstream coincidence, and behave identically on case-SENSITIVE filesystems (Linux),
     * where the old code silently split one logical agent's mail across two boxes.
     */
    private boxPath;
    /**
     * The canonical mutex / shared-state key — instance-independent (X-5).
     *
     * RB-162: this went through the physical path but NOT the case fold, even though the module header
     * claimed otherwise. The handle component was folded by `boxPath` while the ROOT was not, so two
     * spellings of one directory on a case-insensitive filesystem produced two shared-state slots over one
     * physical file, each allocating its own `seq` — the same contract violation the RB-62 comment above
     * describes, reached by the other half of the same canonicalization. The enumerative guard written for
     * this family tested `src.includes("canonicalStoreKey")`, which the CLAIM satisfied: a guard that reads
     * prose is green while the code diverges.
     */
    private lockKey;
    /**
     * The two-ring critical section (module header): the in-process mutex on the canonical key, then the
     * cross-process box lock on the verbatim path. `fn` runs with both held; the box lock is released on
     * every exit. A compromised release (the lock changed hands) surfaces as a throw AFTER `fn` — an
     * operation whose critical section may have overlapped another process's is never reported as a
     * success; when `fn` itself threw, its own error wins.
     */
    private withBox;
    /**
     * design/385 ring 2 — bring a CACHED box up to date with the file, inside the box lock. A mark that
     * matches means nothing happened since this process last left the file at rest; a moved mark means
     * another process wrote (append/lease/ack/release, or a compaction/drop that replaced the inode),
     * and the cache is replayed from the file. A moved INODE also swap-closes the descriptor (ring 3):
     * the next write reopens the current file instead of landing on the unlinked one.
     */
    private reconcile;
    private load;
    private commit;
    private compact;
    append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
    claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
    ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
    releaseLease(scope: string, handle: string, owner: string): Promise<void>;
    peekCount(scope: string, handle: string): Promise<number>;
    drop(scope: string, handle: string): Promise<void>;
    reap(scope: string, now: number, opts?: {
        maxAgeMs?: number;
    }): Promise<number>;
    /** Release THIS instance's share of the box handles (fd closes when the last instance leaves). */
    close(): void;
}
