up
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Policy.Engine.DeterminismGuard;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a determinism violation detected during static analysis or runtime.
|
||||
/// </summary>
|
||||
public sealed record DeterminismViolation
|
||||
{
|
||||
/// <summary>
|
||||
/// Category of the violation.
|
||||
/// </summary>
|
||||
public required DeterminismViolationCategory Category { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Specific violation type.
|
||||
/// </summary>
|
||||
public required string ViolationType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable description of the violation.
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Source location (file path, if known).
|
||||
/// </summary>
|
||||
public string? SourceFile { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Line number (if known from static analysis).
|
||||
/// </summary>
|
||||
public int? LineNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Member or method name where violation occurred.
|
||||
/// </summary>
|
||||
public string? MemberName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the violation.
|
||||
/// </summary>
|
||||
public required DeterminismViolationSeverity Severity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Suggested remediation.
|
||||
/// </summary>
|
||||
public string? Remediation { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Category of determinism violation.
|
||||
/// </summary>
|
||||
public enum DeterminismViolationCategory
|
||||
{
|
||||
/// <summary>Wall-clock time access (DateTime.Now, etc.).</summary>
|
||||
WallClock,
|
||||
|
||||
/// <summary>Random number generation.</summary>
|
||||
RandomNumber,
|
||||
|
||||
/// <summary>Network access (HttpClient, sockets, etc.).</summary>
|
||||
NetworkAccess,
|
||||
|
||||
/// <summary>Filesystem access.</summary>
|
||||
FileSystemAccess,
|
||||
|
||||
/// <summary>Environment variable access.</summary>
|
||||
EnvironmentAccess,
|
||||
|
||||
/// <summary>GUID generation.</summary>
|
||||
GuidGeneration,
|
||||
|
||||
/// <summary>Thread/Task operations that may introduce non-determinism.</summary>
|
||||
ConcurrencyHazard,
|
||||
|
||||
/// <summary>Floating-point operations that may have platform variance.</summary>
|
||||
FloatingPointHazard,
|
||||
|
||||
/// <summary>Dictionary iteration without stable ordering.</summary>
|
||||
UnstableIteration,
|
||||
|
||||
/// <summary>Other non-deterministic operation.</summary>
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Severity level of a determinism violation.
|
||||
/// </summary>
|
||||
public enum DeterminismViolationSeverity
|
||||
{
|
||||
/// <summary>Informational - may not cause issues.</summary>
|
||||
Info,
|
||||
|
||||
/// <summary>Warning - potential non-determinism.</summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>Error - definite non-determinism source.</summary>
|
||||
Error,
|
||||
|
||||
/// <summary>Critical - must be fixed before deployment.</summary>
|
||||
Critical
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of determinism analysis.
|
||||
/// </summary>
|
||||
public sealed record DeterminismAnalysisResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the analysis passed (no critical/error violations).
|
||||
/// </summary>
|
||||
public required bool Passed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// All violations found.
|
||||
/// </summary>
|
||||
public required ImmutableArray<DeterminismViolation> Violations { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of violations by severity.
|
||||
/// </summary>
|
||||
public required ImmutableDictionary<DeterminismViolationSeverity, int> CountBySeverity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Analysis duration in milliseconds.
|
||||
/// </summary>
|
||||
public required long AnalysisDurationMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the guard is currently enforcing (blocking on violations).
|
||||
/// </summary>
|
||||
public required bool EnforcementEnabled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a passing result with no violations.
|
||||
/// </summary>
|
||||
public static DeterminismAnalysisResult Pass(long durationMs, bool enforcementEnabled) => new()
|
||||
{
|
||||
Passed = true,
|
||||
Violations = ImmutableArray<DeterminismViolation>.Empty,
|
||||
CountBySeverity = ImmutableDictionary<DeterminismViolationSeverity, int>.Empty,
|
||||
AnalysisDurationMs = durationMs,
|
||||
EnforcementEnabled = enforcementEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for determinism guard behavior.
|
||||
/// </summary>
|
||||
public sealed record DeterminismGuardOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether enforcement is enabled (blocks on violations).
|
||||
/// </summary>
|
||||
public bool EnforcementEnabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum severity level to fail enforcement.
|
||||
/// </summary>
|
||||
public DeterminismViolationSeverity FailOnSeverity { get; init; } = DeterminismViolationSeverity.Error;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to log all violations regardless of enforcement.
|
||||
/// </summary>
|
||||
public bool LogAllViolations { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to analyze code statically before execution.
|
||||
/// </summary>
|
||||
public bool EnableStaticAnalysis { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to monitor runtime behavior.
|
||||
/// </summary>
|
||||
public bool EnableRuntimeMonitoring { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Patterns to exclude from analysis (e.g., test code).
|
||||
/// </summary>
|
||||
public ImmutableArray<string> ExcludePatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Default options for production use.
|
||||
/// </summary>
|
||||
public static DeterminismGuardOptions Default { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Options for development/testing (warnings only).
|
||||
/// </summary>
|
||||
public static DeterminismGuardOptions Development { get; } = new()
|
||||
{
|
||||
EnforcementEnabled = false,
|
||||
FailOnSeverity = DeterminismViolationSeverity.Critical,
|
||||
LogAllViolations = true
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.DeterminismGuard;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps policy evaluation with determinism guard protection.
|
||||
/// Enforces static analysis and runtime monitoring during evaluation.
|
||||
/// </summary>
|
||||
public sealed class GuardedPolicyEvaluator
|
||||
{
|
||||
private readonly DeterminismGuardService _guard;
|
||||
private readonly ProhibitedPatternAnalyzer _analyzer;
|
||||
|
||||
public GuardedPolicyEvaluator(DeterminismGuardOptions? options = null)
|
||||
{
|
||||
var opts = options ?? DeterminismGuardOptions.Default;
|
||||
_guard = new DeterminismGuardService(opts);
|
||||
_analyzer = new ProhibitedPatternAnalyzer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-validates policy source code for determinism violations.
|
||||
/// Should be called during policy compilation/registration.
|
||||
/// </summary>
|
||||
public DeterminismAnalysisResult ValidatePolicySource(
|
||||
string sourceCode,
|
||||
string? fileName = null,
|
||||
DeterminismGuardOptions? options = null)
|
||||
{
|
||||
return _guard.AnalyzeSource(sourceCode, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-validates multiple policy source files.
|
||||
/// </summary>
|
||||
public DeterminismAnalysisResult ValidatePolicySources(
|
||||
IEnumerable<(string SourceCode, string FileName)> sources,
|
||||
DeterminismGuardOptions? options = null)
|
||||
{
|
||||
var opts = options ?? DeterminismGuardOptions.Default;
|
||||
return _analyzer.AnalyzeMultiple(sources, opts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a policy within a determinism-guarded scope.
|
||||
/// </summary>
|
||||
public GuardedEvaluationResult<TResult> Evaluate<TResult>(
|
||||
string scopeId,
|
||||
DateTimeOffset evaluationTimestamp,
|
||||
Func<EvaluationScope, TResult> evaluation)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evaluation);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
using var scope = _guard.CreateScope(scopeId, evaluationTimestamp);
|
||||
|
||||
try
|
||||
{
|
||||
var result = evaluation(scope);
|
||||
var guardResult = scope.Complete();
|
||||
stopwatch.Stop();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = guardResult.Passed,
|
||||
Result = result,
|
||||
Violations = guardResult.Violations,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp
|
||||
};
|
||||
}
|
||||
catch (DeterminismViolationException ex)
|
||||
{
|
||||
var guardResult = scope.Complete();
|
||||
stopwatch.Stop();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = false,
|
||||
Result = default,
|
||||
Violations = guardResult.Violations,
|
||||
BlockingViolation = ex.Violation,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var violations = scope.GetViolations();
|
||||
stopwatch.Stop();
|
||||
|
||||
// Record the unexpected exception as a violation
|
||||
var exceptionViolation = new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.Other,
|
||||
ViolationType = "EvaluationException",
|
||||
Message = $"Unexpected exception during evaluation: {ex.Message}",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Review policy logic for errors"
|
||||
};
|
||||
|
||||
var allViolations = violations
|
||||
.Append(exceptionViolation)
|
||||
.ToImmutableArray();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = false,
|
||||
Result = default,
|
||||
Violations = allViolations,
|
||||
BlockingViolation = exceptionViolation,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp,
|
||||
Exception = ex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a policy asynchronously within a determinism-guarded scope.
|
||||
/// </summary>
|
||||
public async Task<GuardedEvaluationResult<TResult>> EvaluateAsync<TResult>(
|
||||
string scopeId,
|
||||
DateTimeOffset evaluationTimestamp,
|
||||
Func<EvaluationScope, Task<TResult>> evaluation,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evaluation);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
using var scope = _guard.CreateScope(scopeId, evaluationTimestamp);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await evaluation(scope).ConfigureAwait(false);
|
||||
var guardResult = scope.Complete();
|
||||
stopwatch.Stop();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = guardResult.Passed,
|
||||
Result = result,
|
||||
Violations = guardResult.Violations,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp
|
||||
};
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (DeterminismViolationException ex)
|
||||
{
|
||||
var guardResult = scope.Complete();
|
||||
stopwatch.Stop();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = false,
|
||||
Result = default,
|
||||
Violations = guardResult.Violations,
|
||||
BlockingViolation = ex.Violation,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var violations = scope.GetViolations();
|
||||
stopwatch.Stop();
|
||||
|
||||
var exceptionViolation = new DeterminismViolation
|
||||
{
|
||||
Category = DeterminismViolationCategory.Other,
|
||||
ViolationType = "EvaluationException",
|
||||
Message = $"Unexpected exception during evaluation: {ex.Message}",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Review policy logic for errors"
|
||||
};
|
||||
|
||||
var allViolations = violations
|
||||
.Append(exceptionViolation)
|
||||
.ToImmutableArray();
|
||||
|
||||
return new GuardedEvaluationResult<TResult>
|
||||
{
|
||||
Succeeded = false,
|
||||
Result = default,
|
||||
Violations = allViolations,
|
||||
BlockingViolation = exceptionViolation,
|
||||
EvaluationDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
ScopeId = scopeId,
|
||||
EvaluationTimestamp = evaluationTimestamp,
|
||||
Exception = ex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the determinism guard service for advanced usage.
|
||||
/// </summary>
|
||||
public DeterminismGuardService Guard => _guard;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a guarded policy evaluation.
|
||||
/// </summary>
|
||||
public sealed record GuardedEvaluationResult<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the evaluation succeeded without blocking violations.
|
||||
/// </summary>
|
||||
public required bool Succeeded { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The evaluation result (may be default if failed).
|
||||
/// </summary>
|
||||
public TResult? Result { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// All violations detected during evaluation.
|
||||
/// </summary>
|
||||
public required ImmutableArray<DeterminismViolation> Violations { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The violation that caused evaluation to be blocked (if any).
|
||||
/// </summary>
|
||||
public DeterminismViolation? BlockingViolation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Evaluation duration in milliseconds.
|
||||
/// </summary>
|
||||
public required long EvaluationDurationMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Scope identifier for tracing.
|
||||
/// </summary>
|
||||
public required string ScopeId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The fixed evaluation timestamp used.
|
||||
/// </summary>
|
||||
public required DateTimeOffset EvaluationTimestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception that occurred during evaluation (if any).
|
||||
/// </summary>
|
||||
public Exception? Exception { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of violations by severity.
|
||||
/// </summary>
|
||||
public ImmutableDictionary<DeterminismViolationSeverity, int> ViolationCountBySeverity =>
|
||||
Violations
|
||||
.GroupBy(v => v.Severity)
|
||||
.ToImmutableDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
/// <summary>
|
||||
/// Whether there are any violations (blocking or not).
|
||||
/// </summary>
|
||||
public bool HasViolations => !Violations.IsDefaultOrEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the evaluation was blocked by a violation.
|
||||
/// </summary>
|
||||
public bool WasBlocked => BlockingViolation is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for creating guarded policy evaluator with custom configuration.
|
||||
/// </summary>
|
||||
public sealed class GuardedPolicyEvaluatorBuilder
|
||||
{
|
||||
private bool _enforcementEnabled = true;
|
||||
private DeterminismViolationSeverity _failOnSeverity = DeterminismViolationSeverity.Error;
|
||||
private bool _enableStaticAnalysis = true;
|
||||
private bool _enableRuntimeMonitoring = true;
|
||||
private bool _logAllViolations = true;
|
||||
private ImmutableArray<string> _excludePatterns = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables enforcement (blocking on violations).
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder WithEnforcement(bool enabled)
|
||||
{
|
||||
_enforcementEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum severity level to block evaluation.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder FailOnSeverity(DeterminismViolationSeverity severity)
|
||||
{
|
||||
_failOnSeverity = severity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables static code analysis.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder WithStaticAnalysis(bool enabled)
|
||||
{
|
||||
_enableStaticAnalysis = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables runtime monitoring.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder WithRuntimeMonitoring(bool enabled)
|
||||
{
|
||||
_enableRuntimeMonitoring = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables logging of all violations.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder WithViolationLogging(bool enabled)
|
||||
{
|
||||
_logAllViolations = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds patterns to exclude from analysis.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluatorBuilder ExcludePatterns(params string[] patterns)
|
||||
{
|
||||
_excludePatterns = _excludePatterns.AddRange(patterns);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the configured GuardedPolicyEvaluator.
|
||||
/// </summary>
|
||||
public GuardedPolicyEvaluator Build()
|
||||
{
|
||||
var options = new DeterminismGuardOptions
|
||||
{
|
||||
EnforcementEnabled = _enforcementEnabled,
|
||||
FailOnSeverity = _failOnSeverity,
|
||||
EnableStaticAnalysis = _enableStaticAnalysis,
|
||||
EnableRuntimeMonitoring = _enableRuntimeMonitoring,
|
||||
LogAllViolations = _logAllViolations,
|
||||
ExcludePatterns = _excludePatterns
|
||||
};
|
||||
|
||||
return new GuardedPolicyEvaluator(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a development-mode evaluator (warnings only, no blocking).
|
||||
/// </summary>
|
||||
public static GuardedPolicyEvaluator CreateDevelopment()
|
||||
{
|
||||
return new GuardedPolicyEvaluator(DeterminismGuardOptions.Development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a production-mode evaluator (full enforcement).
|
||||
/// </summary>
|
||||
public static GuardedPolicyEvaluator CreateProduction()
|
||||
{
|
||||
return new GuardedPolicyEvaluator(DeterminismGuardOptions.Default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StellaOps.Policy.Engine.DeterminismGuard;
|
||||
|
||||
/// <summary>
|
||||
/// Static analyzer that detects prohibited non-deterministic patterns in source code.
|
||||
/// </summary>
|
||||
public sealed partial class ProhibitedPatternAnalyzer
|
||||
{
|
||||
private static readonly ImmutableArray<ProhibitedPattern> Patterns = CreatePatterns();
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes source code for prohibited patterns.
|
||||
/// </summary>
|
||||
public DeterminismAnalysisResult AnalyzeSource(
|
||||
string sourceCode,
|
||||
string? fileName,
|
||||
DeterminismGuardOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceCode);
|
||||
options ??= DeterminismGuardOptions.Default;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var violations = new List<DeterminismViolation>();
|
||||
|
||||
// Check exclusions
|
||||
if (fileName is not null && IsExcluded(fileName, options.ExcludePatterns))
|
||||
{
|
||||
return DeterminismAnalysisResult.Pass(stopwatch.ElapsedMilliseconds, options.EnforcementEnabled);
|
||||
}
|
||||
|
||||
// Split into lines for line number tracking
|
||||
var lines = sourceCode.Split('\n');
|
||||
|
||||
for (var lineIndex = 0; lineIndex < lines.Length; lineIndex++)
|
||||
{
|
||||
var line = lines[lineIndex];
|
||||
var lineNumber = lineIndex + 1;
|
||||
|
||||
// Skip comments
|
||||
var trimmedLine = line.TrimStart();
|
||||
if (trimmedLine.StartsWith("//") || trimmedLine.StartsWith("/*") || trimmedLine.StartsWith("*"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var pattern in Patterns)
|
||||
{
|
||||
if (pattern.Regex.IsMatch(line))
|
||||
{
|
||||
violations.Add(new DeterminismViolation
|
||||
{
|
||||
Category = pattern.Category,
|
||||
ViolationType = pattern.ViolationType,
|
||||
Message = pattern.Message,
|
||||
SourceFile = fileName,
|
||||
LineNumber = lineNumber,
|
||||
MemberName = ExtractMemberContext(lines, lineIndex),
|
||||
Severity = pattern.Severity,
|
||||
Remediation = pattern.Remediation
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// Analyzes multiple source files.
|
||||
/// </summary>
|
||||
public DeterminismAnalysisResult AnalyzeMultiple(
|
||||
IEnumerable<(string SourceCode, string FileName)> sources,
|
||||
DeterminismGuardOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sources);
|
||||
options ??= DeterminismGuardOptions.Default;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var allViolations = new List<DeterminismViolation>();
|
||||
|
||||
foreach (var (sourceCode, fileName) in sources)
|
||||
{
|
||||
var result = AnalyzeSource(sourceCode, fileName, options with { EnforcementEnabled = false });
|
||||
allViolations.AddRange(result.Violations);
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsExcluded(string fileName, ImmutableArray<string> excludePatterns)
|
||||
{
|
||||
if (excludePatterns.IsDefaultOrEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return excludePatterns.Any(pattern =>
|
||||
fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static string? ExtractMemberContext(string[] lines, int lineIndex)
|
||||
{
|
||||
// Look backwards for method/property/class declaration
|
||||
for (var i = lineIndex; i >= 0 && i > lineIndex - 20; i--)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
|
||||
// Method pattern
|
||||
var methodMatch = MethodDeclarationRegex().Match(line);
|
||||
if (methodMatch.Success)
|
||||
{
|
||||
return methodMatch.Groups[1].Value;
|
||||
}
|
||||
|
||||
// Property pattern
|
||||
var propertyMatch = PropertyDeclarationRegex().Match(line);
|
||||
if (propertyMatch.Success)
|
||||
{
|
||||
return propertyMatch.Groups[1].Value;
|
||||
}
|
||||
|
||||
// Class pattern
|
||||
var classMatch = ClassDeclarationRegex().Match(line);
|
||||
if (classMatch.Success)
|
||||
{
|
||||
return classMatch.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(?:public|private|protected|internal)\s+.*?\s+(\w+)\s*\(")]
|
||||
private static partial Regex MethodDeclarationRegex();
|
||||
|
||||
[GeneratedRegex(@"(?:public|private|protected|internal)\s+.*?\s+(\w+)\s*\{")]
|
||||
private static partial Regex PropertyDeclarationRegex();
|
||||
|
||||
[GeneratedRegex(@"(?:class|struct|record)\s+(\w+)")]
|
||||
private static partial Regex ClassDeclarationRegex();
|
||||
|
||||
private static ImmutableArray<ProhibitedPattern> CreatePatterns()
|
||||
{
|
||||
return ImmutableArray.Create(
|
||||
// Wall-clock violations
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "DateTime.Now",
|
||||
Regex = DateTimeNowRegex(),
|
||||
Message = "DateTime.Now usage detected - non-deterministic wall-clock access",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use injected timestamp from evaluation context (context.Now)"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "DateTime.UtcNow",
|
||||
Regex = DateTimeUtcNowRegex(),
|
||||
Message = "DateTime.UtcNow usage detected - non-deterministic wall-clock access",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use injected timestamp from evaluation context (context.Now)"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "DateTimeOffset.Now",
|
||||
Regex = DateTimeOffsetNowRegex(),
|
||||
Message = "DateTimeOffset.Now usage detected - non-deterministic wall-clock access",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use injected timestamp from evaluation context (context.Now)"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.WallClock,
|
||||
ViolationType = "DateTimeOffset.UtcNow",
|
||||
Regex = DateTimeOffsetUtcNowRegex(),
|
||||
Message = "DateTimeOffset.UtcNow usage detected - non-deterministic wall-clock access",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use injected timestamp from evaluation context (context.Now)"
|
||||
},
|
||||
|
||||
// Random number violations
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.RandomNumber,
|
||||
ViolationType = "Random",
|
||||
Regex = RandomClassRegex(),
|
||||
Message = "Random class usage detected - non-deterministic random number generation",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use deterministic seeded random if needed, or remove randomness"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.RandomNumber,
|
||||
ViolationType = "RandomNumberGenerator",
|
||||
Regex = CryptoRandomRegex(),
|
||||
Message = "Cryptographic random usage detected - non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Remove cryptographic random from evaluation path"
|
||||
},
|
||||
|
||||
// GUID generation
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.GuidGeneration,
|
||||
ViolationType = "Guid.NewGuid",
|
||||
Regex = GuidNewGuidRegex(),
|
||||
Message = "Guid.NewGuid() usage detected - non-deterministic identifier generation",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use deterministic ID generation based on content hash"
|
||||
},
|
||||
|
||||
// Network access
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.NetworkAccess,
|
||||
ViolationType = "HttpClient",
|
||||
Regex = HttpClientRegex(),
|
||||
Message = "HttpClient usage detected - network access is non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Remove network access from evaluation path"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.NetworkAccess,
|
||||
ViolationType = "WebClient",
|
||||
Regex = WebClientRegex(),
|
||||
Message = "WebClient usage detected - network access is non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Remove network access from evaluation path"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.NetworkAccess,
|
||||
ViolationType = "Socket",
|
||||
Regex = SocketRegex(),
|
||||
Message = "Socket usage detected - network access is non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Remove socket access from evaluation path"
|
||||
},
|
||||
|
||||
// Environment access
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.EnvironmentAccess,
|
||||
ViolationType = "Environment.GetEnvironmentVariable",
|
||||
Regex = EnvironmentGetEnvRegex(),
|
||||
Message = "Environment variable access detected - host-dependent",
|
||||
Severity = DeterminismViolationSeverity.Error,
|
||||
Remediation = "Use evaluation context environment properties instead"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.EnvironmentAccess,
|
||||
ViolationType = "Environment.MachineName",
|
||||
Regex = EnvironmentMachineNameRegex(),
|
||||
Message = "Environment.MachineName access detected - host-dependent",
|
||||
Severity = DeterminismViolationSeverity.Warning,
|
||||
Remediation = "Remove host-specific information from evaluation"
|
||||
},
|
||||
|
||||
// Filesystem access
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.FileSystemAccess,
|
||||
ViolationType = "File.Read",
|
||||
Regex = FileReadRegex(),
|
||||
Message = "File read operation detected - filesystem access is non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Remove file access from evaluation path"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.FileSystemAccess,
|
||||
ViolationType = "File.Write",
|
||||
Regex = FileWriteRegex(),
|
||||
Message = "File write operation detected - filesystem access is non-deterministic",
|
||||
Severity = DeterminismViolationSeverity.Critical,
|
||||
Remediation = "Remove file access from evaluation path"
|
||||
},
|
||||
|
||||
// Floating-point hazards
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.FloatingPointHazard,
|
||||
ViolationType = "double comparison",
|
||||
Regex = DoubleComparisonRegex(),
|
||||
Message = "Direct double comparison detected - may have platform variance",
|
||||
Severity = DeterminismViolationSeverity.Warning,
|
||||
Remediation = "Use decimal type for precise comparisons"
|
||||
},
|
||||
|
||||
// Unstable iteration
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.UnstableIteration,
|
||||
ViolationType = "Dictionary iteration",
|
||||
Regex = DictionaryIterationRegex(),
|
||||
Message = "Dictionary iteration detected - may have unstable ordering",
|
||||
Severity = DeterminismViolationSeverity.Warning,
|
||||
Remediation = "Use SortedDictionary or OrderBy before iteration"
|
||||
},
|
||||
new ProhibitedPattern
|
||||
{
|
||||
Category = DeterminismViolationCategory.UnstableIteration,
|
||||
ViolationType = "HashSet iteration",
|
||||
Regex = HashSetIterationRegex(),
|
||||
Message = "HashSet iteration detected - may have unstable ordering",
|
||||
Severity = DeterminismViolationSeverity.Warning,
|
||||
Remediation = "Use SortedSet or OrderBy before iteration"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Generated regex patterns for prohibited patterns
|
||||
[GeneratedRegex(@"DateTime\.Now(?!\w)")]
|
||||
private static partial Regex DateTimeNowRegex();
|
||||
|
||||
[GeneratedRegex(@"DateTime\.UtcNow(?!\w)")]
|
||||
private static partial Regex DateTimeUtcNowRegex();
|
||||
|
||||
[GeneratedRegex(@"DateTimeOffset\.Now(?!\w)")]
|
||||
private static partial Regex DateTimeOffsetNowRegex();
|
||||
|
||||
[GeneratedRegex(@"DateTimeOffset\.UtcNow(?!\w)")]
|
||||
private static partial Regex DateTimeOffsetUtcNowRegex();
|
||||
|
||||
[GeneratedRegex(@"new\s+Random\s*\(")]
|
||||
private static partial Regex RandomClassRegex();
|
||||
|
||||
[GeneratedRegex(@"RandomNumberGenerator")]
|
||||
private static partial Regex CryptoRandomRegex();
|
||||
|
||||
[GeneratedRegex(@"Guid\.NewGuid\s*\(")]
|
||||
private static partial Regex GuidNewGuidRegex();
|
||||
|
||||
[GeneratedRegex(@"HttpClient")]
|
||||
private static partial Regex HttpClientRegex();
|
||||
|
||||
[GeneratedRegex(@"WebClient")]
|
||||
private static partial Regex WebClientRegex();
|
||||
|
||||
[GeneratedRegex(@"(?:TcpClient|UdpClient|Socket)\s*\(")]
|
||||
private static partial Regex SocketRegex();
|
||||
|
||||
[GeneratedRegex(@"Environment\.GetEnvironmentVariable")]
|
||||
private static partial Regex EnvironmentGetEnvRegex();
|
||||
|
||||
[GeneratedRegex(@"Environment\.MachineName")]
|
||||
private static partial Regex EnvironmentMachineNameRegex();
|
||||
|
||||
[GeneratedRegex(@"File\.(?:Read|Open|ReadAll)")]
|
||||
private static partial Regex FileReadRegex();
|
||||
|
||||
[GeneratedRegex(@"File\.(?:Write|Create|Append)")]
|
||||
private static partial Regex FileWriteRegex();
|
||||
|
||||
[GeneratedRegex(@"(?:double|float)\s+\w+\s*[=<>!]=")]
|
||||
private static partial Regex DoubleComparisonRegex();
|
||||
|
||||
[GeneratedRegex(@"foreach\s*\([^)]+\s+in\s+\w*[Dd]ictionary")]
|
||||
private static partial Regex DictionaryIterationRegex();
|
||||
|
||||
[GeneratedRegex(@"foreach\s*\([^)]+\s+in\s+\w*[Hh]ashSet")]
|
||||
private static partial Regex HashSetIterationRegex();
|
||||
|
||||
private sealed record ProhibitedPattern
|
||||
{
|
||||
public required DeterminismViolationCategory Category { get; init; }
|
||||
public required string ViolationType { get; init; }
|
||||
public required Regex Regex { get; init; }
|
||||
public required string Message { get; init; }
|
||||
public required DeterminismViolationSeverity Severity { get; init; }
|
||||
public string? Remediation { get; init; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user