save progress
This commit is contained in:
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Storage;
|
||||
@@ -26,16 +27,19 @@ public sealed class PostgresConsensusProjectionStore : IConsensusProjectionStore
|
||||
private readonly IConsensusEventEmitter? _eventEmitter;
|
||||
private readonly ILogger<PostgresConsensusProjectionStore> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public PostgresConsensusProjectionStore(
|
||||
NpgsqlDataSource dataSource,
|
||||
ILogger<PostgresConsensusProjectionStore> logger,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null,
|
||||
IConsensusEventEmitter? eventEmitter = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? new SystemGuidProvider();
|
||||
_eventEmitter = eventEmitter;
|
||||
}
|
||||
|
||||
@@ -52,7 +56,7 @@ public sealed class PostgresConsensusProjectionStore : IConsensusProjectionStore
|
||||
activity?.SetTag("vulnerabilityId", result.VulnerabilityId);
|
||||
activity?.SetTag("productKey", result.ProductKey);
|
||||
|
||||
var projectionId = Guid.NewGuid();
|
||||
var projectionId = _guidProvider.NewGuid();
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Check for previous projection to track history
|
||||
@@ -527,7 +531,7 @@ public sealed class PostgresConsensusProjectionStore : IConsensusProjectionStore
|
||||
// Always emit computed event
|
||||
await _eventEmitter.EmitConsensusComputedAsync(
|
||||
new ConsensusComputedEvent(
|
||||
EventId: Guid.NewGuid().ToString(),
|
||||
EventId: _guidProvider.NewGuid().ToString(),
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
@@ -546,7 +550,7 @@ public sealed class PostgresConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
await _eventEmitter.EmitStatusChangedAsync(
|
||||
new ConsensusStatusChangedEvent(
|
||||
EventId: Guid.NewGuid().ToString(),
|
||||
EventId: _guidProvider.NewGuid().ToString(),
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
@@ -564,7 +568,7 @@ public sealed class PostgresConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
await _eventEmitter.EmitConflictDetectedAsync(
|
||||
new ConsensusConflictDetectedEvent(
|
||||
EventId: Guid.NewGuid().ToString(),
|
||||
EventId: _guidProvider.NewGuid().ToString(),
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.VexLens\StellaOps.VexLens.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -325,6 +325,7 @@ public static class VexLensEndpointExtensions
|
||||
[FromQuery] DateTimeOffset? fromDate,
|
||||
[FromQuery] DateTimeOffset? toDate,
|
||||
[FromServices] IGatingStatisticsStore statsStore,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -340,7 +341,7 @@ public static class VexLensEndpointExtensions
|
||||
TotalSurfaced: stats.TotalSurfaced,
|
||||
TotalDamped: stats.TotalDamped,
|
||||
AverageDampingPercent: stats.AverageDampingPercent,
|
||||
ComputedAt: DateTimeOffset.UtcNow));
|
||||
ComputedAt: timeProvider.GetUtcNow()));
|
||||
}
|
||||
|
||||
private static async Task<IResult> GateSnapshotAsync(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Storage;
|
||||
@@ -43,17 +44,20 @@ public sealed class ConsensusRationaleService : IConsensusRationaleService
|
||||
private readonly IConsensusProjectionStore _projectionStore;
|
||||
private readonly IVexConsensusEngine _consensusEngine;
|
||||
private readonly ITrustWeightEngine _trustWeightEngine;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
private const string AlgorithmVersion = "1.0.0";
|
||||
|
||||
public ConsensusRationaleService(
|
||||
IConsensusProjectionStore projectionStore,
|
||||
IVexConsensusEngine consensusEngine,
|
||||
ITrustWeightEngine trustWeightEngine)
|
||||
ITrustWeightEngine trustWeightEngine,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_projectionStore = projectionStore;
|
||||
_consensusEngine = consensusEngine;
|
||||
_trustWeightEngine = trustWeightEngine;
|
||||
_guidProvider = guidProvider ?? new SystemGuidProvider();
|
||||
}
|
||||
|
||||
public async Task<GenerateRationaleResponse> GenerateRationaleAsync(
|
||||
@@ -177,7 +181,7 @@ public sealed class ConsensusRationaleService : IConsensusRationaleService
|
||||
var outputHash = ComputeOutputHash(result, contributions, conflicts);
|
||||
|
||||
var rationale = new DetailedConsensusRationale(
|
||||
RationaleId: $"rat-{Guid.NewGuid():N}",
|
||||
RationaleId: $"rat-{_guidProvider.NewGuid():N}",
|
||||
VulnerabilityId: result.VulnerabilityId,
|
||||
ProductKey: result.ProductKey,
|
||||
ConsensusStatus: result.ConsensusStatus,
|
||||
|
||||
@@ -137,19 +137,22 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
private readonly IConsensusProjectionStore _projectionStore;
|
||||
private readonly IIssuerDirectory _issuerDirectory;
|
||||
private readonly IVexStatementProvider _statementProvider;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public VexLensApiService(
|
||||
IVexConsensusEngine consensusEngine,
|
||||
ITrustWeightEngine trustWeightEngine,
|
||||
IConsensusProjectionStore projectionStore,
|
||||
IIssuerDirectory issuerDirectory,
|
||||
IVexStatementProvider statementProvider)
|
||||
IVexStatementProvider statementProvider,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_consensusEngine = consensusEngine;
|
||||
_trustWeightEngine = trustWeightEngine;
|
||||
_projectionStore = projectionStore;
|
||||
_issuerDirectory = issuerDirectory;
|
||||
_statementProvider = statementProvider;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ComputeConsensusResponse> ComputeConsensusAsync(
|
||||
@@ -164,7 +167,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
cancellationToken);
|
||||
|
||||
// Compute trust weights
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var weightedStatements = new List<WeightedStatement>();
|
||||
|
||||
foreach (var stmt in statements)
|
||||
@@ -237,7 +240,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
cancellationToken);
|
||||
|
||||
// Compute trust weights
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var weightedStatements = new List<WeightedStatement>();
|
||||
|
||||
foreach (var stmt in statements)
|
||||
@@ -293,7 +296,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
var resolutionResult = await _consensusEngine.ComputeConsensusWithProofAsync(
|
||||
consensusRequest,
|
||||
proofContext,
|
||||
TimeProvider.System,
|
||||
_timeProvider,
|
||||
cancellationToken);
|
||||
|
||||
// Store result if requested
|
||||
@@ -348,7 +351,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
TotalCount: request.Targets.Count,
|
||||
SuccessCount: results.Count,
|
||||
FailureCount: failures,
|
||||
CompletedAt: DateTimeOffset.UtcNow);
|
||||
CompletedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
public async Task<ProjectionDetailResponse?> GetProjectionAsync(
|
||||
@@ -452,7 +455,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
|
||||
var withConflicts = projections.Count(p => p.ConflictCount > 0);
|
||||
|
||||
var last24h = DateTimeOffset.UtcNow.AddDays(-1);
|
||||
var last24h = _timeProvider.GetUtcNow().AddDays(-1);
|
||||
var changesLast24h = projections.Count(p => p.StatusChanged && p.ComputedAt >= last24h);
|
||||
|
||||
return new ConsensusStatisticsResponse(
|
||||
@@ -462,7 +465,7 @@ public sealed class VexLensApiService : IVexLensApiService
|
||||
AverageConfidence: avgConfidence,
|
||||
ProjectionsWithConflicts: withConflicts,
|
||||
StatusChangesLast24h: changesLast24h,
|
||||
ComputedAt: DateTimeOffset.UtcNow);
|
||||
ComputedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
public async Task<IssuerListResponse> ListIssuersAsync(
|
||||
|
||||
@@ -472,15 +472,18 @@ public sealed class TrustScorecardApiService : ITrustScorecardApiService
|
||||
private readonly ISourceTrustScoreCalculator _scoreCalculator;
|
||||
private readonly IConflictAuditStore? _auditStore;
|
||||
private readonly ITrustScoreHistoryStore? _historyStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public TrustScorecardApiService(
|
||||
ISourceTrustScoreCalculator scoreCalculator,
|
||||
IConflictAuditStore? auditStore = null,
|
||||
ITrustScoreHistoryStore? historyStore = null)
|
||||
ITrustScoreHistoryStore? historyStore = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_scoreCalculator = scoreCalculator;
|
||||
_auditStore = auditStore;
|
||||
_historyStore = historyStore;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<TrustScorecardResponse> GetScorecardAsync(
|
||||
@@ -544,7 +547,7 @@ public sealed class TrustScorecardApiService : ITrustScorecardApiService
|
||||
SignatureValidityRate = cachedScore.Breakdown.Verification.SignatureValidityRate,
|
||||
VerificationMethod = cachedScore.Breakdown.Verification.IssuerVerified ? "registry" : null
|
||||
},
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -604,10 +607,11 @@ public sealed class TrustScorecardApiService : ITrustScorecardApiService
|
||||
};
|
||||
}
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var history = await _historyStore.GetHistoryAsync(
|
||||
sourceId,
|
||||
DateTimeOffset.UtcNow.AddDays(-days),
|
||||
DateTimeOffset.UtcNow,
|
||||
now.AddDays(-days),
|
||||
now,
|
||||
cancellationToken);
|
||||
|
||||
if (history.Count == 0)
|
||||
@@ -622,7 +626,7 @@ public sealed class TrustScorecardApiService : ITrustScorecardApiService
|
||||
|
||||
var current = history.LastOrDefault()?.CompositeScore ?? 0.0;
|
||||
var thirtyDaysAgo = history
|
||||
.Where(h => h.Timestamp >= DateTimeOffset.UtcNow.AddDays(-30))
|
||||
.Where(h => h.Timestamp >= now.AddDays(-30))
|
||||
.FirstOrDefault()?.CompositeScore ?? current;
|
||||
var ninetyDaysAgo = history.FirstOrDefault()?.CompositeScore ?? current;
|
||||
|
||||
|
||||
@@ -138,14 +138,16 @@ public sealed class InMemoryConsensusRationaleCache : IConsensusRationaleCache
|
||||
private readonly Dictionary<string, CacheEntry> _cache = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly int _maxEntries;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private long _hitCount;
|
||||
private long _missCount;
|
||||
private DateTimeOffset? _lastCleared;
|
||||
|
||||
public InMemoryConsensusRationaleCache(int maxEntries = 10000)
|
||||
public InMemoryConsensusRationaleCache(int maxEntries = 10000, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_maxEntries = maxEntries;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<DetailedConsensusRationale?> GetAsync(
|
||||
@@ -163,7 +165,7 @@ public sealed class InMemoryConsensusRationaleCache : IConsensusRationaleCache
|
||||
return Task.FromResult<DetailedConsensusRationale?>(null);
|
||||
}
|
||||
|
||||
entry.LastAccessed = DateTimeOffset.UtcNow;
|
||||
entry.LastAccessed = _timeProvider.GetUtcNow();
|
||||
Interlocked.Increment(ref _hitCount);
|
||||
return Task.FromResult<DetailedConsensusRationale?>(entry.Rationale);
|
||||
}
|
||||
@@ -187,12 +189,13 @@ public sealed class InMemoryConsensusRationaleCache : IConsensusRationaleCache
|
||||
EvictOldestEntry();
|
||||
}
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
_cache[cacheKey] = new CacheEntry
|
||||
{
|
||||
Rationale = rationale,
|
||||
Options = options ?? new CacheOptions(),
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
LastAccessed = DateTimeOffset.UtcNow
|
||||
Created = now,
|
||||
LastAccessed = now
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -254,7 +257,7 @@ public sealed class InMemoryConsensusRationaleCache : IConsensusRationaleCache
|
||||
lock (_lock)
|
||||
{
|
||||
_cache.Clear();
|
||||
_lastCleared = DateTimeOffset.UtcNow;
|
||||
_lastCleared = _timeProvider.GetUtcNow();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -277,9 +280,9 @@ public sealed class InMemoryConsensusRationaleCache : IConsensusRationaleCache
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExpired(CacheEntry entry)
|
||||
private bool IsExpired(CacheEntry entry)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
if (entry.Options.AbsoluteExpiration.HasValue &&
|
||||
now >= entry.Options.AbsoluteExpiration.Value)
|
||||
|
||||
@@ -13,10 +13,14 @@ namespace StellaOps.VexLens.Consensus;
|
||||
public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
{
|
||||
private ConsensusConfiguration _configuration;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public VexConsensusEngine(ConsensusConfiguration? configuration = null)
|
||||
public VexConsensusEngine(
|
||||
ConsensusConfiguration? configuration = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_configuration = configuration ?? CreateDefaultConfiguration();
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<VexConsensusResult> ComputeConsensusAsync(
|
||||
@@ -559,7 +563,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
stmt.Statement.Status,
|
||||
stmt.Statement.Justification,
|
||||
weight,
|
||||
GetStatementTimestamp(stmt.Statement),
|
||||
GetStatementTimestamp(stmt.Statement, _timeProvider),
|
||||
HasSignature(stmt.Weight));
|
||||
}
|
||||
|
||||
@@ -574,7 +578,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
stmt.Statement.Status,
|
||||
stmt.Statement.Justification,
|
||||
weight,
|
||||
GetStatementTimestamp(stmt.Statement),
|
||||
GetStatementTimestamp(stmt.Statement, _timeProvider),
|
||||
HasSignature(stmt.Weight),
|
||||
reason);
|
||||
}
|
||||
@@ -704,7 +708,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
stmt.Statement.Status,
|
||||
stmt.Statement.Justification,
|
||||
weight,
|
||||
GetStatementTimestamp(stmt.Statement),
|
||||
GetStatementTimestamp(stmt.Statement, _timeProvider),
|
||||
HasSignature(stmt.Weight));
|
||||
}
|
||||
|
||||
@@ -719,7 +723,7 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
stmt.Statement.Status,
|
||||
stmt.Statement.Justification,
|
||||
weight,
|
||||
GetStatementTimestamp(stmt.Statement),
|
||||
GetStatementTimestamp(stmt.Statement, _timeProvider),
|
||||
HasSignature(stmt.Weight),
|
||||
reason);
|
||||
}
|
||||
@@ -1278,10 +1282,10 @@ public sealed class VexConsensusEngine : IVexConsensusEngine
|
||||
(decimal)breakdown.StatusSpecificityWeight));
|
||||
}
|
||||
|
||||
private static DateTimeOffset GetStatementTimestamp(NormalizedStatement statement)
|
||||
private static DateTimeOffset GetStatementTimestamp(NormalizedStatement statement, TimeProvider timeProvider)
|
||||
{
|
||||
// Use LastSeen if available, otherwise FirstSeen, otherwise current time
|
||||
return statement.LastSeen ?? statement.FirstSeen ?? DateTimeOffset.UtcNow;
|
||||
return statement.LastSeen ?? statement.FirstSeen ?? timeProvider.GetUtcNow();
|
||||
}
|
||||
|
||||
private static bool HasSignature(Trust.TrustWeightResult weight)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Storage;
|
||||
@@ -273,12 +274,19 @@ public enum ExportFormat
|
||||
public sealed class ConsensusExportService : IConsensusExportService
|
||||
{
|
||||
private readonly IConsensusProjectionStore _projectionStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
private const string SnapshotVersion = "1.0.0";
|
||||
|
||||
public ConsensusExportService(IConsensusProjectionStore projectionStore)
|
||||
public ConsensusExportService(
|
||||
IConsensusProjectionStore projectionStore,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_projectionStore = projectionStore;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? new SystemGuidProvider();
|
||||
}
|
||||
|
||||
public async Task<ConsensusSnapshot> CreateSnapshotAsync(
|
||||
@@ -338,12 +346,12 @@ public sealed class ConsensusExportService : IConsensusExportService
|
||||
.GroupBy(p => p.Status)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
var snapshotId = $"snap-{Guid.NewGuid():N}";
|
||||
var snapshotId = $"snap-{_guidProvider.NewGuid():N}";
|
||||
var contentHash = ComputeContentHash(projections);
|
||||
|
||||
return new ConsensusSnapshot(
|
||||
SnapshotId: snapshotId,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
CreatedAt: _timeProvider.GetUtcNow(),
|
||||
Version: SnapshotVersion,
|
||||
TenantId: request.TenantId,
|
||||
Projections: projections,
|
||||
@@ -400,13 +408,13 @@ public sealed class ConsensusExportService : IConsensusExportService
|
||||
|
||||
// For a true incremental, we'd compare with the previous snapshot
|
||||
// Here we just return new/updated since the timestamp
|
||||
var snapshotId = $"snap-inc-{Guid.NewGuid():N}";
|
||||
var snapshotId = $"snap-inc-{_guidProvider.NewGuid():N}";
|
||||
var contentHash = ComputeContentHash(current.Projections);
|
||||
|
||||
return new IncrementalSnapshot(
|
||||
SnapshotId: snapshotId,
|
||||
PreviousSnapshotId: lastSnapshotId,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
CreatedAt: _timeProvider.GetUtcNow(),
|
||||
Version: SnapshotVersion,
|
||||
Added: current.Projections,
|
||||
Removed: [], // Would need previous snapshot to determine removed
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Models;
|
||||
|
||||
namespace StellaOps.VexLens.Normalization;
|
||||
@@ -12,6 +13,13 @@ namespace StellaOps.VexLens.Normalization;
|
||||
/// </summary>
|
||||
public sealed class CsafVexNormalizer : IVexNormalizer
|
||||
{
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public CsafVexNormalizer(IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public VexSourceFormat SourceFormat => VexSourceFormat.CsafVex;
|
||||
|
||||
public bool CanNormalize(string content)
|
||||
@@ -77,7 +85,7 @@ public sealed class CsafVexNormalizer : IVexNormalizer
|
||||
var documentId = ExtractDocumentId(documentElement);
|
||||
if (string.IsNullOrWhiteSpace(documentId))
|
||||
{
|
||||
documentId = $"csaf:{Guid.NewGuid():N}";
|
||||
documentId = $"csaf:{_guidProvider.NewGuid():N}";
|
||||
warnings.Add(new NormalizationWarning(
|
||||
"WARN_CSAF_001",
|
||||
"Document tracking ID not found; generated a random ID",
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Models;
|
||||
|
||||
namespace StellaOps.VexLens.Normalization;
|
||||
@@ -12,6 +13,13 @@ namespace StellaOps.VexLens.Normalization;
|
||||
/// </summary>
|
||||
public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
||||
{
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public CycloneDxVexNormalizer(IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public VexSourceFormat SourceFormat => VexSourceFormat.CycloneDxVex;
|
||||
|
||||
public bool CanNormalize(string content)
|
||||
@@ -65,7 +73,7 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
||||
var documentId = ExtractDocumentId(root);
|
||||
if (string.IsNullOrWhiteSpace(documentId))
|
||||
{
|
||||
documentId = $"cyclonedx:{Guid.NewGuid():N}";
|
||||
documentId = $"cyclonedx:{_guidProvider.NewGuid():N}";
|
||||
warnings.Add(new NormalizationWarning(
|
||||
"WARN_CDX_001",
|
||||
"Serial number not found; generated a random ID",
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Models;
|
||||
|
||||
namespace StellaOps.VexLens.Normalization;
|
||||
@@ -11,6 +12,13 @@ namespace StellaOps.VexLens.Normalization;
|
||||
/// </summary>
|
||||
public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
{
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public OpenVexNormalizer(IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public VexSourceFormat SourceFormat => VexSourceFormat.OpenVex;
|
||||
|
||||
public bool CanNormalize(string content)
|
||||
@@ -58,7 +66,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
var documentId = ExtractDocumentId(root);
|
||||
if (string.IsNullOrWhiteSpace(documentId))
|
||||
{
|
||||
documentId = $"openvex:{Guid.NewGuid():N}";
|
||||
documentId = $"openvex:{_guidProvider.NewGuid():N}";
|
||||
warnings.Add(new NormalizationWarning(
|
||||
"WARN_OPENVEX_001",
|
||||
"Document ID not found; generated a random ID",
|
||||
@@ -207,7 +215,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<NormalizedStatement> ExtractStatements(
|
||||
private IReadOnlyList<NormalizedStatement> ExtractStatements(
|
||||
JsonElement root,
|
||||
List<NormalizationWarning> warnings,
|
||||
ref int skipped)
|
||||
@@ -227,7 +235,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
|
||||
foreach (var stmt in statementsArray.EnumerateArray())
|
||||
{
|
||||
var statement = ExtractStatement(stmt, index, warnings, ref skipped);
|
||||
var statement = ExtractStatement(stmt, index, warnings, ref skipped, _guidProvider);
|
||||
if (statement != null)
|
||||
{
|
||||
statements.Add(statement);
|
||||
@@ -243,7 +251,8 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
JsonElement stmt,
|
||||
int index,
|
||||
List<NormalizationWarning> warnings,
|
||||
ref int skipped)
|
||||
ref int skipped,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
// Extract vulnerability
|
||||
string? vulnerabilityId = null;
|
||||
@@ -298,7 +307,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
{
|
||||
foreach (var prod in productsArray.EnumerateArray())
|
||||
{
|
||||
var product = ExtractProduct(prod);
|
||||
var product = ExtractProduct(prod, guidProvider);
|
||||
if (product != null)
|
||||
{
|
||||
products.Add(product);
|
||||
@@ -378,7 +387,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
LastSeen: timestamp);
|
||||
}
|
||||
|
||||
private static NormalizedProduct? ExtractProduct(JsonElement prod)
|
||||
private static NormalizedProduct? ExtractProduct(JsonElement prod, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
string? key = null;
|
||||
string? name = null;
|
||||
@@ -423,8 +432,9 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
return null;
|
||||
}
|
||||
|
||||
var fallbackGuid = guidProvider?.NewGuid() ?? Guid.NewGuid();
|
||||
return new NormalizedProduct(
|
||||
Key: key ?? purl ?? cpe ?? $"unknown-{Guid.NewGuid():N}",
|
||||
Key: key ?? purl ?? cpe ?? $"unknown-{fallbackGuid:N}",
|
||||
Name: name,
|
||||
Version: version,
|
||||
Purl: purl,
|
||||
|
||||
@@ -160,6 +160,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
private readonly IVexConsensusEngine _consensusEngine;
|
||||
private readonly IConsensusProjectionStore _projectionStore;
|
||||
private readonly IConsensusExportService _exportService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private const string SchemaVersion = "1.0.0";
|
||||
|
||||
@@ -172,11 +173,13 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
public ConsensusJobService(
|
||||
IVexConsensusEngine consensusEngine,
|
||||
IConsensusProjectionStore projectionStore,
|
||||
IConsensusExportService exportService)
|
||||
IConsensusExportService exportService,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_consensusEngine = consensusEngine;
|
||||
_projectionStore = projectionStore;
|
||||
_exportService = exportService;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public ConsensusJobRequest CreateComputeJob(
|
||||
@@ -299,7 +302,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
JobType: ConsensusJobTypes.SnapshotCreate,
|
||||
TenantId: request.TenantId,
|
||||
Priority: ConsensusJobTypes.GetDefaultPriority(ConsensusJobTypes.SnapshotCreate),
|
||||
IdempotencyKey: $"snapshot:{requestHash}:{DateTimeOffset.UtcNow:yyyyMMddHHmm}",
|
||||
IdempotencyKey: $"snapshot:{requestHash}:{_timeProvider.GetUtcNow():yyyyMMddHHmm}",
|
||||
Payload: JsonSerializer.Serialize(payload, JsonOptions));
|
||||
}
|
||||
|
||||
@@ -307,7 +310,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
ConsensusJobRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -350,7 +353,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
ConsensusJobRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
var payload = JsonSerializer.Deserialize<ComputePayload>(request.Payload, JsonOptions)
|
||||
?? throw new InvalidOperationException("Invalid compute payload");
|
||||
|
||||
@@ -363,7 +366,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
JobType: request.JobType,
|
||||
ItemsProcessed: 1,
|
||||
ItemsFailed: 0,
|
||||
Duration: DateTimeOffset.UtcNow - startTime,
|
||||
Duration: _timeProvider.GetUtcNow() - startTime,
|
||||
ResultPayload: JsonSerializer.Serialize(new
|
||||
{
|
||||
vulnerabilityId = payload.VulnerabilityId,
|
||||
@@ -377,7 +380,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
ConsensusJobRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
var payload = JsonSerializer.Deserialize<BatchComputePayload>(request.Payload, JsonOptions)
|
||||
?? throw new InvalidOperationException("Invalid batch compute payload");
|
||||
|
||||
@@ -389,7 +392,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
JobType: request.JobType,
|
||||
ItemsProcessed: itemCount,
|
||||
ItemsFailed: 0,
|
||||
Duration: DateTimeOffset.UtcNow - startTime,
|
||||
Duration: _timeProvider.GetUtcNow() - startTime,
|
||||
ResultPayload: JsonSerializer.Serialize(new { processedCount = itemCount }, JsonOptions),
|
||||
ErrorMessage: null);
|
||||
}
|
||||
@@ -398,7 +401,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
ConsensusJobRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
// Create snapshot using export service
|
||||
var snapshotRequest = ConsensusExportExtensions.FullExportRequest(request.TenantId);
|
||||
@@ -409,7 +412,7 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
JobType: request.JobType,
|
||||
ItemsProcessed: snapshot.Projections.Count,
|
||||
ItemsFailed: 0,
|
||||
Duration: DateTimeOffset.UtcNow - startTime,
|
||||
Duration: _timeProvider.GetUtcNow() - startTime,
|
||||
ResultPayload: JsonSerializer.Serialize(new
|
||||
{
|
||||
snapshotId = snapshot.SnapshotId,
|
||||
@@ -419,14 +422,14 @@ public sealed class ConsensusJobService : IConsensusJobService
|
||||
ErrorMessage: null);
|
||||
}
|
||||
|
||||
private static ConsensusJobResult CreateFailedResult(string jobType, DateTimeOffset startTime, string error)
|
||||
private ConsensusJobResult CreateFailedResult(string jobType, DateTimeOffset startTime, string error)
|
||||
{
|
||||
return new ConsensusJobResult(
|
||||
Success: false,
|
||||
JobType: jobType,
|
||||
ItemsProcessed: 0,
|
||||
ItemsFailed: 1,
|
||||
Duration: DateTimeOffset.UtcNow - startTime,
|
||||
Duration: _timeProvider.GetUtcNow() - startTime,
|
||||
ResultPayload: null,
|
||||
ErrorMessage: error);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Storage;
|
||||
@@ -14,6 +15,8 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
{
|
||||
private readonly IOrchestratorLedgerClient? _ledgerClient;
|
||||
private readonly OrchestratorEventOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -23,10 +26,14 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
|
||||
public OrchestratorLedgerEventEmitter(
|
||||
IOrchestratorLedgerClient? ledgerClient = null,
|
||||
OrchestratorEventOptions? options = null)
|
||||
OrchestratorEventOptions? options = null,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_ledgerClient = ledgerClient;
|
||||
_options = options ?? OrchestratorEventOptions.Default;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? new SystemGuidProvider();
|
||||
}
|
||||
|
||||
public async Task EmitConsensusComputedAsync(
|
||||
@@ -144,11 +151,11 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
if (_ledgerClient == null) return;
|
||||
|
||||
var alertEvent = new LedgerEvent(
|
||||
EventId: $"alert-{Guid.NewGuid():N}",
|
||||
EventId: $"alert-{_guidProvider.NewGuid():N}",
|
||||
EventType: ConsensusEventTypes.Alert,
|
||||
TenantId: @event.TenantId,
|
||||
CorrelationId: @event.EventId,
|
||||
OccurredAt: DateTimeOffset.UtcNow,
|
||||
OccurredAt: _timeProvider.GetUtcNow(),
|
||||
IdempotencyKey: $"alert-status-{@event.ProjectionId}-{@event.NewStatus}",
|
||||
Actor: new LedgerActor("system", "vexlens", "alert-engine"),
|
||||
Payload: JsonSerializer.Serialize(new
|
||||
@@ -174,11 +181,11 @@ public sealed class OrchestratorLedgerEventEmitter : IConsensusEventEmitter
|
||||
if (_ledgerClient == null) return;
|
||||
|
||||
var alertEvent = new LedgerEvent(
|
||||
EventId: $"alert-{Guid.NewGuid():N}",
|
||||
EventId: $"alert-{_guidProvider.NewGuid():N}",
|
||||
EventType: ConsensusEventTypes.Alert,
|
||||
TenantId: @event.TenantId,
|
||||
CorrelationId: @event.EventId,
|
||||
OccurredAt: DateTimeOffset.UtcNow,
|
||||
OccurredAt: _timeProvider.GetUtcNow(),
|
||||
IdempotencyKey: $"alert-conflict-{@event.ProjectionId}",
|
||||
Actor: new LedgerActor("system", "vexlens", "alert-engine"),
|
||||
Payload: JsonSerializer.Serialize(new
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2024-2026 StellaOps Contributors.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
|
||||
@@ -13,6 +14,7 @@ namespace StellaOps.VexLens.Proof;
|
||||
public sealed class VexProofBuilder
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly List<VexProofStatement> _statements = [];
|
||||
private readonly List<VexProofMergeStep> _mergeSteps = [];
|
||||
private readonly List<VexProofConflict> _conflicts = [];
|
||||
@@ -48,11 +50,12 @@ public sealed class VexProofBuilder
|
||||
private decimal _conditionCoverage = 1.0m;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new VexProofBuilder with the specified time provider.
|
||||
/// Creates a new VexProofBuilder with the specified time provider and GUID provider.
|
||||
/// </summary>
|
||||
public VexProofBuilder(TimeProvider timeProvider)
|
||||
public VexProofBuilder(TimeProvider timeProvider, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -533,10 +536,10 @@ public sealed class VexProofBuilder
|
||||
_ => ConfidenceTier.VeryLow
|
||||
};
|
||||
|
||||
private static string GenerateProofId(DateTimeOffset timestamp)
|
||||
private string GenerateProofId(DateTimeOffset timestamp)
|
||||
{
|
||||
var timePart = timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ", System.Globalization.CultureInfo.InvariantCulture);
|
||||
var randomPart = Guid.NewGuid().ToString("N")[..8];
|
||||
var randomPart = _guidProvider.NewGuid().ToString("N")[..8];
|
||||
return $"proof-{timePart}-{randomPart}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<!-- NG-001: Noise-gating dependencies -->
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.ReachGraph\StellaOps.ReachGraph.csproj" />
|
||||
<ProjectReference Include="..\..\Policy\StellaOps.Policy.Engine\StellaOps.Policy.Engine.csproj" />
|
||||
<!-- DET-015: Determinism abstractions for TimeProvider and IGuidProvider -->
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Exclude legacy folders with external dependencies -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Services;
|
||||
@@ -16,13 +17,19 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
private readonly IConsensusEventEmitter? _eventEmitter;
|
||||
// LIN-BE-009: Delta service for computing VEX deltas on status change
|
||||
private readonly IVexDeltaComputeService? _deltaComputeService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public InMemoryConsensusProjectionStore(
|
||||
IConsensusEventEmitter? eventEmitter = null,
|
||||
IVexDeltaComputeService? deltaComputeService = null)
|
||||
IVexDeltaComputeService? deltaComputeService = null,
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_eventEmitter = eventEmitter;
|
||||
_deltaComputeService = deltaComputeService;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
public async Task<ConsensusProjection> StoreAsync(
|
||||
@@ -31,7 +38,7 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = GetKey(result.VulnerabilityId, result.ProductKey, options.TenantId);
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Get previous projection for history tracking
|
||||
ConsensusProjection? previous = null;
|
||||
@@ -52,7 +59,7 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
}
|
||||
|
||||
var projection = new ConsensusProjection(
|
||||
ProjectionId: $"proj-{Guid.NewGuid():N}",
|
||||
ProjectionId: $"proj-{_guidProvider.NewGuid():N}",
|
||||
VulnerabilityId: result.VulnerabilityId,
|
||||
ProductKey: result.ProductKey,
|
||||
TenantId: options.TenantId,
|
||||
@@ -283,12 +290,12 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
if (_eventEmitter == null) return;
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Always emit computed event
|
||||
await _eventEmitter.EmitConsensusComputedAsync(
|
||||
new ConsensusComputedEvent(
|
||||
EventId: $"evt-{Guid.NewGuid():N}",
|
||||
EventId: $"evt-{_guidProvider.NewGuid():N}",
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
@@ -307,7 +314,7 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
{
|
||||
await _eventEmitter.EmitStatusChangedAsync(
|
||||
new ConsensusStatusChangedEvent(
|
||||
EventId: $"evt-{Guid.NewGuid():N}",
|
||||
EventId: $"evt-{_guidProvider.NewGuid():N}",
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
@@ -325,7 +332,7 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
await _deltaComputeService.ComputeAndStoreAsync(
|
||||
new VexStatusChangeContext
|
||||
{
|
||||
ProjectionId = Guid.TryParse(projection.ProjectionId, out var pid) ? pid : Guid.NewGuid(),
|
||||
ProjectionId = Guid.TryParse(projection.ProjectionId, out var pid) ? pid : _guidProvider.NewGuid(),
|
||||
VulnerabilityId = projection.VulnerabilityId,
|
||||
ProductKey = projection.ProductKey,
|
||||
ArtifactDigest = projection.ProductKey, // Use ProductKey as artifact identifier
|
||||
@@ -355,7 +362,7 @@ public sealed class InMemoryConsensusProjectionStore : IConsensusProjectionStore
|
||||
|
||||
await _eventEmitter.EmitConflictDetectedAsync(
|
||||
new ConsensusConflictDetectedEvent(
|
||||
EventId: $"evt-{Guid.NewGuid():N}",
|
||||
EventId: $"evt-{_guidProvider.NewGuid():N}",
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Options;
|
||||
@@ -28,19 +29,22 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
private readonly ILogger<PostgresConsensusProjectionStoreProxy> _logger;
|
||||
private readonly VexLensStorageOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public PostgresConsensusProjectionStoreProxy(
|
||||
NpgsqlDataSource dataSource,
|
||||
ILogger<PostgresConsensusProjectionStoreProxy> logger,
|
||||
IConsensusEventEmitter? eventEmitter = null,
|
||||
VexLensStorageOptions? options = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_eventEmitter = eventEmitter;
|
||||
_options = options ?? new VexLensStorageOptions();
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? new SystemGuidProvider();
|
||||
}
|
||||
|
||||
private const string Schema = "vexlens";
|
||||
@@ -108,7 +112,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
activity?.SetTag("vulnerabilityId", result.VulnerabilityId);
|
||||
activity?.SetTag("productKey", result.ProductKey);
|
||||
|
||||
var projectionId = Guid.NewGuid();
|
||||
var projectionId = _guidProvider.NewGuid();
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Check for previous projection to track history
|
||||
@@ -517,7 +521,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var computedEvent = new ConsensusComputedEvent(
|
||||
EventId: $"evt-{Guid.NewGuid():N}",
|
||||
EventId: $"evt-{_guidProvider.NewGuid():N}",
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
@@ -535,7 +539,7 @@ public sealed class PostgresConsensusProjectionStoreProxy : IConsensusProjection
|
||||
if (projection.StatusChanged && previous is not null)
|
||||
{
|
||||
var changedEvent = new ConsensusStatusChangedEvent(
|
||||
EventId: $"evt-{Guid.NewGuid():N}",
|
||||
EventId: $"evt-{_guidProvider.NewGuid():N}",
|
||||
ProjectionId: projection.ProjectionId,
|
||||
VulnerabilityId: projection.VulnerabilityId,
|
||||
ProductKey: projection.ProductKey,
|
||||
|
||||
@@ -65,9 +65,9 @@ public sealed record SourceTrustScoreRequest
|
||||
public required SourceVerificationSummary VerificationSummary { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Time at which to evaluate the score.
|
||||
/// Time at which to evaluate the score. Required for determinism.
|
||||
/// </summary>
|
||||
public DateTimeOffset EvaluationTime { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset EvaluationTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Previous score for trend calculation.
|
||||
|
||||
@@ -9,16 +9,18 @@ public sealed class InMemorySourceTrustScoreCache : ISourceTrustScoreCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
|
||||
private readonly Timer _cleanupTimer;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemorySourceTrustScoreCache()
|
||||
public InMemorySourceTrustScoreCache(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
// Clean up expired entries every 5 minutes
|
||||
_cleanupTimer = new Timer(CleanupExpiredEntries, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
public Task<VexSourceTrustScore?> GetAsync(string sourceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_cache.TryGetValue(sourceId, out var entry) && entry.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
if (_cache.TryGetValue(sourceId, out var entry) && entry.ExpiresAt > _timeProvider.GetUtcNow())
|
||||
{
|
||||
return Task.FromResult<VexSourceTrustScore?>(entry.Score);
|
||||
}
|
||||
@@ -28,7 +30,7 @@ public sealed class InMemorySourceTrustScoreCache : ISourceTrustScoreCache
|
||||
|
||||
public Task SetAsync(string sourceId, VexSourceTrustScore score, TimeSpan duration, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var entry = new CacheEntry(score, DateTimeOffset.UtcNow + duration);
|
||||
var entry = new CacheEntry(score, _timeProvider.GetUtcNow() + duration);
|
||||
_cache[sourceId] = entry;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -41,7 +43,7 @@ public sealed class InMemorySourceTrustScoreCache : ISourceTrustScoreCache
|
||||
|
||||
private void CleanupExpiredEntries(object? state)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var expiredKeys = _cache
|
||||
.Where(kvp => kvp.Value.ExpiresAt <= now)
|
||||
.Select(kvp => kvp.Key)
|
||||
|
||||
@@ -11,13 +11,16 @@ public sealed class ProvenanceChainValidator : IProvenanceChainValidator
|
||||
{
|
||||
private readonly ILogger<ProvenanceChainValidator> _logger;
|
||||
private readonly IIssuerDirectory _issuerDirectory;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ProvenanceChainValidator(
|
||||
ILogger<ProvenanceChainValidator> logger,
|
||||
IIssuerDirectory issuerDirectory)
|
||||
IIssuerDirectory issuerDirectory,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_issuerDirectory = issuerDirectory;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ProvenanceValidationResult> ValidateAsync(
|
||||
@@ -44,7 +47,7 @@ public sealed class ProvenanceChainValidator : IProvenanceChainValidator
|
||||
// Validate chain age
|
||||
if (options.MaxChainAge.HasValue)
|
||||
{
|
||||
var chainAge = DateTimeOffset.UtcNow - chain.Origin.Timestamp;
|
||||
var chainAge = _timeProvider.GetUtcNow() - chain.Origin.Timestamp;
|
||||
if (chainAge > options.MaxChainAge.Value)
|
||||
{
|
||||
issues.Add(new ProvenanceIssue
|
||||
|
||||
@@ -11,6 +11,12 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, IssuerRecord> _issuers = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, string> _fingerprintToIssuer = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemoryIssuerDirectory(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<IssuerRecord?> GetIssuerAsync(
|
||||
string issuerId,
|
||||
@@ -86,7 +92,7 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
IssuerRegistration registration,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var keyRecords = new List<KeyFingerprintRecord>();
|
||||
|
||||
if (registration.InitialKeys != null)
|
||||
@@ -135,7 +141,7 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var updated = current with
|
||||
{
|
||||
Status = IssuerStatus.Revoked,
|
||||
@@ -165,7 +171,7 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
throw new InvalidOperationException($"Issuer '{issuerId}' not found");
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var newKey = new KeyFingerprintRecord(
|
||||
Fingerprint: keyRegistration.Fingerprint,
|
||||
KeyType: keyRegistration.KeyType,
|
||||
@@ -209,7 +215,7 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var revokedKey = keyIndex.k with
|
||||
{
|
||||
Status = KeyFingerprintStatus.Revoked,
|
||||
@@ -284,7 +290,7 @@ public sealed class InMemoryIssuerDirectory : IIssuerDirectory
|
||||
keyStatus = KeyTrustStatus.Revoked;
|
||||
warnings.Add($"Key was revoked: {key.RevocationReason}");
|
||||
}
|
||||
else if (key.ExpiresAt.HasValue && key.ExpiresAt.Value < DateTimeOffset.UtcNow)
|
||||
else if (key.ExpiresAt.HasValue && key.ExpiresAt.Value < _timeProvider.GetUtcNow())
|
||||
{
|
||||
keyStatus = KeyTrustStatus.Expired;
|
||||
warnings.Add($"Key expired on {key.ExpiresAt.Value:O}");
|
||||
|
||||
Reference in New Issue
Block a user