using Microsoft.Extensions.Logging; using StellaOps.Graph.Indexer.Ingestion.Sbom; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace StellaOps.Graph.Indexer.Ingestion.Policy; public sealed class PolicyOverlayProcessor { private readonly PolicyOverlayTransformer _transformer; private readonly IGraphDocumentWriter _writer; private readonly IPolicyOverlayMetrics _metrics; private readonly ILogger _logger; public PolicyOverlayProcessor( PolicyOverlayTransformer transformer, IGraphDocumentWriter writer, IPolicyOverlayMetrics metrics, ILogger logger) { _transformer = transformer ?? throw new ArgumentNullException(nameof(transformer)); _writer = writer ?? throw new ArgumentNullException(nameof(writer)); _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task ProcessAsync(PolicyOverlaySnapshot snapshot, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(snapshot); cancellationToken.ThrowIfCancellationRequested(); var stopwatch = Stopwatch.StartNew(); GraphBuildBatch batch; try { batch = _transformer.Transform(snapshot); } catch (Exception ex) { stopwatch.Stop(); _metrics.RecordBatch(snapshot.Source, snapshot.Tenant, 0, 0, stopwatch.Elapsed, success: false); _logger.LogError( ex, "graph-indexer: failed to transform policy overlay {PolicyPackDigest} for tenant {Tenant}", snapshot.Policy?.PolicyPackDigest ?? string.Empty, snapshot.Tenant); throw; } try { cancellationToken.ThrowIfCancellationRequested(); await _writer.WriteAsync(batch, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); _metrics.RecordBatch(snapshot.Source, snapshot.Tenant, batch.Nodes.Length, batch.Edges.Length, stopwatch.Elapsed, success: true); _logger.LogInformation( "graph-indexer: indexed policy overlay {PolicyPackDigest} (effective {EffectiveFrom}) for tenant {Tenant} with {NodeCount} nodes and {EdgeCount} edges in {DurationMs:F2} ms", snapshot.Policy?.PolicyPackDigest ?? string.Empty, (snapshot.Policy?.EffectiveFrom ?? snapshot.CollectedAt).ToUniversalTime(), snapshot.Tenant, batch.Nodes.Length, batch.Edges.Length, stopwatch.Elapsed.TotalMilliseconds); } catch (Exception ex) { stopwatch.Stop(); _metrics.RecordBatch(snapshot.Source, snapshot.Tenant, batch.Nodes.Length, batch.Edges.Length, stopwatch.Elapsed, success: false); _logger.LogError( ex, "graph-indexer: failed to persist policy overlay {PolicyPackDigest} for tenant {Tenant}", snapshot.Policy?.PolicyPackDigest ?? string.Empty, snapshot.Tenant); throw; } } }