Files
git.stella-ops.org/src/__Libraries/StellaOps.Resolver/VerdictDelta.cs
StellaOps Bot b9f71fc7e9 sprints work
2025-12-24 21:46:08 +02:00

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