Priority, order and enabled
The phases of an evaluation, the order rules run in, and how to switch a rule off.
An evaluation runs in fixed phases: compute, then state, then validation, then the
decision, then action. So a validation rule sees the values compute rules wrote. Inside a
phase, rules run by descending priority (default 0); equal priorities keep document order. A
rule with enabled: false never runs. tags are labels for people and tools; the engine ignores them.
Syntax
- id: order.total.max
priority: 10 # higher runs first within its kind
enabled: true # false switches the rule off
tags: [money] # free labels| Phase | What happens |
|---|---|
| 1. compute | Compute rules write values into a working copy of data |
| 2. state | State rules collect field states |
| 3. validation | Validation rules raise findings |
| 4. decision | deny if any finding blocks, else allow |
| 5. action | Only on the server and only when allowed: commands |
Example
The total is computed first, so the limit rule checks 120. It has priority 10, so its finding comes before the e-mail finding. The note rule is switched off.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.priority-and-order, version: 1.0.0, title: "Priority and order" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.total.compute
kind: compute
target: { entity: Order, field: /total }
operations: [create]
assign:
- { field: /total, value: { op: mul, args: [{ var: data.price }, { var: data.quantity }] }, mode: always }
- id: order.email.required
kind: validation
target: { entity: Order, field: /email }
operations: [create]
tags: [contact]
assert: { op: exists, args: [{ var: data.email }] }
severity: error
finding: { code: LRN-PRI-001, message: order.emailMissing }
- id: order.total.max
kind: validation
target: { entity: Order, field: /total }
operations: [create]
priority: 10
tags: [money]
assert: { op: lte, args: [{ var: data.total }, 100] }
severity: error
finding: { code: LRN-PRI-002, message: order.totalTooHigh }
- id: order.notes.required
kind: validation
target: { entity: Order, field: /notes }
operations: [create]
enabled: false
assert: { op: exists, args: [{ var: data.notes }] }
severity: error
finding: { code: LRN-PRI-003, message: order.notesMissing }
messages:
en:
order.emailMissing: "Enter an e-mail address."
order.totalTooHigh: "An order may cost at most 100."
order.notesMissing: "Add a note for the warehouse."
tests:
- name: the computed total is checked first, then the e-mail
entity: Order
operation: create
given:
data: { price: 30, quantity: 4 }
expect:
decision: deny
findings:
- { rule: order.total.max, fields: [/total] }
- { rule: order.email.required, fields: [/email] }
effects:
- { type: value, field: /total, value: 120 }
- name: a small order with an e-mail is allowed
entity: Order
operation: create
given:
data: { price: 30, quantity: 2, email: ana@example.com }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"price": 30,
"quantity": 4
}
}Result, from the engine
Decisiondeny2 findings, server channel
LRN-PRI-002errorblockingAn order may cost at most 100./totalLRN-PRI-001errorblockingEnter an e-mail address./email
- computed value
/total=120
Common mistakes
- Using
priorityto make one validation rule skip another. Priority only orders rules; every applicable rule still runs. Usewhento skip. - Deleting a rule to switch it off for a while.
enabled: falsekeeps it, reviewed and tested.
Exercise
Switch the note rule on. Update the golden tests so they pass: an order without a note is now denied.
Hint
Change enabled on order.notes.required, then fix the golden tests that now see a new finding.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.priority-and-order, version: 1.0.0, title: "Priority and order" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.total.compute
kind: compute
target: { entity: Order, field: /total }
operations: [create]
assign:
- { field: /total, value: { op: mul, args: [{ var: data.price }, { var: data.quantity }] }, mode: always }
- id: order.email.required
kind: validation
target: { entity: Order, field: /email }
operations: [create]
tags: [contact]
assert: { op: exists, args: [{ var: data.email }] }
severity: error
finding: { code: LRN-PRI-001, message: order.emailMissing }
- id: order.total.max
kind: validation
target: { entity: Order, field: /total }
operations: [create]
priority: 10
tags: [money]
assert: { op: lte, args: [{ var: data.total }, 100] }
severity: error
finding: { code: LRN-PRI-002, message: order.totalTooHigh }
- id: order.notes.required
kind: validation
target: { entity: Order, field: /notes }
operations: [create]
enabled: true
assert: { op: exists, args: [{ var: data.notes }] }
severity: error
finding: { code: LRN-PRI-003, message: order.notesMissing }
messages:
en:
order.emailMissing: "Enter an e-mail address."
order.totalTooHigh: "An order may cost at most 100."
order.notesMissing: "Add a note for the warehouse."
tests:
- name: with the note rule on, a missing note is a finding too
entity: Order
operation: create
given:
data: { price: 30, quantity: 2, email: ana@example.com }
expect:
decision: deny
findings:
- { rule: order.notes.required, fields: [/notes] }
- name: a small order with an e-mail and a note is allowed
entity: Order
operation: create
given:
data: { price: 30, quantity: 2, email: ana@example.com, notes: ring twice }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"price": 30,
"quantity": 2,
"email": "ana@example.com"
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-PRI-003errorblockingAdd a note for the warehouse./notes
- computed value
/total=60