StateDerived State

Derived State

In Causet, all state is derived. Entity snapshots are derived from rules applied to ledger events. Projection tables are derived from events via the projection worker. There is no pathway for a human or external system to directly write to entity state or projection tables outside of the event-driven pipeline.

The Derivation Chain

Every state change in the system traces back to a committed event. Events trace back to intents. Intents trace back to callers. The full chain is auditable.

What “Derived” Means in Practice

Entity Snapshots

An entity snapshot is a materialized view of an entity’s state at the current point in the ledger. It is recomputed by the rules engine whenever a new event affects that entity.

The snapshot does not have its own authoritative state — it is computed state. Delete the entity_snapshots table and replay the ledger through the rules engine to get it back, exactly as it was.

Projection Tables

Projection tables are materialized query views. They are updated asynchronously by the causet-projection-worker as events arrive from Kafka.

Like entity snapshots, projection tables are derived artifacts. They can be dropped and rebuilt from the ledger at any time.

Aggregates as Derived State

Aggregate values — counts, sums, max values — are derived from the event stream rather than maintained with explicit update logic:

aggregates:
  LIKE_CREATED:
    like_count: { op: add, by: 1, floor: 0 }
  LIKE_REMOVED:
    like_count: { op: add, by: -1, floor: 0 }

The like_count value for a given show is derived by summing all LIKE_CREATED and LIKE_REMOVED events for that entity. The floor prevents the count from going negative.

Contrast with Manually Updated Fields

Manually maintained state — where application code writes directly to a database table — has several problems in high-scale systems:

ConcernDerived state (Causet)Manually maintained
AuditabilityEvery transition has an eventChanges may not be logged
ReplayRebuild from ledger at any timeNo replay mechanism
ConsistencyRules enforced on every writeApplication logic may diverge
BackfillAdd new field → replay ledgerAdd new field → write migration
DebuggingCheck ledger for causeCheck application logs

Derived state eliminates a class of bugs where application code updates one table but forgets another, or where a race condition leaves two tables inconsistent.

Freshness Trade-off

Derived state has a freshness trade-off:

  • Entity snapshots: updated synchronously on the write path — always current as of the last committed event for that entity
  • Projection tables: updated asynchronously — there is lag between event commit and projection update

For most read queries, projection lag is acceptable (typically milliseconds to seconds). For queries that must reflect the very latest state of a specific entity, read the entity snapshot directly rather than a projection table.

// Reflects latest committed state (synchronous)
const entity = await causet.getEntity("user", userId);
 
// May lag behind by projection worker latency (asynchronous)
const rows = await db.query(
  "SELECT * FROM user_stats WHERE user_id = $1",
  [userId]
);

Adding Derived Fields

To add a new derived field to entity state:

  1. Add the field to the state: definition with a default value
  2. Add a core rule to accumulate or compute the field
  3. Deploy the updated IR (causet.runtime.json)
  4. Rebuild entity snapshots from the ledger to backfill the new field

The rebuild replays all ledger events through the updated rules and populates the new field for every entity. This is deterministic — the same field value that would have been computed in real-time is computed during replay.

To add a new derived field to a projection:

  1. Add the field to the projection definition
  2. Deploy the updated IR (causet.projections.json)
  3. Trigger a projection rebuild for the affected table

See Rebuilding Memory for the rebuild procedure.

Temporal Queries

Because the ledger is append-only and events are timestamped, you can replay the ledger up to a specific timestamp to reconstruct what any entity’s state looked like at that point in time.

SELECT event_id, event_type, entity_id, ts, payload
FROM ledger_events
WHERE entity_type = 'user'
  AND entity_id = 'user_abc123'
  AND ts <= 1719000000000
ORDER BY ts ASC;

Applying these events through the rules engine produces the entity’s state as of timestamp 1719000000000. This is useful for audits, debugging, and temporal analytics.