# vector-clock _(no submodules)_ ## Purpose `vector-clock.ts` implements the causal-ordering primitives used by every other module in `@suaveplan/vault-sync`. Vector clocks let two divergent peers detect whether one branch is the causal ancestor of the other or whether the edits were concurrent — a prerequisite for safe CRDT merges, durable queue ordering, and explicit conflict reporting in `SyncEngine`. The implementation deliberately avoids secret-dependent branches: every comparison uses integer arithmetic against `nodeId`-keyed counters with no short-circuit on values that could leak side-channel information. ## Features - `increment(clock, nodeId)` — pure, allocates a fresh clock - `merge(left, right)` — component-wise maximum, missing keys treated as zero - `compare(left, right)` — returns `"<" | ">" | "==" | "concurrent"` - `happensBefore(left, right)` — Lamport happens-before predicate - `lwwTotalOrder(left, right)` — total ordering for the `"lww"` strategy with `(clock, updatedAt, nodeId)` tie-breaking - `empty()` — canonical empty vector clock factory - All operations are pure and side-effect free; inputs are never mutated - Saturation guard: counter never exceeds `Number.MAX_SAFE_INTEGER` ## Usage: Basic Example ```ts import { compare, increment, merge, } from "@suaveplan/vault-sync/vector-clock/vector-clock"; const a = increment({}, "node-a"); const b = increment({}, "node-b"); console.log(compare(a, b)); // "concurrent" console.log(merge(a, b)); // { "node-a": 1, "node-b": 1 } ``` ## Usage: Advanced Example ```ts import { increment, lwwTotalOrder, merge, } from "@suaveplan/vault-sync/vector-clock/vector-clock"; import type { VectorClockState } from "@suaveplan/vault-sync/types"; interface Snapshot { clock: VectorClockState; updatedAt: number; nodeId: string; } function applyMutation(local: Snapshot, nodeId: string, now: number): Snapshot { return { clock: increment(local.clock, nodeId), updatedAt: now, nodeId, }; } function pickWinner(left: Snapshot, right: Snapshot): Snapshot { return lwwTotalOrder(left, right) === -1 ? left : right; } // Reconcile a remote snapshot into local state. function reconcile(local: Snapshot, remote: Snapshot): Snapshot { const winner = pickWinner(local, remote); return { clock: merge(local.clock, remote.clock), updatedAt: winner.updatedAt, nodeId: winner.nodeId, }; } ``` ## API Reference - `increment(clock: VectorClockState, nodeId: string): VectorClockState` — returns a new clock with `nodeId`'s counter incremented by `1`. Throws `VaultSyncError(VECTOR_CLOCK.REGRESSION)` if the counter is already at `Number.MAX_SAFE_INTEGER`. - `merge(left, right): VectorClockState` — element-wise max of two clocks. - `compare(left, right): "<" | ">" | "==" | "concurrent"` — total relation classifying causal order. - `happensBefore(left, right): boolean` — `true` iff `compare === "<"`. - `lwwTotalOrder(left, right): -1 | 0 | 1` — total order for last-write-wins resolution: vector-clock dominance, then `updatedAt`, then `nodeId`. - `empty(): VectorClockState` — `{}` literal helper. ## Implementation Notes The clock is modelled as `Readonly>` rather than a class so the data is trivially serialisable and comparable by deep equality without custom `equals()`. `compare` enumerates the union of node ids; both absent counters default to `0`, which preserves the identity that an empty clock causally precedes any non-empty one. The `lwwTotalOrder` resolves remaining ties deterministically so two peers with identical inputs always elect the same winner — an essential property for offline-first convergence. Saturation at `Number.MAX_SAFE_INTEGER` is treated as a hard error rather than rolling over, since silent rollover would silently break causal ordering.