// -----------------------------------------------------------------------------
// 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);
}
}