warnings fixes, tests fixes, sprints completions

This commit is contained in:
Codex Assistant
2026-01-08 08:38:27 +02:00
parent 75611a505f
commit 0b5d786ddb
125 changed files with 14610 additions and 368 deletions

View File

@@ -20,10 +20,12 @@ public sealed class RvaBuilder
private DateTimeOffset? _expiresAt;
private readonly Dictionary<string, string> _metadata = [];
private readonly ICryptoHash _cryptoHash;
private readonly TimeProvider _timeProvider;
public RvaBuilder(ICryptoHash cryptoHash)
public RvaBuilder(ICryptoHash cryptoHash, TimeProvider timeProvider)
{
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public RvaBuilder WithVerdict(RiskVerdictStatus verdict)
@@ -162,7 +164,7 @@ public sealed class RvaBuilder
if (_snapshotId is null)
throw new InvalidOperationException("Knowledge snapshot ID is required");
var createdAt = DateTimeOffset.UtcNow;
var createdAt = _timeProvider.GetUtcNow();
var attestation = new RiskVerdictAttestation
{

View File

@@ -16,14 +16,17 @@ public sealed class RvaVerifier : IRvaVerifier
private readonly ICryptoSigner? _signer;
private readonly ISnapshotService _snapshotService;
private readonly ILogger<RvaVerifier> _logger;
private readonly TimeProvider _timeProvider;
public RvaVerifier(
ISnapshotService snapshotService,
ILogger<RvaVerifier> logger,
TimeProvider timeProvider,
ICryptoSigner? signer = null)
{
_snapshotService = snapshotService ?? throw new ArgumentNullException(nameof(snapshotService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_signer = signer;
}
@@ -51,7 +54,7 @@ public sealed class RvaVerifier : IRvaVerifier
issues.Add($"Signature verification failed: {sigResult.Error}");
if (!options.ContinueOnSignatureFailure)
{
return RvaVerificationResult.Fail(issues);
return RvaVerificationResult.Fail(issues, _timeProvider);
}
}
}
@@ -61,7 +64,7 @@ public sealed class RvaVerifier : IRvaVerifier
if (attestation is null)
{
issues.Add("Failed to parse RVA payload");
return RvaVerificationResult.Fail(issues);
return RvaVerificationResult.Fail(issues, _timeProvider);
}
// Step 3: Verify content-addressed ID
@@ -69,18 +72,18 @@ public sealed class RvaVerifier : IRvaVerifier
if (!idValid)
{
issues.Add("Attestation ID does not match content");
return RvaVerificationResult.Fail(issues);
return RvaVerificationResult.Fail(issues, _timeProvider);
}
// Step 4: Verify expiration
if (options.CheckExpiration && attestation.ExpiresAt.HasValue)
{
if (attestation.ExpiresAt.Value < DateTimeOffset.UtcNow)
if (attestation.ExpiresAt.Value < _timeProvider.GetUtcNow())
{
issues.Add($"Attestation expired at {attestation.ExpiresAt.Value:o}");
if (!options.AllowExpired)
{
return RvaVerificationResult.Fail(issues);
return RvaVerificationResult.Fail(issues, _timeProvider);
}
}
}
@@ -106,7 +109,7 @@ public sealed class RvaVerifier : IRvaVerifier
Attestation = attestation,
SignerIdentity = signerIdentity,
Issues = issues,
VerifiedAt = DateTimeOffset.UtcNow
VerifiedAt = _timeProvider.GetUtcNow()
};
}
@@ -127,18 +130,18 @@ public sealed class RvaVerifier : IRvaVerifier
if (!idValid)
{
issues.Add("Attestation ID does not match content");
return Task.FromResult(RvaVerificationResult.Fail(issues));
return Task.FromResult(RvaVerificationResult.Fail(issues, _timeProvider));
}
// Verify expiration
if (options.CheckExpiration && attestation.ExpiresAt.HasValue)
{
if (attestation.ExpiresAt.Value < DateTimeOffset.UtcNow)
if (attestation.ExpiresAt.Value < _timeProvider.GetUtcNow())
{
issues.Add($"Attestation expired at {attestation.ExpiresAt.Value:o}");
if (!options.AllowExpired)
{
return Task.FromResult(RvaVerificationResult.Fail(issues));
return Task.FromResult(RvaVerificationResult.Fail(issues, _timeProvider));
}
}
}
@@ -152,7 +155,7 @@ public sealed class RvaVerifier : IRvaVerifier
Attestation = attestation,
SignerIdentity = null,
Issues = issues,
VerifiedAt = DateTimeOffset.UtcNow
VerifiedAt = _timeProvider.GetUtcNow()
});
}
@@ -291,10 +294,10 @@ public sealed record RvaVerificationResult
public RiskVerdictAttestation? Attestation { get; init; }
public string? SignerIdentity { get; init; }
public IReadOnlyList<string> Issues { get; init; } = [];
public DateTimeOffset VerifiedAt { get; init; }
public required DateTimeOffset VerifiedAt { get; init; }
public static RvaVerificationResult Fail(IReadOnlyList<string> issues) =>
new() { IsValid = false, Issues = issues, VerifiedAt = DateTimeOffset.UtcNow };
public static RvaVerificationResult Fail(IReadOnlyList<string> issues, TimeProvider timeProvider) =>
new() { IsValid = false, Issues = issues, VerifiedAt = timeProvider.GetUtcNow() };
}
/// <summary>

