Conditions
Operators, ranges, warn levels, baseline comparisons and guards — one per column.
Each column of your result set can carry its own condition, with its own threshold, severity, alert and state.
Operators
Operators are words, not symbols.
| Operator | Fires when | Needs |
|---|---|---|
gt | value > threshold | One value |
gte | value ≥ threshold | One value |
lt | value < threshold | One value |
lte | value ≤ threshold | One value |
eq | value = threshold | One value |
neq | value ≠ threshold | One value |
between | threshold ≤ value ≤ max | Two values |
outside | value < threshold **or** value > max | Two values |
between and outside need both ends. A band with one end set matches
nothing — the rule saves cleanly and never fires. The editor marks a
half-written band in red and refuses to leave it silently incomplete.
Which band do you want?
outside is the one most conditions actually want. A replication lag of 0 is
as suspicious as one of 900 — it means the clock or the query is wrong, not
that the mirror is perfect. A corridor catches both ends:
outside 1 … 60 → fires below 1 and above 60
between 1 … 60 → fires while the value sits inside the bandComparison modes
A condition does not have to compare against a fixed number.
| Mode | Compares | Use for |
|---|---|---|
| Absolute | The value itself | Most conditions |
| Delta % | Change since the last reading, as a percentage | Spikes and drops |
| Delta absolute | Change since the last reading | Counters |
| Ratio of | Value as a percentage of a baseline column | "actual vs expected" |
| Diff from | Value minus a baseline column | "how far behind are we" |
Ratio and diff modes read the baseline from another column of the same row, which means the expectation is computed by your query and can be as clever as you like:
SELECT
subject,
actual_so_far,
expected_so_far, -- the baseline
round(100.0 * actual_so_far / expected_so_far, 1) AS pct_of_expected
FROM ...Set the condition on actual_so_far with mode ratio of expected_so_far
and a threshold of lt 70, and the alert says "412 against an expected 900" —
both numbers, because "40% of expected" alone sends the reader to a dashboard
before they can judge whether it matters.
A change-based condition needs a previous reading. The first evaluation of a new rule records the value and does not fire — otherwise every such rule would alert on its first run, comparing against a zero that was never measured.
Warn levels
Most conditions support a warn threshold alongside the main one. The rule
fires at warning when the value crosses the milder level, and escalates itself
to the configured severity when it crosses the main one.
For ranges, the warn band must sit inside the critical band for outside, and
outside it for between. The editor tells you when it does not — a warn level
on the wrong side of the main threshold never fires.
Guards
Mark a condition a guard and it is evaluated before the measurements. If a guard fails, the measurements are not reported.
Use guards for anything that decides whether the data is worth believing:
SELECT
dateDiff('minute', max(_ingested_at), now()) AS mirror_lag_min, -- guard: outside 0…30
count() AS rows_today,
countIf(status='failed') AS failed -- measurement
FROM warehouse.ordersA stale mirror then reports "the data is 4 hours old" instead of "failures fell to zero".
Per-condition severity and routing
Each condition has its own severity and its own alert. One query can raise a
warning about volume and a critical about failures in the same run, and they
recover independently.
Units and labels
Give a condition a unit (ms, %, min) and a label. The unit is
appended to the value in messages; the label is what the alert calls it. A
condition on p95_ms labelled "checkout latency" with unit ms produces
"checkout latency is 812 ms (threshold ≥ 500)", which is a sentence.
Enabling and disabling
A condition can be disabled without deleting it. A disabled condition is not evaluated and does not hold state — useful while you are tuning a threshold and do not want the rule paging in the meantime.
Where this behaviour lives: backend/internal/domain/dwh_metrics.go. If the code and this page disagree, the code is right — please fix the page.
Part of Data warehouse — Querying the warehouse, and alerting on what the query returns.