/ 01Why OPA / Rego
Authorisation logic in code is hard to audit, hard to change, and hard to prove correct. Rego separates policy from product: policies are declarative, expressed in a language designed for this, and live in a separate signed bundle.
Three properties matter:
- Replayable — every decision logs the bundle version and inputs hash; identical inputs always produce identical outputs
- Auditable — bundles are versioned in git, signed, deployed through a controlled endpoint
- Performant —
regoruspartial-evaluates hot paths; median decision < 2ms
/ 02Bundle layout
policies/ ├── disclosure/ │ ├── ladder.rego # L0..L6 enforcement │ ├── ceiling.rego # Classification ceilings per silo-pair │ └── silo_pair.rego # Sharing matrices ├── access_request/ │ ├── auto_grant.rego # Eligibility for auto-approval │ └── conflict_check.rego # Pre-check on grant ├── ethical_wall/ │ └── deny.rego # Absolute DENY (precedes everything) ├── admin/ │ ├── config_mutation.rego # Who can change what │ └── sub_processor.rego # Sub-processor change controls ├── temporal/ │ ├── grant_expiry.rego │ └── time_of_day.rego └── lib/ ├── helpers.rego # Shared predicates └── inputs.rego # Canonical input shape
/ 03Disclosure policies
The shape of a disclosure policy:
package disclosure # Maximum disclosure level for this requester against this asset. # Result is one of "L0".."L6". max_level := level { not ethical_wall.recused not conflict_screen.applies level := compute_ceiling } # Default — if anything above fails, we deny. default max_level := "L0" compute_ceiling := level { base := ceiling[input.document.classification][input.requester.silo][input.document.silo] author := input.document.author_setting steward := input.document.steward_override level := min({base, author, steward}) }
Each module is small, single-purpose, and unit-tested with golden inputs.
/ 04Access-request policies
An access request goes through three gates: eligibility, conflict pre-check, approval.
package access_request # Auto-approve when the requester is on the matter team # and the requested level is <= L5. auto_grant { input.requester.matter_assignments[_].matter_id == input.target.matter_id input.requested_level <= "L5" not conflict_check.fails }
Manual-approval policies define who can approve: author by default, CRP for sensitive clients, group compliance for L6.
/ 05Ethical wall (absolute DENY)
Wall policies take precedence over everything. They are evaluated before disclosure ceilings, before clearances, before step-up. If a wall denies, nothing else matters.
package ethical_wall # DENY — no override, no exception. The wall is the wall. deny[reason] { matter := input.document.matter matter == input.requester.ethical_wall_recusals[_] reason := sprintf("Recused from matter %s", [matter]) } deny[reason] { client := input.document.client client == input.requester.conflict_screens[_] reason := sprintf("Conflict screen on client %s", [client]) }
/ 06Signing & verification
Bundles are Ed25519-signed. The Atrium engine refuses to load an unsigned or invalid bundle.
Compile
opa build policies/ --bundle policies.tar.gz— produces a bundleSign
atrium policies sign policies.tar.gz --key signing.pem— emitspolicies.tar.gz.sigDeploy
Upload both to the bundle endpoint; engines pull at the next reload tick and verify the signature before loading
Verify in CI
atrium policies verify policies.tar.gz --key signing.pub— required step in your deploy pipeline
/ 07Decision logs
Every decision is shipped to the audit log with:
bundle_version— e.g.atrium-policies@v3.2.1decision—allow·deny· disclosure levelrule— the specific rule that firedinputs_hash— SHA-256 of the canonical input (privacy-preserving)
Decisions are replayable: given the bundle and the inputs, you can re-evaluate locally and confirm the same outcome. This is the foundation of "what did the platform decide, and why" being a defensible answer.
