Summary
Rules using bracket-syntax with defaults (e.g., default config["timeout"] := 30) are misclassified by the RVM compiler as PartialObject instead of Complete. This causes incorrect evaluation when querying the specific rule path.
Reproduction
package test
default config["timeout"] := 30
config["timeout"] := 60 if {
false
}
Expected
| Query |
Expected Result |
data.test.config.timeout |
30 |
data.test.config |
{"timeout": 30} |
data.test |
{"config": {"timeout": 30}} |
Actual
| Query |
Interpreter |
RVM |
data.test.config.timeout |
{} (wrong) |
{} (wrong) |
data.test.config |
{"timeout": 30} ✓ |
N/A (package query) |
data.test |
{"config": {"timeout": 30}} ✓ |
N/A (package query) |
Both the interpreter and RVM return incorrect results for the specific rule query data.test.config.timeout. Only the interpreter's package-level evaluation produces the correct result.
Root Cause
In compute_rule_type() (src/languages/rego/compiler/rules.rs), the rule type classification matches RefBrack with assignment as PartialObject:
RuleHead::Compr { refr, assign, .. } => match refr.as_ref() {
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
RuleType::PartialObject // ← incorrect for literal keys
}
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
_ => RuleType::Complete,
},
The pattern config["timeout"] := 30 is parsed as RefBrack { index: Expr::String("timeout"), .. } with an assignment. The current code treats all RefBrack + assignment as PartialObject, but only variable keys produce partial objects. A string literal key is semantically equivalent to config.timeout — a complete rule assigned to a specific object key.
The distinction:
config[key] := value where key is Expr::Var → PartialObject (dynamic key, generates entries for multiple keys)
config["timeout"] := value where "timeout" is Expr::String → Complete (static key, equivalent to dot access)
Proposed Fix
In compute_rule_type(), inspect the RefBrack.index expression to distinguish literal keys from variable keys:
RuleHead::Compr { refr, assign, .. } => match refr.as_ref() {
crate::ast::Expr::RefBrack { index, .. } if assign.is_some() => {
match index.as_ref() {
// Literal key like config["timeout"] — treated as complete rule
crate::ast::Expr::String { .. }
| crate::ast::Expr::RawString { .. }
| crate::ast::Expr::Number { .. } => RuleType::Complete,
// Variable key like config[key] — partial object
_ => RuleType::PartialObject,
}
}
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
_ => RuleType::Complete,
},
This same fix should also be verified in the interpreter's eval_rule_in_path code path, which currently also returns {} for the specific query.
The skipped test case default_rule_with_object_key in tests/rvm/rego/cases/default_rules.yaml validates this scenario and should be unskipped after the fix.
Impact
Low — this pattern is uncommon in practice.
Summary
Rules using bracket-syntax with defaults (e.g.,
default config["timeout"] := 30) are misclassified by the RVM compiler asPartialObjectinstead ofComplete. This causes incorrect evaluation when querying the specific rule path.Reproduction
Expected
data.test.config.timeout30data.test.config{"timeout": 30}data.test{"config": {"timeout": 30}}Actual
data.test.config.timeout{}(wrong){}(wrong)data.test.config{"timeout": 30}✓data.test{"config": {"timeout": 30}}✓Both the interpreter and RVM return incorrect results for the specific rule query
data.test.config.timeout. Only the interpreter's package-level evaluation produces the correct result.Root Cause
In
compute_rule_type()(src/languages/rego/compiler/rules.rs), the rule type classification matchesRefBrackwith assignment asPartialObject:The pattern
config["timeout"] := 30is parsed asRefBrack { index: Expr::String("timeout"), .. }with an assignment. The current code treats allRefBrack+ assignment asPartialObject, but only variable keys produce partial objects. A string literal key is semantically equivalent toconfig.timeout— a complete rule assigned to a specific object key.The distinction:
config[key] := valuewherekeyisExpr::Var→ PartialObject (dynamic key, generates entries for multiple keys)config["timeout"] := valuewhere"timeout"isExpr::String→ Complete (static key, equivalent to dot access)Proposed Fix
In
compute_rule_type(), inspect theRefBrack.indexexpression to distinguish literal keys from variable keys:This same fix should also be verified in the interpreter's
eval_rule_in_pathcode path, which currently also returns{}for the specific query.The skipped test case
default_rule_with_object_keyintests/rvm/rego/cases/default_rules.yamlvalidates this scenario and should be unskipped after the fix.Impact
Low — this pattern is uncommon in practice.