14 KiB
14 KiB
Tasks — Event-sourcing completion
Every checkbox MUST reflect work actually done in this change. The seven capabilities closed here were silently dropped during the original genesis migration (audit dated 2026-05-02). Do not retroactively tick boxes for surface that already shipped.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Spec authoring
- 1.1 Read
../suaveplan/packages/universal/event-sourcing/src/{outbox,store,replay,subscription,snapshot,versioning,event-sourcing-events}.tsend-to-end - 1.2 Read
openspec/specs/event-sourcing/spec.md(if it exists; create the cap dir if not) - 1.3 Read
openspec/AGENTS.mdSections 0a (parity), 13 (spec-conformance), 14 (source mapping), 14a (dependents) - 1.4 Draft
proposal.mdcovering all seven gaps (EventOutbox,DatabaseEventStore,DatabaseSnapshotStore,EventReplayer,EventBus + SubscriptionFilter,SnapshotManager,UpcasterChain) - 1.5 Fill
## Source mappinginproposal.mdwith upstream line ranges for every requirement; every "no source line" requirement justified - 1.6 Draft spec delta at
specs/event-sourcing/spec.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.8
openspec change validate event-sourcing-completionexits with no errors
2. Implementation
- 2.1
EventOutboxinterface +MemoryEventOutboxclass — port from upstreamoutbox/outbox-pattern.tsintopackages/events/event-sourcing/src/outbox/event-outbox.ts(interface) andpackages/events/event-sourcing/src/outbox/memory-outbox.ts(impl) - 2.2
OutboxRelayworker — extract relay loop into a standalone class with injectableClockandAbortSignal; new filepackages/events/event-sourcing/src/outbox/relay.ts - 2.3
DatabaseEventStore<TEvent>— port from upstreamstore/database-event-store.tsintopackages/events/event-sourcing/src/store/database-event-store.ts; replaceEventStoreDatabaseAdapterwithDatabaseAdapterfrom@suaveplan/db - 2.4
DatabaseSnapshotStore<TState>— port from upstreamstore/database-snapshot-store.tsintopackages/events/event-sourcing/src/store/database-snapshot-store.ts; addserializer/deserializerconfig hooks - 2.5
EventReplayer— port from upstreamreplay/event-replayer.tsintopackages/events/event-sourcing/src/replay/event-replayer.ts; threadAbortSignalthroughreplay,replayByType,replayEvents - 2.6
EventBus+SubscriptionFilter— port from upstreamsubscription/event-subscription.tsintopackages/events/event-sourcing/src/subscription/event-bus.ts; replace module-scope counter with injectableIdGenerator - 2.7
DeadLetterSinkinterface +MemoryDeadLetterSink— new filepackages/events/event-sourcing/src/subscription/dead-letter.ts; capture handler exceptions duringEventBus.publish - 2.8
SnapshotManager<TState>— port from upstreamsnapshot/snapshot-manager.tsintopackages/events/event-sourcing/src/snapshot/snapshot-manager.ts; replacenew Date()with injectableClock - 2.9
UpcasterChainordered registry — port from upstreamversioning/event-upcaster.tsintopackages/events/event-sourcing/src/upcasting/chain.ts - 2.10
detectSchemaVersion,createUpcastedEvent,createRenamedEventhelpers — co-locate next toUpcasterChain - 2.11
createFieldRenameUpcaster,createDefaultFieldUpcaster,createRemoveFieldUpcasterbuilders — co-locate next toUpcasterChain - 2.12 Wire legacy
registerUpcaster/clearUpcasters/upcastEventsto delegate to a module-scopedUpcasterChainso both APIs share one registry - 2.13 Add
ES_*codes (OUTBOX_TABLE_INIT_FAILED,OUTBOX_PUBLISH_FAILED,OUTBOX_RELAY_INTERRUPTED,EVENT_INVALID,EVENT_HANDLER_FAILED,REPLAY_CANCELLED,REPLAY_FILTER_INVALID,SUBSCRIPTION_INVALID,DATABASE_TABLE_INIT_FAILED,SNAPSHOT_HANDLER_FAILED,UPCASTER_DUPLICATE) toerrors/event-sourcing-error.ts - 2.14 Wire OTel counters / spans on every public entrypoint:
outbox.relayed,outbox.failed,outbox.pending_size,database.events_appended,database.snapshots_saved,replay.events_processed,replay.duration_ms,subscription.published,subscription.dead_lettered,snapshot.created,snapshot.pruned,upcaster.applied,upcaster.skipped - 2.15 Wire Zod schemas for
OutboxConfig,DatabaseEventStoreConfig,DatabaseSnapshotStoreConfig,ReplayFilter,ReplayConfig,SubscriptionOptions,SubscriptionFilter,SnapshotManagerConfig,EventUpcastertoschemas/event-sourcing.schemas.ts - 2.16 Update
index.tsbarrel — re-export every new symbol; noexport *(one explicit subpath per module) - 2.17 Update
package.jsonexportsmap — add subpaths./outbox/event-outbox,./outbox/memory-outbox,./outbox/relay,./store/database-event-store,./store/database-snapshot-store,./replay/event-replayer,./subscription/event-bus,./subscription/dead-letter,./snapshot/snapshot-manager,./upcasting/chain - 2.18 Update
package.jsondependencies— add@suaveplan/db(workspace:^)
3. Tests
- 3.1
packages/events/event-sourcing/src/outbox/event-outbox.test.ts— interface contract;MemoryEventOutboxinsertion order; per-row attempts counter;pendingCount/failedCount/cleanup - 3.2
packages/events/event-sourcing/src/outbox/event-outbox.spec-conformance.test.ts— Zod-rejected configs emitOUTBOX_TABLE_INIT_FAILED; injectedIdGeneratordeterministic; OTel counteroutbox.pending_size - 3.3
packages/events/event-sourcing/src/outbox/relay.test.ts— successful relay marks rows published; failure increments attempts;AbortSignalaborts in-flight loops withOUTBOX_RELAY_INTERRUPTED; injectedClockdeterministic - 3.4
packages/events/event-sourcing/src/store/database-event-store.test.ts— append + load round-trip; optimistic-concurrency conflict raisesCONCURRENCY_CONFLICT;getEventsByTypehonourslimit;count()returns total - 3.5
packages/events/event-sourcing/src/store/database-event-store.spec-conformance.test.ts— table auto-create gated onautoCreate: true;transaction()rollback on adapter exception; emitsevent:appendedlifecycle event - 3.6
packages/events/event-sourcing/src/store/database-snapshot-store.test.ts— save / load latest-per-aggregate; configurable serializer round-trips a non-JSON state;deletereturnsfalsefor unknown aggregate;getIntervalreturns config - 3.7
packages/events/event-sourcing/src/replay/event-replayer.test.ts— full replay invokes handler in stream order; filter narrows byeventTypes/aggregateId/fromDate/toDate; pause / resume / cancel / reset state machine;onProgressfinal tick hascompleted: true; batch size honoured - 3.8
packages/events/event-sourcing/src/replay/event-replayer.spec-conformance.test.ts— abort signal mid-batch rejects withREPLAY_CANCELLED; bad filter rejected withREPLAY_FILTER_INVALID; OTel histogramreplay.duration_ms - 3.9
packages/events/event-sourcing/src/subscription/event-bus.test.ts—subscribe/publishround-trip; filter narrows byeventTypes/aggregateIds/aggregateType; group round-robin distribution;unsubscriberemoves record;subscriberCountreflects active subscriptions - 3.10
packages/events/event-sourcing/src/subscription/event-bus.spec-conformance.test.ts— handler exceptions route toDeadLetterSinkwithEVENT_HANDLER_FAILEDcause; injectedIdGeneratorproduces deterministic subscription IDs;clear()resetsgroupCounters - 3.11
packages/events/event-sourcing/src/subscription/dead-letter.test.ts—MemoryDeadLetterSinkcapture; iteration order;clear()resets;getEntries()snapshot is immutable - 3.12
packages/events/event-sourcing/src/snapshot/snapshot-manager.test.ts—loadrebuilds from snapshot + tail events; auto-create atinterval; pruning atmaxSnapshots;shouldCreateSnapshotpredicate;getSnapshotHistoryper aggregate - 3.13
packages/events/event-sourcing/src/snapshot/snapshot-manager.spec-conformance.test.ts— injectedClockstampstakenAtdeterministically;aggregate:loadedlifecycle event carriesrestoredFromSnapshot; reducer exception raisesSNAPSHOT_HANDLER_FAILED - 3.14
packages/events/event-sourcing/src/upcasting/chain.test.ts—registerorders byfromVersion;upcastwalks chain v1→v2→v3;getUpcastersForTypefilters;clear()empties registry;hasUpcastpredicate - 3.15
packages/events/event-sourcing/src/upcasting/chain.spec-conformance.test.ts—detectSchemaVersiondefaults to1; duplicate(eventType, fromVersion)registration raisesUPCASTER_DUPLICATE;createFieldRenameUpcaster/createDefaultFieldUpcaster/createRemoveFieldUpcasterround-trip - 3.16 Cross-package
tests/dep-graph.test.ts—@suaveplan/event-sourcingdeclares@suaveplan/dbas a runtime dep; the package still has zero browser-only deps - 3.17 Every
it(...)carries{ timeout: 5000 }(or 30000 for genuinely-async DB-adapter integration)
4. Documentation
- 4.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference) —outbox/event-outbox.md,outbox/memory-outbox.md,outbox/relay.md,store/database-event-store.md,store/database-snapshot-store.md,replay/event-replayer.md,subscription/event-bus.md,subscription/dead-letter.md,snapshot/snapshot-manager.md,upcasting/chain.md - 4.2 README links every co-located
.mdunder## Modules - 4.3 Update
CLAUDE.mdevent-sourcingpackage listing — remove "only Aggregate / aggregate-repository / MemoryEventStore / Projection / flat upcaster" wording; describe the full restored surface - 4.4 Refresh
STATUS.md"Completed on genesis" entry forevent-sourcingto reflect parity
5. Pre-archive verification (the 5-gate triple)
- 5.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the monorepo - 5.2
bunx turbo lint typecheck test --filter=@suaveplan/event-sourcing— green - 5.3
bunx turbo test:coverage --filter=@suaveplan/event-sourcing— 100/100/100/100 - 5.4
bun scripts/openspec-conformance.ts @suaveplan/event-sourcing— exit 0, noerrorfindings - 5.5
bunx turbo build --filter=@suaveplan/event-sourcing— green - 5.6 Banned-pattern scan zero output:
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/events/event-sourcing/src/ --include="*.ts" - 5.7 Missing
.mdscan zero output (per AGENTS.md §13 step 5) - 5.8 README check
test -f packages/events/event-sourcing/README.md - 5.9 Bare-version check zero output (per AGENTS.md §13 step 7)
- 5.10 Subpath count matches feature module count (per AGENTS.md §13 step 8)
6. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 6.1 Generics threaded;
expectTypeOfassertions on every literal-type claim (especiallyDatabaseEventStore<TEvent>,DatabaseSnapshotStore<TState>,SnapshotManager<TState>) - 6.2 Zod schemas wired at every declared boundary (
OutboxConfig,DatabaseEventStoreConfig,DatabaseSnapshotStoreConfig,ReplayFilter,ReplayConfig,SubscriptionOptions,SubscriptionFilter,SnapshotManagerConfig,EventUpcaster) - 6.3 Every error code added in §2.13 thrown by at least one production code path; no orphans
- 6.4 Strict-mode behaviors tested both on and off (e.g.
autoCreate: truevsfalse;pausedvs running replay;cancel()vsreset()) - 6.5 OTel counters / histograms emitted from production paths and asserted with a real in-memory
MeterProvider - 6.6 Lifecycle dedup tested where claimed (
SnapshotManager.shouldCreateSnapshotidempotent on identical inputs;EventBus.unsubscribe()on already-unsubscribed record is a no-op) - 6.7 No retroactive tick — every box above reflects work done in this change
7. Archive
- 7.1 Fold spec delta into
openspec/specs/event-sourcing/spec.md(creating the cap file if missing) - 7.2 Move
openspec/changes/event-sourcing-completion/→openspec/archive/<yyyy-mm-dd>-event-sourcing-completion/ - 7.3 Refresh
MANIFEST.md(dep set changed:@suaveplan/event-sourcingnow depends on@suaveplan/db) - 7.4 Update
STATUS.mdto mark the seven dropped capabilities as restored
Audit Note (2026-05-02)
Audit verdict: DEFERRED — 0/68 tasks evidenced in code.
Evidence reviewed:
packages/events/event-sourcing/src/containscore/{aggregate,event-store,aggregate-repository}/,projection/builder/,upcasting/upcaster/,errors/,schemas/,types/,constants/. Nooutbox/, noreplay/, nosubscription/, nosnapshot/, nostore/database-*.package.jsondoes not declare@suaveplan/dbas a runtime dep (theDatabaseEventStoreandDatabaseSnapshotStoreneed it).- No new
ES_*error codes (OUTBOX_*,REPLAY_*,SUBSCRIPTION_*,SNAPSHOT_HANDLER_FAILED,UPCASTER_DUPLICATE,EVENT_HANDLER_FAILED, etc.) inerrors/event-sourcing-error.ts. - Existing
upcasting/upcaster/is a single class — noUpcasterChainordered registry, nocreateFieldRenameUpcaster/createDefaultFieldUpcaster/createRemoveFieldUpcasterbuilders.
Missing pieces:
- Outbox subsystem (
EventOutboxinterface,MemoryEventOutbox,OutboxRelayworker with injectableClock+AbortSignal). DatabaseEventStore<TEvent>+DatabaseSnapshotStore<TState>(requires adding@suaveplan/dbdep).EventReplayer(with pause/resume/cancel/reset state machine) +EventBuswithSubscriptionFilter+MemoryDeadLetterSink+SnapshotManager<TState>+UpcasterChainordered registry.
Decision: leave change open. Seven dropped capabilities, ten new subpath exports, eleven new error codes, dep graph change — all blocked behind real implementation work.