# Regorus C# API Documentation
This document describes the C# API for Regorus, focusing on the compiled policy approach for high-performance policy evaluation.
## Overview
The Regorus C# bindings provide a modern, thread-safe API for compiling and evaluating Open Policy Agent (OPA) Rego policies. The API is designed around pre-compiled policies that can be evaluated efficiently multiple times with different inputs.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ CompiledPolicy Workflow │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Policy Modules │ │ Target/Schema │ │ Static Data │
│ (.rego files) │ │ Registries │ │ (JSON) │
└─────────┬───────┘ └────────┬─────────┘ └─────────┬───────┘
│ │ │
└─────────────────────┼────────────────────────┘
│
▼
┌─────────────────────────┐
│ Compile │
│ ┌─────────────────────┐│
│ │ Parse & Analyze ││
│ │ Infer Resource Types││
│ │ Build AST & Rules ││
│ │ Target Integration ││
│ └─────────────────────┘│
└─────────────┬───────────┘
│
▼
┌─────────────────────────┐
│ CompiledPolicy │
│ ┌─────────────────────┐ │
│ │ AST & Rules │ │
│ │ Target Info │ │
│ │ Resource Types │ │
│ │ Function Table │ │
│ │ Compiled Modules │ │
│ └─────────────────────┘ │
└─────────────┬───────────┘
│
▼
┌─────────────────────┐
│ Service Cache │
│ (Policy Framework, │
│ MS Graph, etc.) │
│ ┌─────────────────┐ │
│ │ CompiledPolicy │ │ ◄─── Same LOCK-FREE policy
│ │ (cached) │ │ instance shared across
│ └─────────────────┘ │ all threads
└─────────┬───────────┘
│
┌───────┼───────┬───────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Thread 1 │ │ Thread 2 │ │ Thread N │
│ │ │ │ │ │
│ input1 ────▶│ │ input2 ────▶│ │ inputN ────▶│
│ ◄─── result │ │ ◄─── result │ │ ◄─── result │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Key Benefits │
├─────────────────────────────────────────────────────────────────┤
│ ✓ Compile Once, Evaluate Many ✓ Lock-Free Concurrent Eval │
│ ✓ No Re-parsing Overhead ✓ Reference Counting Safety │
│ ✓ Reduced GC Pressure ✓ Proper Resource Management │
│ ✓ Cache-Friendly Design ✓ Target System Integration │
└─────────────────────────────────────────────────────────────────┘
```
## Key Features
- **Pre-compiled Policies**: Compile once, evaluate many times for optimal performance
- **Target System Support**: Built-in support for Azure Policy targets with resource type inference
- **Thread Safety**: All operations are thread-safe without external synchronization
- **Registry Management**: Centralized management of targets and schemas
- **Policy Introspection**: Rich metadata about compiled policies
- **RVM Memory Budgets**: Optional per-execution memory limits for run-to-completion and suspendable evaluation
## RVM Memory Budgets
`Rvm.SetMemoryBudgetConfig` configures a fresh non-zero budget for the next initial RVM execution in either run-to-completion or suspendable mode. `Rvm.ClearMemoryBudgetConfig` affects the next initial execution; it does not change an active suspendable continuation.
```csharp
public readonly struct MemoryBudgetConfig
{
public MemoryBudgetConfig(ulong limitBytes);
public ulong LimitBytes { get; }
}
public sealed class Rvm : IDisposable
{
public void SetMemoryBudgetConfig(MemoryBudgetConfig config);
public void ClearMemoryBudgetConfig();
}
```
`Execute` and `ExecuteEntryPoint` start a fresh budget for each accepted initial execution. A suspendable execution retains the same budget across `Resume` calls, including resumes on another thread. Suspendable usage is the cumulative total of successful requested allocation sizes; frees do not refund it. Program compilation, program loading, and prior `SetDataJson`, `SetInputJson`, and `SetContextJson` calls occur before and outside the budget; use this path for static or preloaded data.
```csharp
using var vm = new Rvm();
vm.SetMemoryBudgetConfig(new MemoryBudgetConfig(16UL * 1024 * 1024));
try
{
var result = vm.Execute();
}
catch (RegorusMemoryBudgetExceededException)
{
// The execution exceeded its configured budget.
}
```
Native resume C-string copying and JSON parsing, VM resume work, result JSON serialization, and Rust `CString` allocation are included before an `Execute`, `ExecuteEntryPoint`, or `Resume` call returns. Managed UTF-8 decoding and the managed C# `string` allocation after the native call returns are excluded.
Memory budgets require a native library built with allocator memory tracking. Changing or clearing the configuration while a suspendable execution is active affects only the next initial execution. `RegorusMemoryBudgetExceededException` is thrown when a budget is exceeded. Native status 11 and `RegorusMemoryBudgetUnsupportedException` remain reserved for compatibility and are not used to reject suspendable execution.
Enforcement is cooperative and not an allocation-time peak-memory cap. Run-to-completion usage retains its thread-baseline live-byte accounting; suspendable usage accumulates successful requested allocation sizes across execution segments. An instruction, builtin, native input parse, serialization, or `CString` allocation can overshoot before it is rejected. Synchronous host work on the execution thread is included; managed allocations, host I/O, getter serialization, and other allocators are excluded.
The following warm-VM baseline note applies to run-to-completion accounting. Suspendable budgets do not charge allocations made before execution unless they are reallocated during the run; a successful reallocation charges its full new requested size.
Suspendable accounting does not track per-block allocation origins. Frees never refund accumulated usage; successful reallocation charges the full new requested size to the currently selected execution, and failed reallocation charges nothing. Run-to-completion accounting keeps its existing execution-thread baseline and downward ratchet. See the [RVM memory budget documentation](../../docs/limits/memory_budget.md) for the detailed accounting model.
A reused VM gets a fresh baseline, but capacities and pools retained before that baseline can make a warm execution allocate differently from a fresh one. Failed terminal execution clears retained state. Public multi-call begin/end scopes are intentionally absent; the RVM owns the cumulative usage scalar and selects it only during synchronous execution segments. The [detailed RVM memory budget documentation](../../docs/limits/memory_budget.md) is authoritative for accounting behavior.
## Core Classes
### CompiledPolicy
The `CompiledPolicy` class represents a pre-compiled Rego policy that can be evaluated efficiently.
```csharp
public sealed class CompiledPolicy : IDisposable
{
// Evaluate the policy with input data
public string? EvalWithInput(string inputJson);
// Get comprehensive policy metadata
public PolicyInfo GetPolicyInfo();
// Dispose of unmanaged resources
public void Dispose();
}
```
**Thread Safety**: All methods are thread-safe. Multiple threads can call `EvalWithInput()` concurrently, and `Dispose()` will safely wait for active evaluations to complete.
### Compiler
The `Compiler` class provides static methods for compiling policies.
```csharp
public static class Compiler
{
// Compile a policy with a specific entrypoint rule
public static CompiledPolicy CompilePolicyWithEntrypoint(
string dataJson,
IEnumerable<PolicyModule> modules,
string entryPointRule);
// Compile a target-aware policy (requires azure_policy feature)
public static CompiledPolicy CompilePolicyForTarget(
string dataJson,
IEnumerable<PolicyModule> modules);
}
```
### Engine
`AddPolicy(path, rego)` and `AddPolicyFromFile(path)` reject an embedded NUL
in `path` with `ArgumentException` instead of allowing the native call to
silently truncate it.
The stateful `Engine` API can inspect one loaded module for rule declarations
without evaluating the policy. Call
`HasDeclaredRuleRootedAt(sourcePath, rootName)` with either a bare root such as
`"metadata"` or a dotted rule path such as `"metadata.parameters"`. A selector
matches that exact path and its component-wise descendants: `"metadata"`
matches `metadata.parameters`, `metadata.parameters.child`, and
`metadata.other`, while `"metadata.parameters"` matches `metadata.parameters`
and `metadata.parameters.child`, but not
`metadata.other` or `metadata.parametersExtra`.
The method scans authored rule heads in only the selected module, including
defaults, functions, sets, and rules whose bodies would be false or undefined.
Imports, references, comments, strings, and object keys are not declarations;
rules in other modules in the same package are not included.
```csharp
using var engine = new Engine();
engine.AddPolicy(
"customer.rego",
"package customer\nparams.timeout := input.timeout");
bool hasParams = engine.HasDeclaredRuleRootedAt("customer.rego", "params");
```
`sourcePath` must identify exactly one loaded module. For `AddPolicy`, the
stored source label is the supplied path. For `AddPolicyFromFile`, the path is
converted to a string lossily by the native implementation; the lookup does
not compare the original path bytes. Non-UTF-8 filenames can therefore
produce the same source label and an ambiguous-source error. Source selection
occurs before validating `rootName`, so missing or ambiguous paths remain
`InvalidOperationException` even when the selector is invalid.
`rootName` must be a nonempty path accepted by the module's load-time Rego mode
and future-keyword imports. It is parsed as a native rule reference: only a
root identifier followed by zero or more dot-qualified fields is accepted;
whitespace, comments, bracket selectors, calls, and trailing syntax are not.
Path components compare exactly, not by string prefix. Invalid selectors
throw `ArgumentException` with `ParamName` set to `rootName`. A missing or
ambiguous source, or a rule head that cannot be classified, throws
`InvalidOperationException`. Null strings throw `ArgumentNullException`;
embedded NULs in either string throw `ArgumentException` before native
marshalling.
The native call validates the engine and acquires its read lock before source
transport, then validates the root string. A null or invalid-UTF-8 root is an
invalid argument; source-path transport errors retain the normal operation
error status. After valid string transport, exact source selection precedes
selector grammar validation, so a missing or ambiguous source remains an
`InvalidOperationException` even when `rootName` is invalid.
### PolicyModule
Represents a single policy module to be compiled. Each PolicyModule corresponds to a Rego file (.rego), and each Rego file defines a Rego package using the `package` declaration at the top of the file.
```csharp
public struct PolicyModule
{
public string Id { get; set; }
public string Content { get; set; }
public PolicyModule(string id, string content);
}
```
**Properties:**
- `Id`: A unique identifier for the module, typically the filename (e.g., "policy.rego", "rules/storage.rego")
- `Content`: The complete Rego policy content, including the `package` declaration and all rules
**Example:**
```csharp
var module = new PolicyModule("storage-policy.rego", @"
package azure.storage
import rego.v1
default allow := false
allow if input.type == ""Microsoft.Storage/storageAccounts""
");
```
### PolicyInfo
Provides comprehensive metadata about a compiled policy.
```csharp
public class PolicyInfo
{
// List of module identifiers
public List<string> ModuleIds { get; set; }
// Target name (for target-aware policies)
public string? TargetName { get; set; }
// Resource types this policy can evaluate
public List<string> ApplicableResourceTypes { get; set; }
// Primary rule/entrypoint
public string EntrypointRule { get; set; }
// Effect rule (for target-aware policies)
public string? EffectRule { get; set; }
// Policy parameters
public List<PolicyParameters> Parameters { get; set; }
}
```
## Registry Classes
### TargetRegistry
Manages target definitions for Azure Policy-style evaluations.
```csharp
public static class TargetRegistry
{
// Register a target from JSON
public static void RegisterFromJson(string targetJson);
// Check if a target exists
public static bool Contains(string name);
// List all registered targets
public static string ListNames();
// Remove a target
public static bool Remove(string name);
// Clear all targets
public static void Clear();
// Get count of registered targets
public static int Count { get; }
// Check if registry is empty
public static bool IsEmpty { get; }
}
```
### SchemaRegistry
Manages schema definitions for validation.
```csharp
public static class SchemaRegistry
{
// Register resource schemas
public static void RegisterResourceSchema(string name, string schemaJson);
public static bool ContainsResourceSchema(string name);
public static string ListResourceSchemas();
// Register effect schemas
public static void RegisterEffectSchema(string name, string schemaJson);
public static bool ContainsEffectSchema(string name);
public static string ListEffectSchemas();
// Clear methods
public static void ClearResourceSchemas();
public static void ClearEffectSchemas();
}
```
## Usage Examples
### Basic Policy Compilation and Evaluation
```csharp
// Define policy modules
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package example
import rego.v1
default allow := false
allow if input.user == ""admin""
")
};
// Compile the policy
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.example.allow");
// Evaluate with different inputs
var result1 = policy.EvalWithInput(@"{""user"": ""admin""}"); // true
var result2 = policy.EvalWithInput(@"{""user"": ""guest""}"); // false
```
### Target-Aware Policy (Azure Policy Style)
```csharp
// Register target definition
TargetRegistry.RegisterFromJson(@"{
""name"": ""azure.storage"",
""resource_schema_selector"": ""type"",
""resource_types"": {
""Microsoft.Storage/storageAccounts"": {
""schema"": { /* JSON Schema */ }
}
}
}");
// Define policy with target
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package policy
import rego.v1
__target__ := ""azure.storage""
default effect := ""deny""
effect := ""allow"" if {
input.type == ""Microsoft.Storage/storageAccounts""
input.properties.supportsHttpsTrafficOnly == true
}
")
};
// Compile for target
using var policy = Compiler.CompilePolicyForTarget("{}", modules);
// Evaluate Azure resource
var resource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": {
""supportsHttpsTrafficOnly"": true
}
}";
var result = policy.EvalWithInput(resource); // "allow"
```
### Policy Introspection
```csharp
// Get policy metadata
var info = policy.GetPolicyInfo();
Console.WriteLine($"Target: {info.TargetName}");
Console.WriteLine($"Effect Rule: {info.EffectRule}");
Console.WriteLine($"Modules: {string.Join(", ", info.ModuleIds)}");
Console.WriteLine($"Resource Types: {string.Join(", ", info.ApplicableResourceTypes)}");
// Access parameters
if (info.Parameters != null && info.Parameters.Count > 0)
{
foreach (var parameterSet in info.Parameters)
{
Console.WriteLine($"Module: {parameterSet.SourceFile}");
foreach (var param in parameterSet.Parameters)
{
Console.WriteLine($"Parameter: {param.Name} ({param.Type})");
if (param.Default != null)
Console.WriteLine($" Default: {param.Default}");
}
}
}
```
### Concurrent Evaluation
```csharp
// CompiledPolicy is thread-safe
var tasks = Enumerable.Range(0, 100).Select(i =>
Task.Run(() => policy.EvalWithInput($@"{{""id"": {i}}}"))
).ToArray();
var results = await Task.WhenAll(tasks);
```
## RVM Classes
### Program
`Program` represents a compiled RVM bytecode artifact. It can be created from
modules+entrypoints or from an `Engine`, serialized to binary, and loaded into
an `Rvm` for execution.
```csharp
public sealed class Program : IDisposable
{
// Compile from modules with entry points
public static Program CompileFromModules(
string dataJson,
IReadOnlyList<PolicyModule> modules,
IReadOnlyList<string> entryPoints);
// Compile with registered host-await builtins
public static Program CompileFromModules(
string dataJson,
IReadOnlyList<PolicyModule> modules,
IReadOnlyList<string> entryPoints,
IReadOnlyList<HostAwaitBuiltin> hostAwaitBuiltins);
// Compile from an Engine instance
public static Program CompileFromEngine(Engine engine, IReadOnlyList<string> entryPoints);
// Serialize/deserialize
public byte[] SerializeBinary();
public static Program DeserializeBinary(byte[] data, out bool isPartial);
// Debug listing
public string GenerateListing();
// Builtin metadata
public IReadOnlyList<string> GetBuiltinNames();
public void Dispose();
}
```
`GetBuiltinNames` returns the names in the compiled program's builtin-info table,
preserving table order. Registered host-await builtins are excluded. This is
metadata inspection only; it is not complete policy-ingestion validation or
proof of dynamic execution.
### Rvm
`Rvm` is the virtual machine that executes a loaded `Program`.
```csharp
public sealed class Rvm : IDisposable
{
// Load a compiled program
public void LoadProgram(Program program);
// Set data and input documents
public void SetDataJson(string dataJson);
public void SetInputJson(string inputJson);
// Configure execution
public void SetExecutionMode(ExecutionMode mode);
public void SetMaxInstructions(ulong maxInstructions);
// Run
public string? Execute();
public string? ExecuteEntryPoint(string entryPoint);
// Suspendable-mode host-await interaction
public string? Resume(string valueJson);
public string? GetExecutionState();
public string? GetHostAwaitIdentifier();
public string? GetHostAwaitArgument();
// Run-to-completion-mode host-await pre-loading. Atomically replaces all
// previously configured responses for every identifier; pass every
// identifier the policy may invoke in a single call.
public void SetHostAwaitResponses(
IReadOnlyDictionary<string, IReadOnlyList<string>> responsesByIdentifier);
public void Dispose();
}
```
`SetMaxInstructions` configures the maximum number of dispatched RVM bytecode
instructions for the VM. The default is 25,000 and zero permits no
dispatches. A fresh execution and `LoadProgram` reset the consumed count while
preserving the configured maximum; `Resume` is cumulative with the preceding
execution. Updating the maximum while suspended replaces the limit without
resetting consumption. Instruction-limit failures use the existing generic
`InvalidOperationException` projection. The setter is write-only in the C#
binding; consumed instruction counts are not exposed.
### HostAwaitBuiltin
Declares a function name that the compiler should treat as a host-await call.
When the VM encounters a call to this function, it suspends (suspendable mode)
or consumes a pre-loaded response (run-to-completion mode).
`argCount` is the exact number of input arguments in the policy call. The
optional Rego output parameter is not included. Registered calls pass an
ordered array to the host: zero inputs produce `[]`, one input produces
`[value]`, and multiple inputs produce `[value1, value2, ...]`. Values remain
native Rego values inside that array, including nested arrays and objects.
`GetHostAwaitArgument()` returns the JSON representation of the whole array;
it does not unwrap one-input calls. An input that evaluates to undefined skips
the registered HostAwait without consuming a response.
```csharp
public readonly struct HostAwaitBuiltin
{
public string Name { get; }
public int ArgCount { get; }
public HostAwaitBuiltin(string name, int argCount);
}
```
The one-argument constructor was removed; pass the declared input count
explicitly and reject negative counts. Removing `.ctor(string)` is a C# source
and CLR binary break. This is a coordinated breaking change: update host
handlers, managed and native packages, registrations, and recompiled programs
together. New managed code with an older size-aware native library may have
its count ignored and is unsupported; an old name-only caller with the new
native library is rejected for non-empty registrations.
Explicit `__builtin_host_await(payload, identifier)` calls and previously
compiled programs retain raw payloads, while new registered calls send an
ordered input array. Old raw payloads can themselves be arrays, so a handler
cannot reliably infer the program format from payload shape. Deploy matching
program and handler versions together or use application-owned legacy routing;
there is no runtime handshake or universal compatibility handler. Roll back
the matching earlier managed/native package, host handler, and program bundle
together.
### ExecutionMode
Controls how the VM handles host-await instructions.
```csharp
public enum ExecutionMode
{
RunToCompletion = 0,
Suspendable = 1,
}
```
## Performance Considerations
### Compilation Overhead
- Policy compilation has significant overhead due to parsing and analysis
- **Best Practice**: Compile once, reuse many times
- Consider caching compiled policies for repeated use
### Memory Management
- `CompiledPolicy` manages unmanaged resources
- **Always** dispose of compiled policies using `using` statements or explicit `Dispose()`
- Disposal is thread-safe and waits for active evaluations
### Thread Safety
- All classes are thread-safe for concurrent reads/evaluations
- Registry modifications should be done during initialization
- No external synchronization required
## Error Handling
All methods throw `Exception` on errors with descriptive messages:
```csharp
try
{
var policy = Compiler.CompilePolicyWithEntrypoint(data, modules, rule);
var result = policy.EvalWithInput(input);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
```
## Feature Flags
Some functionality requires specific Rust feature flags:
- **azure_policy**: Required for target-aware compilation and the `GetPolicyParameters` metadata API
- Without this feature, target-related methods will not be available
## Version Compatibility
- Requires .NET Standard 2.0 or later
- Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
- Uses System.Text.Json for JSON serialization (added as dependency)
## Best Practices
1. **Compile Once, Evaluate Many**: Pre-compile policies for repeated evaluation
2. **Use Disposable Pattern**: Always dispose of CompiledPolicy instances
3. **Thread-Safe Design**: Take advantage of built-in thread safety
4. **Registry Setup**: Configure targets and schemas during application startup
5. **Error Handling**: Wrap operations in try-catch blocks for robust error handling
6. **Performance Monitoring**: Monitor evaluation times for performance optimization
## Migration from Engine-Based API
If migrating from an engine-based approach:
```csharp
// Old approach (if it existed)
// var engine = new Engine();
// engine.AddPolicy("policy.rego", policyContent);
// engine.SetInputJson(inputJson);
// var result = engine.EvalRule("data.policy.allow");
// New compiled approach
var modules = new[] { new PolicyModule("policy.rego", policyContent) };
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.policy.allow");
var result = policy.EvalWithInput(inputJson);
```
The compiled approach provides better performance for repeated evaluations and clearer resource management.