Skip to content

Rule language reference ​

This restricted DSL is implemented in Go and does not execute Lua. It uses Lua-style and/or/not, ~=, and do/end. Files can contain several rule "id" do ... end blocks. -- comments continue to line end.

Clauses and types ​

ClauseContract
on "record.type"Required; selects one record type
where booleanPre-aggregation filter; missing/non-true values fail
group_by field, fieldAt most eight fields; sensor_id is implicit
window "60s"Fixed event-time window, >0 and ≤1h
when booleanRequired; evaluated per record or at window closure
cooldown "30s"Per-group suppression without windows; incompatible with window
emit "event.name" do ... endRequired; exactly one per rule

Each clause appears at most once. Non-window group_by requires cooldown. Severity and message must be literal strings to protect redaction. Severity supports debug/info/warning/error/critical and defaults to warning. Other output fields can use expressions.

Fields are string/number/bool; undeclared fields reject loading. Check missing values with field ~= nil; there is no implicit conversion. http.status is a string, as is precision-preserving process.start_ns.

Expressions and functions ​

CategorySyntax
LiteralsStrings, numbers, true, false, nil
Comparison== ~= < <= > >=; ordered comparisons need matching types
Arithmetic+ - * / %, unary minus; zero division/non-finite values become missing
LogicShort-circuit and/or, not, parentheses
Stringscontains(value, fragment); matches(value, "RE2 pattern")
Networkcidr_contains("10.0.0.0/8", source.ip)
JSON scalarsjson_string/json_number/json_bool(value, "/0/field")
Aggregationcount, count_if, ratio, distinct_count, sum, avg, max

Regex and JSON pointers must be compile-time literals. Invalid JSON, missing paths, and mismatched types return missing. Pointers allow at most 512 bytes/32 segments. Extracted values retain source sensitivity.

Aggregates are allowed only in window when/emit/recover_when, without nesting or use in where. Count includes filtered records; other aggregates ignore missing arguments. Outside aggregates, window expressions may reference only grouping fields and sensor_id/service/protocol. Explicitly group by service when services must be separated.

Time, reloads, and limits ​

Windows align to the Unix epoch and use [start,end). Each sensor's watermark is its maximum observed event time minus five seconds. Late rule input is dropped and counted. Idle advancement waits for queued input to drain. A new online rule warms up its initial partial window. Offline replay uses event time, flushes remaining windows, and does not restore online checkpoints.

Online execution commits compatible windows, cooldowns, watermarks, and incident state to the WAL alongside events. Restarts restore compatible state. Unchanged rules retain state across reloads; changed rules warm up again. Failed reloads retain the previous engine and report file, line, column, and cause.

Default limits: 256 rules; 10000 active windows/cooldowns and another 10000 lifecycle groups per rule; 4096 values per distinct aggregate; 64 MiB rule state. Group strings allow 1024 bytes, expression depth 64 / 4096 nodes, source files 1 MiB, and string literals 64 KiB. There are no loops, recursion, function definitions, dynamic execution, or arbitrary I/O.

Incident lifecycle ​

Insert this between a window's when and emit clauses:

lua
lifecycle do
  trigger_windows 2
  recovery_windows 2
  recover_when count() >= 20 and ratio(error) <= 0.05
  repeat "5m"
end

Trigger/recovery windows range from 1..120, defaulting to one. Recover_when is required. Repeat is optional, otherwise >0 and ≤1h. Consecutive bad windows fire, continued failures repeat as ongoing, and explicit healthy windows resolve. Idle time, missing/truncated/incomplete input, or failed calculation cannot establish recovery. Removing/replacing an active rule emits closed with reason rule_replaced_or_removed. See detection rules for complete examples.

Common record fields ​

Common fields include sensor_id, connection_id, service, protocol, duration_ms, error, incomplete, truncated, and source/destination.ip/port. Common types include http.request/transaction, db.transaction/message, dns.transaction, tls.handshake, messaging.transaction/message, rpc.transaction, directory.transaction, and analysis.anomaly.

Plugin schemas register custom fields. Use plugin info, packaged schemas, and SDK docs for contracts matching your instance. Content remains sensitive even when assigned a different output field name.