Rule Cascade
LearnRule basics

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

priority, enabled, tags
- id: order.total.max
  priority: 10        # higher runs first within its kind
  enabled: true       # false switches the rule off
  tags: [money]       # free labels
PhaseWhat happens
1. computeCompute rules write values into a working copy of data
2. stateState rules collect field states
3. validationValidation rules raise findings
4. decisiondeny if any finding blocks, else allow
5. actionOnly 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.

priority-and-order.ruleset.yaml
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: [] }
request.json
{
  "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./total
  • LRN-PRI-001errorblockingEnter an e-mail address./email
  • computed value /total = 120
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • Using priority to make one validation rule skip another. Priority only orders rules; every applicable rule still runs. Use when to skip.
  • Deleting a rule to switch it off for a while. enabled: false keeps 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
priority-and-order.ruleset.yaml
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: [] }
request.json
{
  "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
Course overview

On this page