Files
correx/docs/decisions/adr-0009-replay-engine-projection-separation.md

190 lines
3.7 KiB
Markdown

---
name: "Adr 0009 Replay Engine Projection Separation"
description: "Generic replay engine separated from domain projections"
depth: 2
links: ["../index.md", "../modules/core-events-submodule-spec.md", "../modules/core-sessions-submodule-spec.md"]
---
# ADR 0009: Generic Replay Engine and Projection Separation
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
Correx uses an append-only event store as the single source of truth.
Session state and future domain states are reconstructed entirely from ordered event streams. Replay determinism and projection purity are core system invariants.
During implementation of Epic 2 (Session Lifecycle FSM), a distinction emerged between:
* generic replay infrastructure
* domain-specific projection semantics
The system already contained reusable replay infrastructure in `:core:events`:
* `Projection<S>`
* `StateBuilder<S>`
* `EventReplayer<S>`
At the same time, session lifecycle logic introduced:
* `SessionProjector`
* `SessionFsm`
* `SessionState`
* `SessionEvent`
A design decision was required to prevent:
* replay logic duplication
* infrastructure/domain coupling
* projection semantics leaking into the event engine
---
## content
The replay architecture is separated into two layers:
### 1. Generic replay engine (`:core:events`)
The `:core:events` module owns:
* replay orchestration
* projection execution contracts
* state folding mechanics
* ordered event replay infrastructure
This layer contains:
* `Projection<S>`
* `StateBuilder<S>`
* `EventReplayer<S>`
* replay implementations
This layer MUST remain domain-agnostic.
It MUST NOT contain:
* session lifecycle logic
* FSM semantics
* domain state models
* business transition rules
---
### 2. Domain projections (`:core:sessions`)
The `:core:sessions` module owns:
* session lifecycle semantics
* FSM transition logic
* session state reconstruction
* event interpretation for session domain
This layer contains:
* `SessionState`
* `SessionStatus`
* `SessionEvent`
* `SessionFsm`
* `SessionProjector`
`SessionProjector` implements:
```kotlin
Projection<SessionState>
```
and is executed by the generic replay engine.
---
### 3. Replay flow
Replay is defined as:
```text
EventStore
EventReplayer<S>
Projection<S>
State
```
For sessions:
```text
EventStore
EventReplayer<SessionState>
SessionProjector
SessionState
```
---
### 4. Projection purity rules
All projections MUST be:
* deterministic
* side-effect free
* replay-safe
* stateless outside reduction input
Projections MUST NOT:
* perform IO
* access system clocks
* mutate external state
* depend on runtime execution order outside event sequence
Projection output MUST depend solely on:
* previous state
* current event
---
### 5. Session replay semantics
Session state is not persisted directly.
`SessionState` is reconstructed entirely from replaying ordered `StoredEvent` streams.
FSM transitions are interpreted from persisted `EventPayload` instances through session-specific mapping logic.
---
## consequences
**positive:**
* replay engine becomes reusable across domains
* deterministic replay guarantees remain centralized
* domain logic remains isolated from persistence mechanics
* multiple independent projections can coexist over same event stream
* session lifecycle logic remains testable in isolation
* future projections can reuse replay infrastructure without duplication
**negative:**
* introduces additional abstraction layers
* domain projections require explicit wiring into replay engine
* event interpretation requires mapper logic between payloads and FSM events
* debugging replay chains may require traversing multiple layers
---
## status
accepted