Initial Commit
This commit is contained in:
347
openspec/archive/2026-04-24-enterprise-crdt/proposal.md
Normal file
347
openspec/archive/2026-04-24-enterprise-crdt/proposal.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# 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<TValue, TState = unknown> {
|
||||
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<number, GCounterState> {
|
||||
increment(nodeId: string, delta?: number): void
|
||||
get(nodeId: string): number
|
||||
total(): number
|
||||
}
|
||||
|
||||
interface GCounterState {
|
||||
type: "g-counter"
|
||||
counters: Record<string, number>
|
||||
}
|
||||
```
|
||||
|
||||
### PN-Counter
|
||||
|
||||
```ts
|
||||
class PNCounter implements CRDT<number, PNCounterState> {
|
||||
increment(nodeId: string, delta?: number): void
|
||||
decrement(nodeId: string, delta?: number): void
|
||||
total(): number
|
||||
}
|
||||
|
||||
interface PNCounterState {
|
||||
type: "pn-counter"
|
||||
increments: Record<string, number>
|
||||
decrements: Record<string, number>
|
||||
}
|
||||
```
|
||||
|
||||
### OR-Set
|
||||
|
||||
```ts
|
||||
class ORSet<T> implements CRDT<Set<T>, ORSetState<T>> {
|
||||
add(element: T, nodeId: string): void
|
||||
remove(element: T): void
|
||||
has(element: T): boolean
|
||||
size(): number
|
||||
}
|
||||
|
||||
interface ORSetState<T> {
|
||||
type: "or-set"
|
||||
entries: Array<{ element: T; tag: string; tombstone: boolean }>
|
||||
}
|
||||
```
|
||||
|
||||
### LWW-Register
|
||||
|
||||
```ts
|
||||
class LWWRegister<T> implements CRDT<T | undefined, LWWRegisterState<T>> {
|
||||
set(value: T, nodeId: string, timestamp?: number): void
|
||||
get(): T | undefined
|
||||
}
|
||||
|
||||
interface LWWRegisterState<T> {
|
||||
type: "lww-register"
|
||||
value: T | undefined
|
||||
timestamp: number
|
||||
nodeId: string
|
||||
}
|
||||
```
|
||||
|
||||
### MV-Register
|
||||
|
||||
```ts
|
||||
class MVRegister<T> implements CRDT<T[], MVRegisterState<T>> {
|
||||
set(value: T, nodeId: string): void
|
||||
get(): T[]
|
||||
conflict(): boolean
|
||||
}
|
||||
|
||||
interface MVRegisterState<T> {
|
||||
type: "mv-register"
|
||||
entries: Array<{ value: T; vectorClock: Record<string, number> }>
|
||||
}
|
||||
```
|
||||
|
||||
### 2P-Set
|
||||
|
||||
```ts
|
||||
class TwoPSet<T> implements CRDT<Set<T>, TwoPSetState<T>> {
|
||||
add(element: T): void
|
||||
remove(element: T): void
|
||||
has(element: T): boolean
|
||||
}
|
||||
|
||||
interface TwoPSetState<T> {
|
||||
type: "2p-set"
|
||||
added: T[]
|
||||
removed: T[]
|
||||
}
|
||||
```
|
||||
|
||||
### RGA (Replicated Growable Array)
|
||||
|
||||
```ts
|
||||
class RGA<T> implements CRDT<T[], RGAState<T>> {
|
||||
insert(index: number, element: T, nodeId: string): void
|
||||
delete(index: number): void
|
||||
get(index: number): T
|
||||
length(): number
|
||||
}
|
||||
|
||||
interface RGAState<T> {
|
||||
type: "rga"
|
||||
elements: Array<{ id: string; value: T | null; tombstone: boolean }>
|
||||
}
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
```ts
|
||||
class CrdtError extends SuaveplanError<CrdtErrorCode>
|
||||
|
||||
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<T extends CRDT<unknown>>(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<string, number>
|
||||
```
|
||||
|
||||
### 4. OTel instrumentation
|
||||
|
||||
- Per-operation spans: `crdt.<type>.merge`, `crdt.<type>.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).
|
||||
82
openspec/archive/2026-04-24-enterprise-crdt/tasks.md
Normal file
82
openspec/archive/2026-04-24-enterprise-crdt/tasks.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Tasks: enterprise-crdt
|
||||
|
||||
- **Change ID**: enterprise-crdt
|
||||
- **Package**: `@suaveplan/crdt`
|
||||
- **Date**: 2026-04-24
|
||||
|
||||
## Prerequisites (must be published to Verdaccio first)
|
||||
|
||||
- [x] `@suaveplan/error`
|
||||
- [x] `@suaveplan/types`
|
||||
- [x] `@suaveplan/events`
|
||||
- [x] `@suaveplan/diff`
|
||||
|
||||
## Tasks
|
||||
|
||||
### 1. Scaffold package
|
||||
|
||||
- [x] Create `universal/data/crdt/` directory tree per file layout in `proposal.md`
|
||||
- [x] Write `package.json` (name `@suaveplan/crdt`, version `0.1.0`, `sideEffects: false`, correct workspace deps and catalog externals)
|
||||
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`
|
||||
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`
|
||||
|
||||
### 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [x] Define `CrdtErrorCode` union type
|
||||
- [x] Call `defineCodes` to register all `CRDT.*` codes
|
||||
- [x] Export `registerCrdtErrorCodes()` and `CrdtError`
|
||||
|
||||
### 3. Types (`src/types/common.ts`)
|
||||
|
||||
- [x] Define and export `CRDT<TValue, TState>` interface and `CrdtOptions`
|
||||
- [x] Write `common.md`
|
||||
|
||||
### 4. Core utilities
|
||||
|
||||
- [x] Implement `VectorClock` in `src/core/vector-clock/` with `increment`, `merge`, `compare`, Zod schema
|
||||
- [x] Implement `CrdtSyncManager` in `src/core/sync-manager/` using `@suaveplan/diff` delta encoding
|
||||
- [x] Each module: `.ts`, `.md`, `.types.ts`, `.test.ts`, `index.ts`
|
||||
|
||||
### 5. CRDT implementations
|
||||
|
||||
- [x] `GCounter` in `src/crdts/g-counter/`
|
||||
- [x] `PNCounter` in `src/crdts/pn-counter/`
|
||||
- [x] `ORSet<T>` in `src/crdts/or-set/`
|
||||
- [x] `LWWRegister<T>` in `src/crdts/lww-register/`
|
||||
- [x] `MVRegister<T>` in `src/crdts/mv-register/`
|
||||
- [x] `TwoPSet<T>` in `src/crdts/two-p-set/`
|
||||
- [x] `RGA<T>` in `src/crdts/rga/`
|
||||
- [x] Each: `.ts`, `.md`, `.types.ts`, `.test.ts`, `index.ts`; Zod state schema; event-bus integration; `toJSON`/`fromJSON`
|
||||
|
||||
### 6. OTel instrumentation
|
||||
|
||||
- [x] Add spans, counters, histograms per `proposal.md` §4
|
||||
|
||||
### 7. Tests
|
||||
|
||||
- [x] Unit tests co-located with each CRDT (100% coverage target)
|
||||
- [x] `tests/integration.test.ts` — two-replica merge scenarios for each CRDT type
|
||||
- [x] `tests/property.test.ts` — fast-check CRDT laws (commutativity, associativity, idempotency) per `proposal.md` §5
|
||||
|
||||
### 8. Benchmarks (`src/__benchmarks__/crdt.bench.ts`)
|
||||
|
||||
- [x] Implement benchmark cases per `proposal.md` §6
|
||||
|
||||
### 9. Docs
|
||||
|
||||
- [x] Every `.ts` implementation file has a sibling `.md`
|
||||
- [x] Root `README.md`
|
||||
|
||||
### 10. Pre-publish gate
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/crdt` green
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/crdt` ≥ 100% on touched modules
|
||||
|
||||
### 11. Publish
|
||||
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/crdt`
|
||||
|
||||
### 12. Archive
|
||||
|
||||
- [x] Fold spec delta into `openspec/specs/data/spec.md`
|
||||
- [x] `mv openspec/changes/enterprise-crdt openspec/archive/2026-04-24-enterprise-crdt`
|
||||
Reference in New Issue
Block a user