= infra-persistence == purpose Provides concrete `EventStore` implementations for persisting and replaying the event log. Supports in-memory storage (for testing and ephemeral sessions) and SQLite-backed durable storage. Also provides a live artifact repository that subscribes to events and maintains an up-to-date in-memory view of artifact state per session. == responsibilities * Implement the `EventStore` interface for both transient and durable storage * Enforce duplicate event ID detection and sequence ordering * Provide reactive subscriptions via `Flow` per session and globally * Maintain an artifact state projection that stays current via event subscription * Support transactional batch appends for atomic event persistence == non-responsibilities * Does not define event serialization — uses `JsonEventSerializer` from `core:events` * Does not implement CAS content-addressable storage — that belongs to `infra:artifacts-cas` * Does not handle event compaction or retention * Does not implement cross-session querying beyond `allSessionIds()` == key types === InMemoryEventStore * **kind**: class * **purpose**: Thread-safe, in-memory event store backed by `ConcurrentHashMap`. Supports duplicate detection via `seenEventIds` set and session-scoped sequence numbers. * **fields**: `streams` (per-session event lists), `sequences` (per-session sequence counters), `globalSeq` (global counter), `seenEventIds` (dedup set) === SqliteEventStore * **kind**: class * **purpose**: Durable event store backed by a SQLite database with WAL mode. Uses JDBC directly. Emits events to reactive flows after append. * **fields**: `connection` (JDBC SQLite connection), `jsonSerializer` (serialization bridge), `artifactStore` (used for `flushBefore` coordination) === LiveArtifactRepository * **kind**: class * **purpose**: Subscribes to an `EventStore` per session and maintains an up-to-date in-memory `ConcurrentHashMap` of `ArtifactState` values. Supports lookup by session, stage, and artifact ID. * **fields**: `artifactCache` (session → artifactId → state), `stageIndex` (session → artifactId → stageId), `subscriptions` (session → coroutine Job), `projector` (reduces events to state) === JDBCHelper * **kind**: object * **purpose**: Utility providing `Connection.transaction()` inline helper with automatic commit/rollback. == event flow *Inbound:* * `ArtifactCreatedEvent`, `ArtifactValidatingEvent`, `ArtifactValidatedEvent`, `ArtifactRejectedEvent`, `ArtifactSupersededEvent`, `ArtifactArchivedEvent`, `ArtifactRelationshipAddedEvent` — consumed by `LiveArtifactRepository` to maintain artifact projections * `NewEvent` — appended to event stores via `append()` / `appendAll()` *Outbound:* * `StoredEvent` — emitted via `Flow` subscriptions after each append == integration points * `core:events` — `EventStore`, `StoredEvent`, `NewEvent`, all artifact event payloads, `EventId`, `SessionId` * `core:artifacts` — `ArtifactProjector`, `ArtifactReducer`, `ArtifactState`, `ArtifactRepository` * `core:artifactstore` — `ArtifactStore` (used by `SqliteEventStore` for flush coordination) == invariants * Duplicate `eventId` values are rejected with an error * Session sequence numbers are strictly monotonic — violations throw `error()` * `InMemoryEventStore.append()` is synchronized per session for thread safety * `SqliteEventStore` wraps appends in a JDBC transaction and coordinates with `ArtifactStore.flushBefore()` * `LiveArtifactRepository` lazily subscribes on first query and never unsubscribes == PlantUML diagram [plantuml, infra-persistence, "png"] ---- include::../../diagrams/infra-persistence.puml[] ---- == known issues * `SqliteEventStore` holds an open JDBC `Connection` for the lifetime of the application * No connection pooling or retry logic for SQLite busy conditions == open questions * Should event stores support snapshot-based recovery for long event streams? * Should there be a read-replica event store for projections to avoid write-contention?