DET-004: Refactor Policy Replay and Deltas for determinism
- ReplayEngine: inject TimeProvider - ReplayReport: inject TimeProvider and IGuidProvider via builder - ReplayResult: add TimeProvider parameter to Failed() method - DeltaComputer: inject TimeProvider - DeltaVerdictBuilder: inject TimeProvider Replace DateTimeOffset.UtcNow and Guid.NewGuid() with injected providers Sprint: SPRINT_20260104_001_BE_determinism_timeprovider_injection
This commit is contained in:
@@ -16,13 +16,16 @@ public sealed class DeltaComputer : IDeltaComputer
|
||||
{
|
||||
private readonly ISnapshotService _snapshotService;
|
||||
private readonly ILogger<DeltaComputer> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public DeltaComputer(
|
||||
ISnapshotService snapshotService,
|
||||
ILogger<DeltaComputer> logger)
|
||||
ILogger<DeltaComputer> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_snapshotService = snapshotService;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -58,7 +61,7 @@ public sealed class DeltaComputer : IDeltaComputer
|
||||
var delta = new SecurityStateDelta
|
||||
{
|
||||
DeltaId = "", // Computed below
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
ComputedAt = _timeProvider.GetUtcNow(),
|
||||
BaselineSnapshotId = baselineSnapshotId,
|
||||
TargetSnapshotId = targetSnapshotId,
|
||||
Artifact = artifact,
|
||||
|
||||
@@ -127,6 +127,7 @@ public sealed class DeltaVerdictBuilder
|
||||
private static readonly IVerdictIdGenerator DefaultIdGenerator = new VerdictIdGenerator();
|
||||
|
||||
private readonly IVerdictIdGenerator _idGenerator;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private DeltaVerdictStatus _status = DeltaVerdictStatus.Pass;
|
||||
private DeltaGateLevel _gate = DeltaGateLevel.G1;
|
||||
private int _riskPoints;
|
||||
@@ -139,7 +140,7 @@ public sealed class DeltaVerdictBuilder
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DeltaVerdictBuilder"/> with the default ID generator.
|
||||
/// </summary>
|
||||
public DeltaVerdictBuilder() : this(DefaultIdGenerator)
|
||||
public DeltaVerdictBuilder() : this(DefaultIdGenerator, TimeProvider.System)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -147,9 +148,11 @@ public sealed class DeltaVerdictBuilder
|
||||
/// Creates a new <see cref="DeltaVerdictBuilder"/> with a custom ID generator.
|
||||
/// </summary>
|
||||
/// <param name="idGenerator">Custom verdict ID generator for testing or specialized scenarios.</param>
|
||||
public DeltaVerdictBuilder(IVerdictIdGenerator idGenerator)
|
||||
/// <param name="timeProvider">Time provider for deterministic timestamps.</param>
|
||||
public DeltaVerdictBuilder(IVerdictIdGenerator idGenerator, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_idGenerator = idGenerator ?? throw new ArgumentNullException(nameof(idGenerator));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public DeltaVerdictBuilder WithStatus(DeltaVerdictStatus status)
|
||||
@@ -241,7 +244,7 @@ public sealed class DeltaVerdictBuilder
|
||||
{
|
||||
VerdictId = verdictId,
|
||||
DeltaId = deltaId,
|
||||
EvaluatedAt = DateTimeOffset.UtcNow,
|
||||
EvaluatedAt = _timeProvider.GetUtcNow(),
|
||||
Status = _status,
|
||||
RecommendedGate = _gate,
|
||||
RiskPoints = _riskPoints,
|
||||
|
||||
@@ -14,16 +14,19 @@ public sealed class ReplayEngine : IReplayEngine
|
||||
private readonly IKnowledgeSourceResolver _sourceResolver;
|
||||
private readonly IVerdictComparer _verdictComparer;
|
||||
private readonly ILogger<ReplayEngine> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ReplayEngine(
|
||||
ISnapshotService snapshotService,
|
||||
IKnowledgeSourceResolver sourceResolver,
|
||||
IVerdictComparer verdictComparer,
|
||||
TimeProvider? timeProvider = null,
|
||||
ILogger<ReplayEngine>? logger = null)
|
||||
{
|
||||
_snapshotService = snapshotService ?? throw new ArgumentNullException(nameof(snapshotService));
|
||||
_sourceResolver = sourceResolver ?? throw new ArgumentNullException(nameof(sourceResolver));
|
||||
_verdictComparer = verdictComparer ?? throw new ArgumentNullException(nameof(verdictComparer));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_logger = logger ?? NullLogger<ReplayEngine>.Instance;
|
||||
}
|
||||
|
||||
@@ -89,7 +92,7 @@ public sealed class ReplayEngine : IReplayEngine
|
||||
OriginalVerdict = originalVerdict,
|
||||
DeltaReport = deltaReport,
|
||||
SnapshotId = request.SnapshotId,
|
||||
ReplayedAt = DateTimeOffset.UtcNow,
|
||||
ReplayedAt = _timeProvider.GetUtcNow(),
|
||||
Duration = stopwatch.Elapsed
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Policy.Replay;
|
||||
|
||||
/// <summary>
|
||||
@@ -111,11 +113,19 @@ public sealed class ReplayReportBuilder
|
||||
private readonly ReplayResult _result;
|
||||
private readonly ReplayRequest _request;
|
||||
private readonly List<string> _recommendations = [];
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public ReplayReportBuilder(ReplayRequest request, ReplayResult result)
|
||||
public ReplayReportBuilder(
|
||||
ReplayRequest request,
|
||||
ReplayResult result,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_request = request ?? throw new ArgumentNullException(nameof(request));
|
||||
_result = result ?? throw new ArgumentNullException(nameof(result));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public ReplayReportBuilder AddRecommendation(string recommendation)
|
||||
@@ -150,8 +160,8 @@ public sealed class ReplayReportBuilder
|
||||
{
|
||||
return new ReplayReport
|
||||
{
|
||||
ReportId = $"rpt:{Guid.NewGuid():N}",
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
ReportId = $"rpt:{_guidProvider.NewGuid():N}",
|
||||
GeneratedAt = _timeProvider.GetUtcNow(),
|
||||
ArtifactDigest = _request.ArtifactDigest,
|
||||
SnapshotId = _request.SnapshotId,
|
||||
OriginalVerdictId = _request.OriginalVerdictId,
|
||||
|
||||
@@ -43,12 +43,12 @@ public sealed record ReplayResult
|
||||
/// <summary>
|
||||
/// Creates a failed result.
|
||||
/// </summary>
|
||||
public static ReplayResult Failed(string snapshotId, string error) => new()
|
||||
public static ReplayResult Failed(string snapshotId, string error, TimeProvider? timeProvider = null) => new()
|
||||
{
|
||||
MatchStatus = ReplayMatchStatus.ReplayFailed,
|
||||
ReplayedVerdict = ReplayedVerdict.Empty,
|
||||
SnapshotId = snapshotId,
|
||||
ReplayedAt = DateTimeOffset.UtcNow,
|
||||
ReplayedAt = (timeProvider ?? TimeProvider.System).GetUtcNow(),
|
||||
DeltaReport = new ReplayDeltaReport
|
||||
{
|
||||
Summary = error,
|
||||
|
||||
Reference in New Issue
Block a user