Runtime Architecture

causet-runtime-service is the write-side engine of Causet. It receives intents, evaluates rules deterministically, appends events to the ledger, and publishes to Kafka. Every write to Causet flows through this service.


Core Responsibilities

  • Receive intents via HTTP (:8080) or from the Kafka intent ingress topic
  • Resolve the active IR version for the fork from causet-saas-cloud
  • Acquire a per-entity cursor lock for serial ordering
  • Execute the rules pipeline: preflight → core → side_effects
  • Append ledger_events to the causet PostgreSQL database (R2DBC)
  • Update entity_snapshots to reflect new state
  • Write intent_status record with outcome
  • Publish enriched events to causet.projection-events.v1 (async, fire-and-forget)
  • Expose EntityBrowserGrpcService on gRPC port :9090
  • Serve /actuator/health for liveness and readiness probes

Intent Processing Pipeline


Step-by-Step: Processing a Single Intent

  1. Receive — intent arrives via HTTP POST /intent or from causet.intents.v1 Kafka topic.
  2. Resolve IR — the active irVersion for the fork is resolved from causet-saas-cloud. The runtime artifact causet.runtime.json is loaded from S3 (cached in Redis by irVersion).
  3. Validate action — the named action must exist in the active IR. Unknown actions are rejected immediately.
  4. Acquire cursor lock — a per-entity advisory lock is acquired in the causet database. This serializes all writes to a single entity, preventing concurrent intent processing from producing conflicting state.
  5. Load snapshot — the current entity_snapshot for (entity_id, fork_id) is loaded. This is the materialized current state used for rule evaluation.
  6. Run preflight rules — read-only validation rules. Any rule may issue op: reject with a code and message. If any preflight rule rejects, processing stops and the rejection is returned to the caller. No writes occur.
  7. Run core rules — state mutation rules. These produce ledger_events (the events to append). Allowed operations: set, unset, increment, decrement, array_push, array_remove, array_set_at, array_remove_at.
  8. Run side-effects rules — emit, schedule, and submit operations. These produce additional ledger events or downstream intents.
  9. Append ledger events — all produced events are appended to ledger_events in a single R2DBC transaction.
  10. Update snapshotentity_snapshots is upserted with the new entity state derived from applying the emitted events.
  11. Write intent statusintent_status is written with the processing outcome (success or rejection code).
  12. Release lock — the cursor lock is released.
  13. Publish to Kafka — enriched projection events are published to causet.projection-events.v1 as a fire-and-forget operation after the DB transaction completes.
  14. Return response — intent result (intent ID + emitted events) is returned to the caller synchronously.

Per-Entity Serial Ordering via Cursor Lock

All intents targeting the same entity_id are serialized through a cursor lock held in the causet database. This guarantees:

  • No two intents for the same entity execute concurrently.
  • Rule evaluation always sees the latest committed snapshot.
  • The event sequence for a given entity is strictly ordered.

Multiple runtime instances can run simultaneously. The cursor lock is database-level (not in-process), so horizontal scaling is safe. Throughput is bounded by per-entity lock contention — entities with very high write rates become a bottleneck.


Determinism Guarantee

The runtime rules engine is deterministic:

same entity state + same intent → same events

Rules may not:

  • Read from external services or APIs
  • Use wall-clock time (time is injected as a deterministic value)
  • Generate random values
  • Access environment variables or configuration

This guarantee makes replay safe: re-running an intent against a known snapshot always produces the same ledger events.


Reactive Stack (R2DBC)

All database access in causet-runtime is reactive using R2DBC. There is no JDBC, no blocking I/O on the write path. The reactive stack allows the runtime to handle concurrent intents (across different entities) efficiently without thread-per-request overhead.


gRPC Entity Browser

causet-runtime exposes EntityBrowserGrpcService on port :9090. This service allows:

  • Fetching the full ledger event history for a specific (entity_id, fork_id)
  • Fetching the current entity snapshot
  • Browsing decision routes

This is used by the control plane UI and operational tooling. It is not in the hot path for intent processing.


Active IR Version Resolution

On each intent (or on startup with caching), the runtime calls causet-saas-cloud to get the active irVersion for the fork:

GetActiveRelease(forkId) → irVersion

The IR artifact is then loaded from:

s3://{bucket}/{irVersion}/causet.runtime.json

The artifact is cached in Redis by irVersion. On a new deployment (version promotion), the cache entry for the old version naturally expires and the new version is loaded on the next request.


Health Endpoint

GET /actuator/health

Returns liveness and readiness status. Used by Kubernetes/ECS health checks. The readiness probe checks database connectivity and Kafka producer connectivity before marking the instance ready.


Ports Summary

PortProtocolPurpose
8080HTTPIntent ingress, health endpoint
9090gRPCEntity browser (EntityBrowserGrpcService)