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,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}");
}
}
}
}
}

View File

@@ -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
};
}

View File

@@ -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);
}
}

View File

@@ -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; }
}
}