/** * The restore-in-progress marker of a store file (T13258). * * Replacing a live `cleo.db` (a `cleo restore backup` or a vault restore) has * a window between its last liveness check and the rename. An ordinary open * never takes the first-open lock, so a cleo process starting inside that * window would open the OLD file and, after the old sidecars were removed, * create a `-wal`/`-shm` BY NAME for it. The next open of the restored file * would then replay that WAL onto the wrong database. * * The restorer writes `.restoring` (with `O_EXCL`) before its final * check and removes it after the swap. Every store open checks it with one * `existsSync`: while a live restorer holds it, the open waits briefly, then * refuses with `E_STORE_RESTORING`. A marker whose process is gone is stale * and ignored (the rename is atomic, so the file is whole either way). * * @task T13258 * @module store/restore-marker */ /** Suffix of the marker beside the store file. */ export declare const RESTORE_MARKER_SUFFIX = ".restoring"; /** * How long an open waits for a restore to finish before refusing; * `CLEO_RESTORE_WAIT_MS` overrides it. */ export declare const RESTORE_MARKER_WAIT_MS = 15000; /** What a marker records. */ export interface RestoreMarker { readonly pid: number; readonly host: string; /** The holder machine's stable device id (a hostname can change with the network). */ readonly deviceId?: string; readonly startedAt: string; /** * `restore` (`cleo restore backup` / `backup recover`), `vault` (a vault * restore), or `genesis` (a sync genesis cut snapshotting the store under * its write lock, T12343). */ readonly kind: 'restore' | 'vault' | 'genesis'; } /** * A marker older than this is stale whoever wrote it: a restore holds it for * seconds (the copy of the replaced store and a rename), and a crashed holder * on another host (or this one under a changed hostname) cannot be probed. * * A genesis cut holds it longest: the cut plus the bundle export, measured on * scratch copies of the largest real stores (2026-10-07, #1953 review): about * 63 s worst on cleocode's 1.3 GB store (cut 2.4 s, export 48.7 s, extract * and manifest 11.8 s) and 2.8 s on claude-todo. The window is the 1 h floor, * over 50x that worst case, and a holder that outlives it never finishes: * {@link RestoreMarkerHold.intact} turns false and the genesis cut aborts. */ export declare const RESTORE_MARKER_MAX_AGE_MS: number; /** * Whether a restore marker on `dbPath` blocks this process right now (no * wait). An opener re-checks it AFTER opening (T13258 LOW-3): a marker * written between its first check and its open means it may hold the file * about to be replaced. * * @param dbPath - The store file. * @returns `true` when another live process holds the marker. * @task T13258 */ export declare function restoreMarkerBlocks(dbPath: string): boolean; /** * Open `dbPath` only while no restore is replacing it: wait out (or refuse * on) a marker before opening, and re-check after; when one appeared in * between, close and wait again (T13258). * * @param dbPath - The store file. * @param open - Opens it. * @returns The open handle. * @throws {CleoError} `E_STORE_RESTORING` when the restore outlasts the wait. * @task T13258 */ export declare function openUnlessRestoring(dbPath: string, open: () => T): T; /** * Refuse to open `dbPath` while another process is replacing it. Waits up to * `waitMs` for the restore to finish first. Costs one `existsSync` when no * restore is running. * * @param dbPath - The store file about to be opened. * @param waitMs - How long to wait for a running restore (default {@link RESTORE_MARKER_WAIT_MS}, or `CLEO_RESTORE_WAIT_MS`). * @throws {CleoError} `E_STORE_RESTORING` while a restore still holds the marker. * @task T13258 */ export declare function assertStoreNotRestoring(dbPath: string, waitMs?: number): void; /** * The write-chokepoint form of {@link assertStoreNotRestoring}: a handle * must not write while its store is replaced or snapshotted, including a * handle opened before the marker was taken, and, for a genesis snapshot, any * writer in the marker's own process (T13297). Waits without blocking the * event loop, up to `waitMs`, then refuses. Costs one `existsSync` when no * marker is present. * * Called from `assertExodusWriteSafe`, which the task accessor's write * transactions, session creation, `insertIdempotent` and `upsertIdempotent` * call first. A writer that bypasses those meets no wait; the genesis cut * detects such a write after its snapshot and undoes itself. * * @param dbPath - The store file about to be written (null for an in-memory database: never blocks). * @param waitMs - How long to wait (default {@link RESTORE_MARKER_WAIT_MS}, or `CLEO_RESTORE_WAIT_MS`). * @throws {CleoError} `E_STORE_GENESIS` or `E_STORE_RESTORING` while another process still holds the marker. * @task T12343 */ export declare function awaitStoreWritable(dbPath: string | null, waitMs?: number): Promise; /** A held marker: its release, and whether it is still this holder's and inside its window. */ export interface RestoreMarkerHold { /** Remove the marker, but only while it is still this holder's (idempotent). */ readonly release: () => void; /** * Whether the marker on disk is still the one written here and younger than * {@link RESTORE_MARKER_MAX_AGE_MS}. False once it went stale (other * processes may already be writing) or another process replaced it. */ readonly intact: () => boolean; /** * Whether another holder took the marker over: the file on disk is no * longer the one written here (replaced, or replaced and already released). * Only a new holder removes or replaces a held marker, and only after it * went stale, so a holder that sees this no longer owns what it guarded. */ readonly takenOver: () => boolean; } /** * Write the marker for a restore of `dbPath` (exclusive) and hold it. A holder * whose work outlives the window must check {@link RestoreMarkerHold.intact} * before it commits to its result, and discard the result when it is false. * * @param dbPath - The store file about to be replaced or snapshotted. * @param kind - Who is holding it. * @returns The hold. * @throws {CleoError} `E_STORE_RESTORING` / `E_STORE_GENESIS` when another live holder has it. * @task T13258 */ export declare function holdRestoreMarker(dbPath: string, kind: RestoreMarker['kind']): RestoreMarkerHold; /** * Write the marker for a restore of `dbPath` (exclusive). Returns its release * ({@link holdRestoreMarker} for a holder that must check it still holds it). * * @param dbPath - The store file about to be replaced. * @param kind - Who is replacing it. * @returns A function that removes the marker while it is still this holder's (idempotent). * @throws {CleoError} `E_STORE_RESTORING` when another live restore holds it. * @task T13258 */ export declare function writeRestoreMarker(dbPath: string, kind: RestoreMarker['kind']): () => void; //# sourceMappingURL=restore-marker.d.ts.map