Add comprehensive documentation and GitHub Copilot configuration: - docs/knowledge/: 17 deep-dive knowledge files covering value semantics, RVM architecture, builtins, FFI boundary, feature composition, error handling migration, policy evaluation security, Rego semantics, interpreter/compiler architecture, Azure Policy/RBAC, engine API, time builtins, language extension guide, tooling architecture, causality/partial eval, Rego compiler, Azure Policy aliases, and telemetry/diagnostics - .github/agents/: 16 role-specific AI agent definitions (red-teamer, semantics-expert, architect, performance-engineer, test-engineer, verification-engineer, security-auditor, reliability-engineer, support-engineer, ci-engineer, refactorer, api-steward, program-manager, demo-engineer, dx-engineer, tech-lead) - .github/skills/: 6 workflow skill definitions (thorough-review, design-alternatives, add-builtin, opa-conformance, security-review, verification) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
7.3 KiB
Knowledge: Rego Semantics
Deep knowledge about how regorus evaluates Rego policies. Read this before
modifying src/interpreter.rs, src/scheduler.rs, src/compiler/, or
any evaluation-related code.
Evaluation Model
Regorus is a compile-then-execute engine. Key passes:
Source → Lexer → Parser → AST → Compiler (scheduling, destructuring, loop hoisting) → Execution
The compiler pre-computes:
- Destructuring plans: how to bind variables from patterns
- Schedules: statement execution order within rule bodies
- Loop hoisting: which iterations can be computed at compile time
Runtime evaluation is then straightforward — no runtime planning.
Rule Evaluation
Rule Types
Complete rules — produce a single value:
allow = true { input.role == "admin" }
Partial rules — can have multiple bodies, first success wins:
allow { input.role == "admin" }
allow { input.role == "superuser" }
Bodies are evaluated in order. When one succeeds, remaining bodies are skipped.
Default rules — fallback when no rule produces a value:
default allow = false
Default rules are explicitly skipped during normal rule evaluation. They fire
only when the path is Undefined and no complete rule exists.
Precedence: initial data > evaluated rules > default rules
Rule Caching
Evaluated rules are tracked in self.processed set to prevent re-evaluation.
Once a rule has been evaluated for a given context, it won't be re-evaluated
unless the context changes (e.g., via with keyword).
Unification and Destructuring
Regorus does NOT use a traditional unification algorithm. Instead:
- The compiler analyzes patterns and generates
DestructuringPlans - At runtime,
execute_destructuring_plan()matches values against patterns - Returns
true(match succeeded, variables bound) orfalse(no match)
This is more like pattern matching than Prolog-style unification. There is no occurs check, no variable-to-variable binding chains.
Backtracking
Backtracking in regorus is limited and explicit — it only occurs with
some...in expressions:
some x in collection
The backtracking mechanism:
- Save current scope
- Iterate over the collection
- For each element, bind variables and evaluate remaining statements
- If remaining statements fail, restore scope and try next element
- Succeed if any element leads to successful evaluation
There is no implicit backtracking in other contexts. Statements in a rule body execute sequentially — if one fails, the entire rule body fails (no trying alternatives for previous statements).
Undefined Propagation in Evaluation
Boolean and Comparison Operations
Undefined <op> anything → Undefined
anything <op> Undefined → Undefined
This applies to all binary operations: ==, !=, <, >, <=, >=,
+, -, *, /, %, &, |.
Negation (the subtle case)
not true → false
not false → true
not Undefined → true
not Undefined is true because negating "this expression has no value"
means "the condition is not met" which is truthy. This is correct OPA
semantics.
Reference Chains
x = input.a.b.c
If input.a exists but input.a.b doesn't, the entire reference returns
Undefined. The interpreter navigates the path and returns Undefined at the
first missing component.
Collection Literals
arr = [1, x, 3] # If x is Undefined, arr is Undefined (not [1, 3])
Any Undefined element poisons the entire collection literal. This is not
intuitive but matches OPA semantics.
Builtin Arguments
count(x) # If x is Undefined, result is Undefined
If any argument to a builtin is Undefined, the result is Undefined. The
function is never called.
Rule Body Statements
When a statement in a rule body evaluates to Undefined or false, the
rule body fails. Statements must succeed sequentially:
allow {
input.role == "admin" # If Undefined → body fails here
input.active == true # Never reached
}
Virtual Documents (Rules as Data)
Rules materialize into the data object. When code references data.pkg.rule,
the interpreter:
- Checks if the path has initial data (from
add_data()) - If not, looks for rules that define that path
- Evaluates those rules (if not already cached)
- Returns the result
ensure_rule_evaluated() is the trigger — it's called during path navigation
when a reference might resolve to a rule-defined value.
The with Keyword
with temporarily overrides data, input, or functions during evaluation:
x = eval { y = f(1) with f as g }
Implementation pattern (save/modify/restore):
- Save current state (data, input, processed rules, rule values, with_functions)
- Apply overrides — modify
self.with_documentand related state - Clear
self.processedto allow re-evaluation with new overrides - Evaluate the expression
- Restore original state
Function override types:
FunctionModifier::Value(v)— replace function with a constant valueFunctionModifier::Function(path)— replace function with another function
Comprehensions
All comprehensions follow the same pattern:
- Push new context with
output_exprand collection type - Evaluate the query (generates solutions)
- For each solution, evaluate
output_exprand add to context's collection - Pop context and return accumulated collection
Array comprehension: [expr | query] → ordered array of expr values
Set comprehension: {expr | query} → set of expr values
Object comprehension: {key: value | query} → object of key-value pairs
Scheduling
The scheduler (src/scheduler.rs) determines statement execution order within
rule bodies. This is a compile-time optimization that:
- Analyzes variable dependencies between statements
- Orders statements to minimize wasted work
- Moves ground-truth checks (constants, type checks) before expensive iterations
- Hoists loop-invariant computations
The schedule is pre-computed and stored — the interpreter follows it directly.
OPA Conformance
Regorus targets faithful OPA semantics. The conformance suite (tests/opa.rs)
runs the official OPA test cases. Key areas where conformance matters:
- Undefined propagation — must match OPA exactly
- Error messages — builtin error messages are compared literally
- Type coercion — number handling, string comparison
- Rule indexing — which rules fire for which inputs
- Comprehension behavior — ordering, deduplication
When behavior differs from OPA, it's a bug unless documented as an intentional
extension (gated behind rego-extensions feature).
Common Pitfalls
- Treating Undefined as false — see value-semantics.md for the full story
- Forgetting
not Undefined = true— the most common subtle bug - Collection literal with Undefined element — entire collection becomes Undefined
- Rule body short-circuit — first failing statement stops the body
- Default rule precedence — defaults only fire when path is truly Undefined
withscope — overrides only apply to the expression, not siblings- Virtual document evaluation order — rules may evaluate lazily