/**
 * SpawnLedger — SQLite-backed compare-and-swap ledger for relay-spawned sessions.
 *
 * Implements Component A of the Threadline Relay-Spawn Ghost-Reply
 * Containment Spec. The ledger is the spawn-creation step itself, not a
 * post-hoc check: tryReserve() atomically claims an eventId or returns null
 * if another spawn already claimed it. This is the idempotency primitive
 * that makes "Spawned session for X (twice)" structurally impossible.
 *
 * Per spec §Component A:
 *  - Backed by SQLite (WAL mode, flock-coordinated for second-instar-on-host)
 *  - Per-spawn HMAC nonce generated here, never written to a path the
 *    spawned session can read after launch (handed to the session via FD-3
 *    by SpawnNonce.ts).
 *  - Per-peer rolling rate cap (1000 spawns / 24h) enforced on tryReserve
 *  - Global hard cap of 100k rows; prune by TTL (30d) via a background job.
 *
 * Authority classification (per docs/signal-vs-authority.md): structural
 * idempotency-key dedup at the transport layer. Permitted authority — not
 * a judgment call, mechanics.
 */
export type SpawnStatus = 'spawning' | 'verified' | 'failed' | 'completed';
export interface SpawnLedgerRow {
    eventId: string;
    peerId: string;
    spawnNonce: Buffer;
    spawnedAt: number;
    status: SpawnStatus;
    terminalAt: number | null;
    failureReason: string | null;
}
export interface ReserveResult {
    reserved: true;
    spawnNonce: Buffer;
    row: SpawnLedgerRow;
}
export interface ReserveCollision {
    reserved: false;
    reason: 'duplicate-event' | 'peer-rate-limit' | 'ledger-full';
    existing?: SpawnLedgerRow;
}
export type ReserveOutcome = ReserveResult | ReserveCollision;
export interface SpawnLedgerOptions {
    /** Maximum spawns per peer in the rolling window. Default 1000. */
    perPeerCap?: number;
    /** Rolling window in ms for the per-peer cap. Default 24h. */
    perPeerWindowMs?: number;
    /** Hard cap on total rows. Default 100_000. */
    globalCap?: number;
}
export declare class SpawnLedger {
    private db;
    private opts;
    private _unregisterSqlite?;
    constructor(dbPath: string, opts?: SpawnLedgerOptions);
    private applySchema;
    /**
     * Atomically reserve a spawn slot for `eventId`. Returns the spawn nonce
     * the caller must hand to the spawned session via FD-3, or a collision
     * with the reason.
     *
     * This is the single point where a spawn is decided. If two relay
     * threads race on the same eventId, exactly one tryReserve() returns
     * {reserved: true}; the other gets {reserved: false, reason: 'duplicate-event'}.
     */
    tryReserve(eventId: string, peerId: string, now?: number): ReserveOutcome;
    /**
     * Mark a reserved spawn as having moved to a terminal state. Verified =
     * heartbeat HMAC validated. Failed = watchdog signal triggered. Completed
     * = session exited cleanly.
     */
    markStatus(eventId: string, status: Exclude<SpawnStatus, 'spawning'>, failureReason?: string | null, now?: number): boolean;
    get(eventId: string): SpawnLedgerRow | null;
    /**
     * Verify a heartbeat HMAC against the stored spawn nonce.
     * Returns true if valid; false if no row or mismatch.
     */
    verifyHeartbeatHmac(eventId: string, payload: string, presentedHmac: string): boolean;
    /**
     * Prune rows older than `olderThanMs` AND in a terminal state.
     * Spawning rows are NEVER pruned (they may still be in flight).
     * Returns count of rows removed.
     */
    pruneTerminal(olderThanMs: number, now?: number): number;
    /**
     * List ALL rows currently in 'spawning' state, regardless of age.
     * Used by HeartbeatWatchdog which applies its own grace-window filter.
     */
    listSpawning(): SpawnLedgerRow[];
    /** Sweep stale 'spawning' rows (heartbeat never confirmed, no terminal mark). */
    sweepStaleSpawning(staleAfterMs: number, now?: number): SpawnLedgerRow[];
    count(): number;
    close(): void;
}
//# sourceMappingURL=SpawnLedger.d.ts.map