Implement TimeProvider injection for deterministic timestamps across various services and modules
This commit is contained in:
@@ -2578,6 +2578,7 @@ Bulk task definitions (applies to every project row below):
|
|||||||
## Execution Log
|
## Execution Log
|
||||||
| Date (UTC) | Update | Owner |
|
| Date (UTC) | Update | Owner |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| 2026-01-11 | AUDIT-0056-A DONE: Fixed DateTimeOffset.TryParse to use CultureInfo.InvariantCulture and DateTimeStyles.RoundtripKind in OrasAttestationAttacher.cs ListAsync method. 33 tests pass. | Agent |
|
||||||
| 2026-01-11 | LEDGER-TESTS-0001 DONE: Fixed missing service registrations for IRuntimeTracesService and IBackportEvidenceService. Created NullRuntimeTracesService.cs and NullBackportEvidenceService.cs. Also fixed Signals module build errors (missing RuntimeAgent project reference, wrong interface method call IngestBatchAsync→IngestAsync, wrong enum member Sample→MethodSample). All 69 tests pass. | Agent |
|
| 2026-01-11 | LEDGER-TESTS-0001 DONE: Fixed missing service registrations for IRuntimeTracesService and IBackportEvidenceService. Created NullRuntimeTracesService.cs and NullBackportEvidenceService.cs. Also fixed Signals module build errors (missing RuntimeAgent project reference, wrong interface method call IngestBatchAsync→IngestAsync, wrong enum member Sample→MethodSample). All 69 tests pass. | Agent |
|
||||||
| 2026-01-08 | Added LEDGER-TESTS-0001 to cover Findings Ledger WebService test harness fixes; status set to DOING. | Codex |
|
| 2026-01-08 | Added LEDGER-TESTS-0001 to cover Findings Ledger WebService test harness fixes; status set to DOING. | Codex |
|
||||||
| 2026-01-08 | Revalidated AUDIT-0108 (StellaOps.Replay); added AGENTS.md/TASKS.md, updated audit report and local TASKS. | Codex |
|
| 2026-01-08 | Revalidated AUDIT-0108 (StellaOps.Replay); added AGENTS.md/TASKS.md, updated audit report and local TASKS. | Codex |
|
||||||
@@ -4005,7 +4006,7 @@ Bulk task definitions (applies to every project row below):
|
|||||||
| 165 | AUDIT-0055-A | DONE | Applied determinism, backend resolver, and Rekor client tests 2026-01-08 | Guild | src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/StellaOps.Attestor.Infrastructure.csproj - APPLY |
|
| 165 | AUDIT-0055-A | DONE | Applied determinism, backend resolver, and Rekor client tests 2026-01-08 | Guild | src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/StellaOps.Attestor.Infrastructure.csproj - APPLY |
|
||||||
| 166 | AUDIT-0056-M | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - MAINT |
|
| 166 | AUDIT-0056-M | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - MAINT |
|
||||||
| 167 | AUDIT-0056-T | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - TEST |
|
| 167 | AUDIT-0056-T | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - TEST |
|
||||||
| 168 | AUDIT-0056-A | DOING | Reopened after revalidation 2026-01-06 | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - APPLY |
|
| 168 | AUDIT-0056-A | DONE | Fixed DateTimeOffset.TryParse to use InvariantCulture | Guild | src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj - APPLY |
|
||||||
| 169 | AUDIT-0057-M | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - MAINT |
|
| 169 | AUDIT-0057-M | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - MAINT |
|
||||||
| 170 | AUDIT-0057-T | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - TEST |
|
| 170 | AUDIT-0057-T | DONE | Revalidation 2026-01-06 | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - TEST |
|
||||||
| 171 | AUDIT-0057-A | DONE | Waived (test project; revalidated 2026-01-06) | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - APPLY |
|
| 171 | AUDIT-0057-A | DONE | Waived (test project; revalidated 2026-01-06) | Guild | src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj - APPLY |
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
| 18 | DET-018 | DONE | DET-004 to DET-017 | Guild | Final audit: verify sprint-scoped modules (Libraries only) have deterministic TimeProvider injection. Remaining scope documented below. |
|
| 18 | DET-018 | DONE | DET-004 to DET-017 | Guild | Final audit: verify sprint-scoped modules (Libraries only) have deterministic TimeProvider injection. Remaining scope documented below. |
|
||||||
| 19 | DET-019 | DONE | DET-018 | Guild | Follow-up: Scanner.WebService determinism refactoring (~40 DateTimeOffset.UtcNow usages) - 12 endpoint/service files + 2 dependency library files fixed |
|
| 19 | DET-019 | DONE | DET-018 | Guild | Follow-up: Scanner.WebService determinism refactoring (~40 DateTimeOffset.UtcNow usages) - 12 endpoint/service files + 2 dependency library files fixed |
|
||||||
| 20 | DET-020 | DONE | DET-018 | Guild | Follow-up: Scanner.Analyzers.Native determinism refactoring - hardening extractors (ELF/MachO/PE), OfflineBuildIdIndex, and RuntimeCapture adapters (eBPF/DYLD/ETW) complete. |
|
| 20 | DET-020 | DONE | DET-018 | Guild | Follow-up: Scanner.Analyzers.Native determinism refactoring - hardening extractors (ELF/MachO/PE), OfflineBuildIdIndex, and RuntimeCapture adapters (eBPF/DYLD/ETW) complete. |
|
||||||
| 21 | DET-021 | DOING | DET-018 | Guild | Follow-up: Other modules (AdvisoryAI, Authority, AirGap, Attestor, Cli, Concelier, Excititor, etc.) - full codebase determinism sweep. Sub-tasks: (a) AirGap DONE, (b) EvidenceLocker DONE, (c) IssuerDirectory DONE, (d) Remaining modules pending |
|
| 21 | DET-021 | DOING | DET-018 | Guild | Follow-up: Other modules (AdvisoryAI, Authority, AirGap, Attestor, Cli, Concelier, Excititor, etc.) - full codebase determinism sweep. Sub-tasks: (a) AirGap DONE, (b) EvidenceLocker DONE, (c) IssuerDirectory DONE, (d) Libraries batch 2026-01-11 DONE: StellaOps.Facet, StellaOps.Verdict, StellaOps.Metrics, StellaOps.Spdx3. (e) Remaining modules pending |
|
||||||
|
|
||||||
## Implementation Pattern
|
## Implementation Pattern
|
||||||
|
|
||||||
@@ -156,6 +156,7 @@ services.AddSingleton<IGuidProvider, SystemGuidProvider>();
|
|||||||
| 2026-01-06 | DET-021 continued: AdvisoryAI module refactored - PolicyBundleCompiler.cs (TimeProvider constructor, 5 usages in CompileAsync/ValidateAsync/SignAsync), AiRemediationPlanner.cs (TimeProvider constructor, GeneratePlanAsync), GitHubPullRequestGenerator.cs (TimeProvider constructor, 5 usages across PR lifecycle), GitLabMergeRequestGenerator.cs (TimeProvider constructor, 5 usages). All builds verified. | Agent |
|
| 2026-01-06 | DET-021 continued: AdvisoryAI module refactored - PolicyBundleCompiler.cs (TimeProvider constructor, 5 usages in CompileAsync/ValidateAsync/SignAsync), AiRemediationPlanner.cs (TimeProvider constructor, GeneratePlanAsync), GitHubPullRequestGenerator.cs (TimeProvider constructor, 5 usages across PR lifecycle), GitLabMergeRequestGenerator.cs (TimeProvider constructor, 5 usages). All builds verified. | Agent |
|
||||||
| 2026-01-06 | DET-021 continued: Concelier module refactored - InterestScoreRepository.cs (TimeProvider constructor, GetLowScoreCanonicalIdsAsync minAge calculation). Remaining Concelier files are mostly static parsers (ChangelogParser) requiring method-level TimeProvider parameters. | Agent |
|
| 2026-01-06 | DET-021 continued: Concelier module refactored - InterestScoreRepository.cs (TimeProvider constructor, GetLowScoreCanonicalIdsAsync minAge calculation). Remaining Concelier files are mostly static parsers (ChangelogParser) requiring method-level TimeProvider parameters. | Agent |
|
||||||
| 2026-01-06 | DET-021 continued: ExportCenter module refactored - RiskBundleJobHandler.cs (already had TimeProvider, fixed remaining DateTime.UtcNow in CreateProviderInfo converted from static to instance method). CLI BinaryCommandHandlers.cs (2 usages fixed using services.GetService<TimeProvider>()). | Agent |
|
| 2026-01-06 | DET-021 continued: ExportCenter module refactored - RiskBundleJobHandler.cs (already had TimeProvider, fixed remaining DateTime.UtcNow in CreateProviderInfo converted from static to instance method). CLI BinaryCommandHandlers.cs (2 usages fixed using services.GetService<TimeProvider>()). | Agent |
|
||||||
|
| 2026-01-11 | DET-021 continued: Library determinism batch - StellaOps.Facet (FacetDriftVexWorkflow.cs, InMemoryFacetSealStore.cs), StellaOps.Verdict (VerdictBuilderService.cs, VerdictAssemblyService.cs, PostgresVerdictStore.cs, VerdictEndpoints.cs, VerdictRow.cs), StellaOps.Metrics (KpiCollector.cs), StellaOps.Spdx3 (Spdx3Parser.cs). All TimeProvider injection with fallback to TimeProvider.System. VerdictRow.CreatedAt changed from default to required. All builds verified. | Agent |
|
||||||
|
|
||||||
## Decisions & Risks
|
## Decisions & Risks
|
||||||
- **Decision:** Defer determinism refactoring from MAINT audit to dedicated sprint for focused, systematic approach.
|
- **Decision:** Defer determinism refactoring from MAINT audit to dedicated sprint for focused, systematic approach.
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public sealed class FacetDriftVexWorkflow
|
|||||||
private readonly FacetDriftVexEmitter _emitter;
|
private readonly FacetDriftVexEmitter _emitter;
|
||||||
private readonly IFacetDriftVexDraftStore _draftStore;
|
private readonly IFacetDriftVexDraftStore _draftStore;
|
||||||
private readonly ILogger<FacetDriftVexWorkflow> _logger;
|
private readonly ILogger<FacetDriftVexWorkflow> _logger;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="FacetDriftVexWorkflow"/> class.
|
/// Initializes a new instance of the <see cref="FacetDriftVexWorkflow"/> class.
|
||||||
@@ -62,11 +63,13 @@ public sealed class FacetDriftVexWorkflow
|
|||||||
public FacetDriftVexWorkflow(
|
public FacetDriftVexWorkflow(
|
||||||
FacetDriftVexEmitter emitter,
|
FacetDriftVexEmitter emitter,
|
||||||
IFacetDriftVexDraftStore draftStore,
|
IFacetDriftVexDraftStore draftStore,
|
||||||
ILogger<FacetDriftVexWorkflow>? logger = null)
|
ILogger<FacetDriftVexWorkflow>? logger = null,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_emitter = emitter ?? throw new ArgumentNullException(nameof(emitter));
|
_emitter = emitter ?? throw new ArgumentNullException(nameof(emitter));
|
||||||
_draftStore = draftStore ?? throw new ArgumentNullException(nameof(draftStore));
|
_draftStore = draftStore ?? throw new ArgumentNullException(nameof(draftStore));
|
||||||
_logger = logger ?? NullLogger<FacetDriftVexWorkflow>.Instance;
|
_logger = logger ?? NullLogger<FacetDriftVexWorkflow>.Instance;
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -261,6 +264,6 @@ public sealed class FacetDriftVexWorkflow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<ImmutableArray<FacetDriftVexDraft>> GetOverdueDraftsAsync(CancellationToken ct = default)
|
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, FacetSeal> _sealsByRoot = new();
|
||||||
private readonly ConcurrentDictionary<string, SortedSet<string>> _rootsByImage = new();
|
private readonly ConcurrentDictionary<string, SortedSet<string>> _rootsByImage = new();
|
||||||
private readonly object _lock = 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/>
|
/// <inheritdoc/>
|
||||||
public Task<FacetSeal?> GetLatestSealAsync(string imageDigest, CancellationToken ct = default)
|
public Task<FacetSeal?> GetLatestSealAsync(string imageDigest, CancellationToken ct = default)
|
||||||
@@ -170,7 +180,7 @@ public sealed class InMemoryFacetSealStore : IFacetSealStore
|
|||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(keepAtLeast);
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(keepAtLeast);
|
||||||
|
|
||||||
var cutoff = DateTimeOffset.UtcNow - retentionPeriod;
|
var cutoff = _timeProvider.GetUtcNow() - retentionPeriod;
|
||||||
int purged = 0;
|
int purged = 0;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
|||||||
@@ -48,19 +48,22 @@ public sealed class KpiCollector : IKpiCollector
|
|||||||
private readonly IVerdictRepository _verdictRepo;
|
private readonly IVerdictRepository _verdictRepo;
|
||||||
private readonly IReplayRepository _replayRepo;
|
private readonly IReplayRepository _replayRepo;
|
||||||
private readonly ILogger<KpiCollector> _logger;
|
private readonly ILogger<KpiCollector> _logger;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public KpiCollector(
|
public KpiCollector(
|
||||||
IKpiRepository repository,
|
IKpiRepository repository,
|
||||||
IFindingRepository findingRepo,
|
IFindingRepository findingRepo,
|
||||||
IVerdictRepository verdictRepo,
|
IVerdictRepository verdictRepo,
|
||||||
IReplayRepository replayRepo,
|
IReplayRepository replayRepo,
|
||||||
ILogger<KpiCollector> logger)
|
ILogger<KpiCollector> logger,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_findingRepo = findingRepo;
|
_findingRepo = findingRepo;
|
||||||
_verdictRepo = verdictRepo;
|
_verdictRepo = verdictRepo;
|
||||||
_replayRepo = replayRepo;
|
_replayRepo = replayRepo;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -200,7 +203,7 @@ public sealed class KpiCollector : IKpiCollector
|
|||||||
BreachesByEnvironment = breaches,
|
BreachesByEnvironment = breaches,
|
||||||
OverridesGranted = overrides.Count,
|
OverridesGranted = overrides.Count,
|
||||||
AvgOverrideAgeDays = overrides.Any()
|
AvgOverrideAgeDays = overrides.Any()
|
||||||
? (decimal)overrides.Average(o => (DateTimeOffset.UtcNow - o.GrantedAt).TotalDays)
|
? (decimal)overrides.Average(o => (_timeProvider.GetUtcNow() - o.GrantedAt).TotalDays)
|
||||||
: 0
|
: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
|||||||
{
|
{
|
||||||
private readonly ISpdx3ContextResolver _contextResolver;
|
private readonly ISpdx3ContextResolver _contextResolver;
|
||||||
private readonly ILogger<Spdx3Parser> _logger;
|
private readonly ILogger<Spdx3Parser> _logger;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
@@ -34,10 +35,12 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Spdx3Parser(
|
public Spdx3Parser(
|
||||||
ISpdx3ContextResolver contextResolver,
|
ISpdx3ContextResolver contextResolver,
|
||||||
ILogger<Spdx3Parser> logger)
|
ILogger<Spdx3Parser> logger,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_contextResolver = contextResolver;
|
_contextResolver = contextResolver;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -697,7 +700,7 @@ public sealed class Spdx3Parser : ISpdx3Parser
|
|||||||
var createdStr = GetStringProperty(ciElement, "created");
|
var createdStr = GetStringProperty(ciElement, "created");
|
||||||
if (!DateTimeOffset.TryParse(createdStr, out var created))
|
if (!DateTimeOffset.TryParse(createdStr, out var created))
|
||||||
{
|
{
|
||||||
created = DateTimeOffset.UtcNow;
|
created = _timeProvider.GetUtcNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
var profileStrings = GetStringArrayProperty(ciElement, "profile");
|
var profileStrings = GetStringArrayProperty(ciElement, "profile");
|
||||||
|
|||||||
@@ -329,10 +329,11 @@ public static class VerdictEndpoints
|
|||||||
IVerdictStore store,
|
IVerdictStore store,
|
||||||
HttpContext context,
|
HttpContext context,
|
||||||
ILogger<VerdictEndpointsLogger> logger,
|
ILogger<VerdictEndpointsLogger> logger,
|
||||||
|
TimeProvider timeProvider,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = GetTenantId(context);
|
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);
|
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 IDbContextFactory<VerdictDbContext> _contextFactory;
|
||||||
private readonly ILogger<PostgresVerdictStore> _logger;
|
private readonly ILogger<PostgresVerdictStore> _logger;
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public PostgresVerdictStore(
|
public PostgresVerdictStore(
|
||||||
IDbContextFactory<VerdictDbContext> contextFactory,
|
IDbContextFactory<VerdictDbContext> contextFactory,
|
||||||
ILogger<PostgresVerdictStore> logger)
|
ILogger<PostgresVerdictStore> logger,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
_jsonOptions = new JsonSerializerOptions
|
_jsonOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
@@ -122,7 +125,7 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
|||||||
|
|
||||||
if (!query.IncludeExpired)
|
if (!query.IncludeExpired)
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = _timeProvider.GetUtcNow();
|
||||||
queryable = queryable.Where(v => v.ExpiresAt == null || v.ExpiresAt > now);
|
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);
|
await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = _timeProvider.GetUtcNow();
|
||||||
var row = await context.Verdicts
|
var row = await context.Verdicts
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(v => v.TenantId == tenantId && v.SubjectPurl == purl && v.SubjectCveId == cveId)
|
.Where(v => v.TenantId == tenantId && v.SubjectPurl == purl && v.SubjectCveId == cveId)
|
||||||
@@ -255,7 +258,7 @@ public sealed class PostgresVerdictStore : IVerdictStore
|
|||||||
VerdictJson = json,
|
VerdictJson = json,
|
||||||
CreatedAt = DateTimeOffset.TryParse(verdict.Provenance.CreatedAt, out var createdAt)
|
CreatedAt = DateTimeOffset.TryParse(verdict.Provenance.CreatedAt, out var createdAt)
|
||||||
? createdAt
|
? createdAt
|
||||||
: DateTimeOffset.UtcNow,
|
: _timeProvider.GetUtcNow(),
|
||||||
ExpiresAt = expiresAt,
|
ExpiresAt = expiresAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public sealed class VerdictRow
|
|||||||
|
|
||||||
// Timestamps
|
// Timestamps
|
||||||
[Column("created_at")]
|
[Column("created_at")]
|
||||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
public required DateTimeOffset CreatedAt { get; set; }
|
||||||
|
|
||||||
[Column("expires_at")]
|
[Column("expires_at")]
|
||||||
public DateTimeOffset? ExpiresAt { get; set; }
|
public DateTimeOffset? ExpiresAt { get; set; }
|
||||||
|
|||||||
@@ -107,6 +107,17 @@ public sealed record ReachabilityInput(bool IsReachable, double Confidence, stri
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class VerdictAssemblyService : IVerdictAssemblyService
|
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)
|
public StellaVerdict AssembleVerdict(VerdictAssemblyContext context)
|
||||||
{
|
{
|
||||||
var subject = BuildSubject(context);
|
var subject = BuildSubject(context);
|
||||||
@@ -115,7 +126,7 @@ public sealed class VerdictAssemblyService : IVerdictAssemblyService
|
|||||||
var evidenceGraph = BuildEvidenceGraph(context.ProofBundle);
|
var evidenceGraph = BuildEvidenceGraph(context.ProofBundle);
|
||||||
var policyPath = BuildPolicyPath(context.ProofBundle);
|
var policyPath = BuildPolicyPath(context.ProofBundle);
|
||||||
var result = BuildResult(context.PolicyVerdict, context.ProofBundle);
|
var result = BuildResult(context.PolicyVerdict, context.ProofBundle);
|
||||||
var provenance = BuildProvenance(context);
|
var provenance = BuildProvenance(context, _timeProvider);
|
||||||
|
|
||||||
var verdict = new StellaVerdict
|
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
|
return new VerdictProvenance
|
||||||
{
|
{
|
||||||
Generator = context.Generator,
|
Generator = context.Generator,
|
||||||
GeneratorVersion = context.GeneratorVersion,
|
GeneratorVersion = context.GeneratorVersion,
|
||||||
RunId = context.RunId,
|
RunId = context.RunId,
|
||||||
CreatedAt = DateTimeOffset.UtcNow.ToString("o"),
|
CreatedAt = timeProvider.GetUtcNow().ToString("o"),
|
||||||
PolicyBundleId = context.ProofBundle?.PolicyBundleId,
|
PolicyBundleId = context.ProofBundle?.PolicyBundleId,
|
||||||
PolicyBundleVersion = context.ProofBundle?.PolicyBundleVersion,
|
PolicyBundleVersion = context.ProofBundle?.PolicyBundleVersion,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user