/** * The origin settles a uid collision it lost (T13397; T12341 §6.4 "Re-key", * §9.2 origin rule). * * Apply holds an incoming row whose uid a local row holds with another birth * fingerprint (T13394) and records a `uid-collision` conflict naming the * loser, the greater fingerprint. When the local row is the loser and this * replica authored it, this replica is the authority that re-keys it: the row * gets a fresh uid in a local `rekey` frame, so the capture triggers journal * the K (and the cascade's K ops) and the next push publishes it. The held * winner then places under the freed uid on the next pass, and every receiver * holding the loser follows the K through its alias (store/sync/uid-alias). * * A loser this replica only received (its origin is elsewhere) is never * re-keyed here: it waits for its origin's K, which moves it like any re-key. * * @module store/sync/collision-settle * @task T13397 * @task T13405 */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; import { type CaptureTableDef } from './capture.js'; import { type StagedTxn } from './inbox.js'; /** What {@link settleLostUidCollisions} needs. */ export interface SettleOptions { readonly scope: TableScope; readonly stream: string; /** This replica's id. */ readonly replica: string; } /** One re-key this replica made as the loser's origin. */ export interface SettledCollision { readonly table: string; readonly oldUid: string; readonly newUid: string; /** The loser's birth fingerprint (it keeps it). */ readonly birthFp: string; } /** * Re-key every row this replica lost a uid collision with and authored * (module doc). Each re-key is its own local transaction in a `rekey` frame; * the caller seals and applies again. * * @param db - The store; must not be inside a transaction. * @param opts - Scope, stream and this replica. * @returns The re-keys made. */ export declare function settleLostUidCollisions(db: DatabaseSync, opts: SettleOptions): SettledCollision[]; /** * Re-mint the counter key of every local row that lost a key collision and * that this replica authored (T13405, T13431). Apply holds an incoming insert * whose counter key (`T####`, `D###`, `SN-###`) a live row of another uid holds * here (`key-collision`); the greater birth fingerprint loses, as for uids. * Its origin gives it the next free key in a local `remint` frame, so the * held insert places on the next pass and every receiver folds the rename * into the insert it holds ({@link foldDisplayRemints}). * * Driven by the open conflicts, not by the uid re-key: a re-mint that fails * (a kill, `SQLITE_BUSY`) leaves the conflict open, and the next apply re-mints * it. A uid collision's K commits first, so receivers record its alias before * they meet the rename. * * @param db - The store; must not be inside a transaction. * @param opts - Scope, stream and this replica. * @returns The re-mints made. */ export declare function remintLostKeyCollisions(db: DatabaseSync, opts: SettleOptions): DisplayRemint[]; /** A decision re-mint this replica applied to a row it had placed (T13433). */ export interface DecisionKeyMove { readonly uid: string; readonly from: string; readonly to: string; } /** * Re-point this replica's own text references to decisions another origin * re-minted after this replica had placed them (T13433): evidence atoms and * page edges it wrote with `decision:` meant the loser, while the old key * now names the winner everywhere. A local `remint` frame, so the rewrite * propagates; a move the apply rolled back (the row no longer has the new key) * is skipped. The caller seals. * * shortcut: the moves are collected in memory during one apply call, so a crash * between that apply's commit and this frame drops them; persist them if that * window matters. * * @param db - The store; must not be inside a transaction. * @param opts - Scope, stream and this replica. * @param moves - Decision key moves applied in this call. * @returns Rows rewritten. */ export declare function repointOwnDecisionText(db: DatabaseSync, opts: SettleOptions, moves: readonly DecisionKeyMove[]): number; /** * Announce every re-key this replica applied to a loser it had placed * (T13399). The loser's origin draws the boundary for its own references with * its K; a replica the origin's insert reached before the winner's may also * have written references to the old uid that meant the loser. Receivers only * read a writer's references through an alias that writer drew, so this * replica re-states the re-key under its own name: an alias-only K in a local * `rekey` frame (it moves nothing here). The caller seals. * * @param db - The store; must not be inside a transaction. * @param opts - Scope, stream and this replica. * @param nowIso - The time. * @returns The announcements captured. */ export declare function announcePlacedRekeys(db: DatabaseSync, opts: SettleOptions, nowIso: string): number; /** * The counter display-key column of `table`, or null when its key is not a * counter (T13405). * * @param table - A sync-set table. * @returns The key column, or null. */ export declare function displayKeyColumn(table: string): string | null; /** A re-mint the origin made. */ export interface DisplayRemint { readonly table: string; readonly uid: string; readonly fromKey: string; readonly toKey: string; /** Rows rewritten per `table.column`, the row itself included. */ readonly rewritten: Readonly>; } /** * ORIGIN ONLY: give the row (`table`, `uid`) a fresh counter key and point its * local references at it. Call inside the caller's transaction and capture * frame; it defers foreign keys for that transaction. * * @param db - The store. * @param scope - Its scope. * @param table - A table with a counter key ({@link displayKeyColumn}). * @param uid - The losing row's (new) uid. * @param birthFp - Its birth fingerprint. * @param opts - The stream (for staged keys) and this replica (the alias origin). * @returns The re-mint, or null when the row is gone. */ export declare function remintDisplayKey(db: DatabaseSync, scope: TableScope, table: string, uid: string, birthFp: string, opts: { readonly stream: string; readonly origin: string; }): DisplayRemint | null; /** * RECEIVER: fold an origin's re-mint into the insert it held (module doc). * An insert of a counter-keyed row whose key a live row of another uid holds * here takes the key of the LATEST later staged U by the same origin on the * same row (uid and birth fingerprint) that sets it. Nothing else changes; the * U still applies (and changes nothing). Pure over `staged`; reads the store. * * @param db - The store. * @param staged - Staged transactions in stream order (aliases followed). * @param defs - Capture definitions by table. * @returns `staged`, with folded inserts replaced. */ export declare function foldDisplayRemints(db: DatabaseSync, staged: readonly StagedTxn[], defs: (table: string) => CaptureTableDef | null): StagedTxn[]; /** * Local columns that reference `table` by `keyColumn` (T13405): every * sync-set table's declared reference to it, with that table's uid. * * @param db - The store. * @param defs - Capture definitions by table. * @param table - The re-keyed table. * @param keyColumn - Its key column. * @returns The referencing (table, column) pairs. */ export declare function displayKeyReferrers(db: DatabaseSync, defs: (table: string) => CaptureTableDef | null, table: string, keyColumn: string): Array<{ readonly table: string; readonly column: string; }>; /** * Point the columns of tables OUTSIDE the sync set that hold a moved task id * at its new id (T13405). They carry no capture and no uid (derived or * local-only tables such as the acceptance projections), so the write API * cannot reach them, and their foreign keys would otherwise fail at commit. * * @param db - The project store. * @param defs - Capture definitions by table (null outside the sync set). * @param fromId - The task's old display id. * @param toId - Its new display id. * @returns Rows rewritten per `table.column`. */ export declare function followLocalTaskRefs(db: DatabaseSync, defs: (table: string) => CaptureTableDef | null, fromId: string, toId: string): Record; //# sourceMappingURL=collision-settle.d.ts.map