Concepts
Transaction monitoring
Activity arrives from two paths and is stored once. A rule is a window and a predicate, and every alert keeps the trace back to what produced it.
Written from docs/adr/0014, docs/adr/0016, docs/adr/0028 — this page is written for an external reader rather than extracted verbatim
Transactions live in one range-partitioned table, written by two paths — batch ingest and the transaction API — that produce identical rows. Nothing downstream can tell which way a transaction arrived, which is the point: a rule that behaved differently for API traffic than for a nightly file would be a rule nobody could reason about.
The shape of a rulePermalink to this section
A rule splits into a window and a predicate. The window says what set of activity is under consideration — this account, these thirty days, these counterparties. The predicate says what about that set is interesting. Keeping them separate is what makes a rule explainable: an analyst can be shown the window that was evaluated and the predicate that fired, rather than a boolean.
window subject = account, span = 30 days, include = credits predicate sum(amount) > threshold AND count(distinct counterparty) >= 5 emits alert with correlation_key derived from configuration
Conditions are authored as a tree over a declared field vocabulary. The vocabulary is data rather than code, and the builder will not open a field it does not know — so a rule cannot come to depend on a value the evaluator has no defined semantics for.
MoneyPermalink to this section
Correlation and duplicate alertsPermalink to this section
Every alert carries a correlation key computed when it is created, derived from configuration rather than hardcoded. Alerts sharing a key are the same finding seen again, not new work, and the queue treats them accordingly. Without this, a rule that fires nightly on a standing pattern buries the queue in re-discoveries of a thing already being investigated.