Skip to content

Events & the System's Narrative

In EvidentSource, events are the record. Every State View your application reads is computed from them.

Most databases treat current state as the source of truth: rows are updated, old values overwritten, and history is something you reconstruct from logs if you’re lucky. EvidentSource inverts this. The event log is the record, and state is a function of it.

  • “What happened?” becomes a query. Not a ticket, and not an investigation through audit logs stitched together from three systems. A read.
  • State becomes debuggable. If a State View looks wrong, the events that produced it are all still there, in order.
  • History isn’t overwritten. Every earlier revision of a database stays readable, so you can see state as it stood at any point. See Bi-temporal reads.
  • AI agents get context. An event records what happened, and the transaction that appended it records how it arrived. See Decision Traces & AI-Readiness.

An event is a CloudEvent. A client sets:

  • type: what happened, such as com.todomvc.todo.created.
  • source: the stream the event belongs to, such as todos. There is no separate stream field. Send a bare name, not a URI; the server expands it.
  • subject: the entity the event is about. It is optional.
  • data: the payload.
  • time: the event’s effective time, when the fact took effect. It may be in the past or the future. Leave it out, and the effective time is when the event was recorded.

Stream, subject and type are the three attributes every query and append condition selects on. Nothing else is selectable, including time.

  • A new id. Every event gets a fresh id, and the reply maps each id you sent to the one assigned, so give the events in one transaction distinct ids.
  • sequence: the event’s revision in its database.
  • recordedtime: when the database recorded it, by the writer’s clock, and never earlier than the transaction before it.
  • causationid: the id of the transaction that appended it.
  • correlationid: when the caller supplies one.
  • authtype: the kind of caller that appended it, and for an authenticated caller its authid.

The server sets authtype, authid and the other auth attributes itself, from the caller’s credentials or, for an asynchronous command, from the caller the command’s record names, and discards any copy a client puts on an event. They are context, not proof: they are not signed, and the server checks the token they came from only where it is configured to verify tokens, so read authid together with authtype.

Each event takes the next revision of its database: 1, 2, 3, with no gaps. A transaction’s events take consecutive revisions, and a read at a revision always sees whole transactions.

  • id, type and subject must be non-blank and under 256 bytes.
  • An event’s estimated stored size must not exceed 64 KiB. The estimate includes a fixed overhead, so the usable payload is somewhat smaller.
  • time can’t be a leap second.
  1. A client appends events directly in a transaction, or sends a command.
  2. For a command, a State Change component’s decide function reads the database, checks the command, and returns events.
  3. EvidentSource appends the events atomically, if every append condition still holds.
  4. State Views fold the new events: on the next read by default, or as they commit if the view is kept current.
  5. Consumers follow the database with SubscribeDatabaseUpdates, which reports its revision after each committed transaction, and read the new events. A consumer that falls behind can miss an update, so read from the last revision you processed, not just the one reported.

The log is append-only, and every State View is computed from it.