import type { CorruptReadInfo } from "./corrupt-read-sink.js";
/**
 * design/157 A9 — the SHARED substrate under the three snapshot+ledger file stores
 * ({@link import("./background-agent-store.js").FileBackgroundAgentStore},
 * {@link import("./checkpoint-store.js").FileCheckpointStore},
 * {@link import("./workflow-run-store.js").FileWorkflowRunStore}).
 *
 * Each of them carried its own copy of the SAME three mechanisms: a per-directory refcounted
 * authority table, the snapshot+ledger replay/commit skeleton, and `compact()`'s
 * close → rewrite → reopen swap. What stays in the stores is what genuinely differs — the ledger
 * EVENT model (`applyEvent`) and the domain predicates (CAS / reap / query projections). The stores
 * COMPOSE this (they hold a {@link LedgerCore}); nothing here is inherited or subclassed.
 *
 * ── CLASS-FIX LEDGER ────────────────────────────────────────────────────────────────────────────
 * Every entry below was first fixed on ONE file and then had to be re-fixed on the siblings, some of
 * them twice. They now live here once, and the old addresses are named so the考据 is not lost with
 * the code that carried it:
 *
 *  · **RB-55 / RB-59 / RB-134 — one authority per DIRECTORY, not per instance.** Two stores over one
 *    directory each replayed a PRIVATE view, so two concurrent same-rev CASes BOTH won: a double
 *    claim (background-agent, RB-55), a double-won run update (workflow-run, RB-59 — the 1.408.0
 *    sweep covered the agent store and not this one, whose cross-instance test only exercised `put`),
 *    and a human approval consumed twice (checkpoint, RB-134, in the file whose own header calls
 *    itself "THE LOAD-BEARING file store"). Now: {@link SharedLedgerTable}.
 *  · **RB-144 — the table KEY is `canonicalStoreKey` (realpath + case fold).** Three stores were
 *    still keying on a lexical `resolve()` while two of them cited the mailbox as the precedent they
 *    copied. Now one call site, in {@link SharedLedgerTable.acquire}.
 *  · **RB-145 — `close()` is IDEMPOTENT per INSTANCE; the refcount belongs to the DIRECTORY.** A
 *    second `close()` on the same object used to decrement again and tear the shared log out from
 *    under a live sibling — `log_closed` on its every later write. The per-instance latch stays in
 *    each store (it is per-object state); the refcount, the identity-checked eviction and the fd
 *    teardown are {@link LedgerCore.release}.
 *  · **RB-149 / RB-167 — the compaction swap must be `closeForSwap()`, never `close()`.** `close()`
 *    means "finished", so a failed rewrite left the log permanently unusable — one transient I/O
 *    fault bricking every instance on that directory. The mailbox's `finally { new AppendLog(path) }`
 *    compensation was the closest of the four and still wrong twice: it does not cover an unwritable
 *    FILE (the reopen throws too), and its reopen error REPLACES the fault that caused the swap. The
 *    answer is AppendLog's lazy reopen — {@link LedgerCore.compact}.
 *  · **(2026-08-22, cross-process defect probe) — one authority per directory needs one WRITER per
 *    directory, and that half was never enforced.** RB-55/59/134 closed the IN-PROCESS shape (two
 *    instances, two private replays, two winners); two OS PROCESSES over one directory reproduced it
 *    verbatim (a human approval and its refusal each reported as the winner) — and worse, the one that
 *    never loaded the other's rows ERASED them at its next {@link LedgerCore.compact} (the snapshot is
 *    rewritten from the in-memory map, and the live ledger is then truncated). The class docs all
 *    credited a fence they did not hold: `root/LOCK` belongs to `FileStorageBackend`, which never
 *    constructs the workflow-run store at all and which a directly-constructed store (all three are root
 *    exports) never goes through. So the fence now lives WITH the authority: {@link LedgerCore.bootstrap}
 *    takes `dir/LOCK` ({@link acquireStoreDirLock}) and the last {@link LedgerCore.release} drops it —
 *    exclusive across processes, refcount-joined in-process, exactly like the authority it guards.
 *  · **RB-150 / RB-167 — compaction is HOUSEKEEPING.** It runs after the operation is durable AND
 *    applied, so its failure must never be reported as the operation's failure: a committed
 *    once-only CAS surfaced as "this did not happen" is the worst possible direction (the caller
 *    retries, hits the new rev, and fails again — one success reported as two failures). The swallow
 *    is safe ONLY because of the lazy reopen above — {@link LedgerCore.commit}.
 *
 * DELIBERATELY NOT FOLDED IN: `mailbox-store.ts` (refcount is per-BOX with a per-instance dedup, and
 * its compaction rewrites the LIVE log as a replay-equivalent event stream instead of a separate
 * snapshot) and `workflow-journal-store.ts` (one ledger per RUN, no snapshot, no row map). Their
 * tables are the same SHAPE, not the same MECHANISM; both keep their own, with a pointer to this
 * ledger for the shared half of the history.
 */
