Rule Cascade
LearnSeverities and resolutions

Accept the risk

An error that an authorised user may accept, with a reason.

Some errors may be accepted by the right person. acceptance on an error rule says who may accept it (roles), whether a reason is needed (justification) and how long the acceptance lasts (expiresAfter). The request carries an accept-risk resolution. When the actor holds one of the roles and gives a reason, the finding becomes accepted and stops blocking.

Syntax

an error that may be accepted
- id: <rule id>
  kind: validation
  severity: error
  acceptance:
    allowed: true
    roles: [risk-officer]        # empty or left out: any actor
    justification: required      # or none
    expiresAfter: P30D           # ISO 8601 duration, for the host's audit trail
the request carries the acceptance
"actor": { "id": "u-7", "roles": ["risk-officer"] },
"resolutions": [ { "rule": "<rule id>", "type": "accept-risk", "justification": "<why>" } ]

Example

A credit limit of 8000 is above 5000. A risk officer accepts the risk and gives a reason, so the request is allowed. The ruleset's other golden tests show that a clerk, or an acceptance without a reason, still gets deny.

accept-risk.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.accept-risk, version: 1.0.0, title: "Accept the risk" }
scope:
  - { level: organization, id: learn }
entities:
  Customer:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Customer" }
rules:
  - id: customer.credit.limit
    kind: validation
    target: { entity: Customer, field: /creditLimit }
    operations: [create, update]
    assert: { op: lte, args: [{ var: data.creditLimit }, 5000] }
    severity: error
    acceptance:
      allowed: true
      roles: [risk-officer]
      justification: required
      expiresAfter: P30D
    finding: { code: LRN-RSK-001, message: customer.creditTooHigh }
messages:
  en:
    customer.creditTooHigh: "A credit limit above 5000 needs a risk acceptance."
tests:
  - name: a risk officer accepts the risk with a reason
    entity: Customer
    operation: create
    given:
      data: { creditLimit: 8000 }
      actor: { id: u-7, roles: [risk-officer] }
      resolutions:
        - { rule: customer.credit.limit, type: accept-risk, justification: "long-standing customer" }
    expect:
      decision: allow
      findings:
        - { rule: customer.credit.limit, status: accepted, blocking: false }
  - name: a clerk cannot accept the risk
    entity: Customer
    operation: create
    given:
      data: { creditLimit: 8000 }
      actor: { id: u-2, roles: [clerk] }
      resolutions:
        - { rule: customer.credit.limit, type: accept-risk, justification: "long-standing customer" }
    expect:
      decision: deny
      findings:
        - { rule: customer.credit.limit, status: open, blocking: true }
  - name: an acceptance without a justification does not count
    entity: Customer
    operation: create
    given:
      data: { creditLimit: 8000 }
      actor: { id: u-7, roles: [risk-officer] }
      resolutions:
        - { rule: customer.credit.limit, type: accept-risk }
    expect:
      decision: deny
      findings:
        - { rule: customer.credit.limit, status: open }
request.json
{
  "entity": "Customer",
  "operation": "create",
  "data": {
    "creditLimit": 8000
  },
  "actor": {
    "id": "u-7",
    "roles": [
      "risk-officer"
    ]
  },
  "resolutions": [
    {
      "rule": "customer.credit.limit",
      "type": "accept-risk",
      "justification": "long-standing customer"
    }
  ]
}

Result, from the engine

Decisionallow1 finding, server channel

  • LRN-RSK-001errornot blocking, acceptedA credit limit above 5000 needs a risk acceptance./creditLimit
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • acceptance on a warning. Only an error can be accepted.
  • Taking actor.roles from the request body. The host must set the actor from its own authentication, or anyone could claim to be a risk officer.

Exercise

Let a manager accept the risk too, and stop asking for a reason. Write a test where a manager accepts without a justification, and one where a clerk is still denied.

Hint

Add manager to roles and set justification: none.

Show answer
accept-risk.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.accept-risk, version: 1.0.0, title: "Accept the risk" }
scope:
  - { level: organization, id: learn }
entities:
  Customer:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Customer" }
rules:
  - id: customer.credit.limit
    kind: validation
    target: { entity: Customer, field: /creditLimit }
    operations: [create, update]
    assert: { op: lte, args: [{ var: data.creditLimit }, 5000] }
    severity: error
    acceptance:
      allowed: true
      roles: [risk-officer, manager]
      justification: none
      expiresAfter: P30D
    finding: { code: LRN-RSK-001, message: customer.creditTooHigh }
messages:
  en:
    customer.creditTooHigh: "A credit limit above 5000 needs a risk acceptance."
tests:
  - name: a manager accepts the risk without a reason
    entity: Customer
    operation: create
    given:
      data: { creditLimit: 8000 }
      actor: { id: u-9, roles: [manager] }
      resolutions:
        - { rule: customer.credit.limit, type: accept-risk }
    expect:
      decision: allow
      findings:
        - { rule: customer.credit.limit, status: accepted }
  - name: a clerk still cannot accept the risk
    entity: Customer
    operation: create
    given:
      data: { creditLimit: 8000 }
      actor: { id: u-2, roles: [clerk] }
      resolutions:
        - { rule: customer.credit.limit, type: accept-risk }
    expect:
      decision: deny
      findings:
        - { rule: customer.credit.limit, status: open }
request.json
{
  "entity": "Customer",
  "operation": "create",
  "data": {
    "creditLimit": 8000
  },
  "actor": {
    "id": "u-9",
    "roles": [
      "manager"
    ]
  },
  "resolutions": [
    {
      "rule": "customer.credit.limit",
      "type": "accept-risk"
    }
  ]
}

Result, from the engine

Decisionallow1 finding, server channel

  • LRN-RSK-001errornot blocking, acceptedA credit limit above 5000 needs a risk acceptance./creditLimit
Course overview

On this page