Files
suaveplan/openspec/changes/crdt-completion/tasks.md
2026-05-27 23:29:53 +01:00

132 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T>` implementing `CRDT<Set<T>, GSetState<T>>`
- [ ] 2.2 Add `GSetState<T>` (`{ type: "g-set"; elements: T[] }`) and `GSetOptions<T>` (`{ initialElements?: T[] }`) to `types/crdt.types.ts`
- [ ] 2.3 Implement `add(value)`, `has(value)`, `value()` (returns `Set<T>`), `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<V>` implementing `CRDT<Map<string, V>, LWWMapState<V>>`
- [ ] 3.2 Add `LWWMapEntry<V>` (`{ value, timestamp }`), `LWWMapTombstone` (`{ timestamp }`), and `LWWMapState<V>` (`{ type: "lww-map"; entries; tombstones }`) types to `types/crdt.types.ts`
- [ ] 3.3 Constructor signature: `new LWWMap<V>(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<string, V>`), `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<T> is CRDT<Set<T>, GSetState<T>>"` — `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<V> is CRDT<Map<string, V>, LWWMapState<V>>"` — `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<T>` and `LWWMap<V>`
- [ ] 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/<yyyy-mm-dd>-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.