fix(ffi): eliminate aliasing UB + add Azure Policy JSON compilation FFI (#727)

* fix(ffi): eliminate aliasing UB via to_shared_ref migration

Add to_shared_ref() helper that creates &T (shared reference) from raw
pointers instead of &mut T. This eliminates undefined behavior caused by
violating Rust's aliasing invariant when C# SafeHandle permits concurrent
FFI calls on the same handle.

With &mut T, the compiler may assume exclusive (noalias) access and
reorder or elide reads/writes — a miscompilation risk when another thread
holds a reference to the same object. Switching to &T removes that
assumption; actual mutation is mediated by the interior RwLock inside
Handle<T>, which is the sole synchronization mechanism.

Migrated sites:
- rvm.rs: 20 non-drop call sites
- engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns
- compiled_policy.rs: 2 call sites
- Fix null-data UB in regorus_program_deserialize_binary

Drop paths retain to_ref() where exclusive access is guaranteed by the
caller contract (preventing use-after-free).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ffi): add Azure Policy JSON compilation FFI and C# bindings

- AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable,
  single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped)
- Azure Policy JSON compilation: regorus_compile_azure_policy_rule and
  regorus_compile_azure_policy_definition with alias registry support
- regorus_rvm_set_context for host-supplied ambient data
- C# AliasRegistryBuilder and AliasRegistry classes with convenience
  factories (FromJson, FromManifest, Empty)
- C# AzurePolicyCompiler static class for policy rule/definition compilation
- Compile functions take *const RegorusAliasRegistry (read-only via
  to_shared_ref for concurrent compilation safety)
