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
ModeCan Write StateCan RejectCan EmitPurpose
preflightNoYesNoRead-only validation. Reject early before any writes.
coreYesNoYes (own stream)State mutations — set fields, push to arrays, emit events on own entity.
projectionNoNoNoCross-stream lookups for use in later modes.
side_effectsNoNoYesEmit events to other streams, submit intents, schedule delayed effects.
fraudNoYesNoPost-core fraud/risk checks. Can reject after state changes are staged but not yet committed.
moderationNoYesNoContent 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_id

when

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)

OperationDescription
op: setSet a field on the entity state
op: unsetRemove a field from the entity state
op: incrementIncrement a numeric field by a value
op: decrementDecrement a numeric field by a value
- op: set
  field: last_followed_at
  value: intent.ts
 
- op: increment
  field: follow_count
  by: 1

Array operations (core mode only)

OperationDescription
op: array_pushAppend a value to an array field
op: array_removeRemove the first matching value from an array field
op: array_set_atSet a value at a specific index
op: array_remove_atRemove the value at a specific index
- op: array_push
  field: following
  value: intent.artist_id
 
- op: array_remove
  field: following
  value: intent.artist_id

Control flow

OperationDescription
op: rejectReject 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

OperationDescription
op: emitEmit an event to the current entity’s stream (core mode) or another stream (side_effects mode)
op: emit_eachEmit 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.ts

Effects (side_effects mode only)

OperationDescription
op: submitSubmit a downstream intent to another entity
op: scheduleSchedule 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_id

Cross-stream lookups (projection mode only)

OperationDescription
op: lookupLoad 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: artist

After 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 fields
  • state.* — the current entity’s snapshot fields
  • event.* — available in derive expressions in projections (not in action rules)
  • Lookup results — accessible as the alias declared in the lookup op

Determinism constraints:

  • No external HTTP or database calls
  • No Date.now() or wall-clock access (use intent.ts for the current timestamp)
  • No random number generation
  • No environment variable access

Operators:

==  !=  <  >  <=  >=
&&  ||  !
+  -  *  /
contains  starts_with  ends_with

Type coercion:

  • Numeric strings are compared as strings unless cast explicitly.
  • Null/missing fields evaluate as null in 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:

NameSource
intentThe submitted intent’s payload
stateThe current entity snapshot
lookup resultsEntities loaded in the projection mode
tsDeterministic timestamp for this intent