/** * Held rows (journal spec §3.5 Rule 5; T13193 R-3, T13269). * * A local op a scoped rebase cannot replay stays rewound and is held until * its echo is decided: the stream refused it (a dangling reference, a typed * rule, a guard, a post-apply check), so it is sent and sequenced as usual, * and the stream applies or voids it. While held: * * - its row meta carries `held = 1`, so the ledger check, the repair diff * and the Gate D manifest skip the row (no spurious repair D for a held * insert, whose meta has no live row); * - `_sync_ledger` moves by the op's row-count effect, in both `live` and * `held`, so `live = count(*) + held` keeps holding; * - its row undo records when, why (the conflict preview `cleo show` returns * with `E_SYNC_HELD`), and, for an insert, what the rewind kept that the * stream cannot bring back (local-only columns, FK children outside the * sync set), restored if the insert applies later. * * A rebase that rewinds a held op lifts the hold first and decides again. * A held transaction's echo never takes the own-echo fast path, so its echo * is always decided in a rebase, whose rewind lifts the hold. * * @task T13193 * @module store/sync/held */ import type { DatabaseSync, SQLInputValue } from 'node:sqlite'; import { LedgerOp, type LedgerWireValue } from '@cleocode/contracts/ledger'; import type { LAFSError } from '@cleocode/lafs'; import { CleoError } from '../../errors.js'; import type { LocalDescendants } from './apply/write-api.js'; import { type RowMetaFull } from './row-meta.js'; /** What a rewound insert kept that the stream cannot bring back (R7-2). */ export interface KeptRow { /** Its local-only columns, or null when the row had none to keep. */ readonly local: Record | null; /** Its FK children outside the sync set, removed by the rewind's DELETE. */ readonly descendants: LocalDescendants; } /** One op to hold. */ export interface HoldInput { readonly txn: string; readonly idx: number; readonly op: LedgerOp; /** Whether the op's row exists now (after the rewind). */ readonly exists: boolean; /** What refused the replay (the conflict preview). */ readonly reason: string; /** The row's meta as the op left it, before the rewind (an insert's meta). */ readonly afterMeta: RowMetaFull | null; readonly kept: KeptRow | null; readonly nowIso: string; } /** A held op, as its row undo records it. */ export interface HeldOp { readonly txn: string; readonly idx: number; readonly tbl: string; readonly uid: string; readonly at: string; readonly reason: string; /** The row-count effect the ledger moved by while held. */ readonly effect: number; readonly kept: KeptRow | null; } /** * Account a rebase's own row-count change to `table` (a rewind's delete or * re-insert, a replay's insert or delete). Rewind and replay write with * capture suspended, so the sealer never sees them; without this the ledger * would drift by every rewound insert whose echo then applies. * * @param db - The store, inside the rebase frame. * @param table - Sync-set table. * @param delta - Rows added (positive) or removed (negative). */ export declare function accountRebaseRows(db: DatabaseSync, table: string, delta: number): void; /** * Hold an op the replay left rewound: mark its row meta held (creating it * from the op's after-meta for an insert, whose rewind removed it), move the * ledger by its row-count effect, and record the hold on its row undo. * * @param db - The store, inside the rebase frame. * @param h - The op and why it is held. */ export declare function holdOp(db: DatabaseSync, h: HoldInput): void; /** * The hold on a local op, or null when it is not held. * * @param db - The store. * @param txn - The local transaction. * @param idx - The op's index. */ export declare function heldOp(db: DatabaseSync, txn: string, idx: number): HeldOp | null; /** * Lift the hold on a local op: reverse its ledger effect, clear `held` in * its row meta, and clear the hold (what it kept stays, for a later apply). * * @param db - The store, inside the frame. * @param txn - The local transaction. * @param idx - The op's index. */ export declare function unholdOp(db: DatabaseSync, txn: string, idx: number): void; /** * Whether a local transaction has a held op. * * @param db - The store. * @param txn - The local transaction. */ export declare function txnHasHold(db: DatabaseSync, txn: string): boolean; /** A held insert, as `cleo show` reports it. */ export interface HeldInsert { readonly table: string; readonly uid: string; readonly txn: string; readonly heldAt: string; /** The conflict preview: what refused the replay. */ readonly reason: string; /** The values the insert would write (its sealed after-image, references as uids). */ readonly values: Readonly>; } /** * Every held op, oldest first (for `cleo sync status` and `cleo doctor`). * * @param db - The store. * @returns The holds, or none when the journal has no row undo. */ export declare function listHeldOps(db: DatabaseSync): HeldOp[]; /** A hold older than this many days is a long hold `cleo doctor sync-journal` lists (§3.5 Rule 5). */ export declare const HELD_WARN_DAYS = 7; /** A held op as the status and doctor surfaces report it (no kept snapshot). */ export interface HeldSummary { readonly txn: string; readonly idx: number; readonly table: string; readonly uid: string; readonly heldAt: string; /** What refused the replay (the conflict preview). */ readonly reason: string; } /** Every hold in a store, and those older than the warn age. */ export interface HoldsReport { readonly total: number; /** The oldest hold's time, or null when none. */ readonly oldestAt: string | null; /** Holds older than {@link HoldsReport.warnDays}, oldest first. */ readonly long: readonly HeldSummary[]; readonly warnDays: number; } /** * The store's holds for `cleo cloud status` and `cleo doctor sync-journal` * (§3.5 Rule 5): how many, the oldest, and the long ones with their reason. * * @param db - The store (a read-only snapshot is fine). * @param nowMs - The current time (epoch ms). * @param warnDays - The long-hold age. @defaultValue HELD_WARN_DAYS * @returns The report; empty when the journal has no row undo. */ export declare function holdsReport(db: DatabaseSync, nowMs: number, warnDays?: number): HoldsReport; /** * The held insert into `table` whose sealed values carry `column = value` * (a held row is absent, so `cleo show` finds it here), or null. * * @param db - The store. * @param table - Sync-set table. * @param column - A column of the insert's after-image (for tasks, `id`). * @param value - The value it must carry. * @returns The held insert, or null. */ export declare function findHeldInsert(db: DatabaseSync, table: string, column: string, value: string): HeldInsert | null; /** * The held insert of task `id` in this project's store, or null (also when * the store cannot be read: the caller's not-found stands). * * @param id - The task id (`T…`). * @param cwd - The project directory. * @returns The held insert, or null. */ export declare function findHeldTask(id: string, cwd?: string): Promise; /** * `cleo show` of a row a rebase holds (§3.5 Rule 5): not `E_NOT_FOUND`, but * `E_SYNC_HELD`, with the held values and the conflict preview. The insert * is sent and the stream decides it: applied (its parent came back) or * voided with a visible conflict. */ export declare class SyncHeldError extends CleoError { /** Stable string error code for envelope `codeName` / log correlation. */ readonly codeName: "E_SYNC_HELD"; /** The held insert. */ readonly held: HeldInsert; /** * @param id - The id the caller asked for. * @param held - The held insert. */ constructor(id: string, held: HeldInsert); /** The LAFS error, coded `E_SYNC_HELD`. */ toLAFSError(): LAFSError; } //# sourceMappingURL=held.d.ts.map