- Fix pre-existing clippy warnings across multiple crates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Anand Krishnamoorthi
2026-05-22 12:50:16 -05:00
committed by GitHub
parent 5467cd9e69
commit 86b4a279fa
26 changed files with 1986 additions and 315 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RegorusPackageVersion>0.10.0</RegorusPackageVersion>
<RegorusPackageVersion>0.10.1</RegorusPackageVersion>
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
+73
View File
@@ -150,3 +150,76 @@ const string ContextJson = """
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
Console.WriteLine($"RBAC condition allowed: {allowed}");
```
## Azure Policy JSON Evaluation
Compile and evaluate Azure Policy JSON `policyRule` definitions directly — no Rego translation required.
The `AzurePolicyCompiler` compiles JSON policy rules into RVM programs that can be executed with the `Rvm` engine.
```csharp
using Regorus;
// 1. Load alias definitions for the resource provider
const string AliasesJson = """
[{
"namespace": "Microsoft.Storage",
"resourceTypes": [{
"resourceType": "storageAccounts",
"aliases": [{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
}]
}]
}]
""";
using var registry = AliasRegistry.FromJson(AliasesJson);
// 2. Compile a JSON policy rule (the native Azure Policy language)
const string PolicyRule = """
{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
]
},
"then": { "effect": "deny" }
}
""";
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, PolicyRule);
// 3. Normalize an ARM resource and evaluate
var armResource = """
{
"type": "Microsoft.Storage/storageAccounts",
"name": "mystorage",
"properties": { "supportsHttpsTrafficOnly": false }
}
""";
var envelope = registry.NormalizeAndWrap(armResource);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(envelope!);
var result = vm.ExecuteEntryPoint("main");
// result: {"effect": "deny"} for non-compliant, "<undefined>" for compliant
Console.WriteLine($"Policy result: {result}");
```
**Context-dependent policies:** If your policy uses context functions like
`subscription()`, `resourceGroup()`, or `requestContext()`, you must also set
the VM context separately:
```csharp
// The context JSON from NormalizeAndWrap is in the input envelope,
// but must also be provided to the VM's ambient context:
vm.SetContextJson(contextJson);
```
You can also compile full policy definitions (with parameters) using
`AzurePolicyCompiler.CompilePolicyDefinition()`. See
`bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs` for comprehensive examples.
@@ -43,31 +43,28 @@ public class AliasRegistryTests
[TestMethod]
public void Create_and_dispose_succeeds()
{
using var registry = new AliasRegistry();
using var registry = AliasRegistry.Empty();
Assert.AreEqual(0, registry.Length);
}
[TestMethod]
public void LoadJson_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void LoadManifest_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
using var registry = AliasRegistry.FromManifest(ManifestJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void NormalizeAndWrap_produces_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -93,8 +90,7 @@ public class AliasRegistryTests
[TestMethod]
public void NormalizeAndWrap_with_context_and_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -115,8 +111,7 @@ public class AliasRegistryTests
[TestMethod]
public void Denormalize_restores_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var normalized = @"{
""name"": ""acct1"",
@@ -137,8 +132,7 @@ public class AliasRegistryTests
[TestMethod]
public void Round_trip_normalize_then_denormalize()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -166,8 +160,7 @@ public class AliasRegistryTests
[TestMethod]
public void DataPlane_manifest_normalize()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
using var registry = AliasRegistry.FromManifest(ManifestJson);
var resource = @"{
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
@@ -185,7 +178,7 @@ public class AliasRegistryTests
[ExpectedException(typeof(InvalidOperationException))]
public void LoadJson_invalid_throws()
{
using var registry = new AliasRegistry();
registry.LoadJson("not valid json");
using var builder = new AliasRegistryBuilder();
builder.LoadJson("not valid json");
}
}
@@ -0,0 +1,436 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
/// <summary>
/// Tests for <see cref="AzurePolicyCompiler"/> — compiling Azure Policy JSON
/// policyRule and policyDefinition into RVM programs and evaluating them.
/// </summary>
[TestClass]
public class AzurePolicyCompilerTests
{
// -----------------------------------------------------------------------
// Test data
// -----------------------------------------------------------------------
private const string StorageAliasesJson = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""capabilities"": ""SupportsTags, SupportsLocation"",
""aliases"": [
{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
},
{
""name"": ""Microsoft.Storage/storageAccounts/minimumTlsVersion"",
""defaultPath"": ""properties.minimumTlsVersion"",
""paths"": []
}
]
}]
}]";
/// <summary>Simple policy rule that checks the resource type.</summary>
private const string SimpleAuditRule = @"{
""if"": {
""field"": ""type"",
""equals"": ""Microsoft.Storage/storageAccounts""
},
""then"": { ""effect"": ""audit"" }
}";
/// <summary>Policy rule that uses an alias to check HTTPS-only.</summary>
private const string HttpsDenyRule = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""deny"" }
}";
/// <summary>Full policy definition with parameters.</summary>
private const string PolicyDefinitionWithParams = @"{
""displayName"": ""Require HTTPS for storage accounts"",
""policyType"": ""Custom"",
""mode"": ""Indexed"",
""parameters"": {
""effect"": {
""type"": ""String"",
""defaultValue"": ""deny""
}
},
""policyRule"": {
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""[parameters('effect')]"" }
}
}";
// -----------------------------------------------------------------------
// Helper
// -----------------------------------------------------------------------
/// <summary>
/// Wrap a normalized resource JSON and parameters into the input envelope
/// expected by compiled Azure Policy RVM programs.
/// </summary>
private static string WrapInput(string resourceJson, string parametersJson = "{}")
{
return $@"{{""resource"": {resourceJson}, ""parameters"": {parametersJson}}}";
}
/// <summary>
/// Compile a policy rule, load it into an RVM, set input, and execute.
/// Returns the result string from <c>ExecuteEntryPoint("main")</c>.
/// </summary>
private static string? CompileAndEval(
AliasRegistry? registry,
string policyRuleJson,
string inputJson)
{
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, policyRuleJson);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(inputJson);
return vm.ExecuteEntryPoint("main");
}
// -----------------------------------------------------------------------
// CompilePolicyRule tests
// -----------------------------------------------------------------------
[TestMethod]
public void CompilePolicyRule_no_aliases_succeeds()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
Assert.IsNotNull(program);
}
[TestMethod]
public void CompilePolicyRule_with_aliases_succeeds()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
Assert.IsNotNull(program);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void CompilePolicyRule_null_json_throws()
{
AzurePolicyCompiler.CompilePolicyRule(null, null!);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CompilePolicyRule_invalid_json_throws()
{
AzurePolicyCompiler.CompilePolicyRule(null, "not valid json");
}
// -----------------------------------------------------------------------
// CompilePolicyDefinition tests
// -----------------------------------------------------------------------
[TestMethod]
public void CompilePolicyDefinition_no_aliases_succeeds()
{
using var program = AzurePolicyCompiler.CompilePolicyDefinition(null, PolicyDefinitionWithParams);
Assert.IsNotNull(program);
}
[TestMethod]
public void CompilePolicyDefinition_with_aliases_succeeds()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyDefinition(registry, PolicyDefinitionWithParams);
Assert.IsNotNull(program);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void CompilePolicyDefinition_null_json_throws()
{
AzurePolicyCompiler.CompilePolicyDefinition(null, null!);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CompilePolicyDefinition_invalid_json_throws()
{
AzurePolicyCompiler.CompilePolicyDefinition(null, @"{""not"": ""a definition""}");
}
// -----------------------------------------------------------------------
// End-to-end evaluation tests
// -----------------------------------------------------------------------
[TestMethod]
public void Eval_simple_rule_matching_resource_returns_effect()
{
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
var result = CompileAndEval(null, SimpleAuditRule, input);
Assert.IsNotNull(result, "expected a result for matching resource");
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>(),
$"expected 'audit' effect, got: {result}");
}
[TestMethod]
public void Eval_simple_rule_non_matching_resource_returns_undefined()
{
var input = WrapInput(
@"{""type"": ""microsoft.compute/virtualmachines""}");
var result = CompileAndEval(null, SimpleAuditRule, input);
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for non-matching resource type");
}
[TestMethod]
public void Eval_alias_rule_non_compliant_returns_deny()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Non-compliant: HTTPS not enabled (normalized/lowercased form)
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' for non-compliant resource, got: {result}");
}
[TestMethod]
public void Eval_alias_rule_compliant_returns_undefined()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Compliant: HTTPS enabled
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": true}");
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for compliant resource");
}
[TestMethod]
public void Eval_definition_with_default_parameters()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyDefinition(
registry, PolicyDefinitionWithParams);
using var vm = new Rvm();
vm.LoadProgram(program);
// Non-compliant resource
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
// Default parameter value is "deny"
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected default 'deny' effect, got: {result}");
}
[TestMethod]
public void Eval_with_normalized_arm_resource_end_to_end()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Simulate the full production flow:
// 1. Start with an ARM resource
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""mystorage"",
""location"": ""eastus"",
""properties"": {
""supportsHttpsTrafficOnly"": false,
""minimumTlsVersion"": ""TLS1_0""
}
}";
// 2. Normalize via AliasRegistry
var normalizedEnvelope = registry.NormalizeAndWrap(
armResource,
apiVersion: null,
contextJson: "{}",
parametersJson: "{}");
Assert.IsNotNull(normalizedEnvelope);
// 3. Compile the policy rule
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
// 4. Execute
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(normalizedEnvelope!);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' for non-HTTPS storage account, got: {result}");
}
[TestMethod]
public void Eval_normalized_compliant_resource_end_to_end()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""secureastorage"",
""location"": ""westus"",
""properties"": {
""supportsHttpsTrafficOnly"": true,
""minimumTlsVersion"": ""TLS1_2""
}
}";
var normalizedEnvelope = registry.NormalizeAndWrap(
armResource,
apiVersion: null,
contextJson: "{}",
parametersJson: "{}");
Assert.IsNotNull(normalizedEnvelope);
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(normalizedEnvelope!);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for compliant HTTPS storage account");
}
[TestMethod]
public void Program_can_be_serialized_and_reloaded()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
// Serialize to binary
var binary = program.SerializeBinary();
Assert.IsTrue(binary.Length > 0, "serialized program should not be empty");
// Deserialize and run
using var restored = Program.DeserializeBinary(binary, out var isPartial);
Assert.IsFalse(isPartial, "program should not be partial");
using var vm = new Rvm();
vm.LoadProgram(restored);
var input = WrapInput(@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>());
}
[TestMethod]
public void Program_generates_listing()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
var listing = program.GenerateListing();
Assert.IsFalse(string.IsNullOrWhiteSpace(listing),
"generated listing should not be empty");
}
// -----------------------------------------------------------------------
// Context-dependent policy tests
// -----------------------------------------------------------------------
/// Policy rule that uses subscription() context function.
private const string ContextPolicyRule = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""value"": ""[subscription().subscriptionId]"", ""equals"": ""sub-123"" }
]
},
""then"": { ""effect"": ""deny"" }
}";
[TestMethod]
public void Eval_context_policy_with_set_context_returns_effect()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetContextJson(@"{""subscription"": {""subscriptionId"": ""sub-123""}}");
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' with matching context, got: {result}");
}
[TestMethod]
public void Eval_context_policy_without_context_returns_undefined()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
// No context set — subscription() will be undefined
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined without context set");
}
}
@@ -62,8 +62,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(
StorageResourceJson,
@@ -84,8 +83,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(StorageResourceJson);
Assert.IsNotNull(result);
@@ -107,8 +105,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(StorageResourceJson);
var doc = JsonNode.Parse(result!);
@@ -125,8 +122,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var parametersJson = @"{ ""effect"": ""Deny"" }";
var result = registry.NormalizeAndWrap(
@@ -143,8 +139,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_Denormalize_roundtrips_correctly()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Normalize the ARM resource.
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
@@ -177,8 +172,7 @@ public class AzurePolicyTests
}
var aliasesJson = File.ReadAllText(aliasesPath);
using var registry = new AliasRegistry();
registry.LoadJson(aliasesJson);
using var registry = AliasRegistry.FromJson(aliasesJson);
// The test_aliases.json file contains multiple providers.
Assert.IsTrue(registry.Length > 0,
+41 -66
View File
@@ -8,51 +8,43 @@ using Regorus.Internal;
namespace Regorus
{
/// <summary>
/// Manages Azure Policy alias definitions used for resource normalization
/// Immutable Azure Policy alias registry used for resource normalization
/// and policy compilation.
/// </summary>
public unsafe sealed class AliasRegistry : SafeHandleWrapper
{
/// <summary>
/// Create an empty alias registry.
/// </summary>
public AliasRegistry()
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
internal AliasRegistry(RegorusAliasRegistryHandle handle)
: base(handle, nameof(AliasRegistry))
{
}
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
/// Create an empty immutable alias registry.
/// </summary>
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
public void LoadJson(string json)
public static AliasRegistry Empty()
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_json(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
using var builder = new AliasRegistryBuilder();
return builder.Build();
}
/// <summary>
/// Load a data-plane policy manifest from a JSON string.
/// Create an immutable alias registry from control-plane alias JSON.
/// </summary>
/// <param name="json">JSON object containing a DataPolicyManifest</param>
public void LoadManifest(string json)
public static AliasRegistry FromJson(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
using var builder = new AliasRegistryBuilder();
builder.LoadJson(json);
return builder.Build();
}
/// <summary>
/// Create an immutable alias registry from a data-plane manifest JSON document.
/// </summary>
public static AliasRegistry FromManifest(string json)
{
using var builder = new AliasRegistryBuilder();
builder.LoadManifest(json);
return builder.Build();
}
/// <summary>
@@ -74,11 +66,6 @@ namespace Regorus
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
/// expected by a compiled Azure Policy program.
/// </summary>
/// <param name="resourceJson">Raw ARM resource JSON</param>
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
/// <returns>JSON string: { "resource": &lt;normalized&gt;, "context": &lt;context&gt;, "parameters": &lt;params&gt; }</returns>
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
{
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
@@ -96,27 +83,22 @@ namespace Regorus
(byte*)ctxPtr, (byte*)paramsPtr));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
}
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
})));
}
/// <summary>
/// Denormalize a previously-normalized resource JSON back to ARM format.
/// </summary>
/// <param name="normalizedJson">The normalized resource JSON</param>
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
/// <returns>Denormalized ARM JSON string</returns>
public string? Denormalize(string normalizedJson, string? apiVersion = null)
{
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
@@ -131,23 +113,16 @@ namespace Regorus
(byte*)normPtr, null));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
}
});
}
private static string? CheckAndDropResult(RegorusResult result)
{
return ResultHelpers.GetStringResult(result);
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
});
}
}
}
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Mutable, single-threaded builder for <see cref="AliasRegistry"/>.
/// Load alias data, then call <see cref="Build"/> to freeze the registry.
/// </summary>
public unsafe sealed class AliasRegistryBuilder : SafeHandleWrapper
{
/// <summary>
/// Create an empty alias registry builder.
/// </summary>
public AliasRegistryBuilder()
: base(RegorusAliasRegistryBuilderHandle.Create(), nameof(AliasRegistryBuilder))
{
}
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
/// </summary>
public void LoadJson(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(builderPtr =>
{
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_json(
(RegorusAliasRegistryBuilder*)builderPtr,
(byte*)jsonPtr));
});
});
}
/// <summary>
/// Load a data-plane policy manifest from a JSON string.
/// </summary>
public void LoadManifest(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(builderPtr =>
{
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_manifest(
(RegorusAliasRegistryBuilder*)builderPtr,
(byte*)jsonPtr));
});
});
}
/// <summary>
/// Freeze the builder into an immutable, thread-safe alias registry.
/// </summary>
public AliasRegistry Build()
{
return UseHandle(builderPtr =>
{
var registryPtr = ResultHelpers.GetPointerResult(
API.regorus_alias_registry_builder_build((RegorusAliasRegistryBuilder*)builderPtr));
return new AliasRegistry(RegorusAliasRegistryHandle.FromPointer(registryPtr));
});
}
}
}
@@ -0,0 +1,183 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for compiling Azure Policy JSON definitions
/// into RVM programs that can be executed by <see cref="Rvm"/>.
/// </summary>
/// <remarks>
/// <para>
/// This class bridges the gap between Azure Policy JSON (the native
/// Azure policy language with <c>policyRule</c>, <c>field</c>,
/// <c>equals</c>, etc.) and Regorus's RVM execution engine.
/// </para>
///
/// <para>
/// <b>Typical workflow:</b>
/// </para>
/// <list type="number">
/// <item>Load alias definitions with <see cref="AliasRegistryBuilder"/> and freeze them into an <see cref="AliasRegistry"/>.</item>
/// <item>Normalize the ARM resource via <see cref="AliasRegistry.NormalizeAndWrap"/>.</item>
/// <item>Compile the JSON policyRule with <see cref="CompilePolicyRule"/> or the
/// full definition with <see cref="CompilePolicyDefinition"/>.</item>
/// <item>Execute the resulting <see cref="Program"/> in an <see cref="Rvm"/>
/// instance with the normalized input.</item>
/// </list>
///
/// <para>
/// <b>Context-dependent policies:</b> Policies that use context functions
/// such as <c>subscription()</c>, <c>resourceGroup()</c>, or
/// <c>requestContext()</c> require the VM context to be set separately via
/// <see cref="Rvm.SetContextJson"/> before execution. The context JSON
/// returned by <see cref="AliasRegistry.NormalizeAndWrap"/> is passed as
/// <c>input.context</c> but is <b>not</b> automatically wired into the VM's
/// ambient context — the caller must do both:
/// <c>vm.SetInputJson(envelope)</c> and <c>vm.SetContextJson(contextJson)</c>.
/// </para>
/// </remarks>
public static unsafe class AzurePolicyCompiler
{
/// <summary>
/// Compile an Azure Policy JSON policy rule into an RVM <see cref="Program"/>.
/// </summary>
/// <param name="aliasRegistry">
/// Alias registry for resolving fully-qualified alias names in field
/// references. Pass <c>null</c> if no alias resolution is needed.
/// <para>
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
/// property paths and will silently produce incorrect evaluation results for
/// policies that use aliases. Modify/Append effect policies will also skip
/// the compile-time modifiability validation. Only pass <c>null</c> when the
/// policy is known to contain no alias references (e.g. simple type/location
/// checks or unit-test scenarios).
/// </para>
/// </param>
/// <param name="policyRuleJson">
/// JSON string containing the policyRule object, e.g.
/// <c>{ "if": { "field": "type", "equals": "..." }, "then": { "effect": "deny" } }</c>
/// </param>
/// <returns>
/// A compiled <see cref="Program"/> ready to be loaded into an
/// <see cref="Rvm"/> instance.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="policyRuleJson"/> is <c>null</c>.
/// </exception>
/// <exception cref="Exception">
/// Thrown when parsing or compilation fails.
/// </exception>
public static Program CompilePolicyRule(AliasRegistry? aliasRegistry, string policyRuleJson)
{
if (policyRuleJson is null)
{
throw new ArgumentNullException(nameof(policyRuleJson));
}
return Utf8Marshaller.WithUtf8(policyRuleJson, rulePtr =>
{
if (aliasRegistry is null)
{
var result = API.regorus_compile_azure_policy_rule(
null, (byte*)rulePtr);
return GetProgramResult(result);
}
else
{
return aliasRegistry.UseHandleForInterop(regPtr =>
{
var result = API.regorus_compile_azure_policy_rule(
(RegorusAliasRegistry*)regPtr, (byte*)rulePtr);
return GetProgramResult(result);
});
}
});
}
/// <summary>
/// Compile a full Azure Policy definition JSON into an RVM <see cref="Program"/>.
/// </summary>
/// <param name="aliasRegistry">
/// Alias registry for resolving fully-qualified alias names in field
/// references. Pass <c>null</c> if no alias resolution is needed.
/// <para>
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
/// property paths and will silently produce incorrect evaluation results for
/// policies that use aliases. Modify/Append effect policies will also skip
/// the compile-time modifiability validation. Only pass <c>null</c> when the
/// policy is known to contain no alias references (e.g. simple type/location
/// checks or unit-test scenarios).
/// </para>
/// </param>
/// <param name="policyDefinitionJson">
/// JSON string containing the full policy definition, which includes
/// <c>policyRule</c>, <c>parameters</c>, <c>displayName</c>, etc.
/// Accepted in both wrapped and unwrapped forms.
/// </param>
/// <returns>
/// A compiled <see cref="Program"/> ready to be loaded into an
/// <see cref="Rvm"/> instance.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="policyDefinitionJson"/> is <c>null</c>.
/// </exception>
/// <exception cref="Exception">
/// Thrown when parsing or compilation fails.
/// </exception>
public static Program CompilePolicyDefinition(AliasRegistry? aliasRegistry, string policyDefinitionJson)
{
if (policyDefinitionJson is null)
{
throw new ArgumentNullException(nameof(policyDefinitionJson));
}
return Utf8Marshaller.WithUtf8(policyDefinitionJson, defnPtr =>
{
if (aliasRegistry is null)
{
var result = API.regorus_compile_azure_policy_definition(
null, (byte*)defnPtr);
return GetProgramResult(result);
}
else
{
return aliasRegistry.UseHandleForInterop(regPtr =>
{
var result = API.regorus_compile_azure_policy_definition(
(RegorusAliasRegistry*)regPtr, (byte*)defnPtr);
return GetProgramResult(result);
});
}
});
}
private static Program GetProgramResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected program pointer but got different data type");
}
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
return new Program(handle);
}
finally
{
API.regorus_result_drop(result);
}
}
}
}
+57 -15
View File
@@ -178,6 +178,14 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
/// <summary>
/// Set the context document for the RVM.
/// The context provides host-supplied ambient data (e.g. resourceGroup(), subscription())
/// that Azure Policy functions can access.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_rvm_set_context(RegorusRvm* vm, byte* context_json);
/// <summary>
/// Execute the program.
/// </summary>
@@ -490,6 +498,20 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
/// <summary>
/// Compile an Azure Policy JSON policy rule into an RVM program.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_azure_policy_rule(
RegorusAliasRegistry* registry, byte* policy_rule_json);
/// <summary>
/// Compile a full Azure Policy definition JSON into an RVM program.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_definition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_azure_policy_definition(
RegorusAliasRegistry* registry, byte* policy_definition_json);
#endregion
#region Compiled Policy Methods
@@ -673,10 +695,34 @@ namespace Regorus.Internal
#region Alias Registry Methods
/// <summary>
/// Create a new, empty AliasRegistry.
/// Create a new alias registry builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusAliasRegistryBuilder* regorus_alias_registry_builder_new();
/// <summary>
/// Drop an alias registry builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_alias_registry_builder_drop(RegorusAliasRegistryBuilder* builder);
/// <summary>
/// Load control-plane alias data into the builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_load_json(RegorusAliasRegistryBuilder* builder, byte* json);
/// <summary>
/// Load a data-plane policy manifest into the builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_load_manifest(RegorusAliasRegistryBuilder* builder, byte* json);
/// <summary>
/// Freeze a builder into an immutable alias registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_build", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_build(RegorusAliasRegistryBuilder* builder);
/// <summary>
/// Drop an AliasRegistry.
@@ -684,18 +730,6 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Load a data-plane policy manifest into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Return the number of resource types loaded in the alias registry.
/// </summary>
@@ -923,6 +957,14 @@ namespace Regorus.Internal
public byte* content;
}
/// <summary>
/// Wrapper for AliasRegistryBuilder.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusAliasRegistryBuilder
{
}
/// <summary>
/// Wrapper for AliasRegistry.
/// </summary>
+1 -1
View File
@@ -15,7 +15,7 @@ namespace Regorus
/// </summary>
public unsafe sealed class Program : SafeHandleWrapper
{
private Program(RegorusProgramHandle handle)
internal Program(RegorusProgramHandle handle)
: base(handle, nameof(Program))
{
}
+24
View File
@@ -69,5 +69,29 @@ namespace Regorus.Internal
API.regorus_result_drop(result);
}
}
internal static IntPtr GetPointerResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new InvalidOperationException("Expected pointer result.");
}
return (IntPtr)result.pointer_value;
}
finally
{
API.regorus_result_drop(result);
}
}
}
}
+18
View File
@@ -106,6 +106,24 @@ namespace Regorus
});
}
/// <summary>
/// Set the context document for the VM.
/// The context provides host-supplied ambient data (e.g. resourceGroup(),
/// subscription()) that Azure Policy functions can access via LoadContext
/// instructions.
/// </summary>
public void SetContextJson(string contextJson)
{
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_context((RegorusRvm*)vmPtr, (byte*)contextPtr));
return 0;
});
});
}
/// <summary>
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
/// </summary>
+36 -16
View File
@@ -184,28 +184,48 @@ namespace Regorus
}
}
internal sealed class RegorusAliasRegistryBuilderHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusAliasRegistryBuilderHandle() : base(ownsHandle: true)
{
}
internal static RegorusAliasRegistryBuilderHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_alias_registry_builder_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus alias registry builder.");
}
var handle = new RegorusAliasRegistryBuilderHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
protected override bool ReleaseHandle()
{
if (!IsInvalid)
{
unsafe
{
Internal.API.regorus_alias_registry_builder_drop((Internal.RegorusAliasRegistryBuilder*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
{
}
internal static RegorusAliasRegistryHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_alias_registry_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus alias registry.");
}
var handle = new RegorusAliasRegistryHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
@@ -232,6 +232,9 @@ allow if {
Console.WriteLine("\n8. RVM host await (suspend/resume):");
DemonstrateRvmHostAwait();
Console.WriteLine("\n9. Azure Policy JSON compilation:");
DemonstrateAzurePolicyJsonCompilation();
}
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
@@ -492,4 +495,80 @@ allow if {
var resumed = vm.Resume("{\"tier\":\"gold\"}");
Console.WriteLine($"HostAwait resumed result: {resumed}");
}
// Azure Policy JSON constants
private const string STORAGE_ALIASES_JSON = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""capabilities"": ""SupportsTags, SupportsLocation"",
""aliases"": [
{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
}
]
}]
}]";
private const string HTTPS_DENY_RULE = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""deny"" }
}";
static void DemonstrateAzurePolicyJsonCompilation()
{
// 1. Set up alias registry
using var registry = Regorus.AliasRegistry.FromJson(STORAGE_ALIASES_JSON);
Console.WriteLine("Loaded storage account aliases");
// 2. Compile the JSON policy rule directly (no Rego needed)
using var program = Regorus.AzurePolicyCompiler.CompilePolicyRule(registry, HTTPS_DENY_RULE);
Console.WriteLine("Compiled Azure Policy JSON rule to RVM program");
// 3. Normalize an ARM resource
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""insecurestorage"",
""location"": ""eastus"",
""properties"": { ""supportsHttpsTrafficOnly"": false }
}";
var envelope = registry.NormalizeAndWrap(armResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
Console.WriteLine($"Normalized ARM resource to evaluation envelope");
// 4. Execute in the RVM
// Note: For policies using context functions (subscription(), resourceGroup()),
// call vm.SetContextJson(contextJson) before execution. The context from
// NormalizeAndWrap is in the envelope but must also be set on the VM separately.
using var vm = new Regorus.Rvm();
vm.LoadProgram(program);
vm.SetInputJson(envelope!);
// vm.SetContextJson(contextJson); // ← required for context-dependent policies
var result = vm.ExecuteEntryPoint("main");
Console.WriteLine($"Evaluation result (non-compliant): {result}");
// 5. Test with a compliant resource
var compliantResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""securestorage"",
""location"": ""eastus"",
""properties"": { ""supportsHttpsTrafficOnly"": true }
}";
var compliantEnvelope = registry.NormalizeAndWrap(compliantResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
using var vm2 = new Regorus.Rvm();
vm2.LoadProgram(program);
vm2.SetInputJson(compliantEnvelope!);
var compliantResult = vm2.ExecuteEntryPoint("main");
Console.WriteLine($"Evaluation result (compliant): {compliantResult}");
// 6. Demonstrate program serialization
var binary = program.SerializeBinary();
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
}
}