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