/** * Apply intents and echo subtraction (journal spec §3.3; H1, N1; T12757). * * An apply frame writes remote ops into the store, and the capture triggers * capture those writes like any other. Sealing them as local writes would * echo every remote change back with fresh HLCs, which corrupts LWW. Exact * image matching under-drops: partial LWW, `$inc` applied as an absolute * value, re-encrypted secrets, remaps and normalisation all make the capture * differ from the incoming op. * * So the apply records what it actually wrote, per (frame, tbl, uid, col), * with the exact `enc()` of the stored value ({@link recordApplyIntents}), * and the sealer subtracts it from the frame's captures field by field * ({@link subtractApplyIntents}). What remains is residual: fields the apply * did not write, or wrote with a different value (a validator side effect, a * re-mint, a trigger cascade, an interleaved writer). Only the residual is * sealed, as local writes with new HLCs. * * Writing intents belongs to the apply API (T12344); the sealer half lives * here so the contract is fixed before apply lands. * * @task T12757 * @module store/sync/apply-intent */ import type { DatabaseSync } from 'node:sqlite'; /** `col` of the intent for a row insert. */ export declare const INTENT_INSERT = "*I"; /** `col` of the intent for a row delete. */ export declare const INTENT_DELETE = "*D"; /** `col` of the intent for a re-key; its `enc` is the new uid. */ export declare const INTENT_REKEY = "*K"; /** `enc` recorded for a secret column (the capture holds ``). */ export declare const SECRET_INTENT = ""; /** One thing an apply frame wrote. */ export interface ApplyIntent { /** Table written. */ readonly tbl: string; /** The row's uid (before a re-key, for `*K`). */ readonly uid: string; /** A column name, or {@link INTENT_INSERT} / {@link INTENT_DELETE} / {@link INTENT_REKEY}. */ readonly col: string; /** * `enc()` of the stored value, computed by the same SQL `enc()` as the * capture triggers (use `RETURNING`); {@link SECRET_INTENT} for a secret * column ({@link recordApplyIntents} binds it to the capture the write * produced); the new uid for `*K`; `''` for `*I` and `*D`. An insert * records `*I` plus one intent for EVERY column it wrote: a captured column * without an intent is treated as something the apply did not write. */ readonly enc: string; } /** * Columns a persistent trigger derives deterministically from a replicated * change, so every replica writes them identically when it applies the op * (§3.3). In an apply frame their residual changes are dropped rather than * sealed. Keyed by table. Empty until a trigger writing a sync-set column is * declared here. */ export declare const SYNC_TRIGGER_DERIVED_COLUMNS: ReadonlyMap>; /** * Record what an apply wrote, in the caller's (apply frame) transaction. * A later write to the same (frame, tbl, uid, col) replaces the earlier * intent: the capture of the last write is what the sealer compares. * * @param db - The store, inside the apply frame's transaction. * @param frame - The apply frame's id (`openCaptureFrame(db, 'apply', …)`). * @param intents - The writes. */ export declare function recordApplyIntents(db: DatabaseSync, frame: string, intents: readonly ApplyIntent[]): void; /** The intents of one frame, keyed `tbl \0 uid \0 col`. */ export type FrameIntents = ReadonlyMap; /** Load one frame's intents. */ export declare function loadFrameIntents(db: DatabaseSync, frame: string): FrameIntents; /** The capture fields subtraction reads. */ export interface IntentCapture { readonly seq: number; readonly tbl: string; readonly op: 'I' | 'U' | 'D' | 'K'; readonly img: string; } /** What {@link subtractApplyIntents} left of a frame's captures. */ export interface IntentSubtraction { /** Captures with residual fields, their image cut down to the residual. */ readonly residual: C[]; /** Captures the apply fully explains. They are consumed, never sealed. */ readonly removed: C[]; } /** * Subtract a frame's intents from its captures (§3.3), against the frame's * NET value per (row, column): within one frame, only the last capture that * changes a column carries it; an earlier change of the same column is * superseded (netting would collapse it) and never seals on its own. * * - **U:** each column whose net after-value equals the intent is removed, * and so is every superseded change. A capture with no column left is * removed. * - **I:** with a `*I` intent (the apply records an intent for EVERY column * it inserted), a column is removed when its net value equals its intent * or a later capture supersedes it. A column with no intent is something * the apply did not write (a local default, a trigger fill) and stays * residual. With nothing residual the insert is removed; otherwise the * residual columns remain as an update from the intent value (`NULL` when * there is none). An I without `*I` is a local insert and stays whole. * - **D:** removed when a `*D` intent exists. * - **K:** removed when a `*K` intent names the same new uid. * - Residual changes to {@link SYNC_TRIGGER_DERIVED_COLUMNS} are dropped. * * Nothing the apply did not write is ever removed: a column with no intent, * or whose net value differs, is residual; a capture with no intent for its * row stays whole. * * @param captures - The frame's captures, in capture order. * @param intents - The frame's intents ({@link loadFrameIntents}). * @param uidOf - The uid of a capture's row, resolved as the sealer resolves it. */ export declare function subtractApplyIntents(captures: readonly C[], intents: FrameIntents, uidOf: (c: C) => string | null): IntentSubtraction; //# sourceMappingURL=apply-intent.d.ts.map