Rules are the executable declarations in the engine. They read input data, evaluate conditions, and either write output or raise exceptions.
At the simplest level, a rule looks like this:
if <condition> then <action>
N.B. Please note that all identifiers (constants, variable names, functions, command keywords, etc.) and operators are case-sensitive.
Positional keywords however (CONST, IF, THEN, SET, THROW, and RUN) are case-insensitive for convenience.
This page shows the main rule forms, how comments and annotations work, and a few practical writing tips.
State rules calculate values without a condition.
set person.is_adult = person.age >= 18
This is useful for derived state that other rules can reference later.
Example:
set vip_customer = customer.total_sales > 10000
set invoice.total_due = invoice.subtotal + invoice.tax
These run an action only when the condition is satisfied.
if person.age >= 18 then person.is_adult = true
if order.total > 100 then order.free_shipping = true
These choose between two actions.
if isActive then status = "active" else status = "inactive"
if x > 10 then nested.value = 10 + 5 / 2 else nested.value = (10 + 5) / 2
These stop normal processing by raising an exception when the condition is met.
if buyer.age < 21 throw 'Only adults can buy tickets'
if invoice.total <= 0 throw "Invalid invoice amount"
Conditions are expressions that evaluate to true or false.
Simple comparison:
if status == "OPEN" then editable = true
Logical combination:
if age >= 18 and country == "US" then eligible = true
Nested expression:
if x > 10 and (y < 5 or z == 0) then match = true
Function-driven condition:
if count(person.children) > 2 then person.has_many_children = true
Array lambda condition:
if every(person.family, member : member.age > 10)
then person.has_old_children = true
else person.has_old_children = false
For the full expression language, see expression.syntax.md.
An action is what the rule does after then, else, or set.
if score >= 60 then passed = true
Use semicolons to chain actions in the same consequence.
if count(person.children) > 2 then person.child_count = count(person.children); person.family_size = "large"
Rules files support comments starting with // or #.
Use these to explain intent, thresholds, or business context.
// Customers above this threshold are considered premium
if customer.spend >= 1000 then customer.segment = "premium"
# Reject impossible invoice totals
if invoice.total <= 0 throw 'Invalid invoice amount'
Inline comments can be placed after a line or a rule.
if x > 10 then result = triple(x) // equivalent to x * 3 in this business context
Rules can be prefixed with annotations for metadata.
Supported annotations are:
@name(...)@hint(...)@disabled()@salience(...)@name(...)Use a short readable name for logs, debugging, and audits.
@name(Adult Status)
if person.age >= 18 then person.is_adult = true
@hint(...)Use a longer explanation when the rule intent is not obvious from the syntax alone.
@hint(Flag invoices that qualify for free shipping)
if order.total > 100 then order.free_shipping = true
@disabled()A rule can be disabled, preventing its evaluation and execution. This can be useful while developing or testing rules.
@disabled()
if order.total > 100 then order.free_shipping = true
A workspace does not type-check disabled rules, since they will not be executed and may be in an invalid state. This allows users to disable rules that are still being developed or debugged without causing type check failures for the entire workspace.
A rule can be type-checked explicitly if rule.checkTypes() is called directly on it, even if it is disabled.
This allows users to check the types of a rule that is still being developed or debugged without enabling it in the workspace.
@salience(...)Use salience to prioritize rules when several could apply.
@salience(7)
if x > 30 then y = 30
Higher salience means higher priority.
Rules can also carry declared custom annotations for grouping, teams, ownership, and related concerns. See Annotations.
Annotations can be combined:
@salience(7) @name(Highest Priority)
if x > 30 then y = 30
They can also be split over lines:
@name(Split over lines)
@hint(A named rule with separate metadata lines)
if x > 10 then result = 10 + 5 / 2
Good for short rules.
if person.age >= 18 then person.is_adult = true
Better when the condition or action is long.
if order.total > 100
then order.shipping = 0
else order.shipping = 15
if count(person.children) > 2 && range(person.children.age) >= 10
then person.summary = concat("Family size: ", count(person.children), ", spread: ", range(person.children.age))
else person.summary = "No summary"
if applicant.age >= 18 and applicant.country == "CA" then applicant.eligible = true
if order.total >= 1000 then order.band = "enterprise" else order.band = "standard"
if not(email contains "@") then throw "Invalid email"
set person.age_band = person.age >= 18 ? "adult" : "minor"
set rules for derived values that should always be calculated when inputs exist.if ... then ... else when the output should explicitly branch.set status = "new"
if person.age >= 18 then person.is_adult = true
if isActive then status = "active" else status = "inactive"
if count(person.children) > 2 then person.child_count = count(person.children); person.age_range = range(person.children)
@salience(7) @name(Highest Priority)
if x > 30 then y = 30
// Premium customers get a manual review flag
@name(Premium Review)
if customer.spend >= 1000 then customer.review_required = true
if every(person.family, member : member.age > 10)
then person.has_old_children = true
else person.has_old_children = false