« Phase 09 · Warmup · Track Overview
Core Contributor — Working on the Engines Themselves
What it takes to contribute to OPA, Cedar, OpenFGA, or the PDP your bank builds in-house. Read this if you want to understand the systems rather than configure them.
Table of Contents
- 1. Why read the engines
- 2. OPA: the architecture
- 3. Rego evaluation
- 4. Partial evaluation
- 5. Cedar: the analyzability bet
- 6. Zanzibar and OpenFGA
- 7. Building an in-house PDP
- 8. Testing a policy engine
- 9. Contributing
1. Why read the engines
Two practical reasons, beyond curiosity.
Performance debugging. When a Rego policy takes 40 ms, the fix follows from knowing how evaluation works — usually a comprehension in a hot rule that should be an indexed lookup. Without that model you are guessing.
Semantic corners. "What does OPA return when a rule is undefined?" and "does Cedar's forbid
beat a permit in a different policy set?" are questions whose answers are your security boundary.
They are documented, and they are also worth confirming in the source.
2. OPA: the architecture
REST / gRPC / Go API
│
┌────▼───────────────────────────────────────────────┐
│ Compiler parse → AST → type check → plan │
│ Topdown the evaluator (topdown/) │
│ Storage inmem store: policy + data │
│ Plugins bundle, decision_logs, status │
└────────────────────────────────────────────────────┘
The pieces worth knowing by name in the repo:
| Package | Does |
|---|---|
ast/ | parser, AST, compiler, the term representation |
topdown/ | the evaluator — start here for semantics questions |
storage/inmem/ | the document store, with transactions |
plugins/bundle/ | download, verify, activate |
rego/ | the embedding API most integrations use |
ir/ | the intermediate representation, feeding Wasm and the planner |
Bundles are activated under a write transaction against the store, which is what makes activation atomic from a query's point of view. Reading that code is the fastest way to understand why "atomic activation" is a real guarantee and not a slogan.
3. Rego evaluation
Rego is Datalog-descended, and the two consequences that matter:
Rules are sets of bindings, not functions. A rule body with unbound variables enumerates every binding that satisfies it. This is why
deny if {
some tool in input.requested_tools
tool.classification == "restricted"
}
reads as "there exists" rather than "loop and check" — and why an accidental unbound variable turns a cheap rule into a cross-product.
Undefined is not false. A rule whose body fails is undefined, and undefined propagates. This is the single most common source of a silent security hole:
# BROKEN: if input.user is missing, `allow` is undefined, not false
allow if { input.user.role == "admin" }
# CORRECT
default allow := false
allow if { input.user.role == "admin" }
Without the default, the caller receives {} rather than {"allow": false}, and whether that
denies depends entirely on how the PEP reads the result. Requiring a default for every decision
rule is a lint rule worth enforcing in CI.
Indexing. OPA builds a rule index over equality expressions on input, so input.action == "crm.read" in a rule body is a hash lookup, not a scan. Structure hot policies so the discriminating
comparison is a top-level equality on input — the difference between an indexed and a scanned rule
set is an order of magnitude.
4. Partial evaluation
The idea that makes OPA fast at scale: given part of the input, specialize the policy and return a residual policy over the rest.
full policy + {"subject": {"tenant": "wholesale", "clearance": "confidential"}}
│
▼ partial evaluation
residual policy — only the rules that could still apply,
with the known parts folded away
Two uses:
Precompilation. Specialize on the parts of the input known at startup (region, environment, service identity) and evaluate the much smaller residual per request.
Data filtering. This is the powerful one. Instead of "may this principal read document 42?", ask "what is the condition under which this principal may read any document?" and get back a residual that compiles to a SQL WHERE clause. Now authorization is a predicate pushed into the query rather than a filter applied to results you already fetched — which is the difference between paginating correctly and not.
Implemented in topdown/save.go and the rego package's Partial API. If you want one thing to
read in OPA, read this.
5. Cedar: the analyzability bet
Cedar (Rust, cedar-policy/cedar) makes a deliberate trade: less expressive, so it can be
reasoned about mechanically.
The structure:
permit (principal, action, resource) when { ... } unless { ... };
forbid (principal, action, resource) when { ... };
forbidalways wins. Deny-overrides is in the language, not in your rule-writing discipline.- The scope (
principal,action,resource) is constrained syntactically, which is what makes slicing cheap. - Conditions are total and terminating — no unbounded loops, no recursion.
That last property is what buys automated reasoning: Cedar policies compile to SMT formulas, so a solver can answer questions no test suite can.
| Question | Answerable |
|---|---|
| Are these two policy sets equivalent? | ✅ |
| Does this change grant anything new? | ✅ |
| Can any principal reach this resource? | ✅ |
| Is this policy set ever satisfiable? | ✅ |
"Does this change grant anything new?" is the one to care about in a bank. A test suite tells you the cases you thought of; the solver tells you about the ones you did not. If you are choosing a policy language for a regulated platform, this is the argument.
Worth reading: cedar-policy-validator/ (the type system) and the cedar-lean formalization — the
core semantics are proved in Lean, which is a rare thing for an authorization engine and the reason
the guarantees are trustworthy.
6. Zanzibar and OpenFGA
Google's Zanzibar answers a different question: not "what attributes does this subject have?" but "is there a relationship path from this subject to this resource?"
document:budget-2026#viewer@group:finance#member
group:finance#member@user:layla
⇒ layla can view budget-2026
The hard parts, and why the paper is worth reading:
Zookies. A consistency token. "Evaluate this check against a snapshot at least as fresh as the one that produced this token" — which is how Zanzibar offers strong consistency where it matters and cheap stale reads everywhere else. It is the cleanest treatment of the freshness/availability trade-off in this phase, from a system that runs at Google scale.
Leopard. An index for deeply nested group expansion, because the naive recursive check is too slow for real group hierarchies.
For an agent platform, ReBAC is the right model for data authorization (which documents may this user see?) and ABAC is the right model for action authorization (may this agent release a payment?). Most banks need both, and the integration point is that a ReBAC check becomes an attribute in the ABAC decision.
7. Building an in-house PDP
Sometimes correct — a thin PDP over a well-chosen rule model, embedded, with your own bundle distribution. What it takes to be respectable:
Semantics first, written down. Default-deny, deny-overrides, all-facets-conjunctive, and what "undefined" means. Write it as a document before code. Every ambiguity you leave becomes a security question later.
A conformance suite. Requests in, expected decisions out, as data. It outlives every refactor and it is how you keep semantics stable across a rewrite.
Determinism. The same request against the same bundle must give the same decision, including which rule is named. Sort. Never iterate a set where order reaches the output.
Property tests. The invariants are unusually well suited to it:
# adding a DENY rule can never turn a DENY into an ALLOW
# adding an ALLOW rule can never turn an ALLOW into a DENY
# permuting the rule order never changes the effect
# the empty bundle denies everything
Hypothesis will find the case where your combining logic is subtly order-dependent, and it will find it in minutes.
A decision log from day one. Not an afterthought. It is the artifact everything downstream consumes, and its schema is much harder to change later than to get right now.
Explain mode. Given a request, why did it decide that? Which rules matched, which conditions failed and why. Without it, every policy question becomes a bisect, and the people who need answers are the ones least able to bisect.
8. Testing a policy engine
Five layers, in increasing value and decreasing frequency:
| Layer | Catches |
|---|---|
| Unit tests per rule | the rule does what its author meant |
| Property tests over the combining logic | order dependence, monotonicity violations |
| Conformance suite | semantic drift across refactors |
| Corpus replay | the empty-facet rule; every unintended difference |
| Shadow evaluation in production | what the corpus did not contain |
Corpus replay deserves the emphasis. Keep a redacted sample of real decision inputs; replay the candidate bundle; diff. Every difference is intended or a bug. It is the only test that reliably catches an over-broad rule, because the failure mode of an over-broad rule is that nothing breaks.
And the test everyone forgets: the bundle-rejection paths. Unsigned, mis-signed, malformed, older-than-active. Those paths run during an incident, at 3 a.m., and if they are wrong the failure is that your policy silently reverts.
9. Contributing
OPA (open-policy-agent/opa) — Go, CNCF-graduated,
active. Good entry points: builtin functions in topdown/ (self-contained, well-specified),
performance work in the evaluator, rego playground fixtures. Read docs/ and the ADRs first;
semantic changes need an RFC.
Cedar (cedar-policy/cedar) — Rust, smaller, unusually rigorous. Changes to the language need a change to the Lean proofs, which is a high bar and exactly why the guarantees hold. Entry points: the validator, error messages, language bindings.
OpenFGA (openfga/openfga) — Go, CNCF sandbox, friendlier to first-time contributors. Entry points: storage adapters, the modelling language, docs.
For all three the useful preparation is the same: implement the semantics yourself first — the lab is a small version of exactly that — then read theirs and find every place they differ. The differences are where the real engineering is.