Files
regorus/tests/rvm/rego/cases/partial_object_rules.yaml
Copilot dae3052781 fix(interpreter,rvm): correct partial object rule iteration and classification (#718)
Partial object rules with dynamic keys (e.g. `violations[k] if { ... }`)
only produced a single entry instead of collecting all bindings. Two
independent bugs caused this:

1. Interpreter: the early-return optimization in eval_output_expr_in_loop
   checked whether the rule_ref was constant but never verified whether
   the key expression was also constant. A variable key like `k` was
   treated as constant output, causing the loop to exit after the first
   iteration. Fixed by gating early-return on key_expr constness.

2. RVM: compute_rule_type incorrectly classified `p[k] if { ... }` as
   PartialSet instead of PartialObject. OPA v1 semantics define this
   form as a partial object (key -> true). Fixed the classification and
   added compiler error guards for patterns the RVM codegen cannot yet
   handle (constant keys, nested bracket keys), ensuring graceful
   fallback to the interpreter.

The OPA test harness now skips RVM validation per-case when partial
object compiler errors are raised, rather than blanket-skipping entire
folders. This preserves RVM coverage for unrelated tests in the same
folders.

Closes #712

Co-authored-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-05-18 15:14:08 -05:00

940 lines
19 KiB
YAML

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: partial_object_variable_key_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
p[k] if {
some k, _ in input.items
}
query: data.test.p
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_explicit_value_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
p[k] := v if {
some k, v in input.items
}
query: data.test.p
want_result:
BAR: 2
BAZ: 3
FOO: 1
- note: partial_object_dynamic_expression_key_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
aliases:
FOO: alias-foo
BAR: alias-bar
BAZ: alias-baz
modules:
- |
package test
p[input.aliases[k]] := v if {
some k, v in input.items
}
query: data.test.p
want_result:
alias-bar: 2
alias-baz: 3
alias-foo: 1
- note: partial_object_undefined_key_skipped
# TODO(#719): RVM incorrectly materializes undefined keys instead of
# skipping iterations where the key is undefined.
skip: true
data: {}
input:
items:
FOO: 1
BAR: 2
aliases:
FOO: alias-foo
modules:
- |
package test
p[input.aliases[k]] := v if {
some k, v in input.items
}
query: data.test.p
want_result:
alias-foo: 1
- note: partial_object_duplicate_key_last_wins
# TODO(#719): regorus silently overwrites conflicting keys instead of
# erroring when the same key is produced with different values.
skip: true
data: {}
input: {}
modules:
- |
package test
p[k] := v if {
some k, v in {"a": 1}
}
p[k] := v if {
some k, v in {"a": 2}
}
query: data.test.p
want_error: "conflict"
- note: partial_object_duplicate_key_same_value_ok
data: {}
input: {}
modules:
- |
package test
p[k] if {
some k, _ in {"a": 1}
}
p[k] if {
some k, _ in {"a": 2}
}
query: data.test.p
want_result:
a: true
- note: partial_object_single_element_input
data: {}
input:
items:
ONLY: 1
modules:
- |
package test
p[k] if {
some k, _ in input.items
}
query: data.test.p
want_result:
ONLY: true
- note: partial_object_static_bracket_prefix_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
p["a"][k] := v if {
some k, v in input.items
}
query: data.test.p.a
want_result:
BAR: 2
BAZ: 3
FOO: 1
- note: partial_object_constant_key_unsupported_in_rvm
data: {}
input:
enabled: true
modules:
- |
package test
p["fixed"] if {
input.enabled
}
query: data.test.p.fixed
want_error: "partial object rules with constant keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_constant_key_explicit_value_unsupported_in_rvm
data: {}
input:
enabled: true
modules:
- |
package test
p["fixed"] := 7 if {
input.enabled
}
query: data.test.p.fixed
want_error: "partial object rules with constant keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_multiple_bodies_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
p[k] if {
some k, _ in input.items
k in {"FOO", "BAR"}
}
p[k] if {
some k, _ in input.items
k == "BAZ"
}
query: data.test.p
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_set_contains_collects_all_bindings
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
p contains k if {
some k, _ in input.items
}
query: data.test.p
want_result:
set!: ["BAR", "BAZ", "FOO"]
- note: issue_712_reproducer_v1_partial_object
data: {}
input:
servers:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.servers
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: issue_712_reproducer_v1_contains_partial_set
data: {}
input:
servers:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations contains k if {
some k, _ in input.servers
}
query: data.test.violations
want_result:
set!: ["BAR", "BAZ", "FOO"]
- note: partial_object_multilevel_key_unsupported_in_rvm
data: {}
input:
nested:
app:
read: 1
write: 2
ops:
deploy: 3
modules:
- |
package test
p[a][b] if {
some a, obj in input.nested
some b, _ in obj
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_multilevel_key_explicit_value_unsupported_in_rvm
data: {}
input:
nested:
app:
read: 1
write: 2
ops:
deploy: 3
modules:
- |
package test
p[a][b] := v if {
some a, obj in input.nested
some b, v in obj
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_hidden_dynamic_prefix_unsupported_in_rvm
data: {}
input:
nested:
app:
q:
read: 1
write: 2
ops:
q:
deploy: 3
modules:
- |
package test
p[a].q[b] if {
some a, obj in input.nested
some b, _ in obj.q
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_array_iteration_collects_all_bindings
data: {}
input:
items: ["FOO", "BAR", "BAZ"]
modules:
- |
package test
p[v] if {
some _, v in input.items
}
query: data.test.p
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_empty_input_is_empty_object
data: {}
input:
items: {}
modules:
- |
package test
p[k] if {
some k, _ in input.items
}
query: data.test.p
want_result: {}
- note: partial_object_duplicate_paths_same_key_same_value_deduplicates
data: {}
input:
pairs:
- alias: shared
value: 1
- alias: alpha
value: 10
- alias: shared
value: 1
modules:
- |
package test
p[entry.alias] := entry.value if {
some entry in input.pairs
}
query: data.test.p
want_result:
alpha: 10
shared: 1
- note: partial_object_duplicate_paths_same_key_different_values_conflict
# TODO(#719): regorus silently overwrites conflicting keys instead of
# erroring when the same key is produced with different values.
skip: true
data: {}
input:
pairs:
- alias: shared
value: 1
- alias: shared
value: 2
modules:
- |
package test
p[entry.alias] := entry.value if {
some entry in input.pairs
}
query: data.test.p
want_error: "conflict"
- note: partial_object_undefined_key_skips_iteration
# TODO(#719): RVM incorrectly materializes undefined keys instead of
# skipping iterations where the key is undefined.
skip: true
data: {}
input:
items:
FOO: 1
BAR: 2
BAZ: 3
aliases:
FOO: alias-foo
BAZ: alias-baz
modules:
- |
package test
p[input.aliases[k]] := v if {
some k, v in input.items
}
query: data.test.p
want_result:
alias-baz: 3
alias-foo: 1
- note: partial_object_undefined_value_skips_iteration
# TODO(#719): RVM incorrectly materializes undefined values instead of
# skipping iterations where the value is undefined.
skip: true
data: {}
input:
keys: ["FOO", "BAR", "BAZ"]
values:
FOO: 1
BAZ: 3
modules:
- |
package test
p[k] := input.values[k] if {
some _, k in input.keys
}
query: data.test.p
want_result:
BAZ: 3
FOO: 1
- note: partial_object_mixed_undefined_key_value_cases_skip_bad_iterations
# TODO(#719): RVM incorrectly materializes undefined keys/values instead of
# skipping iterations where the key or value is undefined.
skip: true
data: {}
input:
rows:
- src: keep
- src: missing_alias
- src: missing_value
- src: missing_both
aliases:
keep: alias-keep
missing_value: alias-no-value
values:
keep: 1
missing_alias: 2
modules:
- |
package test
p[input.aliases[row.src]] := input.values[row.src] if {
some row in input.rows
}
query: data.test.p
want_result:
alias-keep: 1
- note: partial_object_and_partial_set_same_name_conflict
data: {}
input:
items:
FOO: 1
modules:
- |
package test
p[k] if {
some k, _ in input.items
}
p contains "shadow" if {
true
}
query: data.test.p
want_error: "has multiple types"
- note: partial_object_complete_rule_conflicts_with_partial_object
data: {}
input:
items:
a: 1
modules:
- |
package test
p := {"fixed": 1}
p[k] := v if {
some k, v in input.items
}
query: data.test.p
want_error: "multiple types"
- note: partial_object_partial_set_conflicts_with_partial_object
data: {}
input:
items:
a: 1
modules:
- |
package test
p contains k if {
some k, _ in input.items
}
p[k] := 1 if {
some k, _ in input.items
}
query: data.test.p
want_error: "multiple types"
- note: partial_object_large_range_counts_all_entries
data: {}
input: {}
modules:
- |
package test
p[key] := n if {
n := numbers.range(0, 255)[_]
key := sprintf("k-%d", [n])
}
main := count(p)
query: data.test.main
want_result: 256
- note: partial_object_rbac_duplicate_actions_deduplicate
data:
role_permissions:
reader: ["read", "list"]
writer: ["read", "write"]
auditor: ["read", "list"]
input:
user_roles: ["reader", "writer", "auditor"]
modules:
- |
package test
allowed_actions[action] if {
some role in input.user_roles
some action in data.role_permissions[role]
}
query: data.test.allowed_actions
want_result:
list: true
read: true
write: true
- note: partial_object_violations_real_world_pattern
data: {}
input:
spec:
containers:
- name: api
securityContext:
readOnlyRootFilesystem: false
- name: worker
securityContext:
readOnlyRootFilesystem: true
- name: sidecar
modules:
- |
package test
violations[msg] if {
some container in input.spec.containers
not container.securityContext.readOnlyRootFilesystem
msg := sprintf("Container %s must use readOnlyRootFilesystem", [container.name])
}
query: data.test.violations
want_result:
Container api must use readOnlyRootFilesystem: true
Container sidecar must use readOnlyRootFilesystem: true
- note: partial_object_resource_mapping_filters_valid_resources
data: {}
input:
resources:
svc-api:
cpu: 1
job-cleanup:
cpu: 2
svc-worker:
cpu: 4
modules:
- |
package test
valid_resource(name) if {
startswith(name, "svc-")
}
resources[name] := config if {
some name, config in input.resources
valid_resource(name)
}
query: data.test.resources
want_result:
svc-api:
cpu: 1
svc-worker:
cpu: 4
- note: partial_object_computed_concat_key_constant_body
data: {}
modules:
- |
package test
p[concat("", ["edge", "-", "key"])] if {
true
}
query: data.test.p
want_result:
edge-key: true
- note: partial_object_duplicate_computed_key_same_value_merges
data: {}
input:
items:
A: 0
a: 0
modules:
- |
package test
p[lower(k)] := 1 if {
some k, _ in input.items
}
query: data.test.p
want_result:
a: 1
- note: partial_object_function_key_and_object_value
data: {}
input:
items:
a: 1
b: 2
modules:
- |
package test
f(x) := concat(":", [x, "suffix"])
p[f(k)] := {"nested": v + 1} if {
some k, v in input.items
}
query: data.test.p
want_result:
"a:suffix":
nested: 2
"b:suffix":
nested: 3
- note: partial_object_array_index_key_uses_selected_elements
data: {}
input:
keys: ["alpha", "beta"]
values: [10, 20]
modules:
- |
package test
p[input.keys[i]] := v if {
some i, v in input.values
}
query: data.test.p
want_result:
alpha: 10
beta: 20
- note: partial_object_computed_empty_and_special_string_keys
data: {}
modules:
- |
package test
p[concat("", [""])] := "empty" if {
true
}
p[concat("", ["a/b?c#d"])] := "special" if {
true
}
query: data.test.p
want_result:
"": "empty"
a/b?c#d: "special"
- note: partial_object_not_filters_blocked_entries
data: {}
input:
items:
allowed: true
blocked: true
blocked:
blocked: true
modules:
- |
package test
p[k] if {
some k, _ in input.items
not input.blocked[k]
}
query: data.test.p
want_result:
allowed: true
- note: partial_object_dot_bracket_object_value_collects_all_bindings
data: {}
input:
items:
a: 1
b: 2
modules:
- |
package test
p.config[k] := {"nested": v} if {
some k, v in input.items
}
query: data.test.p.config
want_result:
a:
nested: 1
b:
nested: 2
- note: partial_object_dynamic_prefix_static_suffix_unsupported_in_rvm
data: {}
input:
items:
a: 1
b: 2
modules:
- |
package test
p[k]["fixed"] := upper(k) if {
some k, _ in input.items
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_literal_prefix_nested_unsupported_in_rvm
data: {}
input:
items:
a: 1
b: 2
modules:
- |
package test
p[1][k] := v if {
some k, v in input.items
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_three_level_nested_dynamic_unsupported_in_rvm
data: {}
input:
nested:
app:
read: 1
write: 2
ops:
deploy: 3
modules:
- |
package test
p["root"][a][b] := v if {
some a, obj in input.nested
some b, v in obj
}
main := p
query: data.test.main
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
allow_interpreter_success: true
- note: partial_object_with_body_unsupported_in_rvm
data: {}
input:
enabled: false
items:
a: 1
modules:
- |
package test
gate if {
input.enabled
}
p[k] := v if {
some k, v in input.items
data.test.gate with input as {"enabled": true}
}
query: data.test.p
want_error: "the `with` keyword is not supported by the compiler yet"
allow_interpreter_success: true
- note: partial_object_every_vacuous_truth_collects_empty_arrays
# TODO(#719): RVM currently includes the failing `bad` group here, while the
# interpreter returns only `empty` and `ok` (expected per vacuous truth
# semantics).
skip: true
data: {}
input:
groups:
ok: [1, 2]
bad: [1, 0]
empty: []
modules:
- |
package test
p[k] if {
some k, arr in input.groups
every v in arr {
v > 0
}
}
query: data.test.p
want_result:
empty: true
ok: true
- note: partial_object_join_var_multiple_bindings
data:
a: ["1", "2", "3", "4"]
g:
a: ["1", "0", "0", "0"]
b: ["0", "2", "0", "0"]
c: ["0", "0", "0", "4"]
modules:
- |
package test
p[k] := v if {
data.a[i] = v
data.g[k][i] = v
}
query: data.test.p
want_result:
a: "1"
b: "2"
c: "4"
- note: partial_object_composite_value
data:
g:
a: [1, 0, 0, 0]
b: [0, 2, 0, 0]
c: [0, 0, 0, 4]
modules:
- |
package test
p[k] := [i, {"v2": v}] if {
data.g[k] = x
x[i] = v
v != 0
}
query: data.test.p
want_result:
a: [0, {v2: 1}]
b: [1, {v2: 2}]
c: [3, {v2: 4}]
- note: partial_object_true_semantics_dedupes_duplicate_keys
data: {}
modules:
- |
package test
p[k] if {
ks := ["a", "b", "c", "a"]
ks[_] = k
}
query: data.test.p
want_result:
a: true
b: true
c: true