View File

@@ -143,13 +143,15 @@ public sealed record ScoreProvenanceChain
public static ScoreProvenanceChain FromVerdictPredicate(
VerdictPredicate predicate,
ProvenanceFindingRef finding,
ProvenanceEvidenceSet evidenceSet)
ProvenanceEvidenceSet evidenceSet,
TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(predicate);
ArgumentNullException.ThrowIfNull(finding);
ArgumentNullException.ThrowIfNull(evidenceSet);
ArgumentNullException.ThrowIfNull(timeProvider);
var scoreNode = ProvenanceScoreNode.FromVerdictEws(predicate.EvidenceWeightedScore, predicate.FindingId);
var scoreNode = ProvenanceScoreNode.FromVerdictEws(predicate.EvidenceWeightedScore, predicate.FindingId, timeProvider);
var verdictRef = ProvenanceVerdictRef.FromVerdictPredicate(predicate);
return new ScoreProvenanceChain(
@@ -157,7 +159,7 @@ public sealed record ScoreProvenanceChain
evidenceSet: evidenceSet,
score: scoreNode,
verdict: verdictRef,
createdAt: DateTimeOffset.UtcNow
createdAt: timeProvider.GetUtcNow()
);
}
}
@@ -533,8 +535,9 @@ public sealed record ProvenanceScoreNode
/// <summary>
/// Creates a ProvenanceScoreNode from a VerdictEvidenceWeightedScore.
/// </summary>
public static ProvenanceScoreNode FromVerdictEws(VerdictEvidenceWeightedScore? ews, string findingId)
public static ProvenanceScoreNode FromVerdictEws(VerdictEvidenceWeightedScore? ews, string findingId, TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
if (ews is null)
{
// No EWS - create a placeholder node
@@ -545,7 +548,7 @@ public sealed record ProvenanceScoreNode
weights: new VerdictEvidenceWeights(0, 0, 0, 0, 0, 0),
policyDigest: "none",
calculatorVersion: "none",
calculatedAt: DateTimeOffset.UtcNow
calculatedAt: timeProvider.GetUtcNow()
);
}
@@ -560,7 +563,7 @@ public sealed record ProvenanceScoreNode
weights: new VerdictEvidenceWeights(0, 0, 0, 0, 0, 0),
policyDigest: ews.PolicyDigest ?? "unknown",
calculatorVersion: "unknown",
calculatedAt: ews.CalculatedAt ?? DateTimeOffset.UtcNow,
calculatedAt: ews.CalculatedAt ?? timeProvider.GetUtcNow(),
appliedFlags: ews.Flags,
guardrails: ews.Guardrails
);

View File

