172 lines
5.1 KiB
C#
172 lines
5.1 KiB
C#
/**
|
|
* Verdict Delta Detection
|
|
* Sprint: SPRINT_9100_0002_0002 (Per-Node VerdictDigest)
|
|
* Tasks: VDIGEST-9100-006 through VDIGEST-9100-015
|
|
*
|
|
* Provides delta detection between resolution results.
|
|
*/
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
namespace StellaOps.Resolver;
|
|
|
|
/// <summary>
|
|
/// Delta between two resolution results at the verdict level.
|
|
/// </summary>
|
|
/// <param name="ChangedVerdicts">Verdicts where the digest changed (same node, different verdict).</param>
|
|
/// <param name="AddedVerdicts">Verdicts for nodes that are only in the new result.</param>
|
|
/// <param name="RemovedVerdicts">Verdicts for nodes that are only in the old result.</param>
|
|
public sealed record VerdictDelta(
|
|
ImmutableArray<(Verdict Old, Verdict New)> ChangedVerdicts,
|
|
ImmutableArray<Verdict> AddedVerdicts,
|
|
ImmutableArray<Verdict> RemovedVerdicts)
|
|
{
|
|
/// <summary>
|
|
/// Returns true if there are no differences.
|
|
/// </summary>
|
|
public bool IsEmpty => ChangedVerdicts.IsEmpty && AddedVerdicts.IsEmpty && RemovedVerdicts.IsEmpty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface for detecting verdict deltas.
|
|
/// </summary>
|
|
public interface IVerdictDeltaDetector
|
|
{
|
|
/// <summary>
|
|
/// Detects differences between two resolution results.
|
|
/// </summary>
|
|
VerdictDelta Detect(ResolutionResult old, ResolutionResult @new);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default verdict delta detector.
|
|
/// </summary>
|
|
public sealed class DefaultVerdictDeltaDetector : IVerdictDeltaDetector
|
|
{
|
|
public VerdictDelta Detect(ResolutionResult old, ResolutionResult @new)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(old);
|
|
ArgumentNullException.ThrowIfNull(@new);
|
|
|
|
var oldVerdicts = old.Verdicts.ToDictionary(v => v.Node);
|
|
var newVerdicts = @new.Verdicts.ToDictionary(v => v.Node);
|
|
|
|
var changed = new List<(Verdict Old, Verdict New)>();
|
|
var added = new List<Verdict>();
|
|
var removed = new List<Verdict>();
|
|
|
|
// Find changed and removed
|
|
foreach (var (nodeId, oldVerdict) in oldVerdicts)
|
|
{
|
|
if (newVerdicts.TryGetValue(nodeId, out var newVerdict))
|
|
{
|
|
if (oldVerdict.VerdictDigest != newVerdict.VerdictDigest)
|
|
{
|
|
changed.Add((oldVerdict, newVerdict));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
removed.Add(oldVerdict);
|
|
}
|
|
}
|
|
|
|
// Find added
|
|
foreach (var (nodeId, newVerdict) in newVerdicts)
|
|
{
|
|
if (!oldVerdicts.ContainsKey(nodeId))
|
|
{
|
|
added.Add(newVerdict);
|
|
}
|
|
}
|
|
|
|
return new VerdictDelta(
|
|
changed.ToImmutableArray(),
|
|
added.ToImmutableArray(),
|
|
removed.ToImmutableArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Human-readable diff report for verdict changes.
|
|
/// </summary>
|
|
public sealed record VerdictDiffReport(
|
|
ImmutableArray<VerdictDiffEntry> Entries);
|
|
|
|
/// <summary>
|
|
/// Single entry in a verdict diff report.
|
|
/// </summary>
|
|
/// <param name="NodeId">The node that changed.</param>
|
|
/// <param name="ChangeType">Type of change (Changed, Added, Removed).</param>
|
|
/// <param name="OldStatus">Old verdict status (if applicable).</param>
|
|
/// <param name="NewStatus">New verdict status (if applicable).</param>
|
|
/// <param name="OldDigest">Old verdict digest.</param>
|
|
/// <param name="NewDigest">New verdict digest.</param>
|
|
public sealed record VerdictDiffEntry(
|
|
string NodeId,
|
|
string ChangeType,
|
|
string? OldStatus,
|
|
string? NewStatus,
|
|
string? OldDigest,
|
|
string? NewDigest);
|
|
|
|
/// <summary>
|
|
/// Interface for generating verdict diff reports.
|
|
/// </summary>
|
|
public interface IVerdictDiffReporter
|
|
{
|
|
/// <summary>
|
|
/// Generates a diff report from a verdict delta.
|
|
/// </summary>
|
|
VerdictDiffReport GenerateReport(VerdictDelta delta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default verdict diff reporter.
|
|
/// </summary>
|
|
public sealed class DefaultVerdictDiffReporter : IVerdictDiffReporter
|
|
{
|
|
public VerdictDiffReport GenerateReport(VerdictDelta delta)
|
|
{
|
|
var entries = new List<VerdictDiffEntry>();
|
|
|
|
foreach (var (old, @new) in delta.ChangedVerdicts)
|
|
{
|
|
entries.Add(new VerdictDiffEntry(
|
|
old.Node.Value,
|
|
"Changed",
|
|
old.Status.ToString(),
|
|
@new.Status.ToString(),
|
|
old.VerdictDigest,
|
|
@new.VerdictDigest));
|
|
}
|
|
|
|
foreach (var added in delta.AddedVerdicts)
|
|
{
|
|
entries.Add(new VerdictDiffEntry(
|
|
added.Node.Value,
|
|
"Added",
|
|
null,
|
|
added.Status.ToString(),
|
|
null,
|
|
added.VerdictDigest));
|
|
}
|
|
|
|
foreach (var removed in delta.RemovedVerdicts)
|
|
{
|
|
entries.Add(new VerdictDiffEntry(
|
|
removed.Node.Value,
|
|
"Removed",
|
|
removed.Status.ToString(),
|
|
null,
|
|
removed.VerdictDigest,
|
|
null));
|
|
}
|
|
|
|
// Sort by NodeId for determinism
|
|
entries.Sort((a, b) => string.Compare(a.NodeId, b.NodeId, StringComparison.Ordinal));
|
|
|
|
return new VerdictDiffReport(entries.ToImmutableArray());
|
|
}
|
|
}
|