--- 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` * `StateBuilder` * `EventReplayer` 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` * `StateBuilder` * `EventReplayer` * 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 ``` and is executed by the generic replay engine. --- ### 3. Replay flow Replay is defined as: ```text EventStore ↓ EventReplayer ↓ Projection ↓ State ``` For sessions: ```text EventStore ↓ EventReplayer ↓ 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