Event Store API
This page defines the minimal event store API required to support Command Context Consistency. The API is language- and technology-agnostic. It defines observable behavior, data structures, and consistency semantics. It does not prescribe a database, storage engine, serialization format, programming language, transport protocol, or implementation architecture.
API Overview
The Event Store API requires three operations:
append(new_events) -> append_result
query(event_query) -> query_result
append_if(new_events, context_query, expected_context_version) -> append_result | conditional_append_conflict
append commits new events.
query reads committed event records and returns query metadata.
append_if commits new events only when the command context has not changed.
These operations define the minimum observable behavior required for Command Context Consistency. Implementations may provide additional operational features, but those features must not change the contract defined here.
Data Structures
NewEvent
NewEvent
event_type
payload
Rules:
event_typeidentifies the kind of event.payloadcontains the recorded event data.- A
NewEventhas nosequence_number. - A
NewEventbecomes anEventRecordonly after a successful append.
EventRecord
EventRecord
sequence_number
occurred_at
event_type
payload
Rules:
sequence_numberis the global committed position.sequence_numberdefines committed order.occurred_atis metadata.occurred_atdoes not define committed order.
EventFilter
EventFilter
event_types
payload_predicates
Rules:
event_typesis optional.payload_predicatesis optional.- If
event_typesis omitted, event type is unconstrained. - If
event_typesis explicitly empty, that filter matches no event type. - If
payload_predicatesis omitted, payload is unconstrained. - If
payload_predicatesis explicitly empty, that filter matches no payload predicate.
EventQuery
EventQuery
filters
min_sequence_number
Rules:
filtersis optional.min_sequence_numberis optional.- If
filtersis omitted or empty, the query matches all committed event records. min_sequence_numberis an exclusive read cursor.min_sequence_numberaffects returned records only.
The absence of filters does not mean no result. It means all records.
A query that matches all records can be large. Implementations may provide batching, limits, streaming, or named replay operations for operational safety. These mechanisms must not change the matching meaning of the query.
QueryResult
QueryResult
event_records
last_returned_sequence_number
current_context_version
Rules:
event_recordsare returned in ascendingsequence_numberorder.last_returned_sequence_numberis derived from returned records.current_context_versionis derived from the full matching context.current_context_versionignoresmin_sequence_number.- These values must stay separate.
If event_records is empty, last_returned_sequence_number is absent.
If no records match the full query context, current_context_version is absent.
A query can return no records while still having a current_context_version when min_sequence_number filters out all returned records but the full context contains matching records.
AppendResult
AppendResult
first_sequence_number
last_sequence_number
committed_count
Rules:
- Describes one committed append batch.
- Does not represent a context version.
ConditionalAppendConflict
ConditionalAppendConflict
expected_context_version
actual_context_version
Rules:
- Returned when
append_ifdetects that the command context changed. expected_context_versionandactual_context_versionmay be absent when the compared context is empty on one side.- No event is committed on conflict.
append
append(new_events) -> append_result
Input
new_events is a non-empty list of NewEvent.
Each submitted event must contain a valid event_type and payload.
Submitted events must not contain committed metadata such as sequence_number or occurred_at.
Success
A successful append:
- commits all submitted events as one batch.
- assigns global sequence numbers.
- returns
AppendResult.
The returned AppendResult describes the committed batch and the sequence range assigned to that batch.
Failure
Empty input is invalid.
Invalid events are rejected.
Backend failure is reported as backend_failure.
Guarantees
No partial commit.
One successful append creates one consecutive sequence range.
Failed append does not commit events.
Failed append does not consume sequence numbers later successful commits would observe.
query
query(event_query) -> query_result
Input
event_query selects committed event records.
The query may select all records, records matching one or more filters, records after a read cursor, or records matching filters after a read cursor.
Success
A successful query:
- returns
QueryResult. - returned records are ordered by ascending
sequence_number.
Important distinction
last_returned_sequence_number describes only returned records.
current_context_version describes the full matching context.
min_sequence_number affects returned records only.
min_sequence_number does not affect current_context_version.
Failure
Invalid query shape is rejected.
Backend failure is reported as backend_failure.
Example
query:
filters:
- event_types: [tool_registered, tool_checked_out]
payload_predicates:
- tool_id: tool_1
min_sequence_number: 40
For this query:
- returned records have
sequence_number> 40. current_context_versionis calculated without applyingmin_sequence_number.
append_if
append_if(new_events, context_query, expected_context_version)
-> append_result | conditional_append_conflict
Input
new_events is a non-empty list of NewEvent.
context_query selects the facts relevant to the command decision.
expected_context_version is the context version observed before the command produced new events.
expected_context_version is the context version observed from a previous query.
If the previous query had no matching context, expected_context_version is absent.
Success
Store calculates actual context version from context_query.
Calculation ignores min_sequence_number.
If actual equals expected, append succeeds.
Returns AppendResult.
The actual context version may be absent when the context query matches no committed records.
An absent actual context version matches an absent expected context version.
An absent actual context version does not match a present expected context version.
A present actual context version does not match an absent expected context version.
Conflict
If actual differs from expected, append is rejected.
Returns ConditionalAppendConflict.
No event is committed.
No partial batch is committed.
Failed conditional append does not consume sequence numbers later successful commits would observe.
Guarantees
Consistency check and append are one atomic operation.
context_query defines the consistency boundary.
No aggregate, stream, subject, or entity boundary is required by the API.
Event Query Semantics
| Element | Semantics |
|---|---|
filters omitted |
matches all records |
filters: [] |
matches all records |
| multiple filters | OR across filters |
event_types omitted |
event type unconstrained |
event_types: [] |
this filter matches no event type |
multiple event_types |
OR across event types |
payload_predicates omitted |
payload unconstrained |
payload_predicates: [] |
this filter matches no payload predicate |
multiple payload_predicates |
OR across payload predicates |
| event type and payload in same filter | AND between constraints |
min_sequence_number omitted |
no read cursor |
min_sequence_number present |
exclusive read cursor |
A record matches an EventQuery when it matches at least one EventFilter.
A record matches an EventFilter when the event type constraint matches, if present, and the payload predicate constraint matches, if present.
Payload predicate semantics:
- scalar values match by equality.
- objects match recursively by subset.
- arrays match when every predicate element is contained somewhere in the payload array.
- extra keys in the event payload are allowed.
- extra array elements in the event payload are allowed.
Sequence Semantics
- sequence numbers are global.
- sequence numbers are monotonically increasing.
- sequence numbers define committed order.
- one committed append batch receives one consecutive sequence range.
occurred_atdoes not define order.- failed append and failed conditional append do not partially commit.
- failed append and failed conditional append do not consume sequence numbers later successful commits would observe.
Error Semantics
empty_append
empty_append is reported when append or append_if receives no new events.
No event is committed.
conditional_append_conflict
conditional_append_conflict is returned when the actual context version differs from the expected context version.
The result includes the expected and actual context versions.
invalid_event
invalid_event is reported when a submitted NewEvent is malformed or contains fields that belong only to committed records.
No event from the submitted batch is committed.
invalid_query
invalid_query is reported when an EventQuery or EventFilter has an invalid shape.
The store does not execute an invalid query.
backend_failure
backend_failure is reported when the store cannot complete the requested operation because of a storage or execution failure.
The API must not report backend failure as a successful append, query, or conditional conflict.
Compliance Checklist
Append
- rejects empty append.
- commits non-empty batch atomically.
- assigns consecutive sequence range.
- returns append result.
- failed append commits nothing.
Query
- returns records ordered by ascending sequence number.
- applies
min_sequence_numberexclusively. - separates
last_returned_sequence_numberfromcurrent_context_version. - ignores
min_sequence_numberforcurrent_context_version. - implements filter semantics exactly.
Append_if
- computes context version from full context query.
- ignores
min_sequence_numberfor conflict detection. - commits only when expected and actual context versions match.
- returns conflict with expected and actual versions on mismatch.
- failed conditional append commits nothing.