Event-Driven System
Concise architecture contract for game-domain write paths.
Purpose
The game service is event-sourced:
- commands express intent
- deciders produce decisions
- accepted decisions emit immutable events
- projections are derived by applying events in sequence order
The event journal is authoritative. Projection state is derived and rebuildable.
Core lifecycle
request -> command -> decider -> event append -> projection apply
Required outcomes:
- Mutations are represented by emitted events, not direct projection writes.
- Rejected commands emit no domain mutation event.
- Projection state can be reconstructed from the event journal.
Write-path sequence
sequenceDiagram
autonumber
participant H as Handler
participant E as Domain Engine
participant D as Decider
participant J as Event Store
participant P as Projection Applier
H->>E: Execute(command)
E->>D: Decide(command, state)
D-->>E: decision (events or rejection)
E->>J: Append(events)
E->>P: Apply(events by intent)
E-->>H: result
Non-negotiable invariants
- Event append is the source of truth for accepted mutations.
- Request handlers must not write projection stores directly.
- Core command paths must not emit system-owned event types.
- System command paths must not emit core-owned event types.
- System-owned envelopes must carry
system_idandsystem_version. - Event payloads must remain replay-safe and deterministic.
- Replay and projection behavior must be explicit through event intent.
Event intent model
Every event definition declares intent:
IntentProjectionAndReplay: fold + project (default for most events)IntentReplayOnly: fold only, no projection writeIntentAuditOnly: journal-only, neither fold nor projection
Intent defines required handlers and startup validation obligations.
Registration and validation contract
Startup validation must fail fast when contracts are inconsistent.
Minimum guarantees:
- every replay-relevant event has fold coverage
- every projection-relevant event has adapter coverage
- audit-only events do not accumulate dead fold handlers
- unknown system module/adapter routing is treated as a startup or replay error
Event namespacing convention
Core events and system events occupy distinct namespaces:
- Core events use flat
<aggregate>.<action>names (e.g.campaign.created,session.started). - System events are prefixed with
sys.<system_id>.*(e.g.sys.daggerheart.character.created).
System commands must carry system_id and system_version in the command envelope. The command registry validates that the system_id matches the sys.<system_id> prefix of the command type at registration time (ErrSystemTypeNamespaceMismatch). Core commands must not carry system metadata, and system commands must not emit core-owned event types. This namespace boundary is enforced at both registration and execution time.
System extension rules
When adding mechanics or systems:
- Register command and event definitions in the owning module.
- Keep command/event ownership boundaries explicit (
corevssys.<system_id>.*). - Route all mutating paths through shared execute-and-apply orchestration.
- Add fold and adapter coverage for new projection/replay intents.
- Update generated catalogs in
docs/events/.
Consistency model
Projection state is eventually consistent with the event journal, even in the inline-apply path.
The inline-apply write sequence is:
- Domain engine appends events to the journal (SQLite transaction commits).
- Handler loops over emitted events and calls
Applyfor each one (separate SQLite transactions).
Between steps 1 and 2, concurrent readers see durable events but stale projection state. This window is typically sub-millisecond under normal load. It is intentional: the event journal is authoritative and projections are derived state that can always be rebuilt.
The outbox-only apply mode has a wider consistency window (seconds, governed by the outbox worker poll interval) but identical semantics — events are always durable before projections update.
Deep references
Use these pages for detailed mechanics and troubleshooting:
- gRPC write path — transport-layer helpers, error boundaries, and Options
- Event system reference
- Event replay architecture
- Event contracts catalog
- System extension architecture