/** * SQLite store via drizzle-orm/node-sqlite + node:sqlite (DatabaseSync). * * ## E6-L1 — thin-facade migration (T11521) * * `getDb()` and `getNativeTasksDb()` are now thin facades that delegate the * database open to {@link openDualScopeDb}('project', cwd) — the canonical * dual-scope chokepoint introduced by E3/E4 (T11512/T11517). This ensures: * * - Every tasks.db open flows through the single pragma SSoT (ADR-068/069). * - The worktree-isolation guard (T9806) fires consistently on all opens. * - DB Open Guard Gate 3 (`scripts/lint-no-direct-db-open.mjs`) stays green. * * The legacy tables (`tasks`, `sessions`, `schema_meta`, …) are still created * by `runMigrations` (drizzle-tasks folder) inside the shared `cleo.db` file * during the E3→E6 transition period, co-existing with the new `tasks_tasks` * prefix tables from the consolidated schema. E6-L7/L8 will remove them. * * @epic T4454 * @task T4817 - node:sqlite engine migration (ADR-006, ADR-010) * @task T4810 - Data loss prevention guards * @task T11521 - E6-L1: route getDb through openDualScopeDb (SG-DB-SUBSTRATE-V2) */ import type { NodeSQLiteDatabase } from 'drizzle-orm/node-sqlite'; import { type ProjectStore } from './dual-scope-db.js'; import { type DomainBinding } from './ports/domain-binding.js'; export { type DatabaseSync, openNativeDatabase } from './sqlite-native.js'; import type { DatabaseSync } from './sqlite-native.js'; import * as schema from './tasks-schema.js'; /** Schema version for newly created databases. Single source of truth. */ export declare const SQLITE_SCHEMA_VERSION = "2.0.0"; /** * Get the path to the SQLite database file that {@link getDb} opens. * * ## E6-L1 (T11521) * * After the dual-scope migration, `getDb()` opens the consolidated project * `cleo.db` via {@link openDualScopeDb}. This function therefore returns the * dual-scope `cleo.db` path so that callers checking for the file `getDb()` * created (existence / backup / health probes) point at the correct file. */ export declare function getDbPath(cwd?: string): string; /** * Inter-process lock suffix shared by the two destructive first-open paths that * both fire on the `tasks_tasks == 0` (empty-consolidated-`cleo.db`) condition: * * - {@link autoRecoverFromBackup} (T5188) — restore a `tasks-*.db` snapshot over * the live `cleo.db` file (closes the handle, unlinks the WAL, overwrites the * DB file via copy+rename). * - `maybeRunExodusOnOpen` (`exodus/on-open.ts`, T11553) — copy the legacy fleet * into the same `cleo.db` under a single-flight lock keyed by this suffix. * * Before T11662 these paths used DIFFERENT (or, for auto-recovery, NO) locks, so * they could run concurrently against the same file in two processes — one * unlinking the WAL while the other was mid-copy → a torn WAL frame and * `"database disk image is malformed"` (observed on a live 4 687-task DB under 3 * concurrent agents). * * Auto-recovery now contends on the SAME lock the exodus path uses * (`.exodus-on-open.lock`). Mutual exclusion is therefore guaranteed in * BOTH directions: while exodus holds the lock copying the fleet, auto-recovery * blocks; while auto-recovery holds it restoring a snapshot, exodus blocks. The * winner re-checks `tasks_tasks` under the lock (double-checked locking) and the * loser early-exits without touching the WAL or the DB file. * * @task T11662 * @see packages/core/src/store/exodus/on-open.ts — the exodus single-flight lock */ /** * Suffix of the lock file that serialises destructive whole-store operations * on one `cleo.db` (first-open migration, auto-recovery, vault restore). */ export declare const FIRST_OPEN_LOCK_SUFFIX = ".exodus-on-open.lock"; /** * Why auto-recovery must not restore into `dbPath`, or null when it may. * * A `/.cleo/cleo.db` whose `` is a linked git worktree is a * diverged copy: restoring the parent's newest snapshot into it (~1.3 GB, and * again into a `-pre-cleo.db.bak`) made every later write from the worktree * land where nothing merges it back (T12460, field report T12677). The * message names the project whose store the worktree must use. * * T12708: the decision is the shared whole-store rewrite guard * ({@link ownerStoreRewriteRefusal}) as an open-time rewrite: a recovery of * the owning project's store reached from one of its worktrees proceeds (it is * the same restore the project root would run) and is audited at the restore. * * The one refusal left (a worktree-resident target) is reachable only past * the store-open guard's audited `CLEO_ALLOW_WORKTREE_DB_CREATE=1` override, * where the operator chose that worktree store; T12460 already refused it. * * @param dbPath - Absolute path of the empty store auto-recovery would fill. * @param cwd - The caller's optional working directory (the invocation dir). * @returns The refusal message, or null when auto-recovery may restore. * @task T12677 * @task T12708 */ export declare function worktreeRecoveryRefusal(dbPath: string, cwd?: string): string | null; /** * See the doc block above {@link countBackupTasks} for the full T5188/T11662 * rationale. Exported (rather than module-private) ONLY so the T11662 concurrency * regression test can drive ≥4 racing invocations against a single `cleo.db` and * assert exactly one restore occurs under the shared first-open lock. Production * code MUST reach this solely via {@link getDb}. * * ## E6-L13 (T12037) — restore signals, it does not re-point * * Before the ProjectStore cutover this function re-opened the restored file * and re-pointed the module-global `_db` / `_nativeDb` singletons itself. * With those singletons gone it now performs ONLY the file-level restore and * returns `true`, and {@link bindTasksDomain} re-binds every project domain * against the restored file. That is strictly safer: the brain/conduit * bindings sharing the same `cleo.db` are invalidated too, where the old * singleton re-point left them holding a closed handle. * * @param nativeDb - The freshly-opened consolidated handle (its `tasks_tasks` is * probed for emptiness; closed before the file is overwritten on the restore path). * @param dbPath - Absolute path to the consolidated project `cleo.db`. * @param cwd - Working directory used to resolve the `.cleo/backups/sqlite/` dir. * @returns `true` when the DB file was replaced from a backup (callers MUST * re-bind); `false` when no recovery was needed or possible. * @internal * @task T5188 * @task T11662 * @task T12037 */ export declare function autoRecoverFromBackup(nativeDb: DatabaseSync, dbPath: string, cwd: string | undefined): Promise; /** * Open (or return cached) the drizzle tasks-schema handle for the project scope. * * ## E6-L1 façade (T11521) * * Delegates the physical DB open to {@link openDualScopeDb}('project', cwd) — * the canonical dual-scope chokepoint. The returned `NodeSQLiteDatabase` wraps * the same `DatabaseSync` handle as the consolidated `cleo.db` but is typed * against the legacy `tasks-schema` (table name `tasks`, etc.) so all existing * callers compile without change. * * The legacy drizzle-tasks migrations are still applied to this handle during * the E3→E6 transition, creating the `tasks` table family inside `cleo.db` * alongside the new `tasks_tasks` tables. E6-L7/L8 will remove them. * * Uses a promise guard so concurrent callers wait for the same * initialization to complete (migrations are async). */ export declare function getDb(cwd?: string): Promise; /** * Bind the legacy tasks-schema to the {@link ProjectStore} for `cwd` and * return the full binding (store + native handle + typed Drizzle instance). * * ## E6-L13 cutover (T12037) * * This is the ProjectStore-bound typed port that replaces the module-global * singleton quartet (`_db` / `_nativeDb` / `_dbPath` / `_initPromise`) this * facade used to own. Caching, path keying, single-flight, and handle * liveness are now owned by {@link bindProjectDomain}; this function owns * only the tasks domain's schema reconciliation. * * Three defect classes disappear with the singleton: * * - **Last-project-wins** — `_dbPath` tracked ONE project, so alternating * between two projects reset and re-migrated on every switch. Bindings are * now keyed by canonical `cleo.db` path. * - **Cross-domain staleness** (T12019 / T12020) — the tasks singleton could * hold a `DatabaseSync` the brain domain had already closed. A binding is * valid only while the store instance it was established against is still * the live one, so an eviction invalidates it by identity. * - **Band-aid reacquisition** (T12035) — the bounded retry loop that papered * over the above is deleted; the runtime registry handles liveness. * * Prefer this over {@link getDb} when you also need the native handle or an * explicit store for a cross-domain transaction. * * @param cwd - Project working directory. * @returns The live tasks-domain binding. * * @task T12037 (E6-L13) */ export declare function bindTasksDomain(cwd?: string): Promise>; /** * The `task_id_sequence` value a fresh store is seeded with. The sequence module * treats it as "no state" (`isSeedSequence`), so a real counter — e.g. one a * legacy store carries — must always win over it. */ export declare const TASK_ID_SEQUENCE_SEED = "{\"counter\":0,\"lastId\":\"T000\",\"checksum\":\"seed\"}"; /** * Seed the tasks domain's `tasks_schema_meta` defaults (no-op for keys already set). * * @param nativeDb - Connection on the project `cleo.db`. */ export declare function seedTasksMeta(nativeDb: DatabaseSync): void; /** * Create the tables the tasks-domain RUNTIME binds on a project `cleo.db` * without binding the domain: run the `drizzle-tasks` lineage (including the * T12346 rebuild when it cannot be replayed) on the given connection. * * Exodus (on-open and reconcile) copies legacy rows into the tables the runtime * reads — several of which (`audit_log`, `token_usage`, …) only this lineage * creates — so they must exist before the copy. It runs on the migration's own * dedicated connection: binding the domain from inside an open would wait on * that very open. Defaults are NOT seeded here; the caller seeds after the copy * ({@link seedTasksMeta}) so a legacy value is never shadowed by a default. * * @param nativeDb - Idle dedicated connection on the project `cleo.db`. * @param dbPath - Absolute path of that database. * @param cwd - The caller's optional working directory (the invocation dir). * @task T12355 */ export declare function ensureTasksDomainTables(nativeDb: DatabaseSync, dbPath: string, cwd?: string): void; /** * Resolve the absolute path to the drizzle-tasks migrations folder inside * @cleocode/core, using ESM-native module resolution (T1177). * * Delegates to {@link resolveCorePackageMigrationsFolder} which handles * bundled dist/, workspace dev, and global-install layouts uniformly via * `import.meta.resolve()` + `createRequire().resolve()` fallback. */ export declare function resolveMigrationsFolder(): string; /** * Check whether a table exists in the SQLite database. */ /** Re-export isSqliteBusy for external consumers. */ export { isSqliteBusy } from './migration-manager.js'; /** * Close the database connection and release resources. * * ## E6-L1 (T11521) * * Resets the dual-scope cache via {@link _resetDualScopeDbCache} so the next * `getDb()` call re-initialises cleanly rather than receiving a stale cached * handle whose `DatabaseSync` is already closed (which would produce * "database is not open" errors in migration-manager). * * Without the cache eviction, `openDualScopeDb` would return the stale entry * for the same (scope, cwd) key — its `DatabaseSync` already closed by * `_nativeDb.close()` below — causing downstream "database is not open" errors * in `runMigrations → tableExists` for the very next `getDb()` caller. */ export declare function closeDb(): void; /** * Reset database singleton state without saving. * Used during migrations when database file is deleted and recreated. * Safe to call multiple times. * * ## E6-L1 (T11521) * * Also resets the dual-scope cache via {@link _resetDualScopeDbCache} so the * next `getDb()` opens a fresh handle rather than reusing a stale one. * Mirrors the cache-eviction logic in {@link closeDb}. * * E6-L4 (T11524): scoped to `'project'` so it does not close the shared GLOBAL * `cleo.db` handle (nexus/signaldock/skills) — see {@link closeDb}. */ export declare function resetDbState(): void; /** * Get the schema version from the database. */ export declare function getSchemaVersion(cwd?: string): Promise; /** * Check if the database file exists. * * ## E6-L1 (T11521) * * After the dual-scope migration, `getDb()` opens `cleo.db` via * {@link openDualScopeDb} — not the legacy `tasks.db`. This function now * checks for `cleo.db` so that callers (e.g. `migration-sqlite.ts`) correctly * detect the E6 database and skip re-migration. * * {@link getDbPath} still returns the legacy `tasks.db` path for backward-compat * callers that specifically need that file (e.g. backup path construction). */ export declare function dbExists(cwd?: string): boolean; /** * Get the underlying `node:sqlite` `DatabaseSync` for the tasks domain. * Useful for direct PRAGMA calls or raw SQL operations. * * ## E6-L13 (T12037) — now path-keyed * * This used to return a module-global that tracked whichever project was * opened LAST. It now resolves the binding for `cwd`, so two projects live in * one process without clobbering each other. Returns `null` when the tasks * domain has not been bound for that project yet (or its connection has since * closed) — callers must `await` {@link getDb} / {@link bindTasksDomain} first, * which every existing call site already does. * * @param cwd - Project working directory. Defaults to the ambient project. * * @deprecated Read `.native` from {@link bindTasksDomain} instead — it cannot * return `null` and needs no prior await. Removed in T12040 (E6-L16). */ export declare function getNativeDb(cwd?: string): DatabaseSync | null; /** * Get the underlying `node:sqlite` `DatabaseSync` for the tasks domain. * * Thin alias of {@link getNativeDb} retained for call-site clarity in code * that predates the consolidated `cleo.db`. * * @param cwd - Project working directory. Defaults to the ambient project. * * @deprecated See {@link getNativeDb}. */ export declare function getNativeTasksDb(cwd?: string): DatabaseSync | null; export type { NodeSQLiteDatabase }; /** * Re-export schema for external use. */ export { schema }; /** Upper bound {@link closeAllDatabases} waits for tracked background writes (T13630). */ export declare const CLOSE_SETTLE_BUDGET_MS = 5000; /** * Close ALL database singletons (tasks.db, brain.db, nexus.db). * * Must be called before deleting temp directories on Windows, where * SQLite holds exclusive file handles on .db, .db-wal, and .db-shm files. * Safe to call even if some databases were never opened. * * @task T5508 */ export declare function closeAllDatabases(): Promise; //# sourceMappingURL=sqlite.d.ts.map