using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Replay.Core;
namespace StellaOps.Scanner.Reachability;
///
/// Helper that projects reachability artifacts into the replay manifest.
///
public sealed class ReachabilityReplayWriter
{
///
/// Attaches reachability graphs and runtime traces to the supplied replay manifest.
///
public void AttachEvidence(
ReplayManifest manifest,
IEnumerable? graphs,
IEnumerable? traces)
{
ArgumentNullException.ThrowIfNull(manifest);
WriteGraphs(manifest, graphs);
WriteTraces(manifest, traces);
}
private static void WriteGraphs(ReplayManifest manifest, IEnumerable? graphs)
{
if (graphs is null)
{
return;
}
var sanitized = graphs
.Where(graph => graph is not null)
.Select(graph => NormalizeGraph(graph!))
.Where(graph => graph is not null)
.Select(graph => graph!)
.DistinctBy(graph => (graph.Kind, graph.CasUri, graph.Sha256, graph.Analyzer, graph.Version))
.OrderBy(graph => graph.CasUri, StringComparer.Ordinal)
.ThenBy(graph => graph.Kind, StringComparer.Ordinal)
.ToList();
foreach (var graph in sanitized)
{
manifest.AddReachabilityGraph(new ReplayReachabilityGraphReference
{
Kind = graph.Kind,
CasUri = graph.CasUri,
Sha256 = graph.Sha256,
HashAlgorithm = string.IsNullOrWhiteSpace(graph.Sha256) ? "sha256" : "blake3-256",
Analyzer = graph.Analyzer,
Version = graph.Version
});
}
}
private static void WriteTraces(ReplayManifest manifest, IEnumerable? traces)
{
if (traces is null)
{
return;
}
var normalized = traces
.Where(trace => trace is not null)
.Select(trace => NormalizeTrace(trace!))
.Where(trace => trace is not null)
.Select(trace => trace!)
.ToList();
var collapsed = normalized
.GroupBy(trace => (trace.Source, trace.CasUri, trace.Sha256))
.Select(group => group.OrderBy(t => t.RecordedAt).First())
.OrderBy(trace => trace.RecordedAt)
.ThenBy(trace => trace.CasUri, StringComparer.Ordinal)
.ToList();
foreach (var trace in collapsed)
{
manifest.AddReachabilityTrace(new ReplayReachabilityTraceReference
{
Source = trace.Source,
CasUri = trace.CasUri,
Sha256 = trace.Sha256,
HashAlgorithm = string.IsNullOrWhiteSpace(trace.Sha256) ? "sha256" : "sha256",
RecordedAt = trace.RecordedAt
});
}
}
private static NormalizedGraph? NormalizeGraph(ReachabilityReplayGraph graph)
{
var casUri = Normalize(graph.CasUri);
if (string.IsNullOrEmpty(casUri))
{
return null;
}
return new NormalizedGraph(
Kind: Normalize(graph.Kind) ?? "static",
CasUri: casUri,
Sha256: NormalizeHash(graph.Sha256),
Analyzer: Normalize(graph.Analyzer) ?? string.Empty,
Version: Normalize(graph.Version) ?? string.Empty);
}
private static NormalizedTrace? NormalizeTrace(ReachabilityReplayTrace trace)
{
var casUri = Normalize(trace.CasUri);
if (string.IsNullOrEmpty(casUri))
{
return null;
}
return new NormalizedTrace(
Source: Normalize(trace.Source) ?? string.Empty,
CasUri: casUri,
Sha256: NormalizeHash(trace.Sha256),
RecordedAt: trace.RecordedAt.ToUniversalTime());
}
private static string? Normalize(string? value)
=> string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static string NormalizeHash(string? hash)
=> string.IsNullOrWhiteSpace(hash) ? string.Empty : hash.Trim().ToLowerInvariant();
private sealed record NormalizedGraph(
string Kind,
string CasUri,
string Sha256,
string Analyzer,
string Version);
private sealed record NormalizedTrace(
string Source,
string CasUri,
string Sha256,
DateTimeOffset RecordedAt);
}
///
/// Describes a CAS-backed reachability graph emitted by Scanner.
///
public sealed record ReachabilityReplayGraph(
string? Kind,
string? CasUri,
string? Sha256,
string? Analyzer,
string? Version);
///
/// Describes a runtime trace artifact emitted by Scanner/Zastava.
///
public sealed record ReachabilityReplayTrace(
string? Source,
string? CasUri,
string? Sha256,
DateTimeOffset RecordedAt);