Condition Composition
Combine predicates into readable condition trees without losing same-element or null semantics.
Every rule has one conditions root. That root may be a scalar predicate, a boolean branch, or a derived predicate such as a window, model score, vector distance, SQL expression, or array_any scan.
Boolean branches
and and or contain lists of child conditions. not contains one child condition. Branches may be nested to a maximum depth of 128.
conditions:
and:
- {field: order.amount, op: gte, value: 500}
- {field: customer.account_age_days, op: lt, value: 7}conditions:
or:
- {field: device.is_emulator, op: eq, value: true}
- {field: network.connection_type, op: in, values: [vpn, proxy]}conditions:
not:
field: customer.email_domain
op: in_lookup
lookup: trusted_email_domainsMixed nesting
Use explicit branches when precedence must be obvious. This rule reviews a large order only when either the account is new or the device is risky:
- id: large_order_risk
action: review
weight: 35
conditions:
and:
- {field: order.amount, op: gte, value: 750}
- or:
- {field: customer.account_age_days, op: lt, value: 14}
- {field: device.fingerprint, op: in_lookup, lookup: blocked_devices}Keep each branch responsible for one business statement. If a branch becomes difficult to name in one sentence, split it into multiple rules and let decision precedence and scoring combine their effects.
Cross-field conditions
Cross-field operators compare two columns from the same record. They do not require an arithmetic expression:
conditions:
and:
- {field: order.amount, op: gt_field, other_field: customer.avg_order_value_30d}
- {field: billing.country, op: neq_field, other_field: shipping.country}Available cross-field operators are gt_field, lt_field, gte_field, lte_field, eq_field, and neq_field. Numeric comparisons require compatible numeric columns. Equality and inequality also support encoded categorical/entity columns.
Nested object fields
Dotted names address nested object fields consistently across JSON, Arrow structs, Avro, Protobuf, and PyArrow batches:
conditions:
and:
- {field: merchant.risk.score, op: gte, value: 70}
- {field: network.ip_address, op: ip_not_in_subnet, value: "10.0.0.0/8"}Only fields referenced by the compiled ruleset are projected. A missing or null parent makes a normal comparison false; use is_null or is_not_null when null state is itself the condition.
Arrays of objects with array_any
array_anyarray_any evaluates a condition tree against each object in an array. All children under where are scoped to the same array element:
conditions:
array_any:
path: cart.items
where:
and:
- {field: price, op: gt, value: 500}
- {field: category, op: in, values: [electronics, luxury]}
- {field: seller.risk_score, op: gte, value: 60}The rule does not match when one item satisfies price > 500 and a different item satisfies the category condition. A null or empty list produces false. Nested array_any inside another array_any is not supported.
Combining windows and model scores
Derived predicates are ordinary condition children and can be combined with scalar predicates:
conditions:
and:
- window:
entity_field: customer.id
function: count
duration_seconds: 600
op: gte
value: 5
- model_score:
model: checkout_risk
features: [order.amount, customer.account_age_days, device.risk_score]
op: gte
value: 0.82
- {field: merchant.id, op: not_in_lookup, lookup: trusted_merchants}model_score.features must contain numeric scalar fields. Window state is keyed by entity_field; use partition-affine processing when the same entity may arrive through parallel stream workers.
Null-safe patterns
Normal comparisons against null or missing values return false. Express alternate behavior explicitly:
conditions:
or:
- {field: customer.phone, op: is_null}
- {field: customer.phone, op: is_empty}
- {field: customer.phone, op: starts_with, value: "+"}For a condition that requires both presence and comparison, use and:
conditions:
and:
- {field: customer.email, op: is_not_null}
- {field: customer.email, op: ends_with, value: ".invalid"}Related pages
Updated about 2 hours ago