---
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
