Rule chains
Where the rules engine handles simple condition → action logic per point, rule chains are a graph of nodes that route, transform, enrich, and act on messages as they flow through. A chain is the place to build multi-step pipelines: filter, look up a related asset, gate on blockchain finality, create an alarm, and push to an external system — all with explicit branching, retries, and a dead-letter queue.
Rule chains are tenant-scoped and stored in Aerospike namespace rules, set
chains (keyed {tenant_id}:{chain_id}), with immutable deployed versions in
rules/chain_versions (keyed {tenant_id}:{chain_id}:{version}).
The graph model
Section titled “The graph model”A rule chain version is a directed graph: nodes connected by links, with a
designated root_node_id where messages enter. A version record carries
chain_id, version, name, status, nodes, links, root_node_id,
created_by, deployed_at, config_hash, and schema_version.
flowchart TD
input["input:<br/>telemetry"] --> filter{"filter:<br/>CEL<br/>data.temperature > 80"}
filter -->|true| finality["blockchain:<br/>require final proof"]
filter -->|false| done([drop])
finality -->|final| enrich["enrichment:<br/>get related asset"]
finality -->|not final| wait([route to not_final])
enrich --> alarm["action:<br/>create alarm"]
alarm --> connector["connector:<br/>HTTP POST<br/>(SSRF-checked)"]
connector -->|2xx| ok([success])
connector -->|retries exhausted| dlq[("DLQ")]
Node types
Section titled “Node types”The runtime supports these built-in node categories:
| Category | Nodes |
|---|---|
| input | telemetry, device event, alarm event, RPC response, schedule |
| filter | CEL filter, message type switch, originator type switch, relation filter |
| transform | script transform, rename / copy / delete keys, JSON path, split array |
| enrichment | device attributes, customer attributes, related entity data, latest telemetry |
| action | create alarm, clear alarm, save telemetry, update attributes, send RPC, schedule notification |
| connector | HTTP, MQTT, Kafka, RabbitMQ, Google Pub/Sub, AWS SNS/SQS/Lambda, Redpanda, email |
| flow | checkpoint, delay, deduplication, retry, output, rule-chain input |
| blockchain | require final proof, attach proof status, export proof bundle |
A few worth calling out for operators:
- Enrichment from related entities. If device
D1is related to assetA1bylocated_at, a “get related asset” node attachesasset_id=A1and configured asset fields to the message metadata — letting downstream nodes branch on asset context. See the entity model. - Blockchain finality filter. A
require_final_proofnode inspects the message’s proof state. If a point is onlyanchored(not yetfinal), the node delays or routes it to anot_finalrelation per its config; once the point reachesfinalthe message continues. This is how a chain can guarantee it only acts on blockchain-finalized data. - Create alarm. Routes into the first-class alarm subsystem — see Alarms & notifications.
Draft and immutable active versions
Section titled “Draft and immutable active versions”Editing happens on a draft; deploying produces an immutable version.
-
Create / edit a draft. Creating a chain starts version 1 with
status="draft"and publishesrulechain.created.T1.{chain_id}. -
Validate before deploy. Deployment runs graph validation first (see below).
-
Deploy. On success the new version becomes active; the previously active version is retained for rollback. A
rulechain.deployed.T1.{chain_id}.v{n}event is published.
Because deployed versions are immutable, you always know exactly which graph processed a given message — the version is recorded on DLQ records and debug traces.
Deploy-time graph validation
Section titled “Deploy-time graph validation”A chain cannot be deployed unless its graph is structurally sound. Validation
rejects, with INVALID_ARGUMENT and specific errors, problems such as:
- A link pointing to a missing node (e.g. a link to
N9that does not exist) — the error identifies the missing node. - Cycles in the graph and unreachable nodes.
If validation fails, the previously deployed version stays active — a bad draft never takes a running chain down.
Rollback
Section titled “Rollback”Every deployed version is retained, so rolling back is just re-activating an earlier
version. Rolling chain C1 from active version 4 back to version 3 makes version 3
active again and publishes rulechain.rolled_back.T1.C1.v3.
Connectors: secrets and SSRF policy
Section titled “Connectors: secrets and SSRF policy”Connector nodes reach out to external systems (HTTP endpoints, Kafka, MQTT, cloud queues, email). They MUST support bounded retries, timeouts, authentication, secret references, and SSRF-safe outbound networking.
- Secret references. Credentials are referenced, not inlined — connector config holds a reference resolved at runtime, so secrets are never stored in the chain graph or included in export bundles.
- SSRF policy. Outbound targets are validated. A connector aimed at an internal
or metadata address — for example
http://169.254.169.254/latest/meta-data— is rejected at validation time withINVALID_ARGUMENT, and an audit eventrulechain.connector.rejectedrecordsreason="ssrf_blocked".
On success, an HTTP connector routes the message to its success relation on a 2xx response. On failure it retries within its bounded policy; if a connector (e.g. Kafka) exhausts its retries, the message goes to the dead-letter queue and a node failure metric increments with the node type.
Dead-letter queue: replay and discard
Section titled “Dead-letter queue: replay and discard”Failed messages land in a tenant-scoped DLQ with enough context to inspect, replay,
or discard them. A DLQ record holds dlq_id, tenant_id, chain_id,
chain_version, node_id, message_payload, metadata, failure_reason,
retry_count, first_failed_at, last_failed_at, and status.
- On entry. A non-retryable failure (e.g. a validation error at node
N1) creates a DLQ record and publishesrulechain.dlq.created.T1.{dlq_id}. - Replay. Replaying an open record re-runs the message — typically against the
latest deployed version, which is the point of replay after you’ve fixed and
redeployed a chain. The record’s status becomes
replayedand an audit eventrulechain.dlq.replayedis written. - Discard. Records you don’t intend to reprocess can be discarded.
Debug sessions
Section titled “Debug sessions”To inspect a chain without permanently storing full payloads, start a debug session for a bounded time.
- Starting a debug session (e.g. 10 minutes on chain
C1) causes per-node trace records to be emitted torulechain.debug.T1.C1. The UI shows, per node, its status, latency, input metadata, output metadata, and error details. - When the session expires, trace emission stops and runtime overhead returns to normal — debugging is opt-in and time-boxed, not an always-on cost.
Full payloads are not persisted unless you explicitly configure the session to do so.
The graph editor
Section titled “The graph editor”The UI provides a graph editor with a node palette, per-node configuration panels, graph validation, deploy, rollback, the debug trace view, and DLQ replay. Clicking Deploy runs the same validation described above and, on success, publishes a new immutable version whose metadata is shown on the canvas.