Documentation

Rule syntax

Rule shape, operators, helper functions, and the rule-set wrapper.

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.

OperatorYAMLJSONMeaning
Equalitya == b{"==": [a, b]}Strict equality
Not equala != b{"!=": [a, b]}Inequality
Comparisona > b, a >= b, a < b, a \<= b{">": [a, b]}Numeric / date
Logicala AND b, a OR b, NOT a{"and":[a,b]}, {"or":[...]}, {"not": a}Boolean combinators
Membershipa IN list{"in": [a, list]}Value in array
ExistenceEXISTS(field){"exists": "field"}Field is present and not null
Regexa MATCHES /pattern/{"matches": [a, "pattern"]}Regex match
Rangea 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:

HelperUse
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

POST/rules/setsJWT
Create a rule set. Body: {name, description, content, format: 'yaml'|'json', active, version}.
GET/rules/setsJWT
List rule sets.
GET/rules/sets/:idJWT
Read.
PUT/rules/sets/:idJWT
Update.
DELETE/rules/sets/:idJWT
Delete.
PUT/rules/sets/:id/toggle-statusJWT
Activate/deactivate.
GET/rules/sets/:name/versionsJWT
List all versions.
POST/rules/sets/:id/cloneJWT
Clone.

Headers: x-org-id and x-customer (user principal).

Execute

POST/rule-engine/executeJWT
Stateless: pass {rules, data, options}.
POST/rule-engine/execute-ruleset/:ruleSetIdJWT
Execute a saved set.

curl -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.