# Tasks — CRDT completion (G-Set + LWW-Map) > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 1. Spec authoring - [ ] 1.1 Read `../suaveplan/enterprise/crdt.md` end-to-end - [ ] 1.2 Read `openspec/specs/crdt/spec.md` (does not exist yet — confirm and start fresh) - [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14 - [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.5 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification - [ ] 1.6 Draft `specs/crdt/spec.md` from `openspec/templates/spec-delta.md` - [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles ## 2. Implementation — `crdts/g-set/g-set` - [ ] 2.1 Create `packages/flow/crdt/src/crdts/g-set/g-set.ts` exporting `class GSet` implementing `CRDT, GSetState>` - [ ] 2.2 Add `GSetState` (`{ type: "g-set"; elements: T[] }`) and `GSetOptions` (`{ initialElements?: T[] }`) to `types/crdt.types.ts` - [ ] 2.3 Implement `add(value)`, `has(value)`, `value()` (returns `Set`), `state()`, `merge(remote)` (set union), `toJSON()`, `fromJSON(json)`, `clone()` - [ ] 2.4 For non-primitive `T`, key by `JSON.stringify(value)` for dedup (matches upstream); reject `BigInt` / circular-reference values with `CrdtError(GSET_INVALID_ELEMENT)` - [ ] 2.5 Add `g-set.md` sibling doc (≥200 words, basic + advanced examples) - [ ] 2.6 Add subpath export `"./crdts/g-set/g-set"` to `packages/flow/crdt/package.json` - [ ] 2.7 Re-export `GSet` from `index.ts` and `GSetState`, `GSetOptions` types ## 3. Implementation — `crdts/lww-map/lww-map` - [ ] 3.1 Create `packages/flow/crdt/src/crdts/lww-map/lww-map.ts` exporting `class LWWMap` implementing `CRDT, LWWMapState>` - [ ] 3.2 Add `LWWMapEntry` (`{ value, timestamp }`), `LWWMapTombstone` (`{ timestamp }`), and `LWWMapState` (`{ type: "lww-map"; entries; tombstones }`) types to `types/crdt.types.ts` - [ ] 3.3 Constructor signature: `new LWWMap(nodeId: string, options?: { clock?: Clock })`; default clock uses `Date.now()` - [ ] 3.4 Implement `set(key, value, ts?)`, `get(key)`, `has(key)`, `delete(key, ts?)` (returns `boolean`), `keys()`, `value()` (returns `Map`), `state()`, `merge(remote)`, `toJSON()`, `fromJSON(json)`, `clone()`, `reset()` - [ ] 3.5 `set` and `delete` SHALL pull `ts` from the injected clock when the optional argument is omitted - [ ] 3.6 `set` SHALL be a no-op when an existing entry has a strictly greater timestamp; tie-break uses `nodeId` lexicographic comparison (matches `LWWRegister`) - [ ] 3.7 `delete` SHALL leave a tombstone with the supplied (or clock-resolved) timestamp; subsequent `set` with a strictly greater timestamp clears the tombstone - [ ] 3.8 `merge(remote)` SHALL respect tombstones — remote entries older than a local tombstone are dropped; remote tombstones newer than a local entry remove the entry - [ ] 3.9 Reject non-string keys (e.g. `BigInt`-coerced) with `CrdtError(LWWMAP_INVALID_KEY)` - [ ] 3.10 Add `lww-map.md` sibling doc (≥200 words, basic + advanced examples) - [ ] 3.11 Add subpath export `"./crdts/lww-map/lww-map"` to `packages/flow/crdt/package.json` - [ ] 3.12 Re-export `LWWMap` from `index.ts` and `LWWMapEntry`, `LWWMapTombstone`, `LWWMapState` types ## 4. Error codes - [ ] 4.1 Add `GSET_INVALID_ELEMENT` and `LWWMAP_INVALID_KEY` to `errors/crdt-error.ts` `CRDT_CODES` registry - [ ] 4.2 Verify they are registered against the shared `defineCodes` registry on module load - [ ] 4.3 Update `CrdtError` type union to include the new codes ## 5. Tests — G-Set - [ ] 5.1 `g-set.test.ts::"adds an element and reports has() true"` — basic add / has - [ ] 5.2 `g-set.test.ts::"value returns a Set with all current elements"` — value snapshot - [ ] 5.3 `g-set.test.ts::"merge is set union"` — two G-Sets merge to combined elements - [ ] 5.4 `g-set.test.ts::"merge is idempotent"` — `a.merge(b.state()); a.merge(b.state())` no different from one merge - [ ] 5.5 `g-set.test.ts::"merge is commutative"` — `a.merge(b)` then `c.merge(a.state())` equals starting fresh and merging in any order - [ ] 5.6 `g-set.test.ts::"merge is associative"` — `(a ∪ b) ∪ c === a ∪ (b ∪ c)` over same input - [ ] 5.7 `g-set.test.ts::"toJSON / fromJSON round-trip"` — full state round-trip - [ ] 5.8 `g-set.test.ts::"clone produces an independent copy"` — mutation of clone does not affect original - [ ] 5.9 `g-set.test.ts::"initialElements seeds the set"` — constructor option populated correctly - [ ] 5.10 `g-set.spec-conformance.test.ts::"GSet is CRDT, GSetState>"` — `expectTypeOf` assertion - [ ] 5.11 `g-set.spec-conformance.test.ts::"throws GSET_INVALID_ELEMENT for BigInt element"` — error path ## 6. Tests — LWW-Map - [ ] 6.1 `lww-map.test.ts::"set then get returns the value"` — basic round-trip - [ ] 6.2 `lww-map.test.ts::"set with explicit timestamp respects ordering"` — older ts is no-op - [ ] 6.3 `lww-map.test.ts::"tie-break on equal timestamp uses nodeId"` — lexicographic comparison - [ ] 6.4 `lww-map.test.ts::"delete leaves a tombstone"` — get returns undefined, has returns false, internal tombstone present - [ ] 6.5 `lww-map.test.ts::"set after delete with greater ts clears tombstone"` — re-add path - [ ] 6.6 `lww-map.test.ts::"merge respects tombstones — older remote entry dropped"` — convergence property - [ ] 6.7 `lww-map.test.ts::"merge respects tombstones — newer remote tombstone removes local entry"` — convergence property - [ ] 6.8 `lww-map.test.ts::"merge is idempotent / commutative / associative"` — three property tests - [ ] 6.9 `lww-map.test.ts::"toJSON / fromJSON round-trip preserves entries and tombstones"` — full state round-trip - [ ] 6.10 `lww-map.test.ts::"clone produces an independent copy"` — mutation of clone does not affect original - [ ] 6.11 `lww-map.test.ts::"reset clears entries and tombstones"` — empty state restored - [ ] 6.12 `lww-map.test.ts::"keys returns active keys excluding tombstones"` — listing semantics - [ ] 6.13 `lww-map.spec-conformance.test.ts::"LWWMap is CRDT, LWWMapState>"` — `expectTypeOf` assertion - [ ] 6.14 `lww-map.spec-conformance.test.ts::"set without ts pulls from injected clock"` — deterministic clock injection - [ ] 6.15 `lww-map.spec-conformance.test.ts::"throws LWWMAP_INVALID_KEY for non-string key"` — error path - [ ] 6.16 Every `it(...)` in §5 and §6 carries `{ timeout: 5000 }` ## 7. Documentation - [ ] 7.1 Sibling `.md` for `g-set.ts` and `lww-map.ts` (≥200 words each, basic + advanced examples) - [ ] 7.2 Update `packages/flow/crdt/README.md` — add Modules links for the two new files; add Quick-start snippets for both - [ ] 7.3 Update `CLAUDE.md` package listing line for `@suaveplan/crdt` to mention `GSet` and `LWWMap` ## 8. Pre-archive verification (the 5-gate triple) - [ ] 8.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo - [ ] 8.2 `bunx turbo lint typecheck test --filter=@suaveplan/crdt` — green - [ ] 8.3 `bunx turbo test:coverage --filter=@suaveplan/crdt` — 100/100/100/100 - [ ] 8.4 `bun scripts/openspec-conformance.ts @suaveplan/crdt` — exit 0, no `error` findings - [ ] 8.5 `bunx turbo build --filter=@suaveplan/crdt` — green ## 9. Pre-archive 7-row checklist (AGENTS.md Section 13) - [ ] 9.1 Generics threaded through `GSet` and `LWWMap` - [ ] 9.2 Zod schemas wired at any declared boundary if a CRDT envelope is exposed across a network edge (likely deferred — CRDT-side serialisation is JSON-only here) - [ ] 9.3 Every error code in `errors/crdt-error.ts` (including the two new codes) thrown by at least one production path - [ ] 9.4 Strict-mode behaviors tested both on and off (tie-break with equal ts ON/OFF for nodeId comparison) - [ ] 9.5 OTel instruments emitted from production paths (counters for `gset.add`, `lwwmap.set`, `lwwmap.delete`, `lwwmap.merge.entries`, `lwwmap.merge.tombstones`) and asserted with a real in-memory `MeterProvider` - [ ] 9.6 Lifecycle dedup not applicable here — CRDTs are stateful instances, no shared registry - [ ] 9.7 No retroactive tick — every box above reflects work done in this change ## 10. Archive - [ ] 10.1 Fold spec delta into `openspec/specs/crdt/spec.md` (will be the first time this canonical spec exists) - [ ] 10.2 Move `openspec/changes/crdt-completion/` → `openspec/archive/-crdt-completion/` - [ ] 10.3 Refresh `MANIFEST.md` if package set or deps changed (no expected change here) ## Audit Note (2026-05-02) Audit verdict: **DEFERRED — 0/74 tasks evidenced in code**. Evidence reviewed: - `packages/flow/crdt/src/crdts/` contains `g-counter`, `pn-counter`, `lww-register`, `mv-register`, `or-set`, `two-p-set`, `rga`. No `g-set/` directory. No `lww-map/` directory. - `errors/crdt-error.ts` does not export `GSET_INVALID_ELEMENT` or `LWWMAP_INVALID_KEY`. - Package currently green: `bunx turbo lint typecheck test --filter=@suaveplan/crdt` passes (11 files, 145 tests). Missing pieces: 1. `crdts/g-set/g-set.ts` (G-Set CRDT class + state types + tests + sibling `.md`). 2. `crdts/lww-map/lww-map.ts` (LWW-Map CRDT class with tombstones + clock injection + tests + sibling `.md`). 3. New error codes (`GSET_INVALID_ELEMENT`, `LWWMAP_INVALID_KEY`) and the OTel counters listed in §9.5. Decision: leave change open. Implementing both CRDTs with full test parity (idempotent / commutative / associative property suites + spec-conformance) is real implementation work, not an audit-pass tick.