353 lines
10 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|