feat: Support package-level evaluation in RVM
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 346
- Forks
- 75
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 15
Description
Summary
The RVM (Rego Virtual Machine) currently requires individual rule paths as entry points (e.g., data.test.allow). It does not support package-level queries (e.g., data.test) that evaluate all rules in a package and return a merged object of their results.
The interpreter supports this via eval_query("data.test"), but the RVM compiler rejects package paths with "not a valid rule path" since they don't map to any entry in rule_paths.
Motivation
Package-level evaluation is useful for:
- Evaluating all rules in a package in a single call and receiving a merged result object
- Parity with the interpreter's
eval_querybehavior - Supporting patterns where rules with defaults coexist (e.g.,
default allow := false+default deny := true) and the caller wants the full package state
Current Behavior
package test
default allow := false
default deny := true
allow := true if { false }
deny := false if { false }
| Query | Interpreter | RVM |
|---|---|---|
data.test.allow |
false ✓ |
false ✓ |
data.test.deny |
true ✓ |
true ✓ |
data.test |
{"allow": false, "deny": true} ✓ |
Error: "not a valid rule path" |
Expected Behavior
data.test should return {"allow": false, "deny": true} — evaluating all rules in the test package and merging results into an object.
Technical Context
The RVM compiler resolves entry points in compile_from_policy via get_or_assign_rule_index(), which looks up the path in the rules map (keyed by full rule paths like data.test.allow). A package path like data.test has no entry there, so it fails.
The interpreter handles package queries differently — it evaluates all rules, then walks the data tree to extract the subtree at the requested path.
Why this is non-trivial in the RVM
The RVM compiles each entry point to a CALL_RULE + RETURN sequence that produces a single Value. Package-level evaluation would require:
- Discovering all rule paths under a package prefix — e.g., for
data.test, finddata.test.allowanddata.test.deny - Compiling all of them as entry points — the compiler already supports multiple entry points via
execute_entry_point_by_index - Merging results into an object — evaluating each rule and assembling
{"allow": <result>, "deny": <result>}, handlingUndefined(omit from result), nested packages, and partial objects/sets
Possible approaches
Option A — Compiler-level: synthetic package entry point
Add a new entry point type that emits CALL_RULE for each rule in the package, then OBJ_SET instructions to merge results into a single object. This keeps everything in the VM execution loop.
; Synthetic entry point for "data.test"
CALL_RULE r1 ← rule_0 (allow)
CALL_RULE r2 ← rule_1 (deny)
OBJ_NEW r0
OBJ_SET r0 "allow" r1 ; skip if r1 == Undefined
OBJ_SET r0 "deny" r2 ; skip if r2 == Undefined
RETURN r0
Pros: single VM execution, fast. Cons: more compiler complexity, needs Undefined-skip semantics in OBJ_SET.
Option B — Host-side: iterate entry points and merge
Accept a package path at the API level, resolve it to individual rule paths, compile each as an entry point, execute them via execute_entry_point_by_index, and merge in the host code (Rust or bindings).
// Pseudocode
let rule_paths = compiled_policy.get_rule_paths_for_package("data.test");
let mut result = Value::new_object();
for path in rule_paths {
let value = vm.execute_entry_point_by_name(&path)?;
if value != Value::Undefined {
let key = path.strip_prefix("data.test.").unwrap();
result.insert(key, value);
}
}
Pros: simpler, no compiler changes. Cons: multiple VM executions per package, nested packages need recursive handling.
Skipped Tests
The following test cases in tests/rvm/rego/cases/default_rules.yaml are blocked by this limitation:
multiple_default_rules_different_names— querydata.testwithdefault allow := false+default deny := truedefault_rule_undefined_vs_default— querydata.testwith mix of defaulted and undefined rules
Impact
Medium — package-level queries are a common OPA pattern.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/languages/rego/compiler/rules.rs at compile_from_policy and get_or_assign_rule_index(), then compare the interpreter's package-query handling. Review the skipped cases in tests/rvm/rego/cases/default_rules.yaml, especially multiple_default_rules_different_names and default_rule_undefined_vs_default. Done means data.test evaluates all package rules and returns the expected merged object while preserving individual rule-path behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100