Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reactive modules

Reactive module grammar V1 is the compile-time application surface for named event streams, query watches, and contextual subscriptions. Authors place the bounded source in a .riffr file and declare it in Application Source V4. RiffDB resolves every name against one exact contract and immutable query modules before it creates any deployable artifact.

reactive TicketActivity version 1 {
  stream TicketEvents($organization_id: Ticket.organization_id) {
    partition (organization_id = $organization_id);
    event TicketChanged select (organization_id, ticket_id, status);
  }
  watch TicketWatch(
    $organization_id: Ticket.organization_id,
    $ticket_id: Ticket.ticket_id
  ) query GetTicket updates patch;
  subscription TicketAgent($organization_id: Ticket.organization_id) {
    stream TicketEvents(organization_id = $organization_id);
    hydrate ticket query GetTicket(
      organization_id = $organization_id,
      ticket_id = event.ticket_id
    );
    reaction assign command AssignTicket;
    limits { batch 4; in_flight 4; lease_seconds 60; }
  }
}

Static guarantees

  • A stream has one complete partition binding shared by every selected event.
  • Event types and payload fields are explicit. Predicates can use only selected fields and typed parameters.
  • A patch watch is accepted only when its query returns a complete authorized primary key. Other bounded queries use reset updates.
  • A subscription binds one exact stream, no more than 16 bounded hydration queries, no more than 32 named command reactions, and fixed delivery limits.
  • Aggregate query work remains within 500 rows and encoded output remains within four MiB.
  • Batch and in-flight limits are each 1 through 8. Leases are 5 through 900 seconds.

Reactive source, each compiled operation, and the complete module have separate domain-separated hashes. Lock V5 records the grammar and IR versions, all source/module/operation hashes, exact query dependencies, V2 role definitions, and generated artifact hashes.

Least-authority roles

Application Source V4 roles name event_streams, watch_queries, and agent_subscriptions. The compiler lowers those names to exact permissions bound to contract lineage, reactive module hash, and operation name:

Role declarationDerived permission
event_streamsConsumeEventStream
watch_queriesWatchNamedQuery
agent_subscriptionsConsumeContextualSubscription

SeekEventStreamConsumer is separate administrative authority. Normal role compilation never grants it as a consequence of consuming a stream. Reactive permissions also do not imply raw commit-log, entity, index, or ad-hoc query access.

Publication and durable consumption

riffdb application deploy publishes every locked reactive module after its exact contract and immutable query modules are available. Publication compiles the reviewed .riffr source on the server, verifies its name, version, module hash, contract identity, and query dependencies against the application lock, then retains the module atomically through the shared authorized control plane. Publishing the same immutable module again is idempotent. Reusing a name and version for different content is a closed version conflict.

riffdb dev uses this same locked publication path before it binds the selected development role, so generated watches and contextual subscriptions are available when the readiness line is emitted.

A durable consumer is identified by the complete tuple of reactive module hash, operation name, canonical typed parameters, and consumer name. It is therefore not silently rebound when a module changes. Within one consumer, eligible events are leased in partition order with at-least-once delivery. An event is advanced only by an exact acknowledgement carrying the event ID, lease token, and current history incarnation. Negative acknowledgement schedules a bounded retry; lease expiry does the same during recovery. The tenth failed attempt is retained as a dead letter so later eligible events can continue without pretending the failed event was acknowledged.

Use the exact reactive module hash from the application lock or deployment state. Parameters use NAME=JSON_VALUE, so string values include JSON quotes:

riffdb event consume \
  --module-hash <64-hex-module-hash> \
  --operation TicketEvents \
  --parameter 'organization_id={"type":"uuid","value":"018f6f50-6f31-7d62-9a7e-4f8b913d2f11"}' \
  --consumer-name ticket-indexer \
  --batch-limit 4 \
  --lease-seconds 60

riffdb event ack \
  --module-hash <64-hex-module-hash> \
  --operation TicketEvents \
  --parameter 'organization_id={"type":"uuid","value":"018f6f50-6f31-7d62-9a7e-4f8b913d2f11"}' \
  --consumer-name ticket-indexer \
  --event-id <commit-sequence:event-ordinal> \
  --lease-token <64-hex-lease-token> \
  --history-incarnation <incarnation>

riffdb event status \
  --module-hash <64-hex-module-hash> \
  --operation TicketEvents \
  --parameter 'organization_id={"type":"uuid","value":"018f6f50-6f31-7d62-9a7e-4f8b913d2f11"}' \
  --consumer-name ticket-indexer

event nack accepts the same lease identity plus an optional retry delay. event seek is an administrative checkpoint move and requires separately granted seek authority. event retire permanently retires that exact consumer and releases its retention fence. An active consumer’s checkpoint prevents history pruning past the commit that still contains its first required event.

Backup and restore preserve consumer state but publish a new history incarnation. Outstanding pre-restore lease tokens then fail closed, and the consumer resumes from its restored checkpoint. Consumers in sibling named databases remain isolated.

Delivery boundary

The public surface includes immutable publication, durable pull and gRPC stream consumption, leases, acknowledgement, retry, dead-letter, recovery, seek, retire, status, and retention fencing. Live queries use compiler-derived invalidation, checked cursor reconnect, and the update protocol described in Live Named Queries. Generated Rust, Go, TypeScript, Python, MCP, CLI, and application-owned browser relay helpers adapt those same operations as described in Reactive Application Clients.

Contextual subscriptions combine one leased event with freshly authorized, same-snapshot hydration and currently authorized declared reactions. A normal event-consumer permission does not imply contextual, query, or command authority. MCP uses the same service boundary and its resource notifications contain no event or context payload. See Contextual Agent Subscriptions.

See Domain Events, Immutable Query Modules, and Application Source and Exact Lock.