/** * Generic CLEO database auto-recovery pipeline — generalised from the * brain-only T10303 helper (`recoverMalformedBrainDb`) so the same flow * works for every DB declared in {@link DB_INVENTORY}. * * Mirrors the EXACT semantics of {@link recoverMalformedBrainDb}: * * 1. Move the corrupt DB (plus `-wal` / `-shm` sidecars) to * `/quarantine/-malformed-/`. * 2. Enumerate snapshot candidates from three sources: * - `/backups/snapshot/.db.snapshot-*` (system-backup format) * - `/backups/sqlite/-YYYYMMDD-HHmmss.db` (VACUUM INTO format) * - `/.db.PRE-DUP-FIX-*` (legacy artifact fallback) * Sort newest-first. * 3. Validate each candidate via `PRAGMA quick_check` (best-effort, with * sqlite-internal busy timeout). Pick the freshest one that returns `ok`. * 4. `copyFileSync` the chosen source to the canonical DB path and run one * final `quick_check` to confirm the restored file opens cleanly. * * The role is sourced from {@link DB_INVENTORY} via {@link getRoleConfig} — * the inventory is the SSoT for which DBs exist and where they live. * * @task T10318 * @epic T10284 * @saga T10281 * @adr ADR-068 */ import { type DbRecoveredRowCounts, type DbRecoveryResult, type DbRole } from '@cleocode/contracts'; /** * Minimal logger shape used by {@link recoverMalformedDb}. * * Matches the subset of `pino.Logger` invoked from this module. Declared * locally so the recovery pipeline does not pull `pino` into modules that * use it strictly as a value type. * * @task T10318 * @public */ export interface RecoveryLogger { /** Structured warning — used for the single auto-recovery announcement. */ warn(obj: Record, msg: string): void; /** Structured error — used for non-fatal probe failures. */ error(obj: Record, msg: string): void; } /** * Options accepted by {@link recoverMalformedDb}. * * @task T10318 * @public */ export interface RecoverMalformedDbOptions { /** Canonical role of the database to recover. Must exist in {@link DB_INVENTORY}. */ role: DbRole; /** * Absolute path to the corrupt DB file. When omitted, derived from the * inventory entry's `filePathTemplate` with {@link projectRoot} substitution. */ corruptPath?: string; /** * Absolute path to the snapshot directory (`.cleo/backups/snapshot/`). * When omitted, derived from the inventory tier + {@link projectRoot}. */ snapshotDir?: string; /** * Absolute path to the VACUUM-INTO snapshot directory * (`.cleo/backups/sqlite/`). When omitted, derived from inventory tier + * {@link projectRoot}. */ vacuumSnapshotDir?: string; /** * Absolute path to the legacy artifact directory (`.cleo/` itself) used * for `.db.PRE-DUP-FIX-*` fallback enumeration. When omitted, * derived from inventory tier + {@link projectRoot}. Pass an empty * string to disable legacy fallback entirely. */ legacyArtifactDir?: string; /** * Absolute path to the quarantine root. Defaults to * `/quarantine`. */ quarantineRoot?: string; /** * Absolute path to the project root used when resolving inventory paths. * Required when {@link corruptPath} is not supplied for a `project`-tier * role; ignored for `global`-tier roles whose paths resolve via * `getCleoHome()`. */ projectRoot?: string; /** Pino-shaped logger for the single recovery announcement. */ logger: RecoveryLogger; } /** Source taxonomy for a snapshot candidate — diagnostic only. */ type SnapshotSource = 'system-snapshot' | 'vacuum-snapshot' | 'pre-dup-fix'; /** Internal candidate record used during snapshot ranking. */ interface SnapshotCandidate { /** Absolute path to the snapshot file. */ path: string; /** Best-available timestamp for ordering (epoch ms). */ timestampMs: number; /** Source taxonomy — for diagnostic logging only. */ source: SnapshotSource; } /** Result of a single snapshot probe via `PRAGMA quick_check`. */ interface ProbeResult { /** `true` when the file opens cleanly and `quick_check` returns `ok`. */ ok: boolean; /** Best-effort per-table row counts in the probed DB. */ rowCounts: DbRecoveredRowCounts; } /** * Resolve the canonical filesystem path for a role's DB file. * * @remarks * Substitutes the inventory's `filePathTemplate` tokens. The two tokens * recognised today are `` and `$XDG_DATA_HOME/cleo` (the latter * resolves via env-paths through `getCleoHome()`). For project-tier roles, * pass {@link projectRoot}. For global-tier roles, the resolver uses * `getCleoHome()` directly. * * Lives in this module rather than `@cleocode/paths` because resolution is * SSoT-aware (driven by {@link DB_INVENTORY}) and recovery is the only * consumer today. Promote upward when a second consumer appears. * * @task T10318 * @public */ export declare function resolveRoleDbPath(role: DbRole, ctx: { projectRoot?: string; }): string; /** * Resolve the canonical `.cleo/backups/snapshot/` and `.cleo/backups/sqlite/` * dirs for a role's filesystem layout. * * @remarks * For `project`-tier roles these resolve to * `/.cleo/backups/{snapshot,sqlite}`. For `global`-tier roles * they resolve to `$XDG_DATA_HOME/cleo/backups/{snapshot,sqlite}`. The * legacy artifact directory mirrors the parent `.cleo/` (project) or * `cleo/` (global) home for `.db.PRE-DUP-FIX-*` enumeration. * * @task T10318 * @public */ export declare function resolveRoleBackupDirs(role: DbRole, ctx: { projectRoot?: string; }): { /** Absolute path to the canonical DB home (parent directory of the DB file). */ cleoDir: string; /** Absolute path to `/backups/snapshot/`. */ snapshotDir: string; /** Absolute path to `/backups/sqlite/`. */ vacuumSnapshotDir: string; /** Absolute path to `` itself — for legacy PRE-DUP-FIX enumeration. */ legacyArtifactDir: string; /** Absolute path to `/quarantine/` — the quarantine root. */ quarantineRoot: string; }; /** * Enumerate all snapshot candidates for a role from system-snapshot, * vacuum-snapshot, and PRE-DUP-FIX legacy sources. Returns newest-first. * * @internal */ export declare function collectSnapshotCandidatesForRole(opts: { role: DbRole; snapshotDir?: string; vacuumSnapshotDir?: string; legacyArtifactDir?: string; }): SnapshotCandidate[]; /** * Probe a candidate snapshot file by opening it read-only and running * `PRAGMA quick_check`. Returns `{ ok: true, rowCounts }` only when the * database opens cleanly and `quick_check` returns `ok`. * * The handle is always closed before this function returns. * * @internal */ export declare function probeSnapshot(path: string): ProbeResult; /** * Move the corrupt DB and its `-wal`/`-shm` sidecars into a quarantine * directory. Returns the absolute path to the quarantine directory. * * Uses `renameSync` (atomic on same-filesystem) — falls back to * `copyFileSync` + unlink when rename crosses filesystems. We don't bother * detecting cross-fs explicitly; `renameSync` returns EXDEV in that case * which we catch as a recovery failure and the caller surfaces it. * * @internal */ export declare function quarantineCorruptDb(role: DbRole, corruptPath: string, quarantineRoot: string): string; /** * Recover a malformed CLEO database by quarantining the corrupt file and * restoring the freshest validated snapshot. * * @remarks * Synchronous by design — recovery may run on the open-blocking critical * path (e.g. brain.db auto-recovery in {@link memory-sqlite.ts}). Restoration * is short (a few seconds) and the alternative is broken behavior. * * NEVER throws on a recoverable path; instead returns a structured * {@link DbRecoveryResult} where `restoredFrom === null` and * `integrityOK === false` indicate complete failure. The caller surfaces * that case to the operator. Throws only on programmer-error inputs (e.g. * unknown role, missing `projectRoot` for a project-tier role with no * explicit `corruptPath`). * * @param opts - Recovery inputs. * @returns The {@link DbRecoveryResult} envelope. * * @example * ```typescript * import { recoverMalformedDb } from '@cleocode/core/store/recover-malformed-db'; * import { getLogger } from '@cleocode/core/logger'; * * const result = recoverMalformedDb({ * role: 'brain', * projectRoot: '/repo', * logger: getLogger('brain-recover'), * }); * if (result.integrityOK) { * // Retry the open. * } * ``` * * @task T10318 * @epic T10284 * @saga T10281 * @public */ export declare function recoverMalformedDb(opts: RecoverMalformedDbOptions): DbRecoveryResult; export {}; //# sourceMappingURL=recover-malformed-db.d.ts.map