Internals
Read from the source at commit
f75131f. Every claim here points at a file and line.
Code map
The implementation lives under v1/. The root packages are thin shims (see the last section).
| Path | Responsibility |
|---|---|
cmd/ | CLI subcommands on the cobra root (cmd/commands.go:14). |
v1/ast/ | Rego parser, compiler, type checker, and the AST types. |
v1/topdown/ | Top-down evaluation engine, unification, and built-in functions. |
v1/rego/ | High-level compile-then-evaluate API (rego.New().Eval()). |
v1/server/ | REST PDP serving /v1/data/<path>. |
v1/storage/ | Store abstraction over base documents. |
v1/bundle/ | Bundle load, sign, and verify. |
v1/sdk/, v1/plugins/ | Embeddable SDK and decision-log / bundle / status plugins. |
Core data structures
Termis the smallest AST unit:type Term structatv1/ast/term.go:315, carrying aValueand a*Location.Valueis the interface every AST value implements (v1/ast/term.go:61), requiringCompare,Find,Hash,IsGround, andString. Concrete values includeVar(v1/ast/term.go:1148),Ref(v1/ast/term.go:1215), andObject(v1/ast/term.go:2130).Module,Rule,Head, andBodyare the policy structure (v1/ast/policy.go:193,:227,:245,:263). AModuleholds package, imports, rules, and annotations; aRulecarries a default flag, head, body, and else branch.bindingsholds variable bindings during evaluation (v1/topdown/bindings.go:32). It uses an adaptive representation: an array for few bindings, switching to a map past a threshold.Storeis the transactional read/write boundary for base documents (v1/storage/interface.go:20), keeping external data separate from policy evaluation.
A path worth tracing
Evaluating a Rego policy once runs through these hops.
rego.New(opts...)(v1/rego/rego.go:1414) builds the evaluator from query, modules, store, and input options.(*Rego).Eval(v1/rego/rego.go:1502) opens a transaction and prepares the query.gopq, err := r.PrepareForEval(ctx) // ... rs, err := pq.Eval(ctx, evalArgs...)PrepareForEval(v1/rego/rego.go:1788) parses and compiles Rego once, caching it incompiledQueries[evalQueryType]so re-evaluation reuses the compiled form.PreparedEvalQuery.Eval(v1/rego/rego.go:559) sets the compiled query on the eval context and delegates to the engine.goectx.compiledQuery = pq.r.compiledQueries[evalQueryType] return pq.r.eval(ctx, ectx)(*Rego).eval(v1/rego/rego.go:2309) branches on target (rego, wasm, plugin). The default rego target builds atopdown.NewQuery(...)and injects compiler, store, transaction, built-ins, and caches.(*Query).Iter(v1/topdown/query.go:565) initializes theevalstruct and runs the loop in(*eval).eval(v1/topdown/eval.go:404), which drivesevalExpr(v1/topdown/eval.go:408) andevalStep(v1/topdown/eval.go:461). When all expressions are consumed it yields a solution.Variable binding is resolved by
(*eval).biunify(v1/topdown/eval.go:1134), the bi-directional unification step.
Things that surprised me
evalStep (v1/topdown/eval.go:461) carries two nearly identical branches: one for when tracing is enabled and one for when it is not. The comment explains why this duplication is deliberate.
// NOTE(æ): the reason why there's one branch for the tracing case and one almost
// identical branch below for when tracing is disabled is that the tracing case
// allocates wildly. These allocations are cause by the "defined" boolean variable
// escaping to the heap as its value is set from inside of closures.The non-tracing common path is kept allocation-free; without the split, a defined boolean would escape to the heap from inside closures and cost several million allocations on some workloads.
The other surprise is structural: the packages you import at the repository root, such as rego/rego.go, contain no logic. They are type aliases onto v1/* (for example type Rego = v1.Rego). The whole engine moved under v1/ for the 1.0 default-language switch, and the root packages exist only to keep old import paths working.