up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 23:44:42 +02:00
parent ef6e4b2067
commit 3b96b2e3ea
298 changed files with 47516 additions and 1168 deletions

View File

@@ -0,0 +1,229 @@
# 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:
- Reproducible audit trails
- Offline verification of policy decisions
- Content-addressed caching of evaluation results
- Bit-exact replay for debugging and compliance
## 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:
| Category | Prohibited | Rationale |
|----------|-----------|-----------|
| Wall-clock | `DateTime.Now`, `DateTime.UtcNow`, `DateTimeOffset.Now` | Non-deterministic |
| Random | `Random`, `Guid.NewGuid()`, cryptographic RNG | Non-deterministic |
| Network | `HttpClient`, socket operations, DNS lookups | External dependency |
| Filesystem | File I/O during evaluation | External dependency |
| Environment | `Environment.GetEnvironmentVariable()` | Host-dependent |
### Allowed Operations
| Category | Allowed | Usage |
|----------|---------|-------|
| Timestamps | `context.EvaluationTimestamp` | Injected evaluation time |
| Identifiers | Deterministic ID generation from content | See `StableIdGenerator` |
| Collections | `ImmutableArray<T>`, `ImmutableDictionary<K,V>` | Stable iteration order |
| Arithmetic | `decimal` for numeric comparisons | Exact 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)
```csharp
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:
- Rules are evaluated in order until one matches
- The first matching rule determines the base result
- No further rules are evaluated after a match
- If no rules match, a default result is returned
### 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 Case | Type | Rationale |
|----------|------|-----------|
| CVSS scores | `decimal` | Exact representation, no floating-point drift |
| Priority | `int` | Integer ordering |
| Severity comparisons | `decimal` via lookup table | Stable severity ordering |
The severity lookup table maps normalized severity strings to decimal values:
```csharp
"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
| Collection | Usage |
|------------|-------|
| `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:
```csharp
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:
- `AND`: Left-to-right, stops on first `false`
- `OR`: Left-to-right, stops on first `true`
### 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:
- `severity.normalized`, `severity.score`
- `advisory.source`, `advisory.<metadata-key>`
- `vex.status`, `vex.justification`
- `sbom.tags`, `sbom.components`
## 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/`:
| File | Purpose |
|------|---------|
| `test-vectors.json` | Input/output pairs with expected hashes |
| `config-sample.yaml` | Sample evaluator configuration |
### Hash Recording
Each test vector records:
- Input content hash
- Expected output content hash
- Human-readable input/output for inspection
## Implementation Notes
### PolicyEvaluator Class
Located at: `src/Policy/StellaOps.Policy.Engine/Evaluation/PolicyEvaluator.cs`
Key determinism features:
- Uses `ImmutableArray` for ordered rule iteration
- Exception selection uses deterministic tie-breaking
- All collection operations preserve order
### PolicyExpressionEvaluator Class
Located at: `src/Policy/StellaOps.Policy.Engine/Evaluation/PolicyExpressionEvaluator.cs`
Key determinism features:
- Uses decimal for numeric comparisons
- Severity ordering via static lookup table
- Immutable scope objects
## Compliance Checklist
Before shipping changes to the evaluator, verify:
- [ ] No `DateTime.Now` or `DateTime.UtcNow` usage in evaluation path
- [ ] No `Random` or `Guid.NewGuid()` in evaluation path
- [ ] No network or filesystem access in evaluation path
- [ ] All collections use immutable types
- [ ] Numeric comparisons use `decimal`
- [ ] String comparisons use `StringComparer.OrdinalIgnoreCase`
- [ ] Golden tests pass with recorded hashes
## References
- Prep document: `docs/modules/policy/prep/2025-11-20-policy-engine-20-002-prep.md`
- Sprint task: POLICY-ENGINE-20-002 in `docs/implplan/SPRINT_124_policy_reasoning.md`
- Implementation: `src/Policy/StellaOps.Policy.Engine/Evaluation/`