Level cascade
An organisation baseline and a retail feature ruleset that extends it, tightens a limit and raises a severity - and what the result looks like.
Files: examples/contracts/acme-org-base.ruleset.yaml
(acme.org.base@1.2.0) and
examples/contracts/payments-transfer.ruleset.yaml
(acme.payments.transfer@1.0.0). Every listing below is read from them when the site is built.
The organisation sets a baseline, with policies on what may change
blockedCountries is locked: no level below may change it. maxTransferAmount is
tighten-only in the lower direction: a level below may lower it, never raise it.
metadata:
id: acme.org.base
version: 1.2.0
title: Acme organisation-wide money movement rules
owner: enterprise-risk
status: active
scope:
- { level: enterprise, id: acme-group }
- { level: organization, id: acme }
entities:
Transfer:
description: Any outbound movement of money.
schema: { $ref: "./payments.openapi.yaml#/components/schemas/Transfer" }
params:
blockedCountries:
type: stringList
default: [KP, IR]
description: ISO 3166-1 alpha-2 codes no transfer may be sent to.
overridePolicy: locked
maxTransferAmount:
type: number
default: 50000
description: Largest single transfer without a risk acceptance.
overridePolicy: tighten-only
tightenDirection: lowerThe feature extends it and tightens it
The retail feature names its place in the organisation, extends the baseline with a version range, lowers the limit to 25000 and raises the memo rule's severity:
scope:
- { level: enterprise, id: acme-group }
- { level: organization, id: acme }
- { level: businessUnit, id: retail-banking }
- { level: application, id: payments-hub }
- { level: feature, id: transfers }
extends:
- { ruleset: acme.org.base, version: ^1.2.0 }overrides:
params:
maxTransferAmount: 25000 # retail tightens the org limit of 50000
rules:
- rule: org.transfer.memo-recommended
set: { severity: warning }
reason: Retail operations needs memos for dispute handling.rcas check examples/contracts/*.ruleset.yamlacme.org.base@1.2.0 sha256:4dff42ddd5da... 3 rules (2 client-safe), 2 params
3 golden tests, 0 failed
acme.payments.transfer@1.0.0 sha256:c192dd53b5b1... 14 rules (9 client-safe), 4 params
14 golden tests, 0 failedSet the feature's maxTransferAmount to 60000 instead, and the policy refuses it:
payments-transfer.ruleset.yaml: LOAD FAILED
PARAM_LOOSENED: acme.payments.transfer: param maxTransferAmount may only move lowerDone when both rulesets load with 0 failed golden tests, and loosening the limit fails to load
with PARAM_LOOSENED and exit status 1.
See the cascade in a result
A teller sends 30,000. The limit applied is the tightened 25000, and source names the level that
defined the rule:
echo '{"entity":"Transfer","operation":"create",
"data":{"id":"t-5","type":"domestic","amount":30000,"currency":"USD","memo":"house",
"beneficiary":{"name":"Sam","country":"US"}},
"actor":{"id":"u-1","roles":["teller"]},
"resolutions":[{"rule":"transfer.large.review-warning","type":"acknowledge"}]}' |
rcas evaluate --bundle conformance/bundles/acme.payments.transfer.bundle.json - |
jq '{decision, finding: (.findings[] | select(.rule == "org.transfer.amount-limit"))}'{
"decision": "deny",
"finding": {
"rule": "org.transfer.amount-limit",
"code": "ORG-TRF-002",
"severity": "error",
"message": "Amount exceeds the single-transfer limit of 25000.",
"fields": [
"/amount"
],
"blocking": true,
"status": "open",
"resolution": "accept-risk",
"source": "acme.org.base@1.2.0",
"location": {
"component": "amount-panel"
},
"acceptableBy": [
"risk-officer"
]
}
}Done when the message says 25000 (the feature's value, not the organisation's 50000) and
source is acme.org.base@1.2.0. The golden tests of the feature ruleset pin this down: "over the
tightened retail limit is denied for an ordinary user" and "a risk officer can accept the limit
breach with a justification".
What the policies refuse, and how to build your own chain: Multi-level inheritance and overrides.