@@ -12,7 +12,9 @@ public static class ExceptionMapper
/// <summary>
/// Maps an ExceptionObject to a full DTO.
/// </summary>
public static ExceptionDto ToDto(ExceptionObject exception)
/// <param name="exception">The exception to map.</param>
/// <param name="referenceTime">The reference time for IsEffective/HasExpired checks.</param>
public static ExceptionDto ToDto(ExceptionObject exception, DateTimeOffset referenceTime)
{
return new ExceptionDto
{
@@ -34,15 +36,17 @@ public static class ExceptionMapper
CompensatingControls = exception.CompensatingControls.ToList(),
Metadata = exception.Metadata,
TicketRef = exception.TicketRef,
IsEffective = exception.IsEffective,
HasExpired = exception.HasExpired
IsEffective = exception.IsEffectiveAt(referenceTime),
HasExpired = exception.HasExpiredAt(referenceTime)
};
}
/// <summary>
/// Maps an ExceptionObject to a summary DTO for list responses.
/// </summary>
public static ExceptionSummaryDto ToSummaryDto(ExceptionObject exception)
/// <param name="exception">The exception to map.</param>
/// <param name="referenceTime">The reference time for IsEffective check.</param>
public static ExceptionSummaryDto ToSummaryDto(ExceptionObject exception, DateTimeOffset referenceTime)
{
return new ExceptionSummaryDto
{
@@ -54,7 +58,7 @@ public static class ExceptionMapper
OwnerId = exception.OwnerId,
ExpiresAt = exception.ExpiresAt,
ReasonCode = ReasonToString(exception.ReasonCode),
IsEffective = exception.IsEffective
IsEffective = exception.IsEffectiveAt(referenceTime)
};
}

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Persistence.Postgres.Models;
using StellaOps.Policy.Persistence.Postgres.Repositories;
@@ -335,6 +336,8 @@ internal static class ViolationEndpoints
HttpContext context,
[FromBody] CreateViolationRequest request,
IViolationEventRepository repository,
TimeProvider timeProvider,
IGuidProvider guidProvider,
CancellationToken cancellationToken)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
@@ -356,7 +359,7 @@ internal static class ViolationEndpoints
var entity = new ViolationEventEntity
{
Id = Guid.NewGuid(),
Id = guidProvider.NewGuid(),
TenantId = tenantId,
PolicyId = request.PolicyId,
RuleId = request.RuleId,
@@ -366,7 +369,7 @@ internal static class ViolationEndpoints
Details = request.Details ?? "{}",
Remediation = request.Remediation,
CorrelationId = request.CorrelationId,
OccurredAt = request.OccurredAt ?? DateTimeOffset.UtcNow
OccurredAt = request.OccurredAt ?? timeProvider.GetUtcNow()
};
try
@@ -389,6 +392,8 @@ internal static class ViolationEndpoints
HttpContext context,
[FromBody] CreateViolationBatchRequest request,
IViolationEventRepository repository,
TimeProvider timeProvider,
IGuidProvider guidProvider,
CancellationToken cancellationToken)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
@@ -408,9 +413,10 @@ internal static class ViolationEndpoints
});
}
var now = timeProvider.GetUtcNow();
var entities = request.Violations.Select(v => new ViolationEventEntity
{
Id = Guid.NewGuid(),
Id = guidProvider.NewGuid(),
TenantId = tenantId,
PolicyId = v.PolicyId,
RuleId = v.RuleId,
@@ -420,7 +426,7 @@ internal static class ViolationEndpoints
Details = v.Details ?? "{}",
Remediation = v.Remediation,
CorrelationId = v.CorrelationId,
OccurredAt = v.OccurredAt ?? DateTimeOffset.UtcNow
OccurredAt = v.OccurredAt ?? now
}).ToList();
try

View File

@@ -185,7 +185,7 @@ public sealed record VexTrustGateResult
/// <summary>
/// Timestamp when decision was made.
/// </summary>
public DateTimeOffset EvaluatedAt { get; init; } = DateTimeOffset.UtcNow;
public required DateTimeOffset EvaluatedAt { get; init; }
/// <summary>
/// Additional details for audit.
@@ -400,7 +400,7 @@ public sealed class VexTrustGate : IVexTrustGate
};
}
private static VexTrustGateResult CreateAllowResult(
private VexTrustGateResult CreateAllowResult(
string gateId,
string reason,
VexTrustStatus? trustStatus)
@@ -415,7 +415,7 @@ public sealed class VexTrustGate : IVexTrustGate
? ComputeTier(trustStatus.TrustScore)
: null,
IssuerId = trustStatus?.IssuerId,
EvaluatedAt = DateTimeOffset.UtcNow
EvaluatedAt = _timeProvider.GetUtcNow()
};
}

View File

