311 lines
9.2 KiB
C#
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; }
|
|
}
|