// Licensed to StellaOps under the AGPL-3.0-or-later license.
using System.Collections.Immutable;
using StellaOps.ReachGraph.Deduplication;
using StellaOps.ReachGraph.Schema;
using StellaOps.VexLens.Delta;
using StellaOps.VexLens.Models;
namespace StellaOps.VexLens.NoiseGate;
///
/// Central interface for noise-gating operations on vulnerability graphs.
///
///
/// The noise gate provides three core capabilities:
///
/// - Edge deduplication: Collapses semantically equivalent edges from multiple sources
/// - Verdict resolution: Applies stability damping to prevent flip-flopping
/// - Delta reporting: Computes meaningful changes between snapshots
///
///
public interface INoiseGate
{
///
/// Deduplicates edges based on semantic equivalence.
///
/// The edges to deduplicate.
/// Cancellation token.
/// Deduplicated edges with merged provenance.
Task> DedupeEdgesAsync(
IReadOnlyList edges,
CancellationToken cancellationToken = default);
///
/// Resolves a verdict by applying stability damping.
///
/// The verdict resolution request.
/// Cancellation token.
/// The resolved verdict with damping decision.
Task ResolveVerdictAsync(
VerdictResolutionRequest request,
CancellationToken cancellationToken = default);
///
/// Applies noise-gating to a graph snapshot.
///
/// The gating request.
/// Cancellation token.
/// The gated graph snapshot.
Task GateAsync(
NoiseGateRequest request,
CancellationToken cancellationToken = default);
///
/// Computes a delta report between two snapshots.
///
/// The previous snapshot.
/// The current snapshot.
/// Optional report options.
/// Cancellation token.
/// The delta report.
Task DiffAsync(
GatedGraphSnapshot fromSnapshot,
GatedGraphSnapshot toSnapshot,
DeltaReportOptions? options = null,
CancellationToken cancellationToken = default);
}
///
/// Request to resolve a verdict with stability damping.
///
public sealed record VerdictResolutionRequest
{
///
/// Gets the unique key for this verdict (e.g., "artifact:cve").
///
public required string Key { get; init; }
///
/// Gets the vulnerability ID.
///
public required string VulnerabilityId { get; init; }
///
/// Gets the product key (PURL or other identifier).
///
public required string ProductKey { get; init; }
///
/// Gets the proposed VEX status.
///
public required VexStatus ProposedStatus { get; init; }
///
/// Gets the proposed confidence score.
///
public required double ProposedConfidence { get; init; }
///
/// Gets the rationale class for the verdict.
///
public string? RationaleClass { get; init; }
///
/// Gets the justification for the verdict.
///
public VexJustification? Justification { get; init; }
///
/// Gets the contributing sources.
///
public IReadOnlyList? ContributingSources { get; init; }
///
/// Gets the tenant ID for multi-tenant deployments.
///
public string? TenantId { get; init; }
}
///
/// Result of verdict resolution with damping decision.
///
public sealed record ResolvedVerdict
{
///
/// Gets the vulnerability ID.
///
public required string VulnerabilityId { get; init; }
///
/// Gets the product key.
///
public required string ProductKey { get; init; }
///
/// Gets the final VEX status.
///
public required VexStatus Status { get; init; }
///
/// Gets the final confidence score.
///
public required double Confidence { get; init; }
///
/// Gets the rationale class.
///
public string? RationaleClass { get; init; }
///
/// Gets the justification.
///
public VexJustification? Justification { get; init; }
///
/// Gets whether the verdict was surfaced (not damped).
///
public required bool WasSurfaced { get; init; }
///
/// Gets the damping reason if applicable.
///
public string? DampingReason { get; init; }
///
/// Gets the previous status if available.
///
public VexStatus? PreviousStatus { get; init; }
///
/// Gets the previous confidence if available.
///
public double? PreviousConfidence { get; init; }
///
/// Gets the contributing sources.
///
public ImmutableArray ContributingSources { get; init; } = [];
///
/// Gets the timestamp of resolution.
///
public required DateTimeOffset ResolvedAt { get; init; }
}
///
/// Request to apply noise-gating to a graph.
///
public sealed record NoiseGateRequest
{
///
/// Gets the reachability graph to gate.
///
public required ReachGraphMinimal Graph { get; init; }
///
/// Gets the verdicts to include.
///
public required IReadOnlyList Verdicts { get; init; }
///
/// Gets the snapshot ID.
///
public required string SnapshotId { get; init; }
///
/// Gets the tenant ID.
///
public string? TenantId { get; init; }
///
/// Gets whether to compute a previous snapshot diff.
///
public bool ComputeDiff { get; init; } = true;
///
/// Gets the previous snapshot ID for diff computation.
///
public string? PreviousSnapshotId { get; init; }
}
///
/// A gated graph snapshot with deduplicated edges and resolved verdicts.
///
public sealed record GatedGraphSnapshot
{
///
/// Gets the unique snapshot identifier.
///
public required string SnapshotId { get; init; }
///
/// Gets the snapshot digest for integrity verification.
///
public required string Digest { get; init; }
///
/// Gets the artifact this snapshot describes.
///
public required ReachGraphArtifact Artifact { get; init; }
///
/// Gets the deduplicated edges.
///
public required ImmutableArray Edges { get; init; }
///
/// Gets the resolved verdicts.
///
public required ImmutableArray Verdicts { get; init; }
///
/// Gets the verdicts that were damped (not surfaced).
///
public ImmutableArray DampedVerdicts { get; init; } = [];
///
/// Gets the timestamp when this snapshot was created.
///
public required DateTimeOffset CreatedAt { get; init; }
///
/// Gets the gating statistics.
///
public required GatingStatistics Statistics { get; init; }
}
///
/// Statistics from a noise-gating operation.
///
public sealed record GatingStatistics
{
///
/// Gets the original edge count before deduplication.
///
public required int OriginalEdgeCount { get; init; }
///
/// Gets the edge count after deduplication.
///
public required int DeduplicatedEdgeCount { get; init; }
///
/// Gets the edge reduction percentage.
///
public double EdgeReductionPercent =>
OriginalEdgeCount > 0
? (1.0 - (double)DeduplicatedEdgeCount / OriginalEdgeCount) * 100.0
: 0.0;
///
/// Gets the total verdict count.
///
public required int TotalVerdictCount { get; init; }
///
/// Gets the surfaced verdict count.
///
public required int SurfacedVerdictCount { get; init; }
///
/// Gets the damped verdict count.
///
public required int DampedVerdictCount { get; init; }
///
/// Gets the duration of the gating operation.
///
public required TimeSpan Duration { get; init; }
}