126 lines
4.0 KiB
C#
126 lines
4.0 KiB
C#
/**
|
|
* Resolution Verification
|
|
* Sprint: SPRINT_9100_0002_0001 (FinalDigest Implementation)
|
|
* Tasks: DIGEST-9100-011 through DIGEST-9100-014
|
|
*
|
|
* Provides verification of resolution results.
|
|
*/
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
namespace StellaOps.Resolver;
|
|
|
|
/// <summary>
|
|
/// Result of verifying two resolution results.
|
|
/// </summary>
|
|
/// <param name="Match">True if FinalDigests match.</param>
|
|
/// <param name="ExpectedDigest">Expected FinalDigest.</param>
|
|
/// <param name="ActualDigest">Actual FinalDigest.</param>
|
|
/// <param name="Differences">List of differences if not matching.</param>
|
|
public sealed record VerificationResult(
|
|
bool Match,
|
|
string ExpectedDigest,
|
|
string ActualDigest,
|
|
ImmutableArray<string> Differences)
|
|
{
|
|
public static VerificationResult Success(string digest) => new(
|
|
true,
|
|
digest,
|
|
digest,
|
|
ImmutableArray<string>.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface for verifying resolution results.
|
|
/// </summary>
|
|
public interface IResolutionVerifier
|
|
{
|
|
/// <summary>
|
|
/// Verifies that actual matches expected.
|
|
/// </summary>
|
|
VerificationResult Verify(ResolutionResult expected, ResolutionResult actual);
|
|
|
|
/// <summary>
|
|
/// Verifies that actual matches expected digest.
|
|
/// </summary>
|
|
VerificationResult Verify(string expectedDigest, ResolutionResult actual);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default resolution verifier.
|
|
/// </summary>
|
|
public sealed class DefaultResolutionVerifier : IResolutionVerifier
|
|
{
|
|
private readonly IVerdictDeltaDetector _deltaDetector;
|
|
|
|
public DefaultResolutionVerifier(IVerdictDeltaDetector? deltaDetector = null)
|
|
{
|
|
_deltaDetector = deltaDetector ?? new DefaultVerdictDeltaDetector();
|
|
}
|
|
|
|
public VerificationResult Verify(ResolutionResult expected, ResolutionResult actual)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(expected);
|
|
ArgumentNullException.ThrowIfNull(actual);
|
|
|
|
if (expected.FinalDigest == actual.FinalDigest)
|
|
{
|
|
return VerificationResult.Success(expected.FinalDigest);
|
|
}
|
|
|
|
// Drill down to find differences
|
|
var differences = new List<string>();
|
|
|
|
if (expected.GraphDigest != actual.GraphDigest)
|
|
{
|
|
differences.Add($"GraphDigest mismatch: expected {expected.GraphDigest[..16]}..., got {actual.GraphDigest[..16]}...");
|
|
}
|
|
|
|
if (expected.PolicyDigest != actual.PolicyDigest)
|
|
{
|
|
differences.Add($"PolicyDigest mismatch: expected {expected.PolicyDigest[..16]}..., got {actual.PolicyDigest[..16]}...");
|
|
}
|
|
|
|
// Check verdict-level differences
|
|
var delta = _deltaDetector.Detect(expected, actual);
|
|
if (!delta.IsEmpty)
|
|
{
|
|
foreach (var (old, @new) in delta.ChangedVerdicts)
|
|
{
|
|
differences.Add($"Verdict changed for node {old.Node.Value[..16]}...: {old.Status} -> {@new.Status}");
|
|
}
|
|
foreach (var added in delta.AddedVerdicts)
|
|
{
|
|
differences.Add($"Verdict added for node {added.Node.Value[..16]}...: {added.Status}");
|
|
}
|
|
foreach (var removed in delta.RemovedVerdicts)
|
|
{
|
|
differences.Add($"Verdict removed for node {removed.Node.Value[..16]}...: {removed.Status}");
|
|
}
|
|
}
|
|
|
|
return new VerificationResult(
|
|
false,
|
|
expected.FinalDigest,
|
|
actual.FinalDigest,
|
|
differences.ToImmutableArray());
|
|
}
|
|
|
|
public VerificationResult Verify(string expectedDigest, ResolutionResult actual)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(expectedDigest);
|
|
ArgumentNullException.ThrowIfNull(actual);
|
|
|
|
if (expectedDigest == actual.FinalDigest)
|
|
{
|
|
return VerificationResult.Success(expectedDigest);
|
|
}
|
|
|
|
return new VerificationResult(
|
|
false,
|
|
expectedDigest,
|
|
actual.FinalDigest,
|
|
ImmutableArray.Create($"FinalDigest mismatch: expected {expectedDigest[..16]}..., got {actual.FinalDigest[..16]}..."));
|
|
}
|
|
}
|