/** * 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; /// /// Result of verifying two resolution results. /// /// True if FinalDigests match. /// Expected FinalDigest. /// Actual FinalDigest. /// List of differences if not matching. public sealed record VerificationResult( bool Match, string ExpectedDigest, string ActualDigest, ImmutableArray Differences) { public static VerificationResult Success(string digest) => new( true, digest, digest, ImmutableArray.Empty); } /// /// Interface for verifying resolution results. /// public interface IResolutionVerifier { /// /// Verifies that actual matches expected. /// VerificationResult Verify(ResolutionResult expected, ResolutionResult actual); /// /// Verifies that actual matches expected digest. /// VerificationResult Verify(string expectedDigest, ResolutionResult actual); } /// /// Default resolution verifier. /// 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(); 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]}...")); } }