/** * The bounded `fk_orphans` repair (journal spec §3.5 Rule 4 (a)(3), §2.3a * rule 2; C3 T12821, D3 T12826; T12986, S3c). * * An orphan is a sync-set row whose foreign key names a parent row this store * does not have. The repair never deletes user data (C3), and it first asks * whether the orphan is only LOCAL damage, by looking the missing parent up * in the merged stream state (D3): * * - **live elsewhere**: the parent is restored locally (checkpoint bundle or * its latest ops), with capture suspended, so nothing is emitted: every * other replica already agrees. No conflict. * - **tombstoned**: a `delete-with-live-children` conflict naming the * children; nothing changes. * - **unknown** (never seen): re-parent to the deterministic sentinel where * the column defines one, else NULL where the column is nullable, else * report. A conflict record is written in every case. The writes are an * ordinary `repair` frame: captured, sealed, sent, so every replica * converges. * * Before `row_identity_synced` exists there is no merged state: the repair is * report-only and writes nothing (D3(d)). * * The merged-state lookup, the restore and the conflict store belong to S4/S5 * and T12344, so they are injected ({@link FkOrphanRepairSeams}). * * The sentinel (D3(a)(b)): the parent-type matrix has no single task type * that may parent every other (saga→epic, epic→task|subtask, task→subtask), * so the sentinel is a root-level `epic` and parents orphaned tasks and * subtasks; an orphaned epic, saga or other type becomes a root (NULL), which * the matrix allows. Its uid is the natural uid over * `(project, tasks_tasks, 'fk-orphan-sentinel')`, its display id and every * column are constants, so replicas that create it independently write the * same row. * * @module store/sync/fk-orphans * @task T12986 * @epic T12323 */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; /** A local key value (SQLite TEXT or INTEGER). */ export type KeyValue = string | number; /** One orphaned row: a foreign key naming a parent this store lacks. */ export interface FkOrphan { /** The child table. */ readonly table: string; readonly rowid: number | null; /** The child's uid, when its table has one. */ readonly uid: string | null; /** The child's foreign-key column. */ readonly column: string; readonly parentTable: string; readonly parentColumn: string; /** The missing parent's local key. */ readonly parentKey: KeyValue; } /** * Every single-column foreign-key violation in the store, read-only: the * doctor's `fk_orphans` finding. Composite keys and WITHOUT ROWID children * are listed with `rowid: null` where SQLite gives none. */ export declare function findFkOrphans(db: DatabaseSync): FkOrphan[]; /** Display id of the tasks sentinel (matches `T\d{3,}`; allocation never reaches 0). */ export declare const FK_ORPHAN_SENTINEL_ID = "T000"; /** The tasks sentinel's uid: the natural uid over (project, tasks_tasks, 'fk-orphan-sentinel'). */ export declare const FK_ORPHAN_SENTINEL_UID: string; /** The missing parent in the merged stream state. */ export type ParentState = { readonly state: 'live'; readonly uid: string; } | { readonly state: 'tombstoned'; readonly uid: string; } | { readonly state: 'unknown'; }; /** A conflict record for the owner (T12344's store backs it). */ export interface FkOrphanConflict { readonly kind: 'fk-orphan' | 'delete-with-live-children'; readonly parent: { readonly table: string; readonly column: string; readonly key: KeyValue; readonly uid: string | null; }; readonly children: ReadonlyArray<{ readonly table: string; readonly column: string; readonly uid: string | null; }>; /** What the repair did: re-parented to the sentinel, NULLed, reported, or nothing. */ readonly resolution: 'sentinel' | 'null' | 'report' | 'none'; } /** The stream-side services the repair needs (S4/S5, T12344). */ export interface FkOrphanRepairSeams { /** The parent in the merged state: row meta, tombstones, folds, latest verified checkpoint. */ readonly parentState: (table: string, column: string, key: KeyValue) => ParentState; /** * Restore a parent that is live elsewhere, from the checkpoint bundle or a * pull of its latest ops. Called inside a transaction with capture * suspended, so the restore emits nothing. */ readonly restoreParent: (db: DatabaseSync, table: string, uid: string) => void; /** * Persist a conflict record. For a repair that writes (sentinel, NULL), * it is called inside the repair's transaction, before the commit: a throw * rolls the repair back, so no repair lands without its record (D3, * T13044). It must not open its own transaction. */ readonly recordConflict: (conflict: FkOrphanConflict) => void; } /** What happened to one orphan. */ export type FkOrphanOutcome = 'restored' | 'conflict' | 'sentinel' | 'nulled' | 'reported'; /** What {@link repairFkOrphans} did. */ export interface FkOrphanRepairReport { /** `report-only` before `row_identity_synced` exists (nothing written). */ readonly mode: 'report-only' | 'repair'; readonly orphans: ReadonlyArray; readonly sentinelCreated: boolean; } /** * Repair up to `budget` sync-set orphans (C3, D3). Never deletes a row. * * Orphans of tables outside the sync set, or whose parent is outside it, are * reported only: no stream state describes them. */ export declare function repairFkOrphans(db: DatabaseSync, scope: TableScope, seams: FkOrphanRepairSeams, opts?: { readonly budget?: number; }): FkOrphanRepairReport; //# sourceMappingURL=fk-orphans.d.ts.map