Summary
The RVM compiler/codegen has gaps in partial object rule support. These are pre-existing issues uncovered while fixing #712 (which addresses the interpreter-side bug).
In OPA v1, p[k] if { ... } is a partial object rule (key → true). The RVM previously misclassified it as PartialSet. PR #718 corrects the classification to PartialObject, but the codegen only handles the simple variable-key case correctly.
Problem 1: Constant-key partial objects
p["fixed"] if { input.enabled }
The constant key "fixed" gets baked into the rule path by utils.rs path construction. When classified as PartialObject, the rule produces {"fixed": true} via ObjectSet, but leaf lookup at data.test.p.fixed returns the whole object instead of just true.
Expected: data.test.p.fixed → true
Actual: data.test.p.fixed → {"fixed": true} (or lookup fails)
Problem 2: Multi-level bracket keys
p[a][b] if {
some a, obj in input.nested
some b, _ in obj
}
The compiler only keeps one key expression (compiler/rules.rs:353-355) and emits one ObjectSet (compiler/queries.rs:148-196). The outer key is lost, producing a flat object instead of nested.
Expected: {"app": {"read": true, "write": true}, "ops": {"deploy": true}}
Actual: {"read": true, "write": true, "deploy": true} (flattened)
Problem 3: Duplicate key conflict resolution
PartialObject semantics require consistent handling of duplicate keys. OPA raises an error on conflicting values for the same key. Regorus currently silently overwrites (last-writer-wins).
Locked-down tests (skipped in PR #718):
partial_object_duplicate_key_last_wins — single rule, two iterations produce same key with different values
partial_object_duplicate_paths_same_key_different_values_conflict — two rules producing same key with conflicting values
Problem 4: Non-string static-prefix partial objects
p[1][k] if { ... }
p[true][k] if { ... }
p[null][k] if { ... }
PR #718 added is_string_literal() to allow string static prefixes (e.g., p["cfg"][k]), but non-string scalar literals (numbers, booleans, null) are rejected with a misleading "nested bracket keys unsupported" error. These are valid static prefixes and should be handled like string prefixes.
Also: raw-string prefixes (p[`cfg`][k]) pass is_string_literal() validation but fail later in path extraction, which only handles Expr::String, not Expr::RawString.
Problem 5: Undefined key/value materialization
The RVM incorrectly materializes entries with undefined keys or undefined values instead of skipping them.
Locked-down tests (skipped in PR #718):
partial_object_undefined_key_skipped / partial_object_undefined_key_skips_iteration — key expression evaluates to undefined; entry should be skipped
partial_object_undefined_value_skips_iteration — value expression evaluates to undefined; entry should be skipped
partial_object_mixed_undefined_key_value_cases_skip_bad_iterations — mixed cases
Problem 6: Vacuous truth in every body
p[k] if {
every x in input.items { x > 0 }
k := "valid"
}
When input.items is an empty array, every is vacuously true, so the rule should fire. The RVM currently handles this incorrectly for certain groupings.
Locked-down test (skipped in PR #718):
partial_object_every_vacuous_truth_collects_empty_arrays
Current Mitigation (PR #718)
PR #718 adds compiler errors for unsupported patterns (constant-key and multi-level), causing graceful fallback to the interpreter. The simple variable-key case (p[k] if) works correctly in the RVM.
Long-term Fix
A proper fix would involve:
- Logical rule paths (separating static path from dynamic keys)
- Support for multi-key
ObjectSetPath or equivalent
- Proper virtual_data lookup for partial object sub-paths
- Correct undefined propagation (skip entries with undefined key or value)
- Duplicate key conflict detection (error on conflicting values)
- Non-string and raw-string literal prefix support
Relevant files
src/languages/rego/compiler/rules.rs — rule type classification, validate_partial_object_shape()
src/languages/rego/compiler/queries.rs — ObjectSet emission
src/languages/rego/compiler/utils.rs / src/utils.rs — rule path construction
src/rvm/vm/virtual_data.rs — leaf lookup logic
src/rvm/vm/dispatch.rs — ObjectSet instruction implementation
src/rvm/vm/rules.rs — rule frame setup and result aggregation
tests/rvm/rego/cases/partial_object_rules.yaml — skipped tests documenting expected behavior
Related: #712, #718
Summary
The RVM compiler/codegen has gaps in partial object rule support. These are pre-existing issues uncovered while fixing #712 (which addresses the interpreter-side bug).
In OPA v1,
p[k] if { ... }is a partial object rule (key → true). The RVM previously misclassified it asPartialSet. PR #718 corrects the classification toPartialObject, but the codegen only handles the simple variable-key case correctly.Problem 1: Constant-key partial objects
The constant key
"fixed"gets baked into the rule path byutils.rspath construction. When classified asPartialObject, the rule produces{"fixed": true}viaObjectSet, but leaf lookup atdata.test.p.fixedreturns the whole object instead of justtrue.Expected:
data.test.p.fixed→trueActual:
data.test.p.fixed→{"fixed": true}(or lookup fails)Problem 2: Multi-level bracket keys
The compiler only keeps one key expression (
compiler/rules.rs:353-355) and emits oneObjectSet(compiler/queries.rs:148-196). The outer key is lost, producing a flat object instead of nested.Expected:
{"app": {"read": true, "write": true}, "ops": {"deploy": true}}Actual:
{"read": true, "write": true, "deploy": true}(flattened)Problem 3: Duplicate key conflict resolution
PartialObjectsemantics require consistent handling of duplicate keys. OPA raises an error on conflicting values for the same key. Regorus currently silently overwrites (last-writer-wins).Locked-down tests (skipped in PR #718):
partial_object_duplicate_key_last_wins— single rule, two iterations produce same key with different valuespartial_object_duplicate_paths_same_key_different_values_conflict— two rules producing same key with conflicting valuesProblem 4: Non-string static-prefix partial objects
PR #718 added
is_string_literal()to allow string static prefixes (e.g.,p["cfg"][k]), but non-string scalar literals (numbers, booleans, null) are rejected with a misleading "nested bracket keys unsupported" error. These are valid static prefixes and should be handled like string prefixes.Also: raw-string prefixes (
p[`cfg`][k]) passis_string_literal()validation but fail later in path extraction, which only handlesExpr::String, notExpr::RawString.Problem 5: Undefined key/value materialization
The RVM incorrectly materializes entries with undefined keys or undefined values instead of skipping them.
Locked-down tests (skipped in PR #718):
partial_object_undefined_key_skipped/partial_object_undefined_key_skips_iteration— key expression evaluates to undefined; entry should be skippedpartial_object_undefined_value_skips_iteration— value expression evaluates to undefined; entry should be skippedpartial_object_mixed_undefined_key_value_cases_skip_bad_iterations— mixed casesProblem 6: Vacuous truth in
everybodyWhen
input.itemsis an empty array,everyis vacuously true, so the rule should fire. The RVM currently handles this incorrectly for certain groupings.Locked-down test (skipped in PR #718):
partial_object_every_vacuous_truth_collects_empty_arraysCurrent Mitigation (PR #718)
PR #718 adds compiler errors for unsupported patterns (constant-key and multi-level), causing graceful fallback to the interpreter. The simple variable-key case (
p[k] if) works correctly in the RVM.Long-term Fix
A proper fix would involve:
ObjectSetPathor equivalentRelevant files
src/languages/rego/compiler/rules.rs— rule type classification,validate_partial_object_shape()src/languages/rego/compiler/queries.rs— ObjectSet emissionsrc/languages/rego/compiler/utils.rs/src/utils.rs— rule path constructionsrc/rvm/vm/virtual_data.rs— leaf lookup logicsrc/rvm/vm/dispatch.rs— ObjectSet instruction implementationsrc/rvm/vm/rules.rs— rule frame setup and result aggregationtests/rvm/rego/cases/partial_object_rules.yaml— skipped tests documenting expected behaviorRelated: #712, #718