Rule Cascade
LearnLevels and inheritance

Scope and levels

Where a ruleset sits in your organisation, from least to most specific.

Every ruleset has a scope: a list of {level, id} from the least to the most specific. An organisation ruleset has one level. A project ruleset under it repeats that level and adds its own. Level names are open; the recommended ones are enterprise, organization, businessUnit, agency, project, application, module and feature.

Syntax

scope of a project ruleset
scope:
  - { level: organization, id: learn }
  - { level: project, id: shop }

Example

org.ruleset.yaml is the organisation ruleset. The shop ruleset is one level narrower and extends it, so the organisation's rule runs in the shop as well as the shop's own.

scope-and-levels.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
  - { level: organization, id: learn }
  - { level: project, id: shop }
extends:
  - { ruleset: learn.org, version: "^1.0.0" }
rules:
  - id: shop.email.required
    kind: validation
    target: { entity: Order, field: /email }
    operations: [create]
    assert: { op: exists, args: [{ var: data.email }] }
    severity: error
    finding: { code: LRN-SHP-001, message: shop.emailRequired }
messages:
  en:
    shop.emailRequired: "Give an e-mail address for the receipt."
tests:
  - name: the organisation rule applies in the shop too
    entity: Order
    operation: create
    given:
      data: { quantity: 150, email: "ana@example.com" }
    expect:
      decision: deny
      findings:
        - { rule: org.quantity.max, code: LRN-ORG-001 }
  - name: the shop adds its own rule
    entity: Order
    operation: create
    given:
      data: { quantity: 3 }
    expect:
      decision: deny
      findings:
        - { rule: shop.email.required }
org.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.org, version: 1.0.0, title: "Organisation rules" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: org.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 100] }
    severity: error
    finding: { code: LRN-ORG-001, message: org.quantityMax }
messages:
  en:
    org.quantityMax: "No order may have more than 100 items."
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 150,
    "email": "ana@example.com"
  }
}

Result, from the engine

Decisiondeny1 finding, server channel

  • LRN-ORG-001errorblockingNo order may have more than 100 items./quantity
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • A child whose scope is not narrower than its parent's. The child must start with the parent's scope and add at least one level. Here the shop forgot its own level, so it does not load: SCOPE_NOT_NARROWER. Fix it by adding { level: project, id: shop }.
scope-and-levels-fix.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
  - { level: organization, id: learn }
extends:
  - { ruleset: learn.org, version: "^1.0.0" }
rules:
  - id: shop.email.required
    kind: validation
    target: { entity: Order, field: /email }
    operations: [create]
    assert: { op: exists, args: [{ var: data.email }] }
    severity: error
    finding: { code: LRN-SHP-001, message: shop.emailRequired }
messages:
  en:
    shop.emailRequired: "Give an e-mail address for the receipt."
tests:
  - name: the organisation rule applies in the shop too
    entity: Order
    operation: create
    given:
      data: { quantity: 150, email: "ana@example.com" }
    expect:
      decision: deny
      findings:
        - { rule: org.quantity.max, code: LRN-ORG-001 }
  - name: the shop adds its own rule
    entity: Order
    operation: create
    given:
      data: { quantity: 3 }
    expect:
      decision: deny
      findings:
        - { rule: shop.email.required }
org.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.org, version: 1.0.0, title: "Organisation rules" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: org.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 100] }
    severity: error
    finding: { code: LRN-ORG-001, message: org.quantityMax }
messages:
  en:
    org.quantityMax: "No order may have more than 100 items."
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 150,
    "email": "ana@example.com"
  }
}

Result, from the engine

does not loadThe engine refuses the ruleset before it evaluates anything.

  • SCOPE_NOT_NARROWER learn.shop: scope must extend the scope of learn.org
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Exercise

The checkout team owns a narrower ruleset. Add the level feature with the id checkout to the shop ruleset's scope. The golden tests must still pass.

Hint

Add a third entry to scope; keep the first two as they are.

Show answer
scope-and-levels.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
  - { level: organization, id: learn }
  - { level: project, id: shop }
  - { level: feature, id: checkout }
extends:
  - { ruleset: learn.org, version: "^1.0.0" }
rules:
  - id: shop.email.required
    kind: validation
    target: { entity: Order, field: /email }
    operations: [create]
    assert: { op: exists, args: [{ var: data.email }] }
    severity: error
    finding: { code: LRN-SHP-001, message: shop.emailRequired }
messages:
  en:
    shop.emailRequired: "Give an e-mail address for the receipt."
tests:
  - name: the organisation rule applies in the shop too
    entity: Order
    operation: create
    given:
      data: { quantity: 150, email: "ana@example.com" }
    expect:
      decision: deny
      findings:
        - { rule: org.quantity.max, code: LRN-ORG-001 }
  - name: the shop adds its own rule
    entity: Order
    operation: create
    given:
      data: { quantity: 3 }
    expect:
      decision: deny
      findings:
        - { rule: shop.email.required }
org.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.org, version: 1.0.0, title: "Organisation rules" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: org.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 100] }
    severity: error
    finding: { code: LRN-ORG-001, message: org.quantityMax }
messages:
  en:
    org.quantityMax: "No order may have more than 100 items."
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 150,
    "email": "ana@example.com"
  }
}

Result, from the engine

Decisiondeny1 finding, server channel

  • LRN-ORG-001errorblockingNo order may have more than 100 items./quantity
Course overview

On this page