import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
export interface MeshInflightHoldRow {
    taskId: string;
    meshId: string | null;
    holdReason: string | null;
    heldAt: number | null;
    firstIdleSinceAck: number | null;
    readFailureCount: number | null;
    updatedAt: number | null;
}
export declare class MeshRuntimeStore {
    private static instance;
    private readonly db;
    private readonly dbPath;
    private readonly migratedMeshIds;
    private readonly idleReminderState;
    private fingerprintSweepCounter;
    private walWriteCounter;
    private toolCallLogCounter;
    private static readonly WAL_CHECK_INTERVAL;
    private static readonly WAL_MAX_BYTES;
    private constructor();
    private static loggedGetInstanceFailure;
    static getInstance(): MeshRuntimeStore;
    static resetForTests(): void;
    close(): void;
    transaction<T>(fn: () => T): T;
    private migrate;
    private tableColumns;
    /**
     * MESH-ISOLATION-LEAK migration. Two tables historically lacked a `mesh_id` column,
     * letting one machine that belongs to multiple meshes (multiple repos) leak rows
     * across meshes. Both migrations are idempotent and run on every boot — the column
     * check short-circuits once the new schema is in place.
     */
    private migrateMeshIsolationColumns;
    hasCompletionFingerprint(meshId: string, fingerprint: string): boolean;
    recordCompletionFingerprint(meshId: string, fingerprint: string, ttlMs: number): void;
    sweepExpiredFingerprints(): void;
    private maybeCheckpointWal;
    private ensureLegacyQueueMigrated;
    getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[];
    getQueueRevision(meshId: string): string;
    replaceQueue(meshId: string, queue: MeshWorkQueueEntry[]): void;
    deleteQueue(meshId: string): void;
    insertQueueEntry(entry: MeshWorkQueueEntry): void;
    updateQueueEntry(entry: MeshWorkQueueEntry): void;
    findQueueEntryById(meshId: string, id: string): MeshWorkQueueEntry | null;
    hasActiveAssignment(meshId: string, sessionId: string, nodeId: string): boolean;
    /** A session may only execute one task at a time, regardless of task mode. */
    private hasActiveSessionAssignment;
    /** A node may only execute one write task at a time (worktree isolation). */
    private hasActiveNodeAssignment;
    /**
     * Count active (status='assigned') tasks on a node, regardless of provider or
     * task mode. This is the load metric for least-loaded / round-robin ranking:
     * the scheduler prefers the node with the fewest active assignments so
     * untargeted work spreads instead of piling onto whichever node asks first.
     */
    nodeActiveAssignmentCount(meshId: string, nodeId: string): number;
    /**
     * O(1) count of queue tasks in 'pending' status for a mesh. A COUNT(*) over the
     * indexed status column, so it avoids JSON.parse-ing every queue row — used as a
     * cheap guard before the reconcile loop runs a full triggerMeshQueue scan.
     */
    pendingQueueTaskCount(meshId: string): number;
    /**
     * Read the current per-mesh round-robin cursor (0 when unset). Used to rotate
     * the tie-break winner among nodes tied at the least load.
     */
    getSchedulerCursor(meshId: string): number;
    /**
     * Atomically advance the per-mesh round-robin cursor by one and return the
     * value that was current BEFORE the bump (the value the caller should rotate
     * by for this pass). UPSERT keeps it lock-free across concurrent passes.
     */
    bumpSchedulerCursor(meshId: string): number;
    private mapInflightHoldRow;
    upsertInflightHold(entry: {
        taskId: string;
        meshId?: string | null;
        holdReason?: string | null;
        heldAt?: number | null;
        firstIdleSinceAck?: number | null;
        readFailureCount?: number | null;
    }): void;
    getInflightHold(taskId: string): MeshInflightHoldRow | null;
    listInflightHoldsByMesh(meshId: string): MeshInflightHoldRow[];
    deleteInflightHold(taskId: string): void;
    /**
     * Count active (status='assigned') tasks on a (node, provider) combination,
     * matched by the assignedProviderType stamped on the payload at claim time.
     * Drives the per-(node, provider) maxParallel cap (summed across a provider's
     * slots[].maxParallel). The active-assignment set for a single node is tiny, so
     * parsing payloads here is cheap and avoids a schema migration. Pre-cap legacy
     * rows (no provider stamp) and other providers on the same node do not consume
     * this provider's budget, so the cap is fully backward compatible.
     */
    private activeProviderAssignmentCount;
    claimNextQueueTask(meshId: string, nodeId: string, sessionId: string, capabilityTags?: string[], opts?: {
        providerType?: string;
        providerMaxParallel?: number;
        nodeIsWorktree?: boolean;
    }): MeshWorkQueueEntry | null;
    getQueueStatsByStatus(meshId: string): {
        status: string;
        count: number;
    }[];
    getActiveAssignmentDetails(meshId: string): Array<{
        id: string;
        nodeId?: string;
        sessionId?: string;
        message: string;
    }>;
    /**
     * Resolve the `assigned` queue row a completion event belongs to.
     *
     * Clock-skew safety (C2): a completion event's `occurredAtIso` carries the
     * REMOTE WORKER's clock, while `updated_at` carries the COORDINATOR's clock
     * (set at assignment and re-bumped on every mutation). For a remote node,
     * coordinator-clock > worker-clock skew used to make an `updated_at <= occurredAt`
     * filter return nothing, stranding the finished task as `assigned` forever.
     *
     * We therefore NEVER filter completion-matching on the mutable `updated_at`:
     *  1. If `taskId` is given, match the exact `assigned` row by id (no time filter).
     *  2. Otherwise a session holds at most one `assigned` task — match it without a
     *     time filter. If several exist (shouldn't normally), disambiguate by the
     *     IMMUTABLE `dispatchTimestamp`: latest `dispatchTimestamp <= occurredAt`,
     *     and if skew makes ALL of them later than `occurredAt`, fall back to the
     *     most-recent `dispatchTimestamp` rather than returning null.
     */
    findAssignedBySession(meshId: string, sessionId: string, occurredAtIso?: string, taskId?: string): MeshWorkQueueEntry | null;
    private toRow;
    insertDirectDispatch(entry: {
        taskId: string;
        meshId: string;
        nodeId?: string;
        sessionId?: string;
        providerType?: string;
        message: string;
        taskMode?: string;
        via: string;
        dispatchedToIdleSession?: boolean;
        dispatchedAt: string;
    }): void;
    getActiveDirectDispatches(meshId: string): Array<{
        taskId: string;
        meshId: string;
        nodeId: string | null;
        sessionId: string | null;
        providerType: string | null;
        message: string;
        taskMode: string | null;
        via: string;
        status: string;
        dispatchedToIdleSession: boolean;
        dispatchedAt: string;
        updatedAt: string;
    }>;
    updateDirectDispatchStatus(meshId: string, sessionId: string, status: 'acked' | 'completed' | 'failed' | 'stale', taskId?: string): void;
    /**
     * MESH-DISPATCH-MISROUTE (fix 3, consumer residual): resolve the task_id of the SINGLE
     * non-terminal direct dispatch a session owns. Returns the task_id only when the session
     * holds exactly ONE active ('dispatched'/'acked') row — the case where a taskId-less
     * lifecycle event (a legacy/relayed worker whose producer never stamped meshActiveTaskId)
     * unambiguously belongs to that one dispatch. With zero rows there is nothing to ack; with
     * two or more (a re-dispatch/nudge sibling) the firing event's owner is ambiguous, so we
     * return null and the caller MUST NOT fall back to the session_id sweep that would flip a
     * sibling row ("may flip a sibling dispatch row"). This narrows the legacy fallback to the
     * only safe case instead of removing the producer-side TASKIDLESS stamp's safety net.
     */
    getSoleActiveDirectDispatchTaskId(meshId: string, sessionId: string): string | null;
    cleanupTerminalDirectDispatches(olderThanMs: number): void;
    deleteDirectDispatches(meshId: string): void;
    /**
     * Delete specific direct dispatch rows by taskId for a mesh. Used by the staleDirect prune
     * path to remove orphaned/terminal dispatch records whose node/session is no longer in the
     * live mesh. Returns the number of rows actually deleted. No-op for an empty taskId list.
     */
    deleteDirectDispatchesByTaskId(meshId: string, taskIds: string[]): number;
    markStaleDirectDispatches(meshId: string, olderThanMs: number): void;
    setRemoteIdleSession(meshId: string, nodeId: string, sessionId: string, providerType: string, expiresAt: number, metadata?: any): void;
    getRemoteIdleSessions(meshId: string): Array<{
        nodeId: string;
        sessionId: string;
        providerType: string;
        expiresAt: number;
        metadata?: any;
    }>;
    deleteRemoteIdleSession(meshId: string, nodeId: string, sessionId: string): void;
    pruneExpiredRemoteIdleSessions(): void;
    insertSessionDelivery(entry: {
        id: string;
        meshId: string;
        nodeId?: string;
        sessionId?: string;
        providerType?: string;
        taskId?: string;
        kind: string;
        priority?: number;
        message: string;
        status: string;
        deliverAfter?: string;
        expiresAt?: string;
        sourceCoordinatorSessionId?: string;
        sourceCoordinatorDaemonId?: string;
        createdAt: string;
        updatedAt: string;
    }): void;
    private static readonly DELIVERY_PROGRESS_RANK;
    updateSessionDeliveryStatus(id: string, status: string, opts?: {
        lastError?: string;
        incrementAttempt?: boolean;
    }): void;
    /**
     * DELIVERED-NOT-CONSUMED-REDRIVE consume path. Advance a task's delivery record(s) to a
     * CONSUMED status ('acked' or 'completed'), matching on mesh + session (+ taskId when the
     * event names one) and INCLUDING rows already in 'delivered'/'acked'/'delivering'.
     *
     * The ack/terminal callers previously routed through getActiveSessionDeliveries(), whose SQL
     * EXCLUDES 'delivered' — so in the normal event order (transport confirm flips 'delivered'
     * BEFORE the worker's generating_started fires) the ack matched zero rows and the delivery
     * was stranded 'delivered', never 'acked'. This finds the row by (mesh, session[, task])
     * directly and relies on updateSessionDeliveryStatus's monotonic guard to only advance it.
     * Returns the number of rows advanced.
     */
    consumeSessionDelivery(meshId: string, sessionId: string, status: 'acked' | 'completed', taskId?: string): number;
    /**
     * DELIVERED-NOT-CONSUMED-REDRIVE terminal path. Mark every OPEN delivery for a session
     * (queued/delivering/delivered/acked) terminal on task completion/failure. The prior
     * markSessionDeliveriesTerminal() routed through getActiveSessionDeliveries(), whose SQL
     * EXCLUDES 'delivered'/'completed' — so a 'delivered' row (the common case, since the
     * transport confirm flips it before the completion event) was never marked terminal and
     * stayed 'delivered', keeping taskDeliveryConsumed() false and feeding the false re-drive.
     * We match rows in OPEN states directly here. 'completed' advances monotonically (it is the
     * top progress rank); 'failed' is an absorbing outcome written unconditionally.
     */
    markOpenSessionDeliveriesTerminal(meshId: string, sessionId: string, terminalStatus: 'completed' | 'failed'): number;
    getActiveSessionDeliveries(meshId: string, sessionId?: string): Array<{
        id: string;
        meshId: string;
        nodeId: string | null;
        sessionId: string | null;
        providerType: string | null;
        taskId: string | null;
        kind: string;
        priority: number;
        message: string;
        status: string;
        deliverAfter: string | null;
        expiresAt: string | null;
        attemptCount: number;
        sourceCoordinatorSessionId: string | null;
        sourceCoordinatorDaemonId: string | null;
        lastError: string | null;
        createdAt: string;
        updatedAt: string;
    }>;
    /**
     * Bug B watchdog support: true when at least one delivery record for the task has
     * reached a confirmed-handed-off status (delivered / acked / completed). The
     * assigned-stranded watchdog uses this to distinguish a dispatch that was never
     * confirmed (reclaimable) from one that WAS handed to the worker (a genuinely
     * in-flight or completion-lost task, which is PHASE 4's responsibility, not this
     * watchdog's). Indexed by (mesh_id, task_id).
     */
    taskHasConfirmedDelivery(meshId: string, taskId: string): boolean;
    /**
     * DELIVERED-NOT-CONSUMED re-drive support: true when at least one delivery record for
     * the task has reached a CONSUMED status ('acked' / 'completed'). Distinct from
     * {@link taskHasConfirmedDelivery} ('delivered' | 'acked' | 'completed'): a delivery is
     * flipped to 'delivered' the instant the transport hands the dispatch off, but only
     * flipped to 'acked' when the worker's agent:generating_started event arrives (see the
     * generating_started handler in mesh-event-forwarding) — i.e. when the session has
     * actually begun the turn. That distinction is the cross-daemon consumption signal the
     * short-grace re-drive uses: a row whose delivery is 'delivered' but never 'acked' was
     * handed to a REMOTE worker that never started generating — the remote autoLaunch
     * delivered≠consumed gap — even when the session's busy verdict is UNKNOWN (not locally
     * observable). Indexed by (mesh_id, task_id).
     */
    taskDeliveryConsumed(meshId: string, taskId: string): boolean;
    expireStaleSessionDeliveries(meshId: string): void;
    deleteSessionDeliveries(meshId: string): void;
    /**
     * Record a mesh tool call and check whether this mesh+tool combination is
     * being called too rapidly (sliding window rate guard).
     *
     * Returns a rate-limit advisory string when the call rate is too high, null otherwise.
     * windowMs: sliding window size in ms (default 10s)
     * maxCalls: max allowed calls within the window (default 5)
     */
    recordMeshToolCall(opts: {
        meshId: string;
        tool: string;
        sessionId?: string | null;
        windowMs?: number;
        maxCalls?: number;
    }): {
        rateLimitExceeded: boolean;
        callsInWindow: number;
        advisory: string | null;
    };
    /**
     * Prune tool call log entries older than the given age in ms.
     * Returns the number of rows deleted. Also used by the periodic retention
     * sweep (pruneMeshRuntimeRetention) — the in-write sweep in recordMeshToolCall
     * only fires every 200 calls and only covers the rate-limit window, so a
     * quiet mesh otherwise accumulates rows indefinitely.
     */
    pruneToolCallLog(olderThanMs: number): number;
    /**
     * Retention prune for mesh_event_ledger (SoT 1-11 (b)). The ledger is append-only
     * with NO lifecycle GC of its own, so lifecycle events accumulate without bound
     * (the dominant mesh-runtime.db growth). Every production reader is bounded to a
     * recent window (readLedgerEntries tail/limit ≤ a few hundred; task-stats /
     * terminal-evidence scans look at recent tasks), so rows past a generous age only
     * cost space. Excluded from deletion — retained forever:
     *   - coordinator_operating_note / _tombstone: runtime-accumulated lessons whose
     *     whole point is surviving restarts; a tombstone must also outlive the notes
     *     it retracts.
     * Timestamps are ISO-8601 TEXT, so the lexicographic `<` cutoff is a correct time
     * comparison; a malformed timestamp compares greater than any ISO date and is
     * conservatively retained. Returns rows deleted.
     */
    pruneEventLedger(olderThanMs: number): number;
    /**
     * Retention prune for TERMINAL (completed/cancelled/failed) mesh_queue rows
     * (SoT 1-11 (b)). Terminal rows are kept as recent history (mesh_task_history,
     * completion-dedup taskId lookups) but nothing ever deletes them, so the queue
     * table grows monotonically. Rows past the retention window serve no reader —
     * every dedup/attribution path operates on recent tasks — EXCEPT as a dependency
     * anchor: taskDependenciesSatisfied resolves dependsOn by id and treats a MISSING
     * row as not-completed, so deleting a completed row that a still-live
     * (pending/assigned) row depends on would permanently strand the dependent.
     * Those ids are collected first and excluded. Returns rows deleted.
     */
    pruneTerminalQueueEntries(olderThanMs: number): number;
    appendLedgerEntry(entry: {
        id: string;
        meshId: string;
        timestamp: string;
        kind: string;
        nodeId?: string | null;
        sessionId?: string | null;
        providerType?: string | null;
        payload?: unknown;
    }): void;
    readLedgerEntries(meshId: string, opts?: {
        tail?: number;
        since?: string;
        kind?: string;
        limit?: number;
    }): Array<{
        id: string;
        meshId: string;
        timestamp: string;
        kind: string;
        nodeId: string | null;
        sessionId: string | null;
        providerType: string | null;
        payload: unknown;
    }>;
    /**
     * G2 read cutover: read ledger entries in append order (oldest first),
     * matching legacy JSONL file-order semantics. Ties on the same timestamp
     * are broken by rowid (insertion order), preserving the positional
     * guarantee that mesh-events relies on for same-millisecond entries.
     */
    readLedgerEntriesOrdered(meshId: string, opts?: {
        since?: string;
        kinds?: string[];
        tail?: number;
    }): Array<{
        id: string;
        meshId: string;
        timestamp: string;
        kind: string;
        nodeId: string | null;
        sessionId: string | null;
        providerType: string | null;
        payload: unknown;
    }>;
    /** Remove all ledger entries for a mesh (mesh deletion / test cleanup). */
    clearLedgerForMesh(meshId: string): number;
    /** G2: remove entries moved to the JSONL archive so the SQLite runtime set mirrors the active ledger. */
    deleteLedgerEntries(meshId: string, ids: string[]): number;
    hasLedgerEntry(meshId: string, id: string): boolean;
    ledgerEntryCount(meshId: string): number;
    importLedgerEntries(entries: Array<{
        id: string;
        meshId: string;
        timestamp: string;
        kind: string;
        nodeId?: string | null;
        sessionId?: string | null;
        providerType?: string | null;
        payload?: unknown;
    }>): number;
    /**
     * G4: Read a bounded, cursor-addressable ledger slice directly from the SQLite
     * mesh_event_ledger table. This is the P2P reconcile read path; JSONL files are
     * retained as export/import/debug/legacy artifacts only.
     *
     * The return shape is structurally compatible with MeshLedgerSlice so callers
     * in mesh-tools.ts can pass it directly to buildMeshLedgerReplicaEvidence.
     */
    readLedgerSlice(meshId: string, opts?: {
        afterId?: string;
        since?: string;
        kind?: string;
        limit?: number;
    }): {
        protocol: 'adhdev.mesh.ledger.slice.v1';
        meshId: string;
        entries: Array<{
            id: string;
            meshId: string;
            timestamp: string;
            kind: string;
            nodeId: string | null;
            sessionId: string | null;
            providerType: string | null;
            payload: unknown;
        }>;
        cursor: {
            afterId: string | null;
            nextAfterId: string | null;
            limit: number;
            hasMore: boolean;
        };
        sourceOfTruth: {
            kind: 'local_sqlite';
            table: 'mesh_event_ledger';
            bounded: true;
            maxLimit: number;
        };
    };
    insertPendingEvent(event: {
        id: string;
        meshId: string;
        coordinatorDaemonId?: string | null;
        event: string;
        payload?: unknown;
        fingerprint?: string | null;
        queuedAt: number;
        protocolVersion?: string | null;
        eventId?: string | null;
        scope?: string | null;
        dispatchedBy?: string | null;
        intendedFor?: string | null;
    }): boolean;
    /**
     * Drain undrained pending events for a mesh, atomically marking them drained.
     * When `opts.onlyEvents` is supplied, ONLY rows whose `event` is in that set are
     * drained — the rest stay queued (drained=0) for a later drain. This is how the
     * reconcile loop force-drains terminal/force-inject events into a *generating*
     * coordinator while leaving non-force progress events for the coordinator's next
     * idle transition. Filtering happens inside the same transaction as the
     * drained=1 marking, so force-drain + a concurrent full drain can never both
     * consume the same row.
     */
    drainPendingEvents(meshId: string, coordinatorDaemonId?: string | null | ReadonlyArray<string>, opts?: {
        onlyEvents?: ReadonlySet<string>;
    }): Array<{
        id: string;
        event: string;
        payload: unknown;
    }>;
    /** Non-destructive peek — returns undrained events without marking them drained. */
    peekPendingEvents(meshId: string, coordinatorDaemonId?: string | null | ReadonlyArray<string>): Array<{
        id: string;
        event: string;
        payload: unknown;
    }>;
    hasPendingEventFingerprint(meshId: string, fingerprint: string): boolean;
    /**
     * B3a — v2 eventId idempotency. Returns true when a row with this event_id has
     * ALREADY been drained (drained = 1) for the mesh. Drained rows are retained
     * (soft-marked, not deleted until mesh deletion), so this is a durable, restart-
     * surviving dedup: a v2 event whose eventId was already consumed is skipped on
     * re-delivery even when its content fingerprint differs. Scoped by mesh_id +
     * the partial event_id index (idx_mesh_pending_events_event_id).
     */
    hasDrainedEventId(meshId: string, eventId: string): boolean;
    /**
     * B3a — snapshot of the v2 event_ids ALREADY drained (drained = 1) for the mesh.
     * Taken BEFORE a drain call marks the current batch drained=1, so the resulting
     * set names only PRIOR drains — the re-delivery dedup baseline. (Reading it after
     * the drain would self-match the batch's own freshly-drained rows.) Non-v2 rows
     * have a NULL event_id and are excluded by the index/WHERE.
     */
    drainedEventIdsForMesh(meshId: string): Set<string>;
    upsertMission(mission: {
        id: string;
        meshId: string;
        title: string;
        goal?: string;
        status?: string;
        source?: string;
    }): void;
    getMission(meshId: string, missionId: string): {
        id: string;
        meshId: string;
        title: string;
        goal: string;
        status: string;
        source?: string;
        closeCandidateEmittedAt?: string;
        createdAt: string;
        updatedAt: string;
    } | null;
    getMissions(meshId: string, statuses?: string[]): Array<{
        id: string;
        meshId: string;
        title: string;
        goal: string;
        status: string;
        source?: string;
        closeCandidateEmittedAt?: string;
        createdAt: string;
        updatedAt: string;
    }>;
    /**
     * G3: set/clear the mission_close_candidate idempotency marker. Passing an ISO
     * timestamp records that the all-terminal nudge has been emitted for this mission;
     * passing null clears it (mission returned to a non-terminal state, so a future
     * re-completion may nudge again). Touches ONLY this column — never the mission's
     * updated_at — so the marker write is invisible to updatedAt-ordered surfaces and
     * does not masquerade as mission activity. Returns rows changed (0 if no such mission).
     */
    setMissionCloseCandidateEmittedAt(meshId: string, missionId: string, emittedAt: string | null): number;
    /**
     * Read the last idle-active-mission-reminder debounce marker for a mesh, or null if
     * none has fired this process. In-memory only (see idleReminderState) — best-effort
     * spam guard for a coordinator nudge, intentionally not SQLite-backed.
     */
    getIdleReminderState(meshId: string): {
        emittedAt: number;
        missionSetHash: string;
    } | null;
    /** Record that an idle-active-mission reminder just fired for a mesh (debounce marker). */
    setIdleReminderState(meshId: string, state: {
        emittedAt: number;
        missionSetHash: string;
    }): void;
    /** Clear the idle-reminder debounce marker for a mesh — mesh deletion / test cleanup. */
    clearIdleReminderState(meshId: string): void;
    /** Remove all missions for a mesh — mesh deletion / test cleanup. */
    clearMissionsForMesh(meshId: string): number;
    /** Remove all pending-event rows (drained included) for a mesh — mesh deletion / test cleanup. */
    clearPendingEventsForMesh(meshId: string): number;
    pendingEventCount(meshId: string): number;
    /**
     * Mark specific pending-event rows drained by id (ack). Used by the
     * unresolved-delegate durable-forward outbox: an event is peeked (not drained)
     * while its push to the coordinator is unconfirmed, then marked drained ONLY
     * after the push is acked. A failed push leaves the row undrained so the next
     * reconcile tick retries it. Returns the number of rows newly marked drained.
     */
    markPendingEventsDrainedById(ids: ReadonlyArray<string>): number;
    /**
     * Hard-delete pending-event rows by id (including the dedup fingerprint history).
     * Used to expire an unresolved-delegate outbox entry that has exhausted its retry
     * budget — fully removing it frees the fingerprint so a genuinely new completion
     * for the same task could be re-queued later. Returns the number of rows deleted.
     */
    deletePendingEventsById(ids: ReadonlyArray<string>): number;
    /**
     * Retention prune for mesh_pending_events. This table has no lifecycle GC of its
     * own: a drained row is soft-marked (drained=1) and RETAINED — deliberately, so
     * drainedEventIdsForMesh() has a durable v2-eventId dedup baseline — and an
     * undrained row queued for a coordinator that never returned (a dead/evicted
     * coordinator identity) stays drained=0 forever. Both accumulate without bound
     * (observed: tens of thousands of rows, mostly stale). This is the missing
     * retention step. Two independent windows:
     *
     *   - drained rows older than `drainedOlderThanMs`: the coordinator consumed them
     *     long ago; the only thing they still back is the eventId re-delivery guard,
     *     which is only meaningful for the recent past (a re-delivery of a week-old
     *     event cannot occur — its producer session is long gone). Safe to delete.
     *   - UNDRAINED rows older than `undrainedOlderThanMs` (a much wider window):
     *     these are orphaned events for a coordinator identity that never drained
     *     them. Kept wide so a genuinely-offline-but-returning coordinator still
     *     receives its backlog; only genuinely unrecoverable orphans are swept.
     *
     * Both windows key off `queued_at` (always present) — `drained_at` can be NULL on
     * legacy rows. Returns the number of rows deleted. Best-effort / idempotent:
     * running it repeatedly with nothing to prune is a cheap no-op.
     */
    prunePendingEvents(opts: {
        drainedOlderThanMs: number;
        undrainedOlderThanMs: number;
    }): number;
}
export declare const MESH_EVENT_LEDGER_RETENTION_MS: number;
export declare const MESH_TOOL_CALL_LOG_RETENTION_MS: number;
export declare const MESH_TERMINAL_QUEUE_RETENTION_MS: number;
/**
 * Periodic retention sweep for the mesh-runtime.db tables that previously had no
 * lifecycle GC (event ledger, tool-call log, terminal queue rows). Runs on the SAME
 * cadence as the pending-events retention prune (the hourly mesh-event maintenance
 * sweep in mesh-event-forwarding.ts). Best-effort and idempotent: a store failure
 * degrades to a no-op with one warn; an empty table costs three cheap DELETEs.
 */
export declare function pruneMeshRuntimeRetention(): {
    ledger: number;
    toolCalls: number;
    terminalQueue: number;
};
