EventsEvent Metadata

Event Metadata

Every event in Causet carries automatic metadata set by the runtime. This metadata is always present and cannot be overridden by the payload.


Reserved Fields

Three fields are automatically populated on every event from the event envelope:

FieldTypeDescription
typestringThe event type name — e.g. ARTIST_FOLLOWED
tsbigintEvent timestamp in milliseconds since epoch (Unix ms)
entity_idstringThe entity this event belongs to, resolved from entity_expr

These fields are available as event.type, event.ts, and event.entity_id in projection derive expressions and rule contexts.

Note: Do not declare type, ts, or entity_id in your payload definition. The compiler will reject them.


Where Reserved Fields Come From

When causet-runtime emits an event, it populates the envelope:

{
  "type": "ARTIST_FOLLOWED",
  "ts": 1719331200000,
  "entity_id": "user-123",
  "payload": {
    "user_id": "user-123",
    "artist_id": "artist-456"
  }
}
  • type is set from the event_type field of the emit operation.
  • ts is the deterministic timestamp of the intent (injected by the runtime — not Date.now()).
  • entity_id is resolved from the declared entity_expr for the event type.

Additional Envelope Fields

Beyond the three reserved fields, the event envelope also carries:

FieldDescription
fork_idThe fork this event belongs to
ir_versionThe IR version that was active when this event was emitted
sequence_numberMonotonically increasing per (entity_id, fork_id)
intent_idUUID of the intent that caused this event

These fields are stored in ledger_events columns. They are accessible via the gRPC entity browser but are not part of the event payload available in rules or derive expressions.


Intent Reference

Every event is traceable to the intent that caused it via intent_id. This links ledger_events to intent_status:

SELECT
    le.event_type,
    le.entity_id,
    le.ts,
    is_.action,
    is_.status,
    is_.processed_at
FROM ledger_events le
JOIN intent_status is_ ON le.intent_id = is_.id
WHERE le.entity_id = 'user-123'
ORDER BY le.sequence_number;

This enables full causality tracing: given any event, you can find the original intent that caused it.


Fork ID

The fork_id on each event scopes it to a specific fork (e.g. production, staging). Events from different forks are stored in the same ledger_events table, filtered by fork_id. Projection events carry the fork_id and the derived tenant_schema for routing to the correct projection table.


Correlation in Payloads

For complex flows involving multiple services, sagas, or cross-entity events, it is good practice to include explicit correlation fields in event payloads:

events:
  TICKET_PURCHASED:
    state: user
    entity_expr: event.user_id
    payload:
      user_id:        string
      show_id:        string
      artist_id:      string
      ticket_id:      string
      price:          number
      purchased_at:   datetime
      correlation_id: string   # optional: trace ID from the originating request
      caused_by:      string   # optional: intent ID or upstream event ID

Carrying correlation_id through the event chain lets you trace a user action through the full distributed system:

HTTP request → intent (correlation_id: abc123)
  → TICKET_PURCHASED (correlation_id: abc123)
  → SHOW_CAPACITY_DECREMENTED (correlation_id: abc123)
  → PURCHASE_CONFIRMATION_SENT (correlation_id: abc123)

Using event.ts in Projections

event.ts is the canonical timestamp for an event and should be used in projections instead of the database NOW():

projections:
  user_purchases:
    source_events: [TICKET_PURCHASED]
    fields:
      user_id:      TEXT
      ticket_id:    TEXT
      purchased_at: BIGINT
    derive:
      user_id:      event.user_id
      ticket_id:    event.ticket_id
      purchased_at: event.ts     # use event timestamp, not wall-clock

Using event.ts ensures that a projection rebuild produces identical timestamps to the original projection materialization.


Using event.ts in Rules

In rule expressions and payload values, use intent.ts for the current timestamp (not Date.now() or system time):

core:
  rules:
    - name: emit_purchase
      when: {}
      then:
        - op: emit
          event_type: TICKET_PURCHASED
          payload:
            purchased_at: intent.ts   # deterministic — safe for replay

intent.ts is the deterministic timestamp injected by the runtime for the intent’s execution context.