Thoryn

Policy rules · Policy rules

Composite — admin OR (manager AND finance)

A nested rule combining `any`, `all`, and equality leaves. Demonstrates arbitrary nesting and short-circuit evaluation.

Tested against:policyEngine: 1.0.0

policy-engine recipe — shared category architecture: how this pattern composes with Hub, Broker, and the rest of the catalog

Use case

Either: the user is a global admin, OR the user is a manager and in the finance department. Express this as any of two children, the second of which is itself an all.

Rule

{
  "any": [
    { "fact": "role", "operator": "equal", "value": "admin" },
    {
      "all": [
        { "fact": "role", "operator": "equal", "value": "manager" },
        { "fact": "department", "operator": "equal", "value": "finance" }
      ]
    }
  ]
}

Facts shape

data class OrgFacts(val role: String, val department: String)

Short-circuit behaviour

any short-circuits on the first child that returns pass. So if the user is admin, the all block is never evaluated. This matters when child evaluations have a non-trivial cost (fact gathering, IO).

Trace — ALLOW (manager + finance)

{
  "decision": "ALLOW",
  "trace": [
    { "any": [
        { "fact": "role", "operator": "equal", "value": "admin", "actual": "manager", "result": "fail" },
        { "all": [
            { "fact": "role", "operator": "equal", "value": "manager", "actual": "manager", "result": "pass" },
            { "fact": "department", "operator": "equal", "value": "finance", "actual": "finance", "result": "pass" }
          ],
          "result": "pass"
        }
      ],
      "result": "pass"
    }
  ]
}

When to use

  • Composite RBAC where one branch is broad (admin) and the other is narrow (manager AND department)
  • Audit reviewers want a single rule per surface, not 5 rules with overlapping logic

When not to use

  • The same logic can be expressed as a flat any/all — flat is easier to reason about

See also