sprints work
This commit is contained in:
171
src/__Libraries/StellaOps.Resolver/VerdictDelta.cs
Normal file
171
src/__Libraries/StellaOps.Resolver/VerdictDelta.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user