/** The paths one ledger authority owns. `dir` is also what the authority is keyed on. */
export interface LedgerPaths {
    readonly dir: string;
    /** Disclosure seat for a replay row this authority DROPPED as corrupt — without it the loss is
     *  indistinguishable from "fewer rows". Already swallow-guarded by the store that wired it. Only the
     *  FIRST instance over a directory replays, so only its seat can fire: a joiner reads the authority
     *  that instance built, which is the same fact its refcount records. */
    readonly onCorruptRead?: (info: CorruptReadInfo) => void;
    /** Scratch dir for {@link atomicWriteFile} (same filesystem as the targets → rename is atomic). */
    readonly tmpDir: string;
    readonly ledgerPath: string;
    readonly snapshotPath: string;
}
/** The per-store half: how a SNAPSHOT row is keyed, and how one LEDGER event folds into the map. */
export interface LedgerModel<Row, Ev> {
    /** What this ledger IS, in an operator's words ("checkpoint ledger") — the noun the directory-fence
     *  refusal names, so the message points at the store that is actually in use rather than at a path. */
    label: string;
    keyOf(row: Row): string;
    apply(rows: Map<string, Row>, ev: Ev): void;
    /**
     * `@contract store.read_back_shape_judged` floor: is this parsed body one of MY rows / MY events at
     * all? Judged on the DISCRIMINANT only — the key a row is filed under, the tag an event is folded by —
     * because what to do with a discriminant this binary does not KNOW is the model's own business (the
     * checkpoint ledger refuses the whole replay for a kind from a newer worker, deliberately). A body
     * that carries no discriminant carries no such choice: it is damage, and `bootstrap` discloses and
     * skips it. Required rather than optional, so a fourth ledger store cannot inherit the silence by
     * omission — before this floor existed, `{"note":"x"}` in an agent ledger filed a row under the key
     * `undefined` and the next enumeration read members off it.
     */
    judgeRow(x: unknown): boolean;
    judgeEvent(x: unknown): boolean;
}
/**
 * One directory's authority: the replayed row map, the per-key async mutex, the append log and the
 * event counter. Created and owned by {@link SharedLedgerTable}; every store instance over the
 * directory holds a reference to the SAME object.
 */
export declare class LedgerCore<Row, Ev> {
    private readonly key;
    private readonly paths;
    private readonly model;
    private readonly table;
    /** The authoritative replayed state — domain queries read it directly. */
    readonly rows: Map<string, Row>;
    /** How many store instances hold this authority (RB-145: it belongs to the directory). */
    refs: number;
    private readonly locks;
    private events;
    private handle;
    /** This directory's cross-process writer fence, held for as long as the authority exists. */
    private fence;
    /** Set when the last holder left: this object is no longer the directory's authority (see compact). */
    private released;
    constructor(key: string, paths: LedgerPaths, model: LedgerModel<Row, Ev>, table: Map<string, LedgerCore<Row, Ev>>);
    /**
     * Rebuild the authority: take the directory's cross-process writer fence, then the snapshot (the
     * compacted base), then the live ledger's events in order (last-writer-wins), then open the append
     * handle. Only the FIRST instance over the directory runs it.
     *
     * The fence comes FIRST because everything after it assumes what the fence establishes: the replayed
     * map is treated as THE authority for the directory (writes are decided against memory, and
     * compaction rewrites the on-disk state FROM memory). Replaying first and locking after would be a
     * window in which a second process's view is already stale.
     */
    bootstrap(): void;
    /**
     * Serialize an op behind any in-flight op on the same key — the in-process per-key async mutex (the
     * SAME `Map<key, Promise>` chain shape as `TtlSessionStore.pending`). Each call appends a new tail;
     * the next caller awaits THIS op before running. A prior op's rejection never blocks the chain (we
     * await it `.catch`-guarded). The entry is pruned once it is the settled tail with nothing queued.
     */
    withLock<T>(key: string, fn: () => Promise<T> | T): Promise<T>;
    /**
     * Commit one ledger event: fsync'd append FIRST, in-memory flip AFTER — so a crash can never leave
     * memory and disk disagreeing. Then the best-effort housekeeping compaction (RB-150/RB-167).
     */
    commit(ev: Ev, fsync: boolean, compactEvery: number): void;
    /**
     * Rewrite the snapshot from the authoritative map (atomic) and truncate the live ledger. After
     * this a replay reads the whole state from the snapshot alone — the round-trip is identical.
     *
     * REFUSED once this authority has been RELEASED. Compaction is the one operation that does not touch
     * the append fd (it rewrites whole files), so a store object kept alive past its `close()` could still
     * run it — rewriting the directory from a map that stopped being the authority, over rows a SUCCESSOR
     * (here or in another process, holding the fence this core gave back) has since written. That is the
     * same erasure the directory fence exists to prevent, arriving through a stale reference instead of a
     * second process, so it is refused at the same door rather than trusted to caller discipline.
     */
    compact(): void;
    /** Drop one instance's share. The LAST holder evicts the authority — identity-checked, so a stale
     *  holder can never revoke a REBUILT entry — retires it (see {@link compact}) and closes the append fd. */
    release(): void;
    /** Test/inspection: events appended since the last compaction. */
    get pendingEvents(): number;
    private get log();
}
/**
 * The module-level authority table one store family keeps: ONE entry per canonical directory, with
 * eviction (RB-134 — a table without invalidation is RB-73's mistake again: after a `close()` the
 * next store over that root would join a table whose log was already closed and every write would
 * throw `log_closed`, instead of replaying the on-disk truth into a fresh authority).
 */
export declare class SharedLedgerTable<Row, Ev> {
    private readonly model;
    private readonly live;
    constructor(model: LedgerModel<Row, Ev>);
    /** Join the live authority for this directory, or create + replay a fresh one. */
    acquire(paths: LedgerPaths): LedgerCore<Row, Ev>;
}
