/** * The conflict log (T12344; journal spec §3.2 "conflict records"). * * Every conflict an apply decides — a typed rule refusing or overriding a * write, an edit of a deleted row, a delete over newer edits, a concurrent * divergent edit of one field — is a row of `_sync_conflict`, written in the * apply's own transaction. Nothing is silently dropped: `cleo cloud * conflicts` lists the open ones, and a resolution (an ordinary write in a * `write` frame) marks them resolved. * * @module store/sync/conflicts * @task T12344 */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; import { type LedgerOp, type LedgerValue, type LedgerWireValue } from '@cleocode/contracts/ledger'; import type { InboxKey } from './inbox.js'; import type { MergeConflict } from './merge/types.js'; import { type WireValue } from './sealer-values.js'; /** Where a conflict arose: the inbox transaction and the op in it. */ export interface ConflictSite extends InboxKey { /** Index of the op in the (joined) transaction. */ readonly opIdx: number; } /** One stored conflict. */ export interface ConflictRecord extends ConflictSite { readonly id: number; /** A {@link MergeConflict} kind, or a later kind (`dangling-ref`, `guard` …). */ readonly kind: string; readonly table: string; readonly uid: string; readonly columns: readonly string[]; readonly rule: string | null; readonly resolution: string; readonly opHlc: string; readonly localHlc: string | null; /** The replica that wrote the op. */ readonly origin: string; readonly createdAt: string; readonly resolvedAt: string | null; /** * This store's pre-merge values of the conflicting, non-secret columns * (references as uids), or null when unknown (T13624). */ readonly local: Readonly> | null; } /** * Record the conflicts of one op. * * @param db - The store, inside the apply frame's transaction. * @param site - The inbox transaction and op index. * @param conflicts - What the merge decided. * @param origin - The op's origin replica. * @param nowIso - The time. * @param local - This store's pre-merge non-secret values of the row, kept for * each conflict's columns (T13624); omitted when unknown. */ export declare function recordConflicts(db: DatabaseSync, site: ConflictSite, conflicts: readonly MergeConflict[], origin: string, nowIso: string, local?: Readonly> | null): void; /** * Record a conflict of one op unless that op already has one of its kind. * For a transaction re-planned on every apply call while it stays pending * (a held uid collision, T13394), so the hold is listed once, not per pass. * * @param db - The store, inside the apply frame's transaction. * @param site - The inbox transaction and op index. * @param conflict - The conflict. * @param origin - The op's origin replica. * @param nowIso - The time. * @returns Whether it was recorded now. */ export declare function recordConflictOnce(db: DatabaseSync, site: ConflictSite, conflict: MergeConflict, origin: string, nowIso: string): boolean; /** * The recorded conflicts, oldest first. * * @param db - The store. * @param opts - `open` lists only unresolved ones; `stream` narrows to one stream. * @returns The conflicts. */ export declare function listConflicts(db: DatabaseSync, opts?: { readonly open?: boolean; readonly stream?: string; }): ConflictRecord[]; /** * Mark an open conflict resolved (its resolution, an ordinary write, has been * made or the conflict was reviewed and accepted). * * @param db - The store. * @param id - The conflict id. * @param nowIso - The time. * @returns Whether an open conflict had that id. */ export declare function resolveConflict(db: DatabaseSync, id: number, nowIso: string): boolean; /** * Mark the held-collision conflicts of one inbox transaction resolved, once * it has gone through (T13397): the uid collision settled by the loser's * re-key, or the local key freed. Other conflicts stay open. * * @param db - The store, inside the apply frame's transaction. * @param key - The inbox transaction. * @param nowIso - The time. * @returns How many were resolved. */ export declare function resolveHeldConflicts(db: DatabaseSync, key: InboxKey, nowIso: string): number; /** * How many conflicts the store holds, or one stream of it. * * @param db - The store. * @param stream - Only this stream's conflicts, when given. * @returns Open (unresolved) and total counts. */ export declare function conflictCounts(db: DatabaseSync, stream?: string): { open: number; total: number; }; /** One conflicting column, both sides (T12997). */ export interface ConflictFieldValues { readonly column: string; /** * The incoming op's value (a reference as the target's local key when this * store has it); null when the op did not set it or is no longer in the inbox. */ readonly theirs: LedgerValue | null; /** This store's current value; null when the row is gone. */ readonly mine: WireValue | null; /** A secret column: neither value is shown. */ readonly withheld: boolean; } /** What a conflict is about, for `cleo cloud conflicts` (T12997). */ export interface ConflictDetail { /** The row's human id (a task id, a doc slug …) from its declared key, or null. */ readonly humanId: string | null; /** The Nexus device that wrote the op; null once its inbox row is pruned. */ readonly deviceId: string | null; /** Whether the incoming op is still in the inbox (applied rows are pruned after a checkpoint). */ readonly opKnown: boolean; readonly fields: readonly ConflictFieldValues[]; } /** * The op a conflict was recorded for (its split parts joined) and the Nexus * device that wrote it, from the inbox; both absent once the inbox row is * pruned after a checkpoint. * * @param db - The store. * @param c - The conflict. * @returns The op and the device id. */ export declare function conflictIncomingOp(db: DatabaseSync, c: ConflictRecord): { op: LedgerOp | undefined; deviceId: string | null; }; /** * Describe one recorded conflict: the row's human id, the device that wrote * the incoming op, and each conflicting column's incoming and current * values (secret columns withheld). Read-only. * * @param db - The store. * @param scope - The store's scope. * @param c - The conflict. * @returns The detail. */ export declare function conflictDetail(db: DatabaseSync, scope: TableScope, c: ConflictRecord): ConflictDetail; //# sourceMappingURL=conflicts.d.ts.map