Dispatching Events
In Causet, events are emitted by rules — not dispatched directly by clients. Clients submit intents; rules decide which events result.
The Emit Model
Client submits intent
→ preflight rules run (validate)
→ core rules run (emit events on own entity)
→ side_effects rules run (emit to other entities, submit intents, schedule)
→ ledger_events appended
→ Kafka publish (projection fan-out)You never emit events directly via an API call. The only way to produce a ledger event is for a rule to execute an emit operation during intent processing.
op: emit — Emit a Single Event
The primary way to produce events.
In core mode (own entity)
core:
rules:
- name: emit_followed
when: {}
then:
- op: emit
event_type: ARTIST_FOLLOWED
payload:
user_id: intent.user_id
artist_id: intent.artist_idIn core mode, emit produces an event on the same entity that the intent targets. The entity ID is resolved from the event’s declared entity_expr.
In side_effects mode (another entity)
In side_effects mode, emit can target a different entity:
side_effects:
rules:
- name: notify_artist_of_follower
when: {}
then:
- op: emit
event_type: FOLLOWER_ADDED
entity_id: intent.artist_id # emit to the artist entity
payload:
artist_id: intent.artist_id
follower_id: intent.user_id
followed_at: intent.tsThe entity_id field in a side_effects emit specifies which entity receives the event.
Note: The
entity_idin aside_effectsemit must match theentity_exprresolution for the target event type. The runtime validates this at evaluation time.
op: emit_each — Emit Per Item in a Collection
emit_each iterates over an array and emits one event per item:
side_effects:
rules:
- name: notify_all_followers
when: { expr: "state.followers != null" }
then:
- op: emit_each
over: state.followers # iterate over this array
item_as: follower_id # bind each item to this variable
event_type: SHOW_NOTIFICATION_SENT
entity_id: follower_id # emit to each follower's entity
payload:
user_id: follower_id
artist_id: intent.artist_id
show_id: intent.show_idemit_each produces N events — one per element in the over array. All events are appended to the ledger in the same transaction as the parent intent.
op: submit — Submit a Downstream Intent
submit enqueues a new intent targeting another entity. The downstream intent is processed asynchronously after the current intent completes:
side_effects:
rules:
- name: trigger_ticket_confirmation
when: {}
then:
- op: submit
action: SEND_PURCHASE_CONFIRMATION
entity_id: intent.user_id
payload:
ticket_id: intent.ticket_id
show_id: intent.show_id
email: intent.emailThe submitted intent is independent — it has its own cursor lock, rule evaluation, and ledger write. If it fails, it does not roll back the parent intent.
Use submit for cross-entity workflows where the downstream entity needs its own state mutation.
op: schedule — Delayed Intent Submission
schedule submits an intent to be processed after a specified delay:
side_effects:
rules:
- name: send_followup_24h
when: {}
then:
- op: schedule
action: SEND_FOLLOW_UP
entity_id: intent.user_id
delay_seconds: 86400 # 24 hours
payload:
user_id: intent.user_id
artist_id: intent.artist_idThe scheduled intent is stored and will be submitted after delay_seconds elapses. It goes through the full intent processing pipeline when it fires.
Note: Scheduled intents are a feature of
causet-saas-cloud. They require the SaaS layer. Scheduling is not available when callingcauset-runtimedirectly.
What Cannot Be Done in Rules
Rules are deterministic and side-effect-free within the evaluation context:
| Prohibited | Why |
|---|---|
| HTTP calls to external APIs | Non-deterministic, breaks replay |
Database reads (other than via lookup) | Non-deterministic |
Date.now() or system time | Non-deterministic — use intent.ts |
| Writing to a queue directly | Not available in the expression language |
| Random number generation | Non-deterministic |
For external integration (webhooks, emails, third-party APIs), use the submit op to trigger a dedicated action that handles the integration, and track the send state on the entity to guard against duplicates.
Direct Event Injection (Internal/Admin Only)
There is no supported public API for injecting events directly into the ledger without going through intent processing. This is intentional — skipping rules evaluation means the snapshot and downstream projections may become inconsistent.
In operational emergencies, the admin layer can write directly to ledger_events. This is a last-resort procedure that must be followed by a full snapshot rebuild and projection rebuild for the affected entity.
Projection Events (Runtime to Kafka)
After all ledger_events are committed, causet-runtime publishes enriched projection events to causet.projection-events.v1. This is internal infrastructure — your rules do not publish to Kafka directly. The projection event envelope is generated automatically from the ledger events.
Related Pages
- Concepts: Intents — full list of operations and modes
- Defining Events — how event types are declared
- Events — full lifecycle after emit
- Event Metadata — how
emitpopulates reserved fields