mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(rvm): new instructions and loop semantics for Azure Policy support (#659)
The Rego VM was designed around Rego's semantics, but Azure Policy needs a few things Rego doesn't: host-supplied context alongside input/data, undefined-to-null coercion for missing fields, skip-undefined collection behavior for wildcard aliases, and non-vacuous iteration over non-array values. This commit adds five new instructions to bridge those gaps: LoadContext / LoadMetadata — give programs access to host-supplied evaluation context and cached program metadata at runtime. ArrayPushDefined — like ArrayPush but silently drops undefined values, so wildcard alias collection (field[*].property) excludes absent nested properties instead of leaking undefined entries into the array. ReturnUndefinedIfNotTrue — early return with Undefined when a guard condition isn't satisfied, without tripping a VM assertion failure. This models "condition doesn't match" cleanly. CoalesceUndefinedToNull — turns Undefined into Null in-place so that downstream builtins see null rather than short-circuiting on undefined. The loop engine also gains an Azure Policy mode: when the source language is "azure_policy", an Every loop over a non-array value (scalars, null, objects) iterates once over a virtual Null element instead of being vacuously true. This matches how field[*] behaves on non-array fields in Azure Policy — the condition body runs once against Null, which typically evaluates to false. On the plumbing side: the VM gets a context field with set_context(), metadata is cached as a Value on program load, and map_limit_error is inlined into memory_check since it had only one call site. Four new YAML test suites (~880 lines) cover the new instructions and context/metadata loading, along with instruction parser, display, and assembly listing support for everything added here.
This commit is contained in:
committed by
GitHub
parent
8f740e2f6f
commit
e5ac9a2734
@@ -0,0 +1,92 @@
|
||||
# ArrayPushDefined instruction test suite
|
||||
#
|
||||
# Like ArrayPush but silently skips Undefined values. Used by the Azure Policy
|
||||
# compiler when collecting `field('alias[*].property')` results: absent nested
|
||||
# properties produce Undefined and should be excluded from the collected array.
|
||||
#
|
||||
# Semantics:
|
||||
# - If value register is Undefined → no-op (skip).
|
||||
# - If value register is any other value (including Null) → push to array.
|
||||
# - If arr register is not an array → error.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Undefined values are skipped
|
||||
# =========================================================================
|
||||
|
||||
- note: push_undefined_is_skipped
|
||||
description: Pushing an undefined value leaves the array unchanged
|
||||
literals: []
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
# Register 1 is implicitly Undefined
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: []
|
||||
|
||||
- note: push_undefined_among_defined
|
||||
description: Only defined values are collected; undefined ones are silently dropped
|
||||
literals:
|
||||
- 10
|
||||
- 20
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
# Register 2 is Undefined — should be skipped
|
||||
- "ArrayPushDefined { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 3 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [10, 20]
|
||||
|
||||
# =========================================================================
|
||||
# Null and other values are kept
|
||||
# =========================================================================
|
||||
|
||||
- note: push_null_is_kept
|
||||
description: Null is not undefined — it is pushed to the array
|
||||
literals: []
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [null]
|
||||
|
||||
- note: push_bool_is_kept
|
||||
description: Boolean value is pushed normally
|
||||
literals:
|
||||
- true
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [true]
|
||||
|
||||
- note: push_string_is_kept
|
||||
description: String value is pushed normally
|
||||
literals:
|
||||
- "hello"
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: ["hello"]
|
||||
|
||||
# =========================================================================
|
||||
# Non-array target is an error
|
||||
# =========================================================================
|
||||
|
||||
- note: push_to_non_array_errors
|
||||
description: ArrayPushDefined on a non-array register produces an error
|
||||
literals:
|
||||
- 42
|
||||
- 1
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "ArrayPushDefined { arr: 0, value: 1 }"
|
||||
want_error: "Register 0 does not contain an array"
|
||||
@@ -0,0 +1,194 @@
|
||||
# CoalesceUndefinedToNull instruction test suite
|
||||
#
|
||||
# Used by the Azure Policy compiler to convert undefined (missing) field values
|
||||
# to null before passing them to comparison operators. This prevents undefined
|
||||
# propagation from short-circuiting subsequent builtins.
|
||||
#
|
||||
# Semantics:
|
||||
# - If register == Undefined → replaces it with Null (in-place).
|
||||
# - For any other value (null, bool, number, string, array, object) → no-op.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Basic coalescing behavior
|
||||
# =========================================================================
|
||||
|
||||
- note: undefined_becomes_null
|
||||
description: Undefined register value is replaced with null
|
||||
literals: []
|
||||
instructions:
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: null_unchanged
|
||||
description: Null value is not modified
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: true_unchanged
|
||||
description: Boolean true is not modified
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: true
|
||||
|
||||
- note: false_unchanged
|
||||
description: Boolean false is not modified
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadFalse { dest: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: false
|
||||
|
||||
- note: number_unchanged
|
||||
description: Number value is not modified
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: 42
|
||||
|
||||
- note: string_unchanged
|
||||
description: String value is not modified
|
||||
literals:
|
||||
- "hello"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "hello"
|
||||
|
||||
- note: empty_string_unchanged
|
||||
description: Empty string is not modified (not confused with undefined)
|
||||
literals:
|
||||
- ""
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: ""
|
||||
|
||||
- note: array_unchanged
|
||||
description: Array value is not modified
|
||||
literals: []
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: []
|
||||
|
||||
# =========================================================================
|
||||
# Multiple coalesces — only undefined registers are affected
|
||||
# =========================================================================
|
||||
|
||||
- note: selective_coalescing
|
||||
description: Only undefined registers are coalesced; others remain unchanged
|
||||
literals:
|
||||
- 99
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 1 }"
|
||||
- "ArrayNew { dest: 2 }"
|
||||
- "ArrayPush { arr: 2, value: 0 }"
|
||||
- "ArrayPush { arr: 2, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: [99, null]
|
||||
|
||||
# =========================================================================
|
||||
# Interaction with ReturnUndefinedIfNotTrue
|
||||
# =========================================================================
|
||||
|
||||
- note: coalesce_then_guard_null_returns_undefined
|
||||
description: >
|
||||
Coalescing undefined to null, then passing to ReturnUndefinedIfNotTrue
|
||||
returns undefined (null is not true)
|
||||
literals:
|
||||
- "should not reach"
|
||||
instructions:
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: coalesce_preserves_true_for_guard
|
||||
description: >
|
||||
Coalescing a true value is a no-op, so ReturnUndefinedIfNotTrue
|
||||
continues execution
|
||||
literals:
|
||||
- "reached"
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "reached"
|
||||
|
||||
# =========================================================================
|
||||
# Interaction with HostAwait (suspendable mode)
|
||||
# =========================================================================
|
||||
|
||||
- note: coalesce_after_host_await_undefined_response
|
||||
description: >
|
||||
HostAwait returns undefined, CoalesceUndefinedToNull converts it to null
|
||||
literals:
|
||||
- "await-id"
|
||||
- "arg"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses:
|
||||
- id: "await-id"
|
||||
value: "#undefined"
|
||||
want_result: null
|
||||
|
||||
- note: coalesce_after_host_await_value_response
|
||||
description: >
|
||||
HostAwait returns a real value, CoalesceUndefinedToNull is a no-op
|
||||
literals:
|
||||
- "await-id"
|
||||
- "arg"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses:
|
||||
- id: "await-id"
|
||||
value: "real-value"
|
||||
want_result: "real-value"
|
||||
|
||||
- note: suspendable_coalesce_after_host_await
|
||||
description: >
|
||||
In suspendable mode: HostAwait suspends and resumes with value,
|
||||
CoalesceUndefinedToNull is a no-op
|
||||
literals:
|
||||
- "await-id"
|
||||
- "arg"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
||||
- "CoalesceUndefinedToNull { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses_suspendable:
|
||||
- id: "await-id"
|
||||
value: 42
|
||||
ignore_run_to_completion_hostawait_failure: true
|
||||
want_result: 42
|
||||
@@ -0,0 +1,389 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# LoadContext and LoadMetadata Test Suite
|
||||
# Tests LoadContext and LoadMetadata instructions
|
||||
# Covers: no context, populated context, context indexing, metadata fields,
|
||||
# annotations, metadata round-trip, and interaction with other loads
|
||||
|
||||
cases:
|
||||
# ── LoadContext ────────────────────────────────────────────────────────────
|
||||
|
||||
- note: load_context_undefined
|
||||
description: LoadContext returns Undefined when no context is set
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: load_context_empty_object
|
||||
description: LoadContext returns empty object
|
||||
context: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {}
|
||||
|
||||
- note: load_context_string
|
||||
description: LoadContext returns a string value
|
||||
context: "hello world"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "hello world"
|
||||
|
||||
- note: load_context_number
|
||||
description: LoadContext returns a numeric value
|
||||
context: 42
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: 42
|
||||
|
||||
- note: load_context_boolean
|
||||
description: LoadContext returns a boolean value
|
||||
context: true
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_context_with_values
|
||||
description: LoadContext returns a populated object
|
||||
context:
|
||||
api_version: "2021-06-01"
|
||||
tenant_id: "abc-123"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
api_version: "2021-06-01"
|
||||
tenant_id: "abc-123"
|
||||
|
||||
- note: load_context_nested
|
||||
description: LoadContext returns deeply nested data
|
||||
context:
|
||||
level1:
|
||||
level2:
|
||||
value: "deep"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
level1:
|
||||
level2:
|
||||
value: "deep"
|
||||
|
||||
- note: load_context_and_index
|
||||
description: LoadContext followed by indexing into context data
|
||||
context:
|
||||
api_version: "2021-06-01"
|
||||
tenant_id: "abc-123"
|
||||
literals:
|
||||
- "api_version"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "2021-06-01"
|
||||
|
||||
- note: load_context_index_missing_key
|
||||
description: Loading context and indexing a missing key yields Undefined
|
||||
context:
|
||||
api_version: "2021-06-01"
|
||||
literals:
|
||||
- "nonexistent"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: load_context_deep_index
|
||||
description: LoadContext with chained indexing into nested object
|
||||
context:
|
||||
request:
|
||||
headers:
|
||||
content_type: "application/json"
|
||||
literals:
|
||||
- "request"
|
||||
- "headers"
|
||||
- "content_type"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "Index { dest: 6, container: 4, key: 5 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: "application/json"
|
||||
|
||||
- note: load_context_with_array
|
||||
description: LoadContext with an array value
|
||||
context:
|
||||
tags:
|
||||
- "production"
|
||||
- "east-us"
|
||||
literals:
|
||||
- "tags"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Count { dest: 3, collection: 2 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: 2
|
||||
|
||||
- note: load_context_multiple_times
|
||||
description: Loading context multiple times returns the same value
|
||||
context:
|
||||
value: 42
|
||||
literals:
|
||||
- "value"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "LoadContext { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_context_independent_of_input
|
||||
description: Context and Input are independent
|
||||
input:
|
||||
input_key: "from_input"
|
||||
context:
|
||||
context_key: "from_context"
|
||||
literals:
|
||||
- "context_key"
|
||||
- "input_key"
|
||||
- "from_context"
|
||||
- "from_input"
|
||||
instructions:
|
||||
# Load context["context_key"] into r2
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
# Load input["input_key"] into r5
|
||||
- "LoadInput { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "Index { dest: 5, container: 3, key: 4 }"
|
||||
# Verify context value == "from_context"
|
||||
- "Load { dest: 6, literal_idx: 2 }"
|
||||
- "Eq { dest: 7, left: 2, right: 6 }"
|
||||
# Verify input value == "from_input"
|
||||
- "Load { dest: 8, literal_idx: 3 }"
|
||||
- "Eq { dest: 9, left: 5, right: 8 }"
|
||||
# Both must be true
|
||||
- "And { dest: 10, left: 7, right: 9 }"
|
||||
- "Return { value: 10 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_context_independent_of_data
|
||||
description: Context and Data are independent
|
||||
data:
|
||||
data_key: "from_data"
|
||||
context:
|
||||
context_key: "from_context"
|
||||
literals:
|
||||
- "context_key"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "from_context"
|
||||
|
||||
# ── LoadMetadata ────────────────────────────────────────────────────────────
|
||||
|
||||
- note: load_metadata_default
|
||||
description: LoadMetadata returns object with metadata fields - check by indexing language
|
||||
literals:
|
||||
- "language"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
# Default language is empty string
|
||||
want_result: ""
|
||||
|
||||
- note: load_metadata_language
|
||||
description: LoadMetadata returns language field when set
|
||||
metadata_language: "azure_policy"
|
||||
literals:
|
||||
- "language"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "azure_policy"
|
||||
|
||||
- note: load_metadata_annotations_string
|
||||
description: LoadMetadata includes string annotations
|
||||
metadata_language: "azure_policy"
|
||||
metadata_annotations:
|
||||
policy_mode: "indexed"
|
||||
policy_category: "Security"
|
||||
literals:
|
||||
- "annotations"
|
||||
- "policy_mode"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "indexed"
|
||||
|
||||
- note: load_metadata_annotations_number
|
||||
description: LoadMetadata includes numeric annotations
|
||||
metadata_annotations:
|
||||
max_retries: 3
|
||||
literals:
|
||||
- "annotations"
|
||||
- "max_retries"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: 3
|
||||
|
||||
- note: load_metadata_annotations_boolean
|
||||
description: LoadMetadata includes boolean annotations
|
||||
metadata_annotations:
|
||||
is_audit: true
|
||||
literals:
|
||||
- "annotations"
|
||||
- "is_audit"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_metadata_annotations_nested
|
||||
description: LoadMetadata includes nested annotations (object)
|
||||
metadata_annotations:
|
||||
compliance:
|
||||
framework: "nist"
|
||||
controls:
|
||||
- "AC-1"
|
||||
- "AC-2"
|
||||
literals:
|
||||
- "annotations"
|
||||
- "compliance"
|
||||
- "framework"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "Index { dest: 6, container: 4, key: 5 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: "nist"
|
||||
|
||||
- note: load_metadata_annotations_absent
|
||||
description: LoadMetadata without annotations - indexing annotations gives Undefined
|
||||
literals:
|
||||
- "annotations"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: load_metadata_multiple_times
|
||||
description: Loading metadata multiple times returns same value
|
||||
metadata_language: "rego"
|
||||
literals:
|
||||
- "language"
|
||||
instructions:
|
||||
- "LoadMetadata { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "LoadMetadata { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "Index { dest: 5, container: 3, key: 4 }"
|
||||
- "Eq { dest: 6, left: 2, right: 5 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: true
|
||||
|
||||
# ── Combined LoadContext + LoadMetadata ──────────────────────────────────
|
||||
|
||||
- note: context_and_metadata_independent
|
||||
description: Context and metadata are independent values
|
||||
context:
|
||||
env: "production"
|
||||
metadata_language: "rego"
|
||||
literals:
|
||||
- "env"
|
||||
- "language"
|
||||
instructions:
|
||||
- "LoadContext { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
# r2 = "production"
|
||||
- "LoadMetadata { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "Index { dest: 5, container: 3, key: 4 }"
|
||||
# r5 = "rego"
|
||||
- "Ne { dest: 6, left: 2, right: 5 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: true
|
||||
|
||||
- note: all_four_loads
|
||||
description: LoadData, LoadInput, LoadContext, LoadMetadata all coexist
|
||||
data:
|
||||
role: "admin"
|
||||
input:
|
||||
action: "read"
|
||||
context:
|
||||
region: "us-east"
|
||||
metadata_language: "test"
|
||||
literals:
|
||||
- "role"
|
||||
- "action"
|
||||
- "region"
|
||||
- "language"
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
# r2 = "admin"
|
||||
- "LoadInput { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "Index { dest: 5, container: 3, key: 4 }"
|
||||
# r5 = "read"
|
||||
- "LoadContext { dest: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 2 }"
|
||||
- "Index { dest: 8, container: 6, key: 7 }"
|
||||
# r8 = "us-east"
|
||||
- "LoadMetadata { dest: 9 }"
|
||||
- "Load { dest: 10, literal_idx: 3 }"
|
||||
- "Index { dest: 11, container: 9, key: 10 }"
|
||||
# r11 = "test"
|
||||
# Return context region as witness
|
||||
- "Return { value: 8 }"
|
||||
want_result: "us-east"
|
||||
@@ -0,0 +1,209 @@
|
||||
# ReturnUndefinedIfNotTrue instruction test suite
|
||||
#
|
||||
# This instruction is used by the Azure Policy compiler to short-circuit
|
||||
# evaluation when a policy condition does not match.
|
||||
#
|
||||
# Semantics:
|
||||
# - If condition register == true (Bool), execution continues to the next instruction.
|
||||
# - For ANY other value (false, null, undefined, numbers, strings, arrays, objects),
|
||||
# the instruction immediately returns Value::Undefined.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Basic behavior — true continues, everything else returns undefined
|
||||
# =========================================================================
|
||||
|
||||
- note: true_continues
|
||||
description: When condition is true, execution continues and the next value is returned
|
||||
literals:
|
||||
- "success"
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "success"
|
||||
|
||||
- note: false_returns_undefined
|
||||
description: When condition is false, returns undefined immediately
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadFalse { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: null_returns_undefined
|
||||
description: When condition is null, returns undefined
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_returns_undefined
|
||||
description: When condition is undefined (uninitialized register), returns undefined
|
||||
literals: []
|
||||
instructions:
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: number_returns_undefined
|
||||
description: Numbers are not boolean true — returns undefined
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: string_returns_undefined
|
||||
description: Non-empty strings are not boolean true — returns undefined
|
||||
literals:
|
||||
- "truthy"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# =========================================================================
|
||||
# Multiple guards in sequence
|
||||
# =========================================================================
|
||||
|
||||
- note: two_guards_both_true
|
||||
description: Two consecutive guards — both true, execution reaches the end
|
||||
literals:
|
||||
- "final"
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "final"
|
||||
|
||||
- note: two_guards_first_false
|
||||
description: First guard fails — immediately returns undefined, second guard not reached
|
||||
literals:
|
||||
- "final"
|
||||
instructions:
|
||||
- "LoadFalse { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: two_guards_second_false
|
||||
description: First guard passes, second fails — returns undefined
|
||||
literals:
|
||||
- "final"
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
||||
- "LoadFalse { dest: 1 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# =========================================================================
|
||||
# Combined with HostAwait (the auditIfNotExists pattern)
|
||||
# =========================================================================
|
||||
|
||||
- note: host_await_then_guard_true
|
||||
description: >
|
||||
Simulates auditIfNotExists: HostAwait returns true (non-compliant),
|
||||
guard passes, effect string is returned
|
||||
literals:
|
||||
- "auditIfNotExists"
|
||||
- "azure.policy.audit_if_not_exists"
|
||||
- "request"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 3, arg: 1, id: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
host_await_responses:
|
||||
- id: "azure.policy.audit_if_not_exists"
|
||||
value: true
|
||||
want_result: "auditIfNotExists"
|
||||
|
||||
- note: host_await_then_guard_false
|
||||
description: >
|
||||
Simulates auditIfNotExists: HostAwait returns false (compliant),
|
||||
guard fails, returns undefined (no policy violation)
|
||||
literals:
|
||||
- "auditIfNotExists"
|
||||
- "azure.policy.audit_if_not_exists"
|
||||
- "request"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 3, arg: 1, id: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
host_await_responses:
|
||||
- id: "azure.policy.audit_if_not_exists"
|
||||
value: false
|
||||
want_result: "#undefined"
|
||||
|
||||
# =========================================================================
|
||||
# Suspendable mode — HostAwait + ReturnUndefinedIfNotTrue
|
||||
# =========================================================================
|
||||
|
||||
- note: suspendable_host_await_guard_true
|
||||
description: >
|
||||
In suspendable mode: HostAwait suspends, resumes with true,
|
||||
ReturnUndefinedIfNotTrue continues, returns effect
|
||||
literals:
|
||||
- "deny"
|
||||
- "await-id"
|
||||
- "arg"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 3, arg: 1, id: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
host_await_responses_suspendable:
|
||||
- id: "await-id"
|
||||
value: true
|
||||
ignore_run_to_completion_hostawait_failure: true
|
||||
want_result: "deny"
|
||||
|
||||
- note: suspendable_host_await_guard_false
|
||||
description: >
|
||||
In suspendable mode: HostAwait suspends, resumes with false,
|
||||
ReturnUndefinedIfNotTrue returns undefined
|
||||
literals:
|
||||
- "deny"
|
||||
- "await-id"
|
||||
- "arg"
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 3, arg: 1, id: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "ReturnUndefinedIfNotTrue { condition: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
host_await_responses_suspendable:
|
||||
- id: "await-id"
|
||||
value: false
|
||||
ignore_run_to_completion_hostawait_failure: true
|
||||
want_result: "#undefined"
|
||||
@@ -96,7 +96,7 @@ cases:
|
||||
want_error: "#undefined"
|
||||
|
||||
- note: logical_not_int
|
||||
description: NOT with non-boolean defined operand should yield false
|
||||
description: NOT with int operand treats non-boolean as truthy (result false)
|
||||
example_rego: "not 42"
|
||||
literals:
|
||||
- 42
|
||||
|
||||
Reference in New Issue
Block a user