/** * The pure merge engine (T12344; journal spec §1.7, §2.6, §2.9, §3.2, §3.6). * * {@link applyOp} decides one incoming I, U or D op against the stream-derived * state of its row and returns the next state, the row-level effect and the * conflicts to record. Receivers apply in stream `seq` order and decide by HLC * (§2.9), so every replica that folds the same stream reaches the same state: * * - **Per-field LWW by HLC.** A column's incoming HLC is `fh[col] ?? h`; it * wins when it is greater than the column's stored HLC. LWW is commutative, * so plain columns converge whatever the order. * - **Groups** merge as one LWW unit and travel whole (T13222): a U op * carries every member of a group it touches (the capture trigger records * the whole group, subtraction keeps it whole), an I op's omitted members * are NULL, and the winner sets every member. A U op carrying part of a * group is refused (`malformed`), never half-applied. * - **Counters** merge by delta (`sum`: a U carries `{ $inc }` only) or by * `max`/`min` (absolute numbers only), never by plain LWW; any other shape * is refused (`malformed`), since mixing deltas and absolute values would * make the result depend on order. * - **Rank-max** (pipeline_stage) is a max over (rank, HLC), with NULL and * unranked values lowest, and the winner keeps its own HLC; a restore * raises a floor below which writes are dead (T13223). It converges in * every order, HLCs included. * - **Other typed rules** (absorbing states, frozen-while, write-once) are * evaluated against the current state, so where an explicit op races them * they depend on stream order and are deterministic for a given stream * (§3.5 Rule 1). * - **Leave, restore and unfreeze are granted per transaction:** every op of * a transaction whose `actor.op` is such a command may use it, on every row * the transaction writes. That is deliberate: the command's own cascade * (children restored or reopened with their parent) is part of its * transaction. * - **Deletes:** a D removes a live row whatever its HLC, recording the newer * edits it removes. Tombstones never resurrect: an op older than the delete * is skipped; a newer update of a deleted row is voided with an * `edit-vs-delete` conflict. A newer insert re-creates the row (a * natural-key re-add), so a re-insert racing a delete is ORDER-SENSITIVE: * deterministic per stream (§3.5 Rule 1), not order-free, since the row * state keeps no birth HLC to compare the delete against. * - **Conflicts are never dropped:** a concurrent divergent edit (the op's * before-image differs from the current value) is recorded whichever side * wins, as is every typed-rule refusal or override, rank-max included. * - **Schema skew is refused** explicitly: an op naming a column this schema * lacks is `refused-schema`, never partly applied ({@link checkSchemaVersion} * covers the segment level). * * K ops are not field merges: the re-key path of the applier handles them. * * @module store/sync/merge/engine * @task T12344 */ import { type LedgerOp, type LedgerWireValue } from '@cleocode/contracts/ledger'; import type { FieldState, MergeContext, OpOutcome, RowState } from './types.js'; /** A K op reached the field merge, or a spec is inconsistent. */ export declare class MergeEngineError extends Error { readonly code = "E_SYNC_MERGE_ENGINE"; constructor(message: string); } /** Why a payload is refused before any op is applied. */ export interface SchemaRefusal { readonly code: 'E_SCHEMA_AHEAD'; readonly message: string; } /** * Refuse a segment or transaction written by a newer schema or format (§2.9): * a segment `schemaVersion` above {@link SYNC_SCHEMA_VERSION}, or a transaction * `v` above {@link LEDGER_TXN_VERSION}. The receiver stages it `refused-schema` * and replays it after an upgrade; it is never silently skipped. * * @param segmentSchemaVersion - The segment's `schemaVersion`. * @param txnVersion - The transaction's `v`, when checking one. * @returns The refusal, or `null` when this build can apply it. */ export declare function checkSchemaVersion(segmentSchemaVersion: number, txnVersion?: number): SchemaRefusal | null; /** One rank-max candidate write. */ export interface RankCandidate { readonly value: LedgerWireValue; readonly hlc: string; } /** * The alive, non-dominated candidates of a rank-max column after `write`, * oldest first (T13223). The current value is ALWAYS a candidate, even when * a stored frontier omits it (T13232): a local write changes the row without * the merge, and a frontier that dropped it would lose that write and * diverge from replicas that folded it. * * @param rule - The column's rank-max rule. * @param cur - The column's current state, or undefined. * @param write - The incoming write. * @param floor - The restore floor after the write, or undefined. * @returns The candidates; the best by (rank, HLC) is the column's value. */ export declare function rankMaxFrontier(rule: { readonly order: readonly string[]; }, cur: FieldState | undefined, write: RankCandidate, floor: string | undefined): RankCandidate[]; /** * Decide one incoming op against the stream-derived state of its row. * * @param row - The row's state at the op's stream position. * @param op - An I, U or D op (K ops go through the re-key path). * @param ctx - The table's merge spec and the transaction's `actor.op`. * @returns The next state, the row-level effect, written and skipped columns, * and every conflict to record. * @throws {MergeEngineError} On a K op or an inconsistent spec. * * @example * ```ts * const out = applyOp(row, op, { table: spec, actorOp: txn.actor?.op }); * if (out.status === 'refused-schema') stage('refused-schema'); * ``` */ export declare function applyOp(row: RowState, incoming: LedgerOp, ctx: MergeContext): OpOutcome; //# sourceMappingURL=engine.d.ts.map