A rule is a JSON object — YAML is just a serialization. The engine parses one or many rules out of a rule-set document and evaluates them against an input data payload, walking conditions and accumulating actions.
The Rule type
interface Rule {
id: string;
vars?: string[]; // local variable bindings
when?: any; // condition expression
then?: any; // actions on true
else?: any; // actions on false
for?: string; // "item in expression"
do?: any; // body of a for-loop
switch?: any; // value to switch on
cases?: { when?: any; default?: any; do: any }[]; // switch cases
utils?: { [key: string]: string }; // inline utility functions
}
Rules are independent — they don't share state by default. To carry results between rules, write to fields on the input or accumulate via output messages.
Conditions
A condition is a tree of operator expressions. Operators take the form { "<op>": [arg1, arg2] } in JSON, or infix in YAML.
| Operator | YAML | JSON | Meaning |
|---|---|---|---|
| Equality | a == b | {"==": [a, b]} | Strict equality |
| Not equal | a != b | {"!=": [a, b]} | Inequality |
| Comparison | a > b, a >= b, a < b, a \<= b | {">": [a, b]} | Numeric / date |
| Logical | a AND b, a OR b, NOT a | {"and":[a,b]}, {"or":[...]}, {"not": a} | Boolean combinators |
| Membership | a IN list | {"in": [a, list]} | Value in array |
| Existence | EXISTS(field) | {"exists": "field"} | Field is present and not null |
| Regex | a MATCHES /pattern/ | {"matches": [a, "pattern"]} | Regex match |
| Range | a BETWEEN x AND y | {"between": [a, [x, y]]} | Numeric range |
Field paths walk the input via dot notation: order.customer.country, lines[0].sku.
Helper functions
Helpers are uppercase function calls usable inside any expression. Built-ins:
| Helper | Use |
|---|---|
COUNT(list) | Length of a list |
SUM(list, expr?) | Sum the list, optionally projecting via expr |
FILTER(list, predicate) | Keep elements where _ (the iterator) satisfies the predicate |
MAP(list, expr) | Transform each element |
MIN(list), MAX(list), AVG(list) | Aggregates |
EXISTS(field) | Truthy/non-null check |
LENGTH(string) | String length |
CONTAINS(list, value) | Membership test |
NOW() | Current timestamp |
DATE_DIFF(a, b, unit) | Date arithmetic |
_ is the iterator placeholder inside FILTER/MAP/SUM:
vars:
- bad = FILTER(order.lines, _.qty > _.stock)
condition: COUNT(bad) == 0
Register custom helpers via POST /rules/helpers. Names must be UPPER_SNAKE_CASE.
Actions (the then / else block)
The then and else blocks are objects whose keys are action names and whose values are the action parameters. The engine doesn't execute side effects directly — it returns the resolved actions to the caller, who applies them. Common patterns:
then:
apply_discount:
type: percent
value: 10
add_message: "Bulk discount applied"
set_field:
path: order.metadata.tier
value: gold
For full side effects (database writes, notifications, calls), pair the rule engine with an Automation action that consumes the rule output.
Iteration
- id: VALIDATE_LINES
for: line in order.lines
do:
when: line.qty > line.stock
then:
add_error:
sku: line.sku
reason: out_of_stock
for defines the iteration variable. do runs once per element with that variable bound.
Switch / case
- id: TIER_PRICING
switch: customer.tier
cases:
- when: gold
do:
apply_discount: { type: percent, value: 15 }
- when: silver
do:
apply_discount: { type: percent, value: 10 }
- default: true
do:
apply_discount: { type: percent, value: 0 }
A complete rule-set
A rule set is an envelope around the rules:
version: 2.0
name: stock-validation
rules:
- id: STOCK_OK
vars:
- bad = FILTER(order.lines, _.qty > _.stock)
when: COUNT(bad) == 0
then:
add_message: "All items in stock"
else:
add_error:
type: out_of_stock
items: MAP(bad, _.sku)
CRUD
/rules/setsJWT{name, description, content, format: 'yaml'|'json', active, version}.
/rules/setsJWT/rules/sets/:idJWT/rules/sets/:idJWT/rules/sets/:idJWT/rules/sets/:id/toggle-statusJWT/rules/sets/:name/versionsJWT/rules/sets/:id/cloneJWTHeaders: x-org-id and x-customer (user principal).
Execute
/rule-engine/executeJWT{rules, data, options}.
/rule-engine/execute-ruleset/:ruleSetIdJWTcurl -X POST https://appengine.appmint.io/rule-engine/execute-ruleset/$ID \
-H "x-org-id: $ORG" -H "x-customer: $USER" \
-H "Content-Type: application/json" \
-d '{"data": {"order": {"lines": [{"sku":"A","qty":3,"stock":2}]}}}'
The response contains the matched rules, their resolved then/else blocks, evaluation time, and any messages or errors emitted.
Look at /Users/imzee/projects/appengine/src/rule-engine/examples/ for canonical YAML examples — bulk-discount.yaml, fraud-gate.yaml, shipping-path.yaml, order-pipeline.yaml. Copy from those before writing from scratch.