diff --git a/src/__Libraries/StellaOps.Provcache.Postgres/PostgresEvidenceChunkRepository.cs b/src/__Libraries/StellaOps.Provcache.Postgres/PostgresEvidenceChunkRepository.cs index 3489db0f9..1ac853ec9 100644 --- a/src/__Libraries/StellaOps.Provcache.Postgres/PostgresEvidenceChunkRepository.cs +++ b/src/__Libraries/StellaOps.Provcache.Postgres/PostgresEvidenceChunkRepository.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using StellaOps.Determinism; using StellaOps.Provcache.Entities; namespace StellaOps.Provcache.Postgres; @@ -11,13 +12,19 @@ public sealed class PostgresEvidenceChunkRepository : IEvidenceChunkRepository { private readonly ProvcacheDbContext _context; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; + private readonly IGuidProvider _guidProvider; public PostgresEvidenceChunkRepository( ProvcacheDbContext context, - ILogger logger) + ILogger logger, + TimeProvider? timeProvider = null, + IGuidProvider? guidProvider = null) { _context = context ?? throw new ArgumentNullException(nameof(context)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _timeProvider = timeProvider ?? TimeProvider.System; + _guidProvider = guidProvider ?? SystemGuidProvider.Instance; } /// @@ -131,7 +138,7 @@ public sealed class PostgresEvidenceChunkRepository : IEvidenceChunkRepository TotalChunks = chunks.Count, TotalSize = chunks.Sum(c => (long)c.BlobSize), Chunks = metadata, - GeneratedAt = DateTimeOffset.UtcNow + GeneratedAt = _timeProvider.GetUtcNow() }; } @@ -240,11 +247,11 @@ public sealed class PostgresEvidenceChunkRepository : IEvidenceChunkRepository }; } - private static ProvcacheEvidenceChunkEntity MapToEntity(EvidenceChunk chunk, string proofRoot) + private ProvcacheEvidenceChunkEntity MapToEntity(EvidenceChunk chunk, string proofRoot) { return new ProvcacheEvidenceChunkEntity { - ChunkId = chunk.ChunkId == Guid.Empty ? Guid.NewGuid() : chunk.ChunkId, + ChunkId = chunk.ChunkId == Guid.Empty ? _guidProvider.NewGuid() : chunk.ChunkId, ProofRoot = proofRoot, ChunkIndex = chunk.ChunkIndex, ChunkHash = chunk.ChunkHash, diff --git a/src/__Libraries/StellaOps.Provcache.Postgres/PostgresProvcacheRepository.cs b/src/__Libraries/StellaOps.Provcache.Postgres/PostgresProvcacheRepository.cs index 74e2f37eb..0a8f1579f 100644 --- a/src/__Libraries/StellaOps.Provcache.Postgres/PostgresProvcacheRepository.cs +++ b/src/__Libraries/StellaOps.Provcache.Postgres/PostgresProvcacheRepository.cs @@ -1,6 +1,7 @@ using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using StellaOps.Determinism; using StellaOps.Provcache.Entities; namespace StellaOps.Provcache.Postgres; @@ -12,14 +13,20 @@ public sealed class PostgresProvcacheRepository : IProvcacheRepository { private readonly ProvcacheDbContext _context; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; + private readonly IGuidProvider _guidProvider; private readonly JsonSerializerOptions _jsonOptions; public PostgresProvcacheRepository( ProvcacheDbContext context, - ILogger logger) + ILogger logger, + TimeProvider? timeProvider = null, + IGuidProvider? guidProvider = null) { _context = context ?? throw new ArgumentNullException(nameof(context)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _timeProvider = timeProvider ?? TimeProvider.System; + _guidProvider = guidProvider ?? SystemGuidProvider.Instance; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -193,12 +200,13 @@ public sealed class PostgresProvcacheRepository : IProvcacheRepository /// public async Task IncrementHitCountAsync(string veriKey, CancellationToken cancellationToken = default) { + var now = _timeProvider.GetUtcNow(); await _context.ProvcacheItems .Where(e => e.VeriKey == veriKey) .ExecuteUpdateAsync( setters => setters .SetProperty(e => e.HitCount, e => e.HitCount + 1) - .SetProperty(e => e.LastAccessedAt, DateTimeOffset.UtcNow), + .SetProperty(e => e.LastAccessedAt, now), cancellationToken) .ConfigureAwait(false); } @@ -206,7 +214,7 @@ public sealed class PostgresProvcacheRepository : IProvcacheRepository /// public async Task GetStatisticsAsync(CancellationToken cancellationToken = default) { - var now = DateTimeOffset.UtcNow; + var now = _timeProvider.GetUtcNow(); var hourFromNow = now.AddHours(1); var totalEntries = await _context.ProvcacheItems @@ -266,12 +274,12 @@ public sealed class PostgresProvcacheRepository : IProvcacheRepository { var revocation = new ProvcacheRevocationEntity { - RevocationId = Guid.NewGuid(), + RevocationId = _guidProvider.NewGuid(), RevocationType = type, TargetHash = targetHash, Reason = reason, EntriesAffected = entriesAffected, - CreatedAt = DateTimeOffset.UtcNow + CreatedAt = _timeProvider.GetUtcNow() }; _context.Revocations.Add(revocation); @@ -329,7 +337,7 @@ public sealed class PostgresProvcacheRepository : IProvcacheRepository HitCount = entry.HitCount, CreatedAt = entry.CreatedAt, ExpiresAt = entry.ExpiresAt, - UpdatedAt = DateTimeOffset.UtcNow, + UpdatedAt = _timeProvider.GetUtcNow(), LastAccessedAt = entry.LastAccessedAt }; } diff --git a/src/__Libraries/StellaOps.Provcache.Postgres/StellaOps.Provcache.Postgres.csproj b/src/__Libraries/StellaOps.Provcache.Postgres/StellaOps.Provcache.Postgres.csproj index 691a89690..431ac9bc4 100644 --- a/src/__Libraries/StellaOps.Provcache.Postgres/StellaOps.Provcache.Postgres.csproj +++ b/src/__Libraries/StellaOps.Provcache.Postgres/StellaOps.Provcache.Postgres.csproj @@ -22,6 +22,7 @@ + diff --git a/src/__Libraries/StellaOps.Provcache.Valkey/ValkeyProvcacheStore.cs b/src/__Libraries/StellaOps.Provcache.Valkey/ValkeyProvcacheStore.cs index 7f9e8de2d..925024f7f 100644 --- a/src/__Libraries/StellaOps.Provcache.Valkey/ValkeyProvcacheStore.cs +++ b/src/__Libraries/StellaOps.Provcache.Valkey/ValkeyProvcacheStore.cs @@ -14,6 +14,7 @@ public sealed class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable private readonly IConnectionMultiplexer _connectionMultiplexer; private readonly ProvcacheOptions _options; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; private readonly JsonSerializerOptions _jsonOptions; private readonly SemaphoreSlim _connectionLock = new(1, 1); private IDatabase? _database; @@ -24,11 +25,13 @@ public sealed class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable public ValkeyProvcacheStore( IConnectionMultiplexer connectionMultiplexer, IOptions options, - ILogger logger) + ILogger logger, + TimeProvider? timeProvider = null) { _connectionMultiplexer = connectionMultiplexer ?? throw new ArgumentNullException(nameof(connectionMultiplexer)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _timeProvider = timeProvider ?? TimeProvider.System; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -65,7 +68,7 @@ public sealed class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable // Optionally refresh TTL on read (sliding expiration) if (_options.SlidingExpiration) { - var ttl = entry.ExpiresAt - DateTimeOffset.UtcNow; + var ttl = entry.ExpiresAt - _timeProvider.GetUtcNow(); if (ttl > TimeSpan.Zero) { await db.KeyExpireAsync(redisKey, ttl).ConfigureAwait(false); @@ -169,7 +172,7 @@ public sealed class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable var redisKey = BuildKey(entry.VeriKey); var value = JsonSerializer.Serialize(entry, _jsonOptions); - var ttl = entry.ExpiresAt - DateTimeOffset.UtcNow; + var ttl = entry.ExpiresAt - _timeProvider.GetUtcNow(); if (ttl <= TimeSpan.Zero) { _logger.LogDebug("Skipping expired entry for VeriKey {VeriKey}", entry.VeriKey); @@ -205,13 +208,14 @@ public sealed class ValkeyProvcacheStore : IProvcacheStore, IAsyncDisposable var db = await GetDatabaseAsync().ConfigureAwait(false); var batch = db.CreateBatch(); var tasks = new List(); + var now = _timeProvider.GetUtcNow(); foreach (var entry in entryList) { var redisKey = BuildKey(entry.VeriKey); var value = JsonSerializer.Serialize(entry, _jsonOptions); - var ttl = entry.ExpiresAt - DateTimeOffset.UtcNow; + var ttl = entry.ExpiresAt - now; if (ttl <= TimeSpan.Zero) continue; diff --git a/src/__Libraries/StellaOps.Provcache/Chunking/EvidenceChunker.cs b/src/__Libraries/StellaOps.Provcache/Chunking/EvidenceChunker.cs index d9de757c5..f65596e3d 100644 --- a/src/__Libraries/StellaOps.Provcache/Chunking/EvidenceChunker.cs +++ b/src/__Libraries/StellaOps.Provcache/Chunking/EvidenceChunker.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using StellaOps.Determinism; namespace StellaOps.Provcache; @@ -87,11 +88,16 @@ public sealed class EvidenceChunker : IEvidenceChunker { private readonly ProvcacheOptions _options; private readonly TimeProvider _timeProvider; + private readonly IGuidProvider _guidProvider; - public EvidenceChunker(ProvcacheOptions options, TimeProvider? timeProvider = null) + public EvidenceChunker( + ProvcacheOptions options, + TimeProvider? timeProvider = null, + IGuidProvider? guidProvider = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); _timeProvider = timeProvider ?? TimeProvider.System; + _guidProvider = guidProvider ?? SystemGuidProvider.Instance; } /// @@ -122,7 +128,7 @@ public sealed class EvidenceChunker : IEvidenceChunker chunks.Add(new EvidenceChunk { - ChunkId = Guid.NewGuid(), + ChunkId = _guidProvider.NewGuid(), ProofRoot = string.Empty, // Will be set after computing Merkle root ChunkIndex = chunkIndex, ChunkHash = chunkHash, @@ -171,7 +177,7 @@ public sealed class EvidenceChunker : IEvidenceChunker yield return new EvidenceChunk { - ChunkId = Guid.NewGuid(), + ChunkId = _guidProvider.NewGuid(), ProofRoot = string.Empty, // Caller must compute after all chunks ChunkIndex = chunkIndex, ChunkHash = chunkHash, diff --git a/src/__Libraries/StellaOps.Provcache/Events/FeedEpochAdvancedEvent.cs b/src/__Libraries/StellaOps.Provcache/Events/FeedEpochAdvancedEvent.cs index dc6f792ca..71d2b75c0 100644 --- a/src/__Libraries/StellaOps.Provcache/Events/FeedEpochAdvancedEvent.cs +++ b/src/__Libraries/StellaOps.Provcache/Events/FeedEpochAdvancedEvent.cs @@ -80,6 +80,17 @@ public sealed record FeedEpochAdvancedEvent /// /// Creates a new FeedEpochAdvancedEvent. /// + /// The feed identifier. + /// The previous epoch identifier. + /// The new epoch identifier. + /// When the new epoch became effective. + /// Number of advisories added (for metrics). + /// Number of advisories modified (for metrics). + /// Number of advisories withdrawn (for metrics). + /// Tenant ID if multi-tenant. + /// Correlation ID for tracing. + /// Optional event ID (defaults to new GUID). + /// Optional timestamp (defaults to current UTC time). public static FeedEpochAdvancedEvent Create( string feedId, string previousEpoch, @@ -89,12 +100,14 @@ public sealed record FeedEpochAdvancedEvent int? advisoriesModified = null, int? advisoriesWithdrawn = null, string? tenantId = null, - string? correlationId = null) + string? correlationId = null, + Guid? eventId = null, + DateTimeOffset? timestamp = null) { return new FeedEpochAdvancedEvent { - EventId = Guid.NewGuid(), - Timestamp = DateTimeOffset.UtcNow, + EventId = eventId ?? Guid.NewGuid(), + Timestamp = timestamp ?? DateTimeOffset.UtcNow, FeedId = feedId, PreviousEpoch = previousEpoch, NewEpoch = newEpoch, diff --git a/src/__Libraries/StellaOps.Provcache/Events/SignerRevokedEvent.cs b/src/__Libraries/StellaOps.Provcache/Events/SignerRevokedEvent.cs index 78d4bba5b..fc5bdf4b2 100644 --- a/src/__Libraries/StellaOps.Provcache/Events/SignerRevokedEvent.cs +++ b/src/__Libraries/StellaOps.Provcache/Events/SignerRevokedEvent.cs @@ -71,6 +71,15 @@ public sealed record SignerRevokedEvent /// /// Creates a new SignerRevokedEvent. /// + /// The trust anchor ID that owns the revoked key. + /// The revoked key identifier. + /// Hash of the revoked signer's certificate/public key. + /// When the revocation became effective. + /// Reason for the revocation. + /// Actor who initiated the revocation. + /// Correlation ID for tracing. + /// Optional event ID (defaults to new GUID). + /// Optional timestamp (defaults to current UTC time). public static SignerRevokedEvent Create( Guid anchorId, string keyId, @@ -78,12 +87,14 @@ public sealed record SignerRevokedEvent DateTimeOffset effectiveAt, string? reason = null, string? actor = null, - string? correlationId = null) + string? correlationId = null, + Guid? eventId = null, + DateTimeOffset? timestamp = null) { return new SignerRevokedEvent { - EventId = Guid.NewGuid(), - Timestamp = DateTimeOffset.UtcNow, + EventId = eventId ?? Guid.NewGuid(), + Timestamp = timestamp ?? DateTimeOffset.UtcNow, AnchorId = anchorId, KeyId = keyId, SignerHash = signerHash, diff --git a/src/__Libraries/StellaOps.Provcache/Export/MinimalProofExporter.cs b/src/__Libraries/StellaOps.Provcache/Export/MinimalProofExporter.cs index 9fed0123c..73344bfee 100644 --- a/src/__Libraries/StellaOps.Provcache/Export/MinimalProofExporter.cs +++ b/src/__Libraries/StellaOps.Provcache/Export/MinimalProofExporter.cs @@ -1,6 +1,7 @@ using System.Security.Cryptography; using System.Text.Json; using Microsoft.Extensions.Logging; +using StellaOps.Determinism; using StellaOps.Provenance.Attestation; namespace StellaOps.Provcache; @@ -15,6 +16,7 @@ public sealed class MinimalProofExporter : IMinimalProofExporter private readonly IEvidenceChunkRepository _chunkRepository; private readonly ISigner? _signer; private readonly TimeProvider _timeProvider; + private readonly IGuidProvider _guidProvider; private readonly ILogger _logger; private static readonly JsonSerializerOptions s_jsonOptions = new() @@ -29,12 +31,14 @@ public sealed class MinimalProofExporter : IMinimalProofExporter IEvidenceChunkRepository chunkRepository, ISigner? signer = null, TimeProvider? timeProvider = null, + IGuidProvider? guidProvider = null, ILogger? logger = null) { _provcacheService = provcacheService ?? throw new ArgumentNullException(nameof(provcacheService)); _chunkRepository = chunkRepository ?? throw new ArgumentNullException(nameof(chunkRepository)); _signer = signer; _timeProvider = timeProvider ?? TimeProvider.System; + _guidProvider = guidProvider ?? SystemGuidProvider.Instance; _logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; } @@ -192,7 +196,7 @@ public sealed class MinimalProofExporter : IMinimalProofExporter chunksToStore.Add(new EvidenceChunk { - ChunkId = Guid.NewGuid(), + ChunkId = _guidProvider.NewGuid(), ProofRoot = bundle.Digest.ProofRoot, ChunkIndex = bundleChunk.Index, ChunkHash = bundleChunk.Hash, diff --git a/src/__Libraries/StellaOps.Provcache/LazyFetch/LazyFetchOrchestrator.cs b/src/__Libraries/StellaOps.Provcache/LazyFetch/LazyFetchOrchestrator.cs index 117ce827b..d7b4ac889 100644 --- a/src/__Libraries/StellaOps.Provcache/LazyFetch/LazyFetchOrchestrator.cs +++ b/src/__Libraries/StellaOps.Provcache/LazyFetch/LazyFetchOrchestrator.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Security.Cryptography; using Microsoft.Extensions.Logging; +using StellaOps.Determinism; namespace StellaOps.Provcache; @@ -13,6 +14,7 @@ public sealed class LazyFetchOrchestrator private readonly IEvidenceChunkRepository _repository; private readonly ILogger _logger; private readonly TimeProvider _timeProvider; + private readonly IGuidProvider _guidProvider; /// /// Creates a lazy fetch orchestrator. @@ -20,14 +22,17 @@ public sealed class LazyFetchOrchestrator /// The chunk repository for local storage. /// Logger instance. /// Optional time provider. + /// Optional GUID provider. public LazyFetchOrchestrator( IEvidenceChunkRepository repository, ILogger logger, - TimeProvider? timeProvider = null) + TimeProvider? timeProvider = null, + IGuidProvider? guidProvider = null) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _timeProvider = timeProvider ?? TimeProvider.System; + _guidProvider = guidProvider ?? SystemGuidProvider.Instance; } /// @@ -154,7 +159,7 @@ public sealed class LazyFetchOrchestrator // Convert FetchedChunk to EvidenceChunk for storage var evidenceChunk = new EvidenceChunk { - ChunkId = Guid.NewGuid(), + ChunkId = _guidProvider.NewGuid(), ProofRoot = proofRoot, ChunkIndex = fetchedChunk.Index, ChunkHash = fetchedChunk.Hash, diff --git a/src/__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj b/src/__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj index 39cf43999..b6fc1e769 100644 --- a/src/__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj +++ b/src/__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj @@ -25,6 +25,7 @@ +