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

@@ -103,6 +103,7 @@ public sealed class FindingScoringService : IFindingScoringService
private readonly IMemoryCache _cache;
private readonly FindingScoringOptions _options;
private readonly ILogger<FindingScoringService> _logger;
private readonly TimeProvider _timeProvider;
private static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromMinutes(60);
@@ -116,7 +117,8 @@ public sealed class FindingScoringService : IFindingScoringService
IScoreHistoryStore historyStore,
IMemoryCache cache,
IOptions<FindingScoringOptions> options,
ILogger<FindingScoringService> logger)
ILogger<FindingScoringService> logger,
TimeProvider? timeProvider = null)
{
_normalizer = normalizer;
_calculator = calculator;
@@ -126,6 +128,7 @@ public sealed class FindingScoringService : IFindingScoringService
_cache = cache;
_options = options.Value;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
_environment = Environment.GetEnvironmentVariable("STELLAOPS_ENVIRONMENT") ?? "production";
}
@@ -160,7 +163,7 @@ public sealed class FindingScoringService : IFindingScoringService
var input = _normalizer.Aggregate(evidence);
var result = _calculator.Calculate(input, policy);
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var cacheDuration = TimeSpan.FromMinutes(_options.CacheTtlMinutes);
var response = MapToResponse(result, request.IncludeBreakdown, now, cacheDuration);
@@ -288,7 +291,7 @@ public sealed class FindingScoringService : IFindingScoringService
Summary = summary,
Errors = errors.Count > 0 ? errors : null,
PolicyDigest = policy.ComputeDigest(),
CalculatedAt = DateTimeOffset.UtcNow
CalculatedAt = _timeProvider.GetUtcNow()
};
}

View File

