Detection rules — ingest → detect → investigate in one binary

Relata collapses the SIEM + sidecar + database triangle into one engine. A detection rule is a SQL WHERE against any governed type; it fires within the request cycle on commit (not on a 30-second poll), emits governed Alert rows with full PROV-O provenance, and pushes to per-tenant webhooks with queryable delivery status. Import Sigma's 10 000+ community detection rules as-is. The result: ingest → detect → investigate is one closed loop, and alerts are bi-temporal — "would this rule have fired last Tuesday?" is an AS OF query.

Create a rule

curl -X POST http://127.0.0.1:9090/rules \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
  -d '{
    "name": "suspicious-dns-exfil",
    "target_type": "DnsEvent",
    "condition_sql": "query_length > 100 AND rdata_type = '\''TXT'\'' AND query LIKE '\''%.xyz'\''",
    "severity": "high",
    "mitre_technique": "T1048",
    "purpose": "security"
  }'
# Python SDK
client.governance_client.create_rule({
    "name": "suspicious-dns-exfil",
    "target_type": "DnsEvent",
    "condition_sql": "query_length > 100 AND rdata_type = 'TXT'",
    "severity": "high",
    "mitre_technique": "T1048",
}, purpose="security")

From then on, every governed DnsEvent ingest that matches the condition produces an Alert row — in the same request, before the ingest returns. No poller, no lag.

Commit-driven firing (the differentiator)

Most rule engines run on a cron/poll loop — ingest a row now, the alert lands 30 seconds later (or never, if the poller is down). Relata fires rules on the commit bus (the GraphChangeEvent stream, in crates/relata-cli/src/serve/rule_eval.rs) — the candidate check runs as part of the write transaction, so:

  • Latency is sub-request. By the time POST /ingest returns 200, any alerts it triggered are already in the audit log.
  • No missed events. If the server is up enough to accept the write, it's up enough to fire the rule. A down poller can't silently drop detections.
  • Bitmap-indexed candidate filter. Each rule compiles to a bitmap predicate over the target type, so the commit-time check is a cheap set-membership test against just-committed rows — not a full table scan per rule.

Import Sigma rules (10000+ community detections, as-is)

Sigma is the open standard for generic detection rules — the community publishes thousands for known attacker TTPs. Relata imports them natively:

relata import-sigma rules/suspicious-powershell.yaml
# or via HTTP
curl -X POST http://127.0.0.1:9090/rules/sigma \
  -H 'Authorization: Bearer <token>' -H 'Content-Type: application/x-yaml' \
  --data-binary @rules/suspicious-powershell.yaml
client.governance_client.import_sigma(open("rules/suspicious-powershell.yaml").read(),
                                       purpose="security")

Each Sigma rule's logsource → detection → fields map onto your registered governed types and the SQL WHERE condition.

Bi-temporal alerts — backtest and "what would have fired?"

Alert rows are bi-temporal just like every other governed row. Run the rule as-of a past window to backtest detection quality without re-ingesting:

PURPOSE 'security'
SELECT alert_id, rule_name, severity, target_id, valid_from
FROM Alert AS OF '2026-01-15T00:00:00'
WHERE severity IN ('high', 'critical')
ORDER BY valid_from DESC;

This is impossible in a SIEM that overwrites alerts — the historical alert state is gone the moment it ages out. Here it's a first-class query.

Per-tenant webhooks with delivery status

Configure a webhook per tenant; alerts push automatically. Crucially, the delivery status is queryable — worst-outcome-wins ranking collapses per-attempt outcomes into one AlertRecord.delivery_status:

# Find alerts whose webhook delivery failed (retry loop exhausted)
curl 'http://127.0.0.1:9090/alerts/list?delivery_status=failed' \
  -H 'Authorization: Bearer <token>'
# Python — register a webhook, then inspect delivery health
client.register_webhook("https://soar.example.com/relata-alerts",
                        event_types=["alert.high"])
failed = client.query("SELECT * FROM Alert WHERE delivery_status = 'failed'")

The retry loop (deliver_webhook_retry_loop) returns a typed WebhookDeliveryOutcome per attempt; failures are logged and recoverable, not silently lost.

Stream alerts in real time

For a live dashboard or a SOAR integration, stream alerts over SSE:

# Python
for event in client.streaming_client.alerts():
    print(event["severity"], event["rule_name"], event["target_id"])
# Raw SSE
curl -N http://127.0.0.1:9090/alerts/stream -H 'Authorization: Bearer <token>'

Tips & takeaways

  • Write the condition_sql to be selective. The commit-time bitmap filter is fast, but a rule that matches 50% of rows fires constantly and floods the audit log. Use specific thresholds and AND conjunctions.
  • Pair rules with mitre_technique. Tagging alerts with MITRE ATT&CK technique IDs makes them correlatable across rules and against threat-intel — and the field is queryable.
  • Use backtesting before going live. SELECT FROM Alert AS OF '<last-week>' against a newly-created rule tells you what it would have fired — tune thresholds before the rule touches production.
  • Webhook delivery status is your safety net. Set up a periodic ?delivery_status=failed check so a SOAR outage doesn't silently drop alerts.
  • Sigma import is the fast path to coverage. Before authoring custom rules, search the Sigma repo for your log source — chances are someone already wrote the detection.

See also