Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
160 lines
5.0 KiB
C#
160 lines
5.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using StellaOps.Replay.Core;
|
|
|
|
namespace StellaOps.Scanner.Reachability;
|
|
|
|
/// <summary>
|
|
/// Helper that projects reachability artifacts into the replay manifest.
|
|
/// </summary>
|
|
public sealed class ReachabilityReplayWriter
|
|
{
|
|
/// <summary>
|
|
/// Attaches reachability graphs and runtime traces to the supplied replay manifest.
|
|
/// </summary>
|
|
public void AttachEvidence(
|
|
ReplayManifest manifest,
|
|
IEnumerable<ReachabilityReplayGraph>? graphs,
|
|
IEnumerable<ReachabilityReplayTrace>? traces)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(manifest);
|
|
|
|
WriteGraphs(manifest, graphs);
|
|
WriteTraces(manifest, traces);
|
|
}
|
|
|
|
private static void WriteGraphs(ReplayManifest manifest, IEnumerable<ReachabilityReplayGraph>? 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<ReachabilityReplayTrace>? 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Describes a CAS-backed reachability graph emitted by Scanner.
|
|
/// </summary>
|
|
public sealed record ReachabilityReplayGraph(
|
|
string? Kind,
|
|
string? CasUri,
|
|
string? Sha256,
|
|
string? Analyzer,
|
|
string? Version);
|
|
|
|
/// <summary>
|
|
/// Describes a runtime trace artifact emitted by Scanner/Zastava.
|
|
/// </summary>
|
|
public sealed record ReachabilityReplayTrace(
|
|
string? Source,
|
|
string? CasUri,
|
|
string? Sha256,
|
|
DateTimeOffset RecordedAt);
|