Deterministic Policy Evaluator Design

Status: Final Version: 1.0 Owner: Policy Guild Last Updated: 2025-11-27

Overview

The Policy Engine evaluator is designed for deterministic, reproducible execution. Given identical inputs, the evaluator produces byte-for-byte identical outputs regardless of host, timezone, or execution timing. This enables:

Contract and Guarantees

Determinism Guarantees

  1. Input Determinism: All inputs are content-addressed or explicitly provided via the evaluation context.
  2. Output Determinism: Given identical PolicyEvaluationRequest, the evaluator returns identical PolicyEvaluationResult objects.
  3. Ordering Determinism: Rule evaluation order is stable and deterministic.
  4. Value Determinism: All computed values use deterministic types (decimal vs float, immutable collections).

Prohibited Operations

The following operations are prohibited during policy evaluation:

CategoryProhibitedRationale
Wall-clockDateTime.Now, DateTime.UtcNow, DateTimeOffset.NowNon-deterministic
RandomRandom, Guid.NewGuid(), cryptographic RNGNon-deterministic
NetworkHttpClient, socket operations, DNS lookupsExternal dependency
FilesystemFile I/O during evaluationExternal dependency
EnvironmentEnvironment.GetEnvironmentVariable()Host-dependent

Allowed Operations

CategoryAllowedUsage
Timestampscontext.EvaluationTimestampInjected evaluation time
IdentifiersDeterministic ID generation from contentSee StableIdGenerator
CollectionsImmutableArray<T>, ImmutableDictionary<K,V>Stable iteration order
Arithmeticdecimal for numeric comparisonsExact representation

Rule Ordering Semantics

Evaluation Order

Rules are evaluated in the following deterministic order:

  1. Primary Sort: rule.Priority (ascending - lower priority number evaluates first)
  2. Secondary Sort: Declaration order (index in the compiled IR document)
var orderedRules = document.Rules
    .Select((rule, index) => new { rule, index })
    .OrderBy(x => x.rule.Priority)
    .ThenBy(x => x.index)
    .ToImmutableArray();

First-Match Semantics

The evaluator uses first-match semantics:

Exception Application Order

When multiple exceptions could apply, specificity scoring determines the winner:

  1. Specificity Score: Computed from scope constraints (rule names, severities, sources, tags)
  2. Tie-breaker 1: CreatedAt timestamp (later wins)
  3. Tie-breaker 2: Id lexicographic comparison (earlier wins)

This ensures deterministic exception selection even with identical specificity scores.

Safe Value Types

Numeric Types

Use CaseTypeRationale
CVSS scoresdecimalExact representation, no floating-point drift
PriorityintInteger ordering
Severity comparisonsdecimal via lookup tableStable severity ordering

The severity lookup table maps normalized severity strings to decimal values:

"critical" => 5m
"high"     => 4m
"medium"   => 3m
"moderate" => 3m
"low"      => 2m
"info"     => 1m
"none"     => 0m
"unknown"  => -1m

String Comparisons

All string comparisons use StringComparer.OrdinalIgnoreCase for deterministic, culture-invariant comparison.

Collection Types

CollectionUsage
ImmutableArray<T>Ordered sequences with stable iteration
ImmutableDictionary<K,V>Key-value stores
ImmutableHashSet<T>Membership tests

Timestamp Handling

Context-Injected Timestamp

The evaluation timestamp is provided via the evaluation context, not read from the system clock:

public sealed record PolicyEvaluationContext(
    PolicyEvaluationSeverity Severity,
    PolicyEvaluationEnvironment Environment,
    PolicyEvaluationAdvisory Advisory,
    PolicyEvaluationVexEvidence Vex,
    PolicyEvaluationSbom Sbom,
    PolicyEvaluationExceptions Exceptions,
    DateTimeOffset EvaluationTimestamp);  // Injected, not DateTime.UtcNow

Timestamp Format

All timestamps in outputs use ISO-8601 format with UTC timezone:

2025-11-27T14:30:00.000Z

Expression Evaluation

Boolean Expressions

Short-circuit evaluation is deterministic:

Identifier Resolution

Identifiers resolve in deterministic order:

  1. Local scope (loop variables, predicates)
  2. Global context (severity, env, vex, advisory, sbom)
  3. Built-in constants (true, false)
  4. Null (unresolved)

Member Access

Member access on scoped objects follows a fixed schema:

Verification

Content Hashing

Evaluation inputs and outputs can be content-addressed using SHA-256:

Input Hash:  SHA256(canonical_json(PolicyEvaluationRequest))
Output Hash: SHA256(canonical_json(PolicyEvaluationResult))

Golden Test Vectors

Test vectors are provided in docs/modules/policy/samples/deterministic-evaluator/:

FilePurpose
test-vectors.jsonInput/output pairs with expected hashes
config-sample.yamlSample evaluator configuration

Hash Recording

Each test vector records:

Implementation Notes

PolicyEvaluator Class

Located at: src/Policy/StellaOps.Policy.Engine/Evaluation/PolicyEvaluator.cs

Key determinism features:

PolicyExpressionEvaluator Class

Located at: src/Policy/StellaOps.Policy.Engine/Evaluation/PolicyExpressionEvaluator.cs

Key determinism features:

Compliance Checklist

Before shipping changes to the evaluator, verify:

References