Files
git.stella-ops.org/src/SbomService/StellaOps.SbomService/Services/IReplayVerificationService.cs

256 lines
7.1 KiB
C#

// -----------------------------------------------------------------------------
// IReplayVerificationService.cs
// Sprint: SPRINT_20251228_007_BE_sbom_lineage_graph_ii (LIN-BE-033)
// Task: Replay verification endpoint
// Description: Interface for verifying replay hashes and detecting drift.
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
namespace StellaOps.SbomService.Services;
/// <summary>
/// Service for verifying replay hashes and detecting drift in security evaluations.
/// </summary>
public interface IReplayVerificationService
{
/// <summary>
/// Verifies a replay hash by re-computing it with provided or current inputs.
/// </summary>
/// <param name="request">Verification request.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Verification result with match status and drift details.</returns>
Task<ReplayVerificationResult> VerifyAsync(
ReplayVerificationRequest request,
CancellationToken ct = default);
/// <summary>
/// Compares two replay hashes and identifies drift between them.
/// </summary>
/// <param name="hashA">First replay hash.</param>
/// <param name="hashB">Second replay hash.</param>
/// <param name="tenantId">Tenant identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Drift analysis between the two evaluations.</returns>
Task<ReplayDriftAnalysis> CompareDriftAsync(
string hashA,
string hashB,
string tenantId,
CancellationToken ct = default);
}
/// <summary>
/// Request for replay verification.
/// </summary>
public sealed record ReplayVerificationRequest
{
/// <summary>
/// The replay hash to verify.
/// </summary>
public required string ReplayHash { get; init; }
/// <summary>
/// Tenant identifier.
/// </summary>
public required string TenantId { get; init; }
/// <summary>
/// Optional: SBOM digest to use for verification.
/// If not provided, will try to lookup from stored hash metadata.
/// </summary>
public string? SbomDigest { get; init; }
/// <summary>
/// Optional: Feeds snapshot digest to use.
/// If not provided, uses current feeds.
/// </summary>
public string? FeedsSnapshotDigest { get; init; }
/// <summary>
/// Optional: Policy version to use.
/// If not provided, uses current policy.
/// </summary>
public string? PolicyVersion { get; init; }
/// <summary>
/// Optional: VEX verdicts digest to use.
/// If not provided, uses current VEX state.
/// </summary>
public string? VexVerdictsDigest { get; init; }
/// <summary>
/// Optional: Timestamp to use for verification.
/// If not provided, uses current time.
/// </summary>
public DateTimeOffset? Timestamp { get; init; }
/// <summary>
/// Whether to freeze time to the original evaluation timestamp.
/// </summary>
public bool FreezeTime { get; init; } = true;
/// <summary>
/// Whether to re-evaluate policy with frozen feeds.
/// </summary>
public bool ReEvaluatePolicy { get; init; } = false;
}
/// <summary>
/// Result of replay verification.
/// </summary>
public sealed record ReplayVerificationResult
{
/// <summary>
/// Whether the replay hash matches.
/// </summary>
public required bool IsMatch { get; init; }
/// <summary>
/// The expected replay hash.
/// </summary>
public required string ExpectedHash { get; init; }
/// <summary>
/// The computed replay hash.
/// </summary>
public required string ComputedHash { get; init; }
/// <summary>
/// Overall verification status.
/// </summary>
public required ReplayVerificationStatus Status { get; init; }
/// <summary>
/// The inputs used for the expected hash (from storage).
/// </summary>
public ReplayHashInputs? ExpectedInputs { get; init; }
/// <summary>
/// The inputs used to compute the verification hash.
/// </summary>
public ReplayHashInputs? ComputedInputs { get; init; }
/// <summary>
/// Field-level differences between expected and computed.
/// </summary>
public ImmutableArray<ReplayFieldDrift> Drifts { get; init; } = ImmutableArray<ReplayFieldDrift>.Empty;
/// <summary>
/// When the verification was performed.
/// </summary>
public DateTimeOffset VerifiedAt { get; init; } = DateTimeOffset.UtcNow;
/// <summary>
/// Optional message with additional context.
/// </summary>
public string? Message { get; init; }
/// <summary>
/// Error message if verification failed.
/// </summary>
public string? Error { get; init; }
}
/// <summary>
/// Verification status enumeration.
/// </summary>
public enum ReplayVerificationStatus
{
/// <summary>
/// Hash matches exactly - evaluation is reproducible.
/// </summary>
Match,
/// <summary>
/// Hash doesn't match - drift detected.
/// </summary>
Drift,
/// <summary>
/// Unable to lookup original inputs.
/// </summary>
InputsNotFound,
/// <summary>
/// Verification failed due to error.
/// </summary>
Error
}
/// <summary>
/// Field-level drift in replay verification.
/// </summary>
public sealed record ReplayFieldDrift
{
/// <summary>
/// Name of the field that drifted.
/// </summary>
public required string FieldName { get; init; }
/// <summary>
/// Expected value (from original evaluation).
/// </summary>
public required string ExpectedValue { get; init; }
/// <summary>
/// Actual/computed value.
/// </summary>
public required string ActualValue { get; init; }
/// <summary>
/// Severity of the drift: "info", "warning", "critical".
/// </summary>
public required string Severity { get; init; }
/// <summary>
/// Human-readable description of the drift impact.
/// </summary>
public string? Description { get; init; }
}
/// <summary>
/// Analysis of drift between two replay evaluations.
/// </summary>
public sealed record ReplayDriftAnalysis
{
/// <summary>
/// First replay hash.
/// </summary>
public required string HashA { get; init; }
/// <summary>
/// Second replay hash.
/// </summary>
public required string HashB { get; init; }
/// <summary>
/// Whether the hashes are identical.
/// </summary>
public required bool IsIdentical { get; init; }
/// <summary>
/// Inputs for first hash.
/// </summary>
public ReplayHashInputs? InputsA { get; init; }
/// <summary>
/// Inputs for second hash.
/// </summary>
public ReplayHashInputs? InputsB { get; init; }
/// <summary>
/// Field-level drifts between A and B.
/// </summary>
public ImmutableArray<ReplayFieldDrift> Drifts { get; init; } = ImmutableArray<ReplayFieldDrift>.Empty;
/// <summary>
/// Summary of drift severity.
/// </summary>
public required string DriftSummary { get; init; }
/// <summary>
/// When the analysis was performed.
/// </summary>
public DateTimeOffset AnalyzedAt { get; init; } = DateTimeOffset.UtcNow;
}