// ----------------------------------------------------------------------------- // AttestingRichGraphWriter.cs // Sprint: SPRINT_3620_0001_0001_reachability_witness_dsse // Description: RichGraphWriter wrapper that produces DSSE attestation alongside graph. // ----------------------------------------------------------------------------- using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace StellaOps.Scanner.Reachability.Attestation; /// /// Result of writing a rich graph with attestation. /// /// Path to the richgraph-v1.json file. /// Path to the meta.json file. /// Content-addressed hash of the graph. /// Number of nodes in the graph. /// Number of edges in the graph. /// Path to the attestation DSSE envelope (if produced). /// Detailed witness publication result (if attestation enabled). public sealed record AttestingRichGraphWriteResult( string GraphPath, string MetaPath, string GraphHash, int NodeCount, int EdgeCount, string? AttestationPath, ReachabilityWitnessPublishResult? WitnessResult); /// /// Writes richgraph-v1 documents with optional DSSE attestation. /// Wraps and integrates with . /// public sealed class AttestingRichGraphWriter { private readonly RichGraphWriter _graphWriter; private readonly IReachabilityWitnessPublisher _witnessPublisher; private readonly ReachabilityWitnessOptions _options; private readonly ILogger _logger; /// /// Creates a new attesting rich graph writer. /// public AttestingRichGraphWriter( RichGraphWriter graphWriter, IReachabilityWitnessPublisher witnessPublisher, IOptions options, ILogger logger) { _graphWriter = graphWriter ?? throw new ArgumentNullException(nameof(graphWriter)); _witnessPublisher = witnessPublisher ?? throw new ArgumentNullException(nameof(witnessPublisher)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// Writes the rich graph and produces attestation if enabled. /// /// The rich graph to write. /// Root output directory. /// Analysis identifier. /// Subject artifact digest for attestation. /// Optional policy hash for attestation. /// Optional source commit for attestation. /// Cancellation token. /// Write result including attestation details. public async Task WriteWithAttestationAsync( RichGraph graph, string outputRoot, string analysisId, string subjectDigest, string? policyHash = null, string? sourceCommit = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(graph); ArgumentException.ThrowIfNullOrWhiteSpace(outputRoot); ArgumentException.ThrowIfNullOrWhiteSpace(analysisId); ArgumentException.ThrowIfNullOrWhiteSpace(subjectDigest); // Step 1: Write the graph using the standard writer var writeResult = await _graphWriter.WriteAsync(graph, outputRoot, analysisId, cancellationToken) .ConfigureAwait(false); _logger.LogDebug( "Wrote rich graph: {GraphPath}, hash={GraphHash}, nodes={NodeCount}, edges={EdgeCount}", writeResult.GraphPath, writeResult.GraphHash, writeResult.NodeCount, writeResult.EdgeCount); // Step 2: Produce attestation if enabled string? attestationPath = null; ReachabilityWitnessPublishResult? witnessResult = null; if (_options.Enabled) { // Read the graph bytes for attestation var graphBytes = await File.ReadAllBytesAsync(writeResult.GraphPath, cancellationToken) .ConfigureAwait(false); // Publish witness attestation witnessResult = await _witnessPublisher.PublishAsync( graph, graphBytes, writeResult.GraphHash, subjectDigest, policyHash, sourceCommit, cancellationToken).ConfigureAwait(false); // Write DSSE envelope to disk alongside the graph if (witnessResult.DsseEnvelopeBytes.Length > 0) { var graphDir = Path.GetDirectoryName(writeResult.GraphPath)!; attestationPath = Path.Combine(graphDir, "richgraph-v1.dsse.json"); await File.WriteAllBytesAsync(attestationPath, witnessResult.DsseEnvelopeBytes, cancellationToken) .ConfigureAwait(false); _logger.LogInformation( "Wrote reachability witness attestation: {AttestationPath}, statementHash={StatementHash}", attestationPath, witnessResult.StatementHash); } } else { _logger.LogDebug("Reachability witness attestation is disabled"); } return new AttestingRichGraphWriteResult( GraphPath: writeResult.GraphPath, MetaPath: writeResult.MetaPath, GraphHash: writeResult.GraphHash, NodeCount: writeResult.NodeCount, EdgeCount: writeResult.EdgeCount, AttestationPath: attestationPath, WitnessResult: witnessResult); } }