/** * The rebind reconcile (journal spec §1.5 N7; T12763, T13278). * * After a rebind, what the old replica changed and the stream never saw is * `inherited` (`inherit.ts`): live captures, sealed transactions, and the * transactions of segments it never pushed. The new replica re-emits those * fields, deciding each one by the three-way rule * ({@link decideReconcileField}): * * 1. changed by an inherited capture or an inherited sealed-but-unpushed op: * emit it at `tick(at_ms)` of that change, under the new replica; * 2. untouched, and the local field HLC is below the merged one: adopt the * merged value locally, emitting nothing; * 3. otherwise (a local HLC at or above the merged one that no inherited * change explains): emit it with that HLC unchanged. * * These are `repair` transactions with `via: 'rebind'`, and they break the * per-replica monotonic-HLC promise on purpose (§1.3, K10). * * {@link reconcileInPlace} is the reconcile of a rebind of the SAME file * after a pull to head (the undo-budget rebind, D5): the store itself is the * merged state, since the pull applied the stream and rebased the old * replica's unsequenced writes over it. Every field no inherited change * touched therefore already equals the merged value (rule 2 has nothing left * to adopt and rule 3 nothing to explain), and every touched field is rule 1. * It writes the touched rows as the captures of one `rebind` frame, read * from the live rows (the current value, which is what every replica must * converge on, whichever side the rebase let win), at the time of the last * inherited change of each row; the sealer seals the frame as one `repair` * transaction, `via: 'rebind'`, ticking each op at its row's time. A copy's * reconcile against a restored checkpoint (rules 2 and 3 over a scratch * store) is a separate pass. * * @task T12763 * @task T13278 * @module store/sync/reconcile */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; /** What the three-way rule does with one differing field (§1.5 N7). */ export type ReconcileRule = 'emit-tick' | 'adopt-merged' | 'emit-pinned'; /** The facts the three-way rule decides one field by. */ export interface ReconcileField { /** An inherited capture, or an inherited sealed-but-unpushed op, changed the field. */ readonly touchedByInherited: boolean; /** The field's HLC in this store's row meta, or null when the store has no meta for it. */ readonly localHlc: string | null; /** The field's HLC in the merged state (checkpoint plus stream to head). */ readonly mergedHlc: string; } /** * The three-way rule for one field that differs from the merged state. * Pure. * * @param f - Whether an inherited change touched it, and both HLCs. * @returns `emit-tick` (rule 1), `adopt-merged` (rule 2) or `emit-pinned` (rule 3). */ export declare function decideReconcileField(f: ReconcileField): ReconcileRule; /** One row an inherited change touched, under its current uid. */ export interface TouchedRow { readonly tbl: string; uid: string; /** The stream never saw the row: an inherited change inserted it. */ inserted: boolean; /** An inherited change deleted it. */ deleted: boolean; /** Columns an inherited U changed. */ readonly cols: Set; /** The time of its last inherited change (ms). */ atMs: number; } /** A re-key the stream never saw, re-emitted ahead of the row changes. */ export interface Rekey { readonly tbl: string; readonly rk: string; readonly oldUid: string; readonly img: string; readonly atMs: number; } /** What {@link reconcileInPlace} did. */ export interface ReconcileReport { /** The `rebind` frame the re-emitted rows were captured in, or null when nothing was touched. */ readonly frame: string | null; /** Rows re-emitted as I, U and D, and re-keys re-emitted. */ readonly inserts: number; readonly updates: number; readonly deletes: number; readonly rekeys: number; /** Inherited captures consumed. */ readonly captures: number; /** Inherited transactions whose ops were read. */ readonly txns: number; } /** * Re-emit what the previous replica changed and the stream never saw, as the * captures of one `rebind` frame, in the caller's (rebind) transaction * (module docs). Afterwards: * - the inherited captures are consumed (deleted): their changes now travel * in the frame; * - the ledger forgets the row-count effect of the previous replica's * inherited sealed transactions, which the frame's I and D ops count again * when they seal; * - while undo is on, each capture gets its undo row (a reconcile repair * keeps its undo until its echo, D1). * * @param db - The store, inside the rebind transaction. * @param o - Scope and the previous (retired) replica. * @returns What was re-emitted. */ export declare function reconcileInPlace(db: DatabaseSync, o: { readonly scope: TableScope; readonly previousReplica: string; }): ReconcileReport; /** What the previous replica changed and the stream never saw ({@link gatherInherited}). */ export interface InheritedChanges { /** Touched rows, keyed by {@link touchedRowKey}. */ readonly rows: ReadonlyMap; readonly rekeys: readonly Rekey[]; /** Row-count effect of the inherited sealed transactions, per table. */ readonly ledger: ReadonlyMap; /** Inherited captures read. */ readonly captures: number; /** Inherited transactions whose ops were read. */ readonly txns: number; } /** The key of a touched row in {@link InheritedChanges.rows}. */ export declare const touchedRowKey: (tbl: string, uid: string) => string; /** * Read what the previous replica changed and the stream never saw (rule 1 * of §1.5 N7): its inherited sealed transactions, in commit order, then the * inherited captures, in capture order. Read-only. * * @param db - The store. * @param scope - The store's scope. * @param previousReplica - The retired replica whose inherited transactions * count, or null for every replica's (a store rebound more than once * before it reconciled). * @returns The touched rows, the re-keys and the inherited row counts. */ export declare function gatherInherited(db: DatabaseSync, scope: TableScope, previousReplica: string | null): InheritedChanges; /** * A field the copy reconcile publishes with the HLC the original replica * issued (rule 3 of §1.5 N7): the capture carries `pin_json` and the sealer * uses `h` as the op's HLC and `fh` as its field HLCs instead of ticking. */ export interface PinnedEmit { readonly tbl: string; readonly uid: string; /** `I` the whole live row, `U` the `cols` of the live row, `D` a row gone here. */ readonly op: 'I' | 'U' | 'D'; readonly cols?: ReadonlySet; readonly pin: { readonly h?: string; readonly fh?: Readonly>; }; /** The capture time (ms). */ readonly atMs: number; } /** * Write one `rebind` frame (module docs): the inherited re-keys, every * touched row from its live state (rule 1), then the `pinned` emits (rule 3, * the copy reconcile), and consume what was gathered: the inherited * captures are deleted and the ledger forgets the inherited sealed row * counts. In the caller's transaction. * * @param db - The store, inside a transaction. * @param scope - The store's scope. * @param gathered - {@link gatherInherited}'s result. * @param pinned - Rule-3 emits; empty for the rebind at head. * @returns What was re-emitted. */ export declare function emitRebindFrame(db: DatabaseSync, scope: TableScope, gathered: InheritedChanges, pinned: readonly PinnedEmit[]): ReconcileReport; //# sourceMappingURL=reconcile.d.ts.map