sprints work

This commit is contained in:
master
2026-01-11 11:19:40 +02:00
parent f6ef1ef337
commit 582a41d7a9
72 changed files with 2680 additions and 390 deletions

View File

@@ -11,6 +11,7 @@ using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.BackportProof.Models;
using StellaOps.Concelier.BackportProof.Repositories;
using StellaOps.Determinism;
namespace StellaOps.Concelier.BackportProof.Services;
@@ -22,6 +23,8 @@ public sealed class FixIndexService : IFixIndexService
{
private readonly IFixRuleRepository _repository;
private readonly ILogger<FixIndexService> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
// Active in-memory index
private FixIndexState? _activeIndex;
@@ -32,10 +35,14 @@ public sealed class FixIndexService : IFixIndexService
public FixIndexService(
IFixRuleRepository repository,
ILogger<FixIndexService> logger)
ILogger<FixIndexService> logger,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_repository = repository;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public ValueTask<string?> GetActiveSnapshotIdAsync(CancellationToken ct = default)
@@ -52,7 +59,7 @@ public sealed class FixIndexService : IFixIndexService
{
_logger.LogInformation("Creating fix index snapshot: {Label}", sourceLabel);
var startTime = DateTimeOffset.UtcNow;
var startTime = _timeProvider.GetUtcNow();
// Load all rules from repository
// In a real implementation, this would need pagination for large datasets
@@ -66,7 +73,7 @@ public sealed class FixIndexService : IFixIndexService
var index = BuildIndex(allRules);
// Generate snapshot ID and digest
var snapshotId = $"fix-index-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}";
var snapshotId = $"fix-index-{_timeProvider.GetUtcNow():yyyyMMddHHmmss}-{_guidProvider.NewGuid():N}";
var digest = ComputeIndexDigest(allRules);
var snapshot = new FixIndexSnapshot(
@@ -84,7 +91,7 @@ public sealed class FixIndexService : IFixIndexService
// Store snapshot
_snapshots[snapshotId] = indexState;
var elapsed = DateTimeOffset.UtcNow - startTime;
var elapsed = _timeProvider.GetUtcNow() - startTime;
_logger.LogInformation(
"Created snapshot {SnapshotId} with {Count} rules in {Elapsed}ms",
snapshotId, allRules.Count, elapsed.TotalMilliseconds);

View File

@@ -15,6 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.Determinism.Abstractions/StellaOps.Determinism.Abstractions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Infrastructure.Postgres/StellaOps.Infrastructure.Postgres.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.VersionComparison/StellaOps.VersionComparison.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.DistroIntel/StellaOps.DistroIntel.csproj" />

View File

@@ -20,15 +20,18 @@ public sealed partial class ProvenanceScopeService : IProvenanceScopeService
private readonly IProvenanceScopeStore _store;
private readonly IBackportEvidenceResolver? _evidenceResolver;
private readonly ILogger<ProvenanceScopeService> _logger;
private readonly TimeProvider _timeProvider;
public ProvenanceScopeService(
IProvenanceScopeStore store,
ILogger<ProvenanceScopeService> logger,
IBackportEvidenceResolver? evidenceResolver = null)
IBackportEvidenceResolver? evidenceResolver = null,
TimeProvider? timeProvider = null)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_evidenceResolver = evidenceResolver; // Optional - if not provided, uses advisory data only
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
@@ -89,8 +92,8 @@ public sealed partial class ProvenanceScopeService : IProvenanceScopeService
PatchOrigin = evidence?.PatchOrigin ?? DeterminePatchOrigin(request.Source),
EvidenceRef = null, // Will be linked separately
Confidence = evidence?.Confidence ?? DetermineDefaultConfidence(request.Source),
CreatedAt = existing?.CreatedAt ?? DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
CreatedAt = existing?.CreatedAt ?? _timeProvider.GetUtcNow(),
UpdatedAt = _timeProvider.GetUtcNow()
};
// 5. Upsert scope
@@ -154,8 +157,8 @@ public sealed partial class ProvenanceScopeService : IProvenanceScopeService
PatchOrigin = evidence.PatchOrigin,
EvidenceRef = null,
Confidence = evidence.Confidence,
CreatedAt = existing?.CreatedAt ?? DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
CreatedAt = existing?.CreatedAt ?? _timeProvider.GetUtcNow(),
UpdatedAt = _timeProvider.GetUtcNow()
};
var scopeId = await _store.UpsertAsync(scope, ct).ConfigureAwait(false);

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Determinism;
namespace StellaOps.Concelier.Persistence.Postgres.Conversion;
@@ -15,6 +16,20 @@ public sealed class AdvisoryConverter
WriteIndented = false
};
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
/// <summary>
/// Initializes a new instance of the <see cref="AdvisoryConverter"/> class.
/// </summary>
/// <param name="timeProvider">Time provider for deterministic timestamps.</param>
/// <param name="guidProvider">GUID provider for deterministic ID generation.</param>
public AdvisoryConverter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
/// <summary>
/// Converts an Advisory domain model to PostgreSQL entities.
/// </summary>
@@ -22,8 +37,8 @@ public sealed class AdvisoryConverter
{
ArgumentNullException.ThrowIfNull(advisory);
var advisoryId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
var advisoryId = _guidProvider.NewGuid();
var now = _timeProvider.GetUtcNow();
var primaryVulnId = advisory.Aliases
.FirstOrDefault(a => a.StartsWith("CVE-", StringComparison.OrdinalIgnoreCase))
@@ -62,7 +77,7 @@ public sealed class AdvisoryConverter
aliasEntities.Add(new AdvisoryAliasEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
AliasType = aliasType,
AliasValue = alias,
@@ -78,7 +93,7 @@ public sealed class AdvisoryConverter
{
cvssEntities.Add(new AdvisoryCvssEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
CvssVersion = metric.Version,
VectorString = metric.Vector,
@@ -103,7 +118,7 @@ public sealed class AdvisoryConverter
affectedEntities.Add(new AdvisoryAffectedEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
Ecosystem = ecosystem,
PackageName = pkg.Identifier,
@@ -119,7 +134,7 @@ public sealed class AdvisoryConverter
// References
var referenceEntities = advisory.References.Select(reference => new AdvisoryReferenceEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
RefType = reference.Kind ?? "web",
Url = reference.Url,
@@ -129,7 +144,7 @@ public sealed class AdvisoryConverter
// Credits
var creditEntities = advisory.Credits.Select(credit => new AdvisoryCreditEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
Name = credit.DisplayName,
Contact = credit.Contacts.FirstOrDefault(),
@@ -140,7 +155,7 @@ public sealed class AdvisoryConverter
// Weaknesses
var weaknessEntities = advisory.Cwes.Select(weakness => new AdvisoryWeaknessEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
CweId = weakness.Identifier,
Description = weakness.Name,
@@ -157,7 +172,7 @@ public sealed class AdvisoryConverter
{
kevFlags.Add(new KevFlagEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
AdvisoryId = advisoryId,
CveId = cveId,
VendorProject = null,

View File

@@ -9,6 +9,7 @@ using System.Globalization;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Determinism;
using StellaOps.Infrastructure.Postgres.Repositories;
namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
@@ -19,10 +20,18 @@ namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
public sealed class SyncLedgerRepository : RepositoryBase<ConcelierDataSource>, ISyncLedgerRepository
{
private const string SystemTenantId = "_system";
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public SyncLedgerRepository(ConcelierDataSource dataSource, ILogger<SyncLedgerRepository> logger)
public SyncLedgerRepository(
ConcelierDataSource dataSource,
ILogger<SyncLedgerRepository> logger,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
: base(dataSource, logger)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
#region Ledger Operations
@@ -93,7 +102,7 @@ public sealed class SyncLedgerRepository : RepositoryBase<ConcelierDataSource>,
RETURNING id
""";
var id = entry.Id == Guid.Empty ? Guid.NewGuid() : entry.Id;
var id = entry.Id == Guid.Empty ? _guidProvider.NewGuid() : entry.Id;
await ExecuteAsync(
SystemTenantId,
@@ -106,7 +115,7 @@ public sealed class SyncLedgerRepository : RepositoryBase<ConcelierDataSource>,
AddParameter(cmd, "bundle_hash", entry.BundleHash);
AddParameter(cmd, "items_count", entry.ItemsCount);
AddParameter(cmd, "signed_at", entry.SignedAt);
AddParameter(cmd, "imported_at", entry.ImportedAt == default ? DateTimeOffset.UtcNow : entry.ImportedAt);
AddParameter(cmd, "imported_at", entry.ImportedAt == default ? _timeProvider.GetUtcNow() : entry.ImportedAt);
},
ct).ConfigureAwait(false);
@@ -144,13 +153,13 @@ public sealed class SyncLedgerRepository : RepositoryBase<ConcelierDataSource>,
{
var entry = new SyncLedgerEntity
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
SiteId = siteId,
Cursor = newCursor,
BundleHash = bundleHash,
ItemsCount = itemsCount,
SignedAt = signedAt,
ImportedAt = DateTimeOffset.UtcNow
ImportedAt = _timeProvider.GetUtcNow()
};
await InsertAsync(entry, ct).ConfigureAwait(false);

View File

@@ -18,13 +18,16 @@ public sealed class SitePolicyEnforcementService
{
private readonly ISyncLedgerRepository _repository;
private readonly ILogger<SitePolicyEnforcementService> _logger;
private readonly TimeProvider _timeProvider;
public SitePolicyEnforcementService(
ISyncLedgerRepository repository,
ILogger<SitePolicyEnforcementService> logger)
ILogger<SitePolicyEnforcementService> logger,
TimeProvider? timeProvider = null)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
@@ -301,7 +304,7 @@ public sealed class SitePolicyEnforcementService
WindowHours: windowHours);
}
var windowStart = DateTimeOffset.UtcNow.AddHours(-windowHours);
var windowStart = _timeProvider.GetUtcNow().AddHours(-windowHours);
var recentHistory = history.Where(h => h.ImportedAt >= windowStart).ToList();
return new SiteBudgetInfo(

View File

@@ -29,6 +29,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.EfCore\StellaOps.Infrastructure.EfCore.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />

View File

@@ -19,19 +19,22 @@ public sealed class BackportProofService
private readonly ISourceArtifactRepository _sourceRepo;
private readonly IPatchRepository _patchRepo;
private readonly BinaryFingerprintFactory _fingerprintFactory;
private readonly TimeProvider _timeProvider;
public BackportProofService(
ILogger<BackportProofService> logger,
IDistroAdvisoryRepository advisoryRepo,
ISourceArtifactRepository sourceRepo,
IPatchRepository patchRepo,
BinaryFingerprintFactory fingerprintFactory)
BinaryFingerprintFactory fingerprintFactory,
TimeProvider? timeProvider = null)
{
_logger = logger;
_advisoryRepo = advisoryRepo;
_sourceRepo = sourceRepo;
_patchRepo = patchRepo;
_fingerprintFactory = fingerprintFactory;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
@@ -251,7 +254,7 @@ public sealed class BackportProofService
EvidenceId = $"evidence:binary:{matchResult.Method}:{matchResult.MatchedFingerprintId}",
Type = EvidenceType.BinaryFingerprint,
Source = matchResult.Method.ToString(),
Timestamp = DateTimeOffset.UtcNow,
Timestamp = _timeProvider.GetUtcNow(),
Data = fingerprintData,
DataHash = dataHash
});

View File

@@ -21,13 +21,16 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
{
private readonly ICanonicalAdvisoryService _canonicalService;
private readonly ILogger<SbomAdvisoryMatcher> _logger;
private readonly TimeProvider _timeProvider;
public SbomAdvisoryMatcher(
ICanonicalAdvisoryService canonicalService,
ILogger<SbomAdvisoryMatcher> logger)
ILogger<SbomAdvisoryMatcher> logger,
TimeProvider? timeProvider = null)
{
_canonicalService = canonicalService ?? throw new ArgumentNullException(nameof(canonicalService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
@@ -142,7 +145,7 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
Method = DetermineMatchMethod(purl),
IsReachable = false,
IsDeployed = false,
MatchedAt = DateTimeOffset.UtcNow
MatchedAt = _timeProvider.GetUtcNow()
};
}
@@ -167,6 +170,7 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
var isReachable = reachabilityMap?.TryGetValue(purl, out var reachable) == true && reachable;
var isDeployed = deploymentMap?.TryGetValue(purl, out var deployed) == true && deployed;
var matchMethod = DetermineMatchMethod(purl);
var matchedAt = _timeProvider.GetUtcNow();
return advisories.Select(advisory => new SbomAdvisoryMatch
{
@@ -178,7 +182,7 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
Method = matchMethod,
IsReachable = isReachable,
IsDeployed = isDeployed,
MatchedAt = DateTimeOffset.UtcNow
MatchedAt = matchedAt
}).ToList();
}
catch (Exception ex)

View File

@@ -21,13 +21,16 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
{
private readonly ICanonicalAdvisoryService _canonicalService;
private readonly ILogger<SbomAdvisoryMatcher> _logger;
private readonly TimeProvider _timeProvider;
public SbomAdvisoryMatcher(
ICanonicalAdvisoryService canonicalService,
ILogger<SbomAdvisoryMatcher> logger)
ILogger<SbomAdvisoryMatcher> logger,
TimeProvider? timeProvider = null)
{
_canonicalService = canonicalService ?? throw new ArgumentNullException(nameof(canonicalService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
@@ -142,7 +145,7 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
Method = DetermineMatchMethod(purl),
IsReachable = false,
IsDeployed = false,
MatchedAt = DateTimeOffset.UtcNow
MatchedAt = _timeProvider.GetUtcNow()
};
}
@@ -168,6 +171,8 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
var isDeployed = deploymentMap?.TryGetValue(purl, out var deployed) == true && deployed;
var matchMethod = DetermineMatchMethod(purl);
var matchedAt = _timeProvider.GetUtcNow();
return advisories.Select(advisory => new SbomAdvisoryMatch
{
Id = ComputeDeterministicMatchId(sbomDigest, purl, advisory.Id),
@@ -178,7 +183,7 @@ public sealed class SbomAdvisoryMatcher : ISbomAdvisoryMatcher
Method = matchMethod,
IsReachable = isReachable,
IsDeployed = isDeployed,
MatchedAt = DateTimeOffset.UtcNow
MatchedAt = matchedAt
}).ToList();
}
catch (Exception ex)

View File

@@ -26,19 +26,22 @@ public sealed class SbomRegistryService : ISbomRegistryService
private readonly IInterestScoringService _scoringService;
private readonly IEventStream<SbomLearnedEvent>? _eventStream;
private readonly ILogger<SbomRegistryService> _logger;
private readonly TimeProvider _timeProvider;
public SbomRegistryService(
ISbomRegistryRepository repository,
ISbomAdvisoryMatcher matcher,
IInterestScoringService scoringService,
ILogger<SbomRegistryService> logger,
IEventStream<SbomLearnedEvent>? eventStream = null)
IEventStream<SbomLearnedEvent>? eventStream = null,
TimeProvider? timeProvider = null)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_matcher = matcher ?? throw new ArgumentNullException(nameof(matcher));
_scoringService = scoringService ?? throw new ArgumentNullException(nameof(scoringService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_eventStream = eventStream;
_timeProvider = timeProvider ?? TimeProvider.System;
}
#region Registration
@@ -72,7 +75,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
PrimaryVersion = input.PrimaryVersion,
ComponentCount = input.Purls.Count,
Purls = input.Purls,
RegisteredAt = DateTimeOffset.UtcNow,
RegisteredAt = _timeProvider.GetUtcNow(),
Source = input.Source,
TenantId = input.TenantId
};
@@ -161,7 +164,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
// Step 4: Update registration metadata
await _repository.UpdateAffectedCountAsync(registration.Digest, matches.Count, cancellationToken)
.ConfigureAwait(false);
await _repository.UpdateLastMatchedAsync(registration.Digest, DateTimeOffset.UtcNow, cancellationToken)
await _repository.UpdateLastMatchedAsync(registration.Digest, _timeProvider.GetUtcNow(), cancellationToken)
.ConfigureAwait(false);
// Step 5: Update interest scores for affected canonicals
@@ -210,7 +213,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
Registration = registration with
{
AffectedCount = matches.Count,
LastMatchedAt = DateTimeOffset.UtcNow
LastMatchedAt = _timeProvider.GetUtcNow()
},
Matches = matches,
ScoresUpdated = scoresUpdated,
@@ -270,7 +273,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
await _repository.UpdateAffectedCountAsync(digest, matches.Count, cancellationToken)
.ConfigureAwait(false);
await _repository.UpdateLastMatchedAsync(digest, DateTimeOffset.UtcNow, cancellationToken)
await _repository.UpdateLastMatchedAsync(digest, _timeProvider.GetUtcNow(), cancellationToken)
.ConfigureAwait(false);
sw.Stop();
@@ -289,7 +292,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
Registration = registration with
{
AffectedCount = matches.Count,
LastMatchedAt = DateTimeOffset.UtcNow
LastMatchedAt = _timeProvider.GetUtcNow()
},
Matches = matches,
ScoresUpdated = 0, // Rematch doesn't update scores
@@ -374,7 +377,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
await _repository.UpdateAffectedCountAsync(digest, allMatches.Count, cancellationToken)
.ConfigureAwait(false);
await _repository.UpdateLastMatchedAsync(digest, DateTimeOffset.UtcNow, cancellationToken)
await _repository.UpdateLastMatchedAsync(digest, _timeProvider.GetUtcNow(), cancellationToken)
.ConfigureAwait(false);
// Update interest scores only for newly added matches
@@ -424,7 +427,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
{
ComponentCount = newPurls.Count,
AffectedCount = allMatches.Count,
LastMatchedAt = DateTimeOffset.UtcNow,
LastMatchedAt = _timeProvider.GetUtcNow(),
Purls = newPurls
},
Matches = allMatches,