@@ -6,12 +6,18 @@ namespace StellaOps.Policy.Engine.Services;
internal sealed class InMemoryPolicyPackRepository : IPolicyPackRepository
{
private readonly ConcurrentDictionary<string, PolicyPackRecord> packs = new(StringComparer.OrdinalIgnoreCase);
private readonly TimeProvider _timeProvider;
public InMemoryPolicyPackRepository(TimeProvider timeProvider)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public Task<PolicyPackRecord> CreateAsync(string packId, string? displayName, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(packId);
var created = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, displayName, DateTimeOffset.UtcNow));
var created = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, displayName, _timeProvider.GetUtcNow()));
return Task.FromResult(created);
}
@@ -25,15 +31,16 @@ internal sealed class InMemoryPolicyPackRepository : IPolicyPackRepository
public Task<PolicyRevisionRecord> UpsertRevisionAsync(string packId, int version, bool requiresTwoPersonApproval, PolicyRevisionStatus initialStatus, CancellationToken cancellationToken)
{
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, DateTimeOffset.UtcNow));
var now = _timeProvider.GetUtcNow();
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, now));
int revisionVersion = version > 0 ? version : pack.GetNextVersion();
var revision = pack.GetOrAddRevision(
revisionVersion,
v => new PolicyRevisionRecord(v, requiresTwoPersonApproval, initialStatus, DateTimeOffset.UtcNow));
v => new PolicyRevisionRecord(v, requiresTwoPersonApproval, initialStatus, now));
if (revision.Status != initialStatus)
{
revision.SetStatus(initialStatus, DateTimeOffset.UtcNow);
revision.SetStatus(initialStatus, now);
}
return Task.FromResult(revision);
@@ -95,9 +102,10 @@ internal sealed class InMemoryPolicyPackRepository : IPolicyPackRepository
{
ArgumentNullException.ThrowIfNull(bundle);
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, DateTimeOffset.UtcNow));
var now = _timeProvider.GetUtcNow();
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, now));
var revision = pack.GetOrAddRevision(version > 0 ? version : pack.GetNextVersion(),
v => new PolicyRevisionRecord(v, requiresTwoPerson: false, status: PolicyRevisionStatus.Draft, DateTimeOffset.UtcNow));
v => new PolicyRevisionRecord(v, requiresTwoPerson: false, status: PolicyRevisionStatus.Draft, now));
revision.SetBundle(bundle);
return Task.FromResult(bundle);

View File

@@ -5,6 +5,7 @@
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using StellaOps.Determinism.Abstractions;
using StellaOps.SbomService.Repositories;
namespace StellaOps.Policy.Engine.Services;
@@ -94,13 +95,19 @@ public sealed class VerdictLinkService : IVerdictLinkService
{
private readonly ISbomVerdictLinkRepository _repository;
private readonly ILogger<VerdictLinkService> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public VerdictLinkService(
ISbomVerdictLinkRepository repository,
ILogger<VerdictLinkService> logger)
ILogger<VerdictLinkService> logger,
TimeProvider timeProvider,
IGuidProvider guidProvider)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_guidProvider = guidProvider ?? throw new ArgumentNullException(nameof(guidProvider));
}
/// <inheritdoc/>
@@ -114,14 +121,14 @@ public sealed class VerdictLinkService : IVerdictLinkService
return;
}
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var links = new List<SbomVerdictLink>();
foreach (var verdict in request.Verdicts)
{
var link = new SbomVerdictLink
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
SbomVersionId = request.SbomVersionId,
Cve = verdict.Cve,
ConsensusProjectionId = verdict.ConsensusProjectionId,

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Text.RegularExpressions;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Persistence.Postgres.Models;
using StellaOps.Policy.Persistence.Postgres.Repositories;
@@ -10,13 +11,15 @@ namespace StellaOps.Policy.Engine.Storage.InMemory;
/// In-memory implementation of IExceptionRepository for offline/test runs.
/// Provides minimal semantics needed for lifecycle processing.
/// </summary>
public sealed class InMemoryExceptionRepository : IExceptionRepository
public sealed class InMemoryExceptionRepository(TimeProvider timeProvider, IGuidProvider guidProvider) : IExceptionRepository
{
private readonly TimeProvider _timeProvider = timeProvider;
private readonly IGuidProvider _guidProvider = guidProvider;
private readonly ConcurrentDictionary<(string Tenant, Guid Id), ExceptionEntity> _exceptions = new();
public Task<ExceptionEntity> CreateAsync(ExceptionEntity exception, CancellationToken cancellationToken = default)
{
var id = exception.Id == Guid.Empty ? Guid.NewGuid() : exception.Id;
var id = exception.Id == Guid.Empty ? _guidProvider.NewGuid() : exception.Id;
var stored = Copy(exception, id);
_exceptions[(Normalize(exception.TenantId), id)] = stored;
return Task.FromResult(stored);
@@ -123,7 +126,7 @@ public sealed class InMemoryExceptionRepository : IExceptionRepository
_exceptions[key] = Copy(
existing,
statusOverride: ExceptionStatus.Revoked,
revokedAtOverride: DateTimeOffset.UtcNow,
revokedAtOverride: _timeProvider.GetUtcNow(),
revokedByOverride: revokedBy);
return Task.FromResult(true);
}
@@ -133,7 +136,7 @@ public sealed class InMemoryExceptionRepository : IExceptionRepository
public Task<int> ExpireAsync(string tenantId, CancellationToken cancellationToken = default)
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var normalizedTenant = Normalize(tenantId);
var expired = 0;