Files
git.stella-ops.org/src/VexLens/StellaOps.VexLens/NoiseGate/INoiseGate.cs
StellaOps Bot 3098e84de4 save progress
2026-01-04 14:54:52 +02:00

311 lines
9.2 KiB
C#

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