Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/DeterminismGuard/DeterminismGuardService.cs
StellaOps Bot 3b96b2e3ea
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
up
2025-11-27 23:45:09 +02:00

353 lines
10 KiB
C#

using System.Collections.Immutable;
using System.Diagnostics;
namespace StellaOps.Policy.Engine.DeterminismGuard;
/// <summary>
/// Service that enforces determinism constraints during policy evaluation.
/// Combines static analysis and runtime monitoring.
/// </summary>
public sealed class DeterminismGuardService
{
private readonly ProhibitedPatternAnalyzer _analyzer;
private readonly DeterminismGuardOptions _options;
private readonly RuntimeDeterminismMonitor _runtimeMonitor;
public DeterminismGuardService(DeterminismGuardOptions? options = null)
{
_options = options ?? DeterminismGuardOptions.Default;
_analyzer = new ProhibitedPatternAnalyzer();
_runtimeMonitor = new RuntimeDeterminismMonitor(_options);
}
/// <summary>
/// Analyzes source code for determinism violations.
/// </summary>
public DeterminismAnalysisResult AnalyzeSource(string sourceCode, string? fileName = null)
{
return _analyzer.AnalyzeSource(sourceCode, fileName, _options);
}
/// <summary>
/// Creates a guarded execution scope for policy evaluation.
/// </summary>
public EvaluationScope CreateScope(string scopeId, DateTimeOffset evaluationTimestamp)
{
return new EvaluationScope(scopeId, evaluationTimestamp, _options, _runtimeMonitor);
}
/// <summary>
/// Validates that a policy evaluation context is deterministic.
/// </summary>
public DeterminismAnalysisResult ValidateContext<TContext>(TContext context, string contextName)
{
var stopwatch = Stopwatch.StartNew();
var violations = new List<DeterminismViolation>();
// Check for null
if (context is null)
{
violations.Add(new DeterminismViolation
{
Category = DeterminismViolationCategory.Other,
ViolationType = "NullContext",
Message = $"Evaluation context '{contextName}' is null",
Severity = DeterminismViolationSeverity.Error,
Remediation = "Provide a valid evaluation context"
});
}
stopwatch.Stop();
var countBySeverity = violations
.GroupBy(v => v.Severity)
.ToImmutableDictionary(g => g.Key, g => g.Count());
var hasBlockingViolation = violations.Any(v => v.Severity >= _options.FailOnSeverity);
var passed = !_options.EnforcementEnabled || !hasBlockingViolation;
return new DeterminismAnalysisResult
{
Passed = passed,
Violations = violations.ToImmutableArray(),
CountBySeverity = countBySeverity,
AnalysisDurationMs = stopwatch.ElapsedMilliseconds,
EnforcementEnabled = _options.EnforcementEnabled
};
}
/// <summary>
/// Gets a determinism-safe time provider that only returns injected timestamps.
/// </summary>
public DeterministicTimeProvider GetTimeProvider(DateTimeOffset fixedTimestamp)
{
return new DeterministicTimeProvider(fixedTimestamp);
}
}
/// <summary>
/// A guarded scope for policy evaluation that tracks determinism violations.
/// </summary>
public sealed class EvaluationScope : IDisposable
{
private readonly string _scopeId;
private readonly DateTimeOffset _evaluationTimestamp;
private readonly DeterminismGuardOptions _options;
private readonly RuntimeDeterminismMonitor _monitor;
private readonly Stopwatch _stopwatch;
private readonly List<DeterminismViolation> _violations;
private bool _disposed;
internal EvaluationScope(
string scopeId,
DateTimeOffset evaluationTimestamp,
DeterminismGuardOptions options,
RuntimeDeterminismMonitor monitor)
{
_scopeId = scopeId ?? throw new ArgumentNullException(nameof(scopeId));
_evaluationTimestamp = evaluationTimestamp;
_options = options;
_monitor = monitor;
_stopwatch = Stopwatch.StartNew();
_violations = new List<DeterminismViolation>();
if (_options.EnableRuntimeMonitoring)
{
_monitor.EnterScope(scopeId);
}
}
/// <summary>
/// Scope identifier for tracing.
/// </summary>
public string ScopeId => _scopeId;
/// <summary>
/// The fixed evaluation timestamp for this scope.
/// </summary>
public DateTimeOffset EvaluationTimestamp => _evaluationTimestamp;
/// <summary>
/// Reports a runtime violation detected during evaluation.
/// </summary>
public void ReportViolation(DeterminismViolation violation)
{
ArgumentNullException.ThrowIfNull(violation);
lock (_violations)
{
_violations.Add(violation);
}
if (_options.EnforcementEnabled && violation.Severity >= _options.FailOnSeverity)
{
throw new DeterminismViolationException(violation);
}
}
/// <summary>
/// Gets the current timestamp (always returns the fixed evaluation timestamp).
/// </summary>
public DateTimeOffset GetTimestamp() => _evaluationTimestamp;
/// <summary>
/// Gets all violations recorded in this scope.
/// </summary>
public IReadOnlyList<DeterminismViolation> GetViolations()
{
lock (_violations)
{
return _violations.ToList();
}
}
/// <summary>
/// Completes the scope and returns analysis results.
/// </summary>
public DeterminismAnalysisResult Complete()
{
_stopwatch.Stop();
IReadOnlyList<DeterminismViolation> allViolations;
lock (_violations)
{
allViolations = _violations.ToList();
}
var countBySeverity = allViolations
.GroupBy(v => v.Severity)
.ToImmutableDictionary(g => g.Key, g => g.Count());
var hasBlockingViolation = allViolations.Any(v => v.Severity >= _options.FailOnSeverity);
var passed = !_options.EnforcementEnabled || !hasBlockingViolation;
return new DeterminismAnalysisResult
{
Passed = passed,
Violations = allViolations.ToImmutableArray(),
CountBySeverity = countBySeverity,
AnalysisDurationMs = _stopwatch.ElapsedMilliseconds,
EnforcementEnabled = _options.EnforcementEnabled
};
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_options.EnableRuntimeMonitoring)
{
_monitor.ExitScope(_scopeId);
}
}
}
/// <summary>
/// Exception thrown when a determinism violation is detected with enforcement enabled.
/// </summary>
public sealed class DeterminismViolationException : Exception
{
public DeterminismViolationException(DeterminismViolation violation)
: base($"Determinism violation: {violation.Message}")
{
Violation = violation;
}
public DeterminismViolation Violation { get; }
}
/// <summary>
/// Time provider that always returns a fixed timestamp.
/// </summary>
public sealed class DeterministicTimeProvider : TimeProvider
{
private readonly DateTimeOffset _fixedTimestamp;
public DeterministicTimeProvider(DateTimeOffset fixedTimestamp)
{
_fixedTimestamp = fixedTimestamp;
}
public override DateTimeOffset GetUtcNow() => _fixedTimestamp;
public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.Utc;
}
/// <summary>
/// Runtime monitor for detecting non-deterministic operations.
/// </summary>
internal sealed class RuntimeDeterminismMonitor
{
private readonly DeterminismGuardOptions _options;
private readonly HashSet<string> _activeScopes = new(StringComparer.Ordinal);
private readonly object _lock = new();
public RuntimeDeterminismMonitor(DeterminismGuardOptions options)
{
_options = options;
}
public void EnterScope(string scopeId)
{
lock (_lock)
{
_activeScopes.Add(scopeId);
}
}
public void ExitScope(string scopeId)
{
lock (_lock)
{
_activeScopes.Remove(scopeId);
}
}
public bool IsInScope => _activeScopes.Count > 0;
/// <summary>
/// Checks if we're in a guarded scope and should intercept operations.
/// </summary>
public bool ShouldIntercept()
{
return _options.EnableRuntimeMonitoring && IsInScope;
}
}
/// <summary>
/// Extension methods for integrating determinism guard with evaluation.
/// </summary>
public static class DeterminismGuardExtensions
{
/// <summary>
/// Executes an evaluation function within a determinism-guarded scope.
/// </summary>
public static TResult ExecuteGuarded<TResult>(
this DeterminismGuardService guard,
string scopeId,
DateTimeOffset evaluationTimestamp,
Func<EvaluationScope, TResult> evaluation)
{
ArgumentNullException.ThrowIfNull(guard);
ArgumentNullException.ThrowIfNull(evaluation);
using var scope = guard.CreateScope(scopeId, evaluationTimestamp);
try
{
return evaluation(scope);
}
finally
{
var result = scope.Complete();
if (!result.Passed)
{
// Log violations even if not throwing
foreach (var violation in result.Violations)
{
// In production, this would log to structured logging
System.Diagnostics.Debug.WriteLine(
$"[DeterminismGuard] {violation.Severity}: {violation.Message}");
}
}
}
}
/// <summary>
/// Executes an async evaluation function within a determinism-guarded scope.
/// </summary>
public static async Task<TResult> ExecuteGuardedAsync<TResult>(
this DeterminismGuardService guard,
string scopeId,
DateTimeOffset evaluationTimestamp,
Func<EvaluationScope, Task<TResult>> evaluation)
{
ArgumentNullException.ThrowIfNull(guard);
ArgumentNullException.ThrowIfNull(evaluation);
using var scope = guard.CreateScope(scopeId, evaluationTimestamp);
try
{
return await evaluation(scope).ConfigureAwait(false);
}
finally
{
var result = scope.Complete();
if (!result.Passed)
{
foreach (var violation in result.Violations)
{
System.Diagnostics.Debug.WriteLine(
$"[DeterminismGuard] {violation.Severity}: {violation.Message}");
}
}
}
}
}