# OpenSpec Change: enterprise-crdt - **Change ID**: enterprise-crdt - **Package**: `@suaveplan/crdt` - **Category**: universal - **Target path**: `universal/data/crdt` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/crdt.md` --- ## Summary Deliver `@suaveplan/crdt` on the genesis branch from scratch at enterprise quality. The package provides conflict-free replicated data type primitives: G-Counter, PN-Counter, OR-Set, LWW-Register, MV-Register, 2P-Set, and RGA (Replicated Growable Array). All CRDTs implement a common `CRDT` interface with `merge`, `value`, and `toJSON`/`fromJSON` for serialization. Genesis adds structured errors via `@suaveplan/error`, event-bus integration via `@suaveplan/events` for state-change propagation, diff/patch integration via `@suaveplan/diff` for efficient delta exchange, injectable clock and ID generator, OTel instrumentation, and property-based correctness proofs. --- ## Current-state audit (genesis) The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `CrdtError`; `defineCodes` for `CRDT.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock`, `IdGenerator`, `EventBusLike` injection interfaces | | `@suaveplan/events` (`workspace:^`) | Emit typed state-change events on merge | | `@suaveplan/diff` (`workspace:^`) | Delta encoding and patch application for efficient state sync | | `zod` (`catalog:forms`) | Zod schemas for all public option and serialization types | | `@opentelemetry/api` (`catalog:observability`) | Spans and counters on merge, update, and sync operations | | `tslib` (`catalog:build`) | Runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`). --- ## Public API surface ### Common CRDT interface ```ts interface CRDT { readonly id: string value(): TValue merge(remote: TState): void state(): TState toJSON(): string fromJSON(json: string): void clone(): this } interface CrdtOptions { id?: string clock?: Clock idGenerator?: IdGenerator } ``` ### G-Counter ```ts class GCounter implements CRDT { increment(nodeId: string, delta?: number): void get(nodeId: string): number total(): number } interface GCounterState { type: "g-counter" counters: Record } ``` ### PN-Counter ```ts class PNCounter implements CRDT { increment(nodeId: string, delta?: number): void decrement(nodeId: string, delta?: number): void total(): number } interface PNCounterState { type: "pn-counter" increments: Record decrements: Record } ``` ### OR-Set ```ts class ORSet implements CRDT, ORSetState> { add(element: T, nodeId: string): void remove(element: T): void has(element: T): boolean size(): number } interface ORSetState { type: "or-set" entries: Array<{ element: T; tag: string; tombstone: boolean }> } ``` ### LWW-Register ```ts class LWWRegister implements CRDT> { set(value: T, nodeId: string, timestamp?: number): void get(): T | undefined } interface LWWRegisterState { type: "lww-register" value: T | undefined timestamp: number nodeId: string } ``` ### MV-Register ```ts class MVRegister implements CRDT> { set(value: T, nodeId: string): void get(): T[] conflict(): boolean } interface MVRegisterState { type: "mv-register" entries: Array<{ value: T; vectorClock: Record }> } ``` ### 2P-Set ```ts class TwoPSet implements CRDT, TwoPSetState> { add(element: T): void remove(element: T): void has(element: T): boolean } interface TwoPSetState { type: "2p-set" added: T[] removed: T[] } ``` ### RGA (Replicated Growable Array) ```ts class RGA implements CRDT> { insert(index: number, element: T, nodeId: string): void delete(index: number): void get(index: number): T length(): number } interface RGAState { type: "rga" elements: Array<{ id: string; value: T | null; tombstone: boolean }> } ``` ### Error handling ```ts class CrdtError extends SuaveplanError type CrdtErrorCode = | "CRDT_MERGE_CONFLICT" | "CRDT_INVALID_STATE" | "CRDT_DESERIALIZE_FAILED" | "CRDT_OPERATION_INVALID" function registerCrdtErrorCodes(): void ``` ### Zod schemas ```ts const GCounterStateSchema: ZodSchema const PNCounterStateSchema: ZodSchema const ORSetStateSchema: ZodSchema const LWWRegisterStateSchema: ZodSchema const MVRegisterStateSchema: ZodSchema const TwoPSetStateSchema: ZodSchema const RGAStateSchema: ZodSchema ``` --- ## New additions for genesis ### 1. Event-bus integration Every CRDT constructor accepts an optional `bus?: EventBusLike`. When provided, merge operations emit typed events: - `crdt:merged` — `{ id, type, previousState, nextState }` - `crdt:updated` — `{ id, type, operation, args }` ### 2. Delta-based sync via `@suaveplan/diff` `CrdtSyncManager` wraps one or more CRDTs and uses `@suaveplan/diff` JSON Patch to compute minimal delta payloads between sync points. Callers exchange deltas rather than full state snapshots. ```ts class CrdtSyncManager { register>(crdt: T): void delta(since?: string): SyncDelta apply(delta: SyncDelta): void } ``` ### 3. Vector clock utilities ```ts class VectorClock { increment(nodeId: string): void merge(remote: VectorClockState): void compare(other: VectorClockState): "before" | "after" | "concurrent" | "equal" state(): VectorClockState } type VectorClockState = Record ``` ### 4. OTel instrumentation - Per-operation spans: `crdt..merge`, `crdt..update`, `crdt.sync.delta`, `crdt.sync.apply` - Counters: `crdt.merges_total`, `crdt.updates_total`, `crdt.conflicts_total` - Attributes: `crdt.type`, `crdt.id`, `crdt.node_id` ### 5. Property-based tests (fast-check) - **Commutativity**: `merge(a, b) ≡ merge(b, a)` for all CRDT types - **Associativity**: `merge(merge(a, b), c) ≡ merge(a, merge(b, c))` - **Idempotency**: `merge(a, a) ≡ a` - **Monotonicity**: `value(merge(a, b)) ≥ value(a)` for counter types - **RGA convergence**: concurrent inserts on two replicas converge after mutual merge ### 6. Benchmark suite (`src/__benchmarks__/`) - GCounter with 100 nodes, 10k increments - ORSet with 1k add/remove operations - RGA with 1k concurrent inserts on two replicas --- ## File layout ```text universal/data/crdt/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ crdt-interface/ crdt-interface.ts crdt-interface.md crdt-interface.types.ts index.ts vector-clock/ vector-clock.ts vector-clock.md vector-clock.types.ts vector-clock.test.ts index.ts sync-manager/ sync-manager.ts sync-manager.md sync-manager.types.ts sync-manager.test.ts index.ts crdts/ g-counter/ g-counter.ts g-counter.md g-counter.types.ts g-counter.test.ts index.ts pn-counter/ pn-counter.ts pn-counter.md pn-counter.types.ts pn-counter.test.ts index.ts or-set/ or-set.ts or-set.md or-set.types.ts or-set.test.ts index.ts lww-register/ lww-register.ts lww-register.md lww-register.types.ts lww-register.test.ts index.ts mv-register/ mv-register.ts mv-register.md mv-register.types.ts mv-register.test.ts index.ts two-p-set/ two-p-set.ts two-p-set.md two-p-set.types.ts two-p-set.test.ts index.ts rga/ rga.ts rga.md rga.types.ts rga.test.ts index.ts __benchmarks__/ crdt.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream packages**: collaborative editing, offline-first sync, and multi-peer state reconciliation features. No existing genesis package depends on it yet. **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/diff` must be published to Verdaccio before this package can be built. **Breaking changes**: none (new package).