/** * Foreign-key integrity verification for the consolidated dual-scope * `cleo.db` files. * * ## Why this exists (E6-L10 · T11530) * * The E6 cutover consolidated eight standalone databases into two files * (project `cleo.db` and global `cleo.db`) and re-pointed every cross-domain * foreign key at the PREFIXED consolidated tables. Two properties must hold * afterwards and neither is self-evident: * * 1. **Referential integrity survived the move.** Exodus copies tables in * dependency order, but a partially-applied migration, a hand-edited row, * or a legacy DB restored from a pre-consolidation backup can leave an * orphan whose parent row never made the trip. * 2. **The FKs point at the right tables.** SQLite resolves a `REFERENCES` * clause lazily, so an FK naming a table that no longer exists is not an * error at DDL time — it fails only when a write touches it. A schema that * still references the retired bare `tasks` table looks fine until * something inserts. * * `PRAGMA foreign_key_check` answers both: it walks every FK in the schema and * returns one row per violation, including one whose `parent` names a table * that does not exist. So a structural defect and a row-level orphan surface * through the same path. * * The verification runs against the LIVE consolidated files, not a fixture — * this is the check that proves a real installation is sound. * * @packageDocumentation * @task T11530 (E6-L10) * @epic T11249 (E6) * @saga T11242 (SG-DB-SUBSTRATE-V2) */ import type { DatabaseSync } from 'node:sqlite'; import type { DualScope } from '../dual-scope-db.js'; /** * One referential-integrity violation. * * Mirrors a `PRAGMA foreign_key_check` row, plus the scope it was found in so * a combined project+global report stays attributable. * * @task T11530 */ export interface ForeignKeyViolation { /** Which consolidated `cleo.db` the violation was found in. */ readonly scope: DualScope; /** The child table holding the orphan row. */ readonly table: string; /** * `rowid` of the orphan row, or `null` when the violation is structural * (the referenced parent TABLE does not exist, so there is no single row). */ readonly rowid: number | null; /** The parent table the foreign key references. */ readonly parent: string; /** * Index of the failing foreign key within the child table's FK list, as * reported by `PRAGMA foreign_key_list`. `-1` for structural violations. */ readonly fkid: number; } /** * Result of verifying one or both consolidated scopes. * * @task T11530 */ export interface ForeignKeyIntegrityReport { /** `true` when {@link violations} is empty. */ readonly ok: boolean; /** The scopes actually checked. */ readonly scopes: readonly DualScope[]; /** Every violation found, in scope order. */ readonly violations: readonly ForeignKeyViolation[]; } /** * Run `PRAGMA foreign_key_check` against one open connection. * * @param native - An open consolidated `cleo.db` connection. * @param scope - The scope label to attribute violations to. * @returns Every violation the pragma reported. */ export declare function checkForeignKeys(native: DatabaseSync, scope: DualScope): readonly ForeignKeyViolation[]; /** * Verify foreign-key integrity across the consolidated dual-scope databases. * * Opens each requested scope through the ProjectStore/GlobalStore ports — the * same chokepoint the runtime uses — so the check runs against exactly the * connection (and therefore the schema and pragmas) production code sees. * * The `establish` callbacks are deliberately inert: this is a read-only probe * and must not reconcile or migrate anything as a side effect of checking. * * @param options - Which scopes to check and which project. * @returns A report; `ok` is `true` only when ZERO violations were found. * * @example * ```ts * const report = await verifyForeignKeyIntegrity({ cwd: projectRoot }); * if (!report.ok) { * for (const v of report.violations) { * console.error(`${v.scope}: ${v.table} row ${v.rowid} → missing ${v.parent}`); * } * } * ``` * * @task T11530 */ export declare function verifyForeignKeyIntegrity(options?: { /** Project working directory. Defaults to the ambient project. */ readonly cwd?: string; /** Scopes to check. Defaults to BOTH — the AC requires project AND global. */ readonly scopes?: readonly DualScope[]; }): Promise; /** * Outcome of a repair pass. * * @task T11530 */ export interface ForeignKeyRepairReport { /** Violations found before the repair ran. */ readonly found: readonly ForeignKeyViolation[]; /** Violations the repair deleted (parent FK declared `ON DELETE CASCADE`). */ readonly repaired: readonly ForeignKeyViolation[]; /** * Violations left in place because the schema does NOT authorise deleting * the child — the FK's `ON DELETE` is not `CASCADE`, so removing the row is * a judgement call an operator must make. */ readonly skipped: readonly ForeignKeyViolation[]; /** `true` when a re-check after the repair reported zero violations. */ readonly ok: boolean; } /** * Delete orphan rows whose foreign key declares `ON DELETE CASCADE`. * * These rows are the residue of a parent deletion that happened while FK * enforcement was off — most commonly a pre-consolidation legacy database that * exodus then copied verbatim. The schema already says they should not exist; * this restores that invariant. * * Rows whose FK is NOT `CASCADE` are reported in `skipped` and left untouched. * * @param options - Which scopes to repair and which project. * @returns What was found, repaired, and deliberately skipped. * * @task T11530 */ export declare function repairForeignKeyViolations(options?: { /** Project working directory. Defaults to the ambient project. */ readonly cwd?: string; /** Scopes to repair. Defaults to BOTH. */ readonly scopes?: readonly DualScope[]; }): Promise; //# sourceMappingURL=fk-integrity.d.ts.map