feat: add Attestation Chain and Triage Evidence API clients and models
- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains. - Created models for Attestation Chain, including DSSE envelope structures and verification results. - Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component. - Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence. - Introduced mock implementations for both API clients to facilitate testing and development.
This commit is contained in:
118
src/Signals/StellaOps.Signals/Services/CallGraphSyncService.cs
Normal file
118
src/Signals/StellaOps.Signals/Services/CallGraphSyncService.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Signals.Models;
|
||||
using StellaOps.Signals.Persistence;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes canonical callgraph documents to relational tables.
|
||||
/// </summary>
|
||||
internal sealed class CallGraphSyncService : ICallGraphSyncService
|
||||
{
|
||||
private readonly ICallGraphProjectionRepository _projectionRepository;
|
||||
private readonly ILogger<CallGraphSyncService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public CallGraphSyncService(
|
||||
ICallGraphProjectionRepository projectionRepository,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<CallGraphSyncService> logger)
|
||||
{
|
||||
_projectionRepository = projectionRepository ?? throw new ArgumentNullException(nameof(projectionRepository));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<CallGraphSyncResult> SyncAsync(
|
||||
Guid scanId,
|
||||
string artifactDigest,
|
||||
CallgraphDocument document,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(artifactDigest);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Starting callgraph projection for scan {ScanId}, artifact {ArtifactDigest}, nodes={NodeCount}, edges={EdgeCount}",
|
||||
scanId, artifactDigest, document.Nodes.Count, document.Edges.Count);
|
||||
|
||||
try
|
||||
{
|
||||
// Step 1: Upsert scan record
|
||||
await _projectionRepository.UpsertScanAsync(
|
||||
scanId,
|
||||
artifactDigest,
|
||||
document.GraphHash,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Step 2: Project nodes in stable order
|
||||
var nodesProjected = await _projectionRepository.UpsertNodesAsync(
|
||||
scanId,
|
||||
document.Nodes,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Step 3: Project edges in stable order
|
||||
var edgesProjected = await _projectionRepository.UpsertEdgesAsync(
|
||||
scanId,
|
||||
document.Edges,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Step 4: Project entrypoints in stable order
|
||||
var entrypointsProjected = 0;
|
||||
if (document.Entrypoints is { Count: > 0 })
|
||||
{
|
||||
entrypointsProjected = await _projectionRepository.UpsertEntrypointsAsync(
|
||||
scanId,
|
||||
document.Entrypoints,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Step 5: Mark scan as completed
|
||||
await _projectionRepository.CompleteScanAsync(scanId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Completed callgraph projection for scan {ScanId}: nodes={NodesProjected}, edges={EdgesProjected}, entrypoints={EntrypointsProjected}, duration={DurationMs}ms",
|
||||
scanId, nodesProjected, edgesProjected, entrypointsProjected, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
return new CallGraphSyncResult(
|
||||
ScanId: scanId,
|
||||
NodesProjected: nodesProjected,
|
||||
EdgesProjected: edgesProjected,
|
||||
EntrypointsProjected: entrypointsProjected,
|
||||
WasUpdated: nodesProjected > 0 || edgesProjected > 0,
|
||||
DurationMs: stopwatch.ElapsedMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Failed callgraph projection for scan {ScanId} after {DurationMs}ms: {ErrorMessage}",
|
||||
scanId, stopwatch.ElapsedMilliseconds, ex.Message);
|
||||
|
||||
await _projectionRepository.FailScanAsync(scanId, ex.Message, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteByScanAsync(Guid scanId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Deleting callgraph projection for scan {ScanId}", scanId);
|
||||
|
||||
await _projectionRepository.DeleteScanAsync(scanId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("Deleted callgraph projection for scan {ScanId}", scanId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user