/** * Periodic cleanup scanner for the User_Input projection. * * SQLite is the authority and User_Input is the human input surface; when * that surface is corrupted (duplicated business keys or anchors, empty-ID * rows, orphan rows) the scan rewrites the tab from SQLite canonical state: * bound rows get full-row candidate_reconcile rewrites carrying canonical * values, and every surplus row (duplicates beyond the kept row, empty-ID * rows, orphans) gets a `user_input_delete` effect carrying the full observed * row as its compare-and-set guard. Bound-row corrections stream under the * binding key (`projection-row::`) shared with flush * projections and resolution reconciles, so the resolution's * supersede-and-replan covers them; unbound rows keep the physical anchor as * their stream key. A binding with an OPEN/NEEDS_REBASE conflict (durable * active candidate pointer) is never planned: the candidate evidence is * re-read after the snapshot and again before effect building, and the * conflicted row converges exclusively through resolution. All corrections * flow through the durable outbox and the effect worker's CAS-guarded slow * path, so cleanup never mutates the Sheet directly and can never touch a * row that a human or candidate pipeline changed concurrently. * * Duplicated-anchor groups are converged one row per scan because the real * provider only resolves the first row per anchor value; the group's rewrite * is deferred until the group is down to one row. Scope mirrors the * System_State reconciliation scanner: one snapshot read, one fenced writer * lease, and corrections that flow through the durable outbox. A re-scan of a * converged tab enqueues nothing. */ import type { SqlStorageAdapter } from "../../../../contracts/storage/sql.js"; import { type SyncSheetsProvider } from "../../../../contracts/sheets/syncSheets.js"; import { type ReconciliationIdFactory } from "./shared.js"; /** Construction options for a single User_Input cleanup scan. */ export interface RunCleanupScanOptions { readonly storage: SqlStorageAdapter; readonly provider: SyncSheetsProvider; /** Physical sheet id of the User_Input projection to clean. */ readonly physicalSheetId: string; /** Logical sheet id owning the row bindings and evidence. */ readonly logicalSheetId: string; /** * Business-key header used to detect duplicated and orphan identities on * the User_Input tab. Must be one of the tab's headers. */ readonly identityField: string; /** Schema version shared by every cleanup effect produced here. */ readonly schemaVersion: number; /** Reconciler writer identity. */ readonly writerId: string; /** Injectable clock and id source for deterministic tests. */ readonly now?: () => number; readonly createId?: ReconciliationIdFactory; /** Override the reconciler lease role or duration. */ readonly writerRole?: string; readonly leaseDurationMs?: number; /** Observability hook invoked once after the scan settles. */ readonly onReport?: (report: CleanupScanReport) => void; } /** Observable outcome of one User_Input cleanup scan. */ export interface CleanupScanReport { readonly physicalSheetId: string; readonly rowsScanned: number; readonly duplicateRows: number; readonly emptyIdRows: number; readonly extraRows: number; /** Rows rewritten from SQLite canonical state (full-row overwrite). */ readonly rewrittenRows: number; readonly effectsEnqueued: number; readonly fenceClaimed: boolean; } /** * Runs one cleanup scan and enqueues CAS-carrying delete effects for every * surplus row. The scan never writes to the Sheet directly. */ export declare function runUserInputCleanupScan(options: RunCleanupScanOptions): Promise; //# sourceMappingURL=CleanupScanner.d.ts.map