/** * Brain.db auto-recovery from snapshot — armoring against the live malformation * incident that triggered Saga T10281 SG-BRAIN-DB-RESILIENCE. * * The failure mode this module armors against: * * ``` * $ sqlite3 .cleo/brain.db 'PRAGMA integrity_check;' * Error: in prepare, malformed database schema (epic:T1075) (11) * ``` * * Surfaces in CLEO as `ERR_SQLITE_ERROR errcode=11` → `E_BRAIN_OBSERVE` on * every `cleo memory observe`, and as a silent dialectic-hook crash * (T10260, T10265) on every `cleo update`. The previous behaviour was to * silently degrade cognition until an operator noticed memory writes were * failing. The recovery pipeline below makes the next incident self-healing. * * ## Pipeline * * 1. Move the corrupt DB (plus `-wal` / `-shm` sidecars) to * `/.cleo/quarantine/brain-malformed-/`. * 2. Enumerate `.cleo/backups/snapshot/brain.db.snapshot-*` and * `.cleo/backups/sqlite/brain-YYYYMMDD-HHmmss.db` (system-backup and * VACUUM INTO formats). 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. If no snapshot validates, fall back to `.cleo/brain.db.PRE-DUP-FIX-*` * legacy artifacts (also `quick_check`-validated). * 5. `copyFileSync` the chosen source to `.cleo/brain.db` and run one final * `quick_check` to confirm the restored file opens cleanly. * * ## Invariants * * - **Synchronous**: runs on the open-blocking critical path. Restoration is * short (a few seconds) and the alternative is broken cognition. * - **Idempotent on partial failure**: any throw inside the pipeline leaves * the corrupt DB at the original path AND the quarantined copy in place, * so the next process attempt can retry without losing forensic state. * - **No raw `new DatabaseSync`** outside the canonical leaf * {@link openNativeDatabase} — every snapshot probe flows through the * pragma SSoT (ADR-068). * * @task T10303 * @epic T10286 * @saga T10281 * @adr ADR-068 */ import type { BrainRecoveryResult } from '@cleocode/contracts'; /** * Minimal logger shape used by {@link recoverMalformedBrainDb}. * * 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. */ 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 recoverMalformedBrainDb}. */ export interface RecoverMalformedBrainDbOptions { /** * Absolute path to the corrupt `brain.db`. Recovery moves this file (plus * `-wal`/`-shm` sidecars) into the quarantine directory before restoring. */ corruptPath: string; /** * Absolute path to the project's `.cleo/backups/snapshot/` directory. * Recovery enumerates `brain.db.snapshot-*` files here and selects the * freshest validated snapshot. */ snapshotDir: string; /** * Optional absolute path to the project's `.cleo/backups/sqlite/` * directory containing VACUUM INTO snapshots (`brain-YYYYMMDD-HHmmss.db`). * When present, these snapshots compete with the `snapshotDir` candidates * in the same freshness-ranking pool. */ vacuumSnapshotDir?: string; /** * Optional absolute path to the project's `.cleo/` directory. Used to * enumerate legacy `brain.db.PRE-DUP-FIX-*` artifacts as a last-resort * fallback. When unset, legacy fallback is skipped. */ legacyArtifactDir?: string; /** * Absolute path to the quarantine root. The corrupt DB is moved to * `/brain-malformed-/brain.db.malformed`. * * Defaults to `/quarantine` when omitted. */ quarantineRoot?: string; /** Pino-shaped logger for the single recovery announcement. */ logger: RecoveryLogger; } /** * Recover a malformed `brain.db` by quarantining the corrupt file and * restoring the freshest validated snapshot. * * Intended to be called synchronously from the brain.db open chokepoint * (`packages/core/src/store/memory-sqlite.ts:getBrainDb`) when the open * throws an `ERR_SQLITE_ERROR errcode=11` or when `PRAGMA integrity_check` * fails after open. Callers MUST retry the open after this function * returns — the function does not re-open the DB itself, so the caller * remains the SSoT of the open lifecycle (singletons, drizzle wrapper, * migration journal). * * The function NEVER throws on a recoverable path; instead it returns a * structured {@link BrainRecoveryResult} 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. * `corruptPath` is missing). * * @param opts - Recovery inputs: corrupt path, snapshot directories, * logger, optional quarantine root. * @returns The {@link BrainRecoveryResult} envelope. * * @example * ```typescript * import { recoverMalformedBrainDb } from '@cleocode/core/store/recover-brain-db'; * import { getLogger } from '@cleocode/core/logger'; * * const result = recoverMalformedBrainDb({ * corruptPath: '/repo/.cleo/brain.db', * snapshotDir: '/repo/.cleo/backups/snapshot', * vacuumSnapshotDir: '/repo/.cleo/backups/sqlite', * legacyArtifactDir: '/repo/.cleo', * logger: getLogger('brain-recover'), * }); * if (result.integrityOK) { * // Retry the open. * } * ``` */ export declare function recoverMalformedBrainDb(opts: RecoverMalformedBrainDbOptions): BrainRecoveryResult; //# sourceMappingURL=recover-brain-db.d.ts.map