@@ -46,6 +46,12 @@ public sealed class InMemoryScoreHistoryStore : IScoreHistoryStore
private readonly ConcurrentDictionary<string, List<ScoreRecord>> _history = new();
private readonly TimeSpan _retentionPeriod = TimeSpan.FromDays(90);
private readonly int _maxEntriesPerFinding = 1000;
private readonly TimeProvider _timeProvider;
public InMemoryScoreHistoryStore(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public void RecordScore(ScoreRecord record)
{
@@ -68,7 +74,7 @@ public sealed class InMemoryScoreHistoryStore : IScoreHistoryStore
entries.Add(record);
// Prune old entries
var cutoff = DateTimeOffset.UtcNow - _retentionPeriod;
var cutoff = _timeProvider.GetUtcNow() - _retentionPeriod;
entries.RemoveAll(e => e.CalculatedAt < cutoff);
// Limit total entries

View File

@@ -12,8 +12,14 @@ public sealed class VexConsensusService
private readonly ConcurrentDictionary<string, VexProjectionRecord> _projections = new();
private readonly ConcurrentDictionary<string, VexIssuerRecord> _issuers = new();
private readonly ConcurrentDictionary<string, List<VexStatementRecord>> _statements = new();
private readonly TimeProvider _timeProvider;
private long _projectionCounter = 0;
public VexConsensusService(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Computes consensus for a vulnerability-product pair.
/// </summary>
@@ -84,7 +90,7 @@ public sealed class VexConsensusService
Contributions: [],
Conflicts: null,
ProjectionId: null,
ComputedAt: DateTimeOffset.UtcNow);
ComputedAt: _timeProvider.GetUtcNow());
return Task.FromResult(defaultResponse);
}
@@ -121,7 +127,7 @@ public sealed class VexConsensusService
Contributions: contributions,
Conflicts: null,
ProjectionId: projectionId,
ComputedAt: DateTimeOffset.UtcNow);
ComputedAt: _timeProvider.GetUtcNow());
return Task.FromResult(response);
}
@@ -163,7 +169,7 @@ public sealed class VexConsensusService
TotalCount: request.Targets.Count,
SuccessCount: results.Count,
FailureCount: failures,
CompletedAt: DateTimeOffset.UtcNow);
CompletedAt: _timeProvider.GetUtcNow());
}
/// <summary>
@@ -299,7 +305,7 @@ public sealed class VexConsensusService
: 0;
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 Task.FromResult(new VexConsensusStatisticsResponse(
@@ -309,7 +315,7 @@ public sealed class VexConsensusService
AverageConfidence: avgConfidence,
ProjectionsWithConflicts: withConflicts,
StatusChangesLast24h: changesLast24h,
ComputedAt: DateTimeOffset.UtcNow));
ComputedAt: _timeProvider.GetUtcNow()));
}
/// <summary>
@@ -367,6 +373,7 @@ public sealed class VexConsensusService
RegisterVexIssuerRequest request,
CancellationToken cancellationToken = default)
{
var now = _timeProvider.GetUtcNow();
var record = new VexIssuerRecord(
IssuerId: request.IssuerId,
Name: request.Name,
@@ -378,14 +385,14 @@ public sealed class VexConsensusService
KeyType: k.KeyType,
Algorithm: k.Algorithm,
Status: "active",
RegisteredAt: DateTimeOffset.UtcNow,
RegisteredAt: now,
ExpiresAt: k.ExpiresAt)).ToList() ?? [],
Metadata: request.Metadata != null ? new VexIssuerMetadata(
Description: request.Metadata.Description,
Uri: request.Metadata.Uri,
Email: request.Metadata.Email,
Tags: request.Metadata.Tags?.ToList()) : null,
RegisteredAt: DateTimeOffset.UtcNow,
RegisteredAt: now,
LastUpdatedAt: null,
RevokedAt: null,
RevocationReason: null);
@@ -425,7 +432,7 @@ public sealed class VexConsensusService
string status, string? justification, double confidence, string outcome, int statementCount)
{
var id = $"proj-{Interlocked.Increment(ref _projectionCounter):D8}";
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var record = new VexProjectionRecord(
ProjectionId: id,

View File

@@ -4,6 +4,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.Findings.Ledger.WebService.Contracts;
namespace StellaOps.Findings.Ledger.WebService.Services;
@@ -74,18 +75,26 @@ public interface IWebhookDeliveryService
public sealed class InMemoryWebhookStore : IWebhookStore
{
private readonly ConcurrentDictionary<Guid, WebhookRegistration> _webhooks = new();
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public InMemoryWebhookStore(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public WebhookRegistration Register(RegisterWebhookRequest request)
{
var registration = new WebhookRegistration
{
Id = Guid.NewGuid(),
Id = _guidProvider.NewGuid(),
Url = request.Url,
Secret = request.Secret,
FindingPatterns = request.FindingPatterns,
MinScoreChange = request.MinScoreChange,
TriggerOnBucketChange = request.TriggerOnBucketChange,
CreatedAt = DateTimeOffset.UtcNow,
CreatedAt = _timeProvider.GetUtcNow(),
IsActive = true
};
@@ -171,6 +180,7 @@ public sealed class WebhookDeliveryService : IWebhookDeliveryService
private readonly IWebhookStore _store;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<WebhookDeliveryService> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
@@ -182,11 +192,13 @@ public sealed class WebhookDeliveryService : IWebhookDeliveryService
public WebhookDeliveryService(
IWebhookStore store,
IHttpClientFactory httpClientFactory,
ILogger<WebhookDeliveryService> logger)
ILogger<WebhookDeliveryService> logger,
TimeProvider? timeProvider = null)
{
_store = store;
_httpClientFactory = httpClientFactory;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task NotifyScoreChangeAsync(
@@ -219,7 +231,7 @@ public sealed class WebhookDeliveryService : IWebhookDeliveryService
ScoreChange = scoreChange,
BucketChanged = bucketChanged,
PolicyDigest = policyDigest,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
var payloadJson = JsonSerializer.Serialize(payload, JsonOptions);
@@ -258,7 +270,7 @@ public sealed class WebhookDeliveryService : IWebhookDeliveryService
}
request.Headers.TryAddWithoutValidation("X-Webhook-Id", webhook.Id.ToString());
request.Headers.TryAddWithoutValidation("X-Webhook-Timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());
request.Headers.TryAddWithoutValidation("X-Webhook-Timestamp", _timeProvider.GetUtcNow().ToUnixTimeSeconds().ToString());
using var response = await client.SendAsync(request, ct).ConfigureAwait(false);