Implement TimeProvider injection for deterministic timestamps across various services and modules
This commit is contained in:
@@ -55,6 +55,7 @@ public sealed class FacetDriftVexWorkflow
|
||||
private readonly FacetDriftVexEmitter _emitter;
|
||||
private readonly IFacetDriftVexDraftStore _draftStore;
|
||||
private readonly ILogger<FacetDriftVexWorkflow> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FacetDriftVexWorkflow"/> class.
|
||||
@@ -62,11 +63,13 @@ public sealed class FacetDriftVexWorkflow
|
||||
public FacetDriftVexWorkflow(
|
||||
FacetDriftVexEmitter emitter,
|
||||
IFacetDriftVexDraftStore draftStore,
|
||||
ILogger<FacetDriftVexWorkflow>? logger = null)
|
||||
ILogger<FacetDriftVexWorkflow>? logger = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_emitter = emitter ?? throw new ArgumentNullException(nameof(emitter));
|
||||
_draftStore = draftStore ?? throw new ArgumentNullException(nameof(draftStore));
|
||||
_logger = logger ?? NullLogger<FacetDriftVexWorkflow>.Instance;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,6 +264,6 @@ public sealed class FacetDriftVexWorkflow
|
||||
/// </summary>
|
||||
public Task<ImmutableArray<FacetDriftVexDraft>> GetOverdueDraftsAsync(CancellationToken ct = default)
|
||||
{
|
||||
return _draftStore.GetOverdueAsync(DateTimeOffset.UtcNow, ct);
|
||||
return _draftStore.GetOverdueAsync(_timeProvider.GetUtcNow(), ct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ public sealed class InMemoryFacetSealStore : IFacetSealStore
|
||||
private readonly ConcurrentDictionary<string, FacetSeal> _sealsByRoot = new();
|
||||
private readonly ConcurrentDictionary<string, SortedSet<string>> _rootsByImage = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryFacetSealStore"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Time provider for deterministic timestamps.</param>
|
||||
public InMemoryFacetSealStore(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<FacetSeal?> GetLatestSealAsync(string imageDigest, CancellationToken ct = default)
|
||||
@@ -170,7 +180,7 @@ public sealed class InMemoryFacetSealStore : IFacetSealStore
|
||||
ct.ThrowIfCancellationRequested();
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(keepAtLeast);
|
||||
|
||||
var cutoff = DateTimeOffset.UtcNow - retentionPeriod;
|
||||
var cutoff = _timeProvider.GetUtcNow() - retentionPeriod;
|
||||
int purged = 0;
|
||||
|
||||
lock (_lock)
|
||||
|
||||
@@ -48,19 +48,22 @@ public sealed class KpiCollector : IKpiCollector
|
||||
private readonly IVerdictRepository _verdictRepo;
|
||||
private readonly IReplayRepository _replayRepo;
|
||||
private readonly ILogger<KpiCollector> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public KpiCollector(
|
||||
IKpiRepository repository,
|
||||
IFindingRepository findingRepo,
|
||||
IVerdictRepository verdictRepo,
|
||||
IReplayRepository replayRepo,
|
||||
ILogger<KpiCollector> logger)
|
||||
ILogger<KpiCollector> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_repository = repository;
|
||||
_findingRepo = findingRepo;
|
||||
_verdictRepo = verdictRepo;
|
||||
_replayRepo = replayRepo;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -200,7 +203,7 @@ public sealed class KpiCollector : IKpiCollector
|
||||
BreachesByEnvironment = breaches,
|
||||
OverridesGranted = overrides.Count,
|
||||
AvgOverrideAgeDays = overrides.Any()
|
||||
? (decimal)overrides.Average(o => (DateTimeOffset.UtcNow - o.GrantedAt).TotalDays)
|
||||
? (decimal)overrides.Average(o => (_timeProvider.GetUtcNow() - o.GrantedAt).TotalDays)
|
||||
: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
||||
{
|
||||
private readonly ISpdx3ContextResolver _contextResolver;
|
||||
private readonly ILogger<Spdx3Parser> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -34,10 +35,12 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
||||
/// </summary>
|
||||
public Spdx3Parser(
|
||||
ISpdx3ContextResolver contextResolver,
|
||||
ILogger<Spdx3Parser> logger)
|
||||
ILogger<Spdx3Parser> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_contextResolver = contextResolver;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -697,7 +700,7 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
||||
var createdStr = GetStringProperty(ciElement, "created");
|
||||
if (!DateTimeOffset.TryParse(createdStr, out var created))
|
||||
{
|
||||
created = DateTimeOffset.UtcNow;
|
||||
created = _timeProvider.GetUtcNow();
|
||||
}
|
||||
|
||||
var profileStrings = GetStringArrayProperty(ciElement, "profile");
|
||||
|
||||
@@ -329,10 +329,11 @@ public static class VerdictEndpoints
|
||||
IVerdictStore store,
|
||||
HttpContext context,
|
||||
ILogger<VerdictEndpointsLogger> logger,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = GetTenantId(context);
|
||||
var deletedCount = await store.DeleteExpiredAsync(tenantId, DateTimeOffset.UtcNow, cancellationToken);
|
||||
var deletedCount = await store.DeleteExpiredAsync(tenantId, timeProvider.GetUtcNow(), cancellationToken);
|
||||
|
||||
logger.LogInformation("Deleted {Count} expired verdicts for tenant {TenantId}", deletedCount, tenantId);
|
||||
|
||||
|
||||
@@ -16,13 +16,16 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
||||
private readonly IDbContextFactory<VerdictDbContext> _contextFactory;
|
||||
private readonly ILogger<PostgresVerdictStore> _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresVerdictStore(
|
||||
IDbContextFactory<VerdictDbContext> contextFactory,
|
||||
ILogger<PostgresVerdictStore> logger)
|
||||
ILogger<PostgresVerdictStore> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
@@ -122,7 +125,7 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
||||
|
||||
if (!query.IncludeExpired)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
queryable = queryable.Where(v => v.ExpiresAt == null || v.ExpiresAt > now);
|
||||
}
|
||||
|
||||
@@ -192,7 +195,7 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var row = await context.Verdicts
|
||||
.AsNoTracking()
|
||||
.Where(v => v.TenantId == tenantId && v.SubjectPurl == purl && v.SubjectCveId == cveId)
|
||||
@@ -255,7 +258,7 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
||||
VerdictJson = json,
|
||||
CreatedAt = DateTimeOffset.TryParse(verdict.Provenance.CreatedAt, out var createdAt)
|
||||
? createdAt
|
||||
: DateTimeOffset.UtcNow,
|
||||
: _timeProvider.GetUtcNow(),
|
||||
ExpiresAt = expiresAt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed class VerdictRow
|
||||
|
||||
// Timestamps
|
||||
[Column("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
[Column("expires_at")]
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
|
||||
@@ -107,6 +107,17 @@ public sealed record ReachabilityInput(bool IsReachable, double Confidence, stri
|
||||
/// </summary>
|
||||
public sealed class VerdictAssemblyService : IVerdictAssemblyService
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerdictAssemblyService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Time provider for deterministic timestamps.</param>
|
||||
public VerdictAssemblyService(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public StellaVerdict AssembleVerdict(VerdictAssemblyContext context)
|
||||
{
|
||||
var subject = BuildSubject(context);
|
||||
@@ -115,7 +126,7 @@ public sealed class VerdictAssemblyService : IVerdictAssemblyService
|
||||
var evidenceGraph = BuildEvidenceGraph(context.ProofBundle);
|
||||
var policyPath = BuildPolicyPath(context.ProofBundle);
|
||||
var result = BuildResult(context.PolicyVerdict, context.ProofBundle);
|
||||
var provenance = BuildProvenance(context);
|
||||
var provenance = BuildProvenance(context, _timeProvider);
|
||||
|
||||
var verdict = new StellaVerdict
|
||||
{
|
||||
@@ -379,14 +390,14 @@ public sealed class VerdictAssemblyService : IVerdictAssemblyService
|
||||
};
|
||||
}
|
||||
|
||||
private static VerdictProvenance BuildProvenance(VerdictAssemblyContext context)
|
||||
private static VerdictProvenance BuildProvenance(VerdictAssemblyContext context, TimeProvider timeProvider)
|
||||
{
|
||||
return new VerdictProvenance
|
||||
{
|
||||
Generator = context.Generator,
|
||||
GeneratorVersion = context.GeneratorVersion,
|
||||
RunId = context.RunId,
|
||||
CreatedAt = DateTimeOffset.UtcNow.ToString("o"),
|
||||
CreatedAt = timeProvider.GetUtcNow().ToString("o"),
|
||||
PolicyBundleId = context.ProofBundle?.PolicyBundleId,
|
||||
PolicyBundleVersion = context.ProofBundle?.PolicyBundleVersion,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user