Rules Engine
The rules engine is the core of causet-runtime. Given an entity’s current state and an intent, it evaluates your declared rules to produce ledger events and downstream effects.
Mode Execution Order
Rules are organized into modes. Modes execute in a fixed order:
preflight → core → projection → side_effects → fraud → moderation| Mode | Can Write State | Can Reject | Can Emit | Purpose |
|---|---|---|---|---|
preflight | No | Yes | No | Read-only validation. Reject early before any writes. |
core | Yes | No | Yes (own stream) | State mutations — set fields, push to arrays, emit events on own entity. |
projection | No | No | No | Cross-stream lookups for use in later modes. |
side_effects | No | No | Yes | Emit events to other streams, submit intents, schedule delayed effects. |
fraud | No | Yes | No | Post-core fraud/risk checks. Can reject after state changes are staged but not yet committed. |
moderation | No | Yes | No | Content moderation checks. |
Modes not declared in an action are skipped.
Rule Structure
Each rule has a name, a condition, and a list of operations:
actions:
FOLLOW_ARTIST:
state: user
input:
user_id: { type: string, required: true }
artist_id: { type: string, required: true }
preflight:
rules:
- name: reject_self_follow
when: { expr: "intent.user_id == intent.artist_id" }
then:
- op: reject
code: CANNOT_FOLLOW_SELF
message: "A user cannot follow themselves."
core:
rules:
- name: emit_followed
when: {}
then:
- op: emit
event_type: ARTIST_FOLLOWED
payload:
user_id: intent.user_id
artist_id: intent.artist_idwhen
The condition under which the rule’s operations run:
when: {}— always true (unconditional)when: { expr: "..." }— evaluated expression; rule runs only if the expression is truthy
then
The list of operations to execute. Operations are described below.
Rule naming
Rule names are snake_case and must be unique within a mode. Names appear in intent status records, audit logs, and error messages.
Operations
Mutation (core mode only)
| Operation | Description |
|---|---|
op: set | Set a field on the entity state |
op: unset | Remove a field from the entity state |
op: increment | Increment a numeric field by a value |
op: decrement | Decrement a numeric field by a value |
- op: set
field: last_followed_at
value: intent.ts
- op: increment
field: follow_count
by: 1Array operations (core mode only)
| Operation | Description |
|---|---|
op: array_push | Append a value to an array field |
op: array_remove | Remove the first matching value from an array field |
op: array_set_at | Set a value at a specific index |
op: array_remove_at | Remove the value at a specific index |
- op: array_push
field: following
value: intent.artist_id
- op: array_remove
field: following
value: intent.artist_idControl flow
| Operation | Description |
|---|---|
op: reject | Reject the intent with a code and message (preflight and fraud/moderation only) |
- op: reject
code: ARTIST_NOT_FOUND
message: "The specified artist does not exist."Events
| Operation | Description |
|---|---|
op: emit | Emit an event to the current entity’s stream (core mode) or another stream (side_effects mode) |
op: emit_each | Emit an event for each item in an iterable, with per-item payload |
# core: emit on own stream
- op: emit
event_type: ARTIST_FOLLOWED
payload:
user_id: intent.user_id
artist_id: intent.artist_id
# side_effects: emit to another entity
- op: emit
event_type: FOLLOWER_ADDED
entity_id: intent.artist_id
payload:
follower_id: intent.user_id
followed_at: intent.tsEffects (side_effects mode only)
| Operation | Description |
|---|---|
op: submit | Submit a downstream intent to another entity |
op: schedule | Schedule a delayed intent (fires after delay_seconds) |
- op: submit
action: NOTIFY_ARTIST
entity_id: intent.artist_id
payload:
new_follower_id: intent.user_id
- op: schedule
action: SEND_FOLLOW_UP_EMAIL
entity_id: intent.user_id
delay_seconds: 86400
payload:
artist_id: intent.artist_idCross-stream lookups (projection mode only)
| Operation | Description |
|---|---|
op: lookup | Load the current snapshot of another entity into the evaluation context |
projection:
rules:
- name: load_artist
when: {}
then:
- op: lookup
stream: artist
entity_id: intent.artist_id
as: artistAfter a lookup, the loaded entity’s state is accessible as artist.<field> in subsequent modes.
Expression field access: LOOKUP_FIELD
Within expression strings, use LOOKUP_FIELD(stream, entityId, fieldName) to inline a field from a loaded entity.
Expression Language
Expressions in when conditions and value / payload fields are evaluated by the runtime’s deterministic expression engine.
Accessible contexts:
intent.*— the intent’s input payload fieldsstate.*— the current entity’s snapshot fieldsevent.*— available inderiveexpressions in projections (not in action rules)- Lookup results — accessible as the alias declared in the
lookupop
Determinism constraints:
- No external HTTP or database calls
- No
Date.now()or wall-clock access (useintent.tsfor the current timestamp) - No random number generation
- No environment variable access
Operators:
== != < > <= >=
&& || !
+ - * /
contains starts_with ends_withType coercion:
- Numeric strings are compared as strings unless cast explicitly.
- Null/missing fields evaluate as
nullin expressions.
Bytecode Compilation
Rules are not interpreted at runtime from YAML. The Causet compiler compiles all action rules into a ruleset bytecode representation stored in causet.runtime.json. causet-runtime loads this bytecode at startup (or on IR version change) and evaluates it directly.
This means:
- Rule evaluation is fast — no YAML parsing at request time.
- Compilation errors are caught at compile time, not at runtime.
- The bytecode is versioned alongside the IR.
Rule Evaluation in Context
During evaluation, the following are available:
| Name | Source |
|---|---|
intent | The submitted intent’s payload |
state | The current entity snapshot |
lookup results | Entities loaded in the projection mode |
ts | Deterministic timestamp for this intent |
Related Pages
- Intents — how intents are submitted and validated
- Defining Events — how emitted events are declared in the DSL
- Dispatching Events — emit, emit_each, submit, and schedule
- Runtime Architecture — rules evaluation in the processing pipeline