Files
git.stella-ops.org/src/Signals/StellaOps.Signals/Services/UnknownsIngestionService.cs
StellaOps Bot 505fe7a885
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
update evidence bundle to include new evidence types and implement ProofSpine integration
2025-12-15 09:15:30 +02:00

102 lines
3.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
namespace StellaOps.Signals.Services;
internal sealed class UnknownsIngestionService : IUnknownsIngestionService
{
private readonly IUnknownsRepository repository;
private readonly TimeProvider timeProvider;
private readonly ILogger<UnknownsIngestionService> logger;
public UnknownsIngestionService(IUnknownsRepository repository, TimeProvider timeProvider, ILogger<UnknownsIngestionService> logger)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<UnknownsIngestResponse> IngestAsync(UnknownsIngestRequest request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
if (request.Subject is null)
{
throw new UnknownsValidationException("Subject is required.");
}
if (string.IsNullOrWhiteSpace(request.CallgraphId))
{
throw new UnknownsValidationException("callgraphId is required.");
}
if (request.Unknowns is null || request.Unknowns.Count == 0)
{
throw new UnknownsValidationException("Unknowns list must not be empty.");
}
var subjectKey = request.Subject.ToSubjectKey();
if (string.IsNullOrWhiteSpace(subjectKey))
{
throw new UnknownsValidationException("Subject must include scanId, imageDigest, or component/version.");
}
var now = timeProvider.GetUtcNow();
var normalized = new List<UnknownSymbolDocument>();
foreach (var entry in request.Unknowns)
{
if (entry is null)
{
continue;
}
var hasContent = !(string.IsNullOrWhiteSpace(entry.SymbolId)
&& string.IsNullOrWhiteSpace(entry.CodeId)
&& string.IsNullOrWhiteSpace(entry.Purl)
&& string.IsNullOrWhiteSpace(entry.EdgeFrom)
&& string.IsNullOrWhiteSpace(entry.EdgeTo));
if (!hasContent)
{
continue;
}
normalized.Add(new UnknownSymbolDocument
{
SubjectKey = subjectKey,
CallgraphId = request.CallgraphId,
SymbolId = entry.SymbolId?.Trim(),
CodeId = entry.CodeId?.Trim(),
Purl = entry.Purl?.Trim(),
EdgeFrom = entry.EdgeFrom?.Trim(),
EdgeTo = entry.EdgeTo?.Trim(),
Reason = entry.Reason?.Trim(),
CreatedAt = now,
UpdatedAt = now,
LastAnalyzedAt = now
});
}
if (normalized.Count == 0)
{
throw new UnknownsValidationException("Unknown entries must include at least one symbolId, codeId, purl, or edge.");
}
await repository.UpsertAsync(subjectKey, normalized, cancellationToken).ConfigureAwait(false);
logger.LogInformation("Stored {Count} unknown symbols for subject {SubjectKey}", normalized.Count, subjectKey);
return new UnknownsIngestResponse
{
SubjectKey = subjectKey,
UnknownsCount = normalized.Count
};
}
}