//
// SPDX-License-Identifier: AGPL-3.0-or-later
//
namespace StellaOps.VulnExplorer.WebService.Contracts;
using System.Text.Json.Serialization;
///
/// Response containing the evidence subgraph for a finding.
///
public sealed record EvidenceSubgraphResponse
{
///
/// Finding identifier this subgraph explains.
///
public required string FindingId { get; init; }
///
/// Vulnerability identifier (CVE ID or similar).
///
public required string VulnId { get; init; }
///
/// Root node of the evidence graph (typically the artifact).
///
public required EvidenceNode Root { get; init; }
///
/// All edges in the evidence graph.
///
public required IReadOnlyList Edges { get; init; }
///
/// Summary verdict for this finding.
///
public required VerdictSummary Verdict { get; init; }
///
/// Available triage actions for this finding.
///
public required IReadOnlyList AvailableActions { get; init; }
///
/// Optional metadata about the evidence collection.
///
public EvidenceMetadata? Metadata { get; init; }
}
///
/// Node in the evidence graph.
///
public sealed record EvidenceNode
{
///
/// Unique identifier for this node.
///
public required string Id { get; init; }
///
/// Type of evidence this node represents.
///
public required EvidenceNodeType Type { get; init; }
///
/// Human-readable label for display.
///
public required string Label { get; init; }
///
/// Optional longer description.
///
public string? Description { get; init; }
///
/// Type-specific metadata.
///
public IReadOnlyDictionary? Metadata { get; init; }
///
/// Child nodes (for expandable tree view).
///
public IReadOnlyList? Children { get; init; }
///
/// Whether this node is expandable (has or can load children).
///
public bool IsExpandable { get; init; }
///
/// Status indicator for this node (pass/fail/info).
///
public EvidenceNodeStatus Status { get; init; } = EvidenceNodeStatus.Info;
}
///
/// Type of evidence node.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum EvidenceNodeType
{
/// Container image or artifact.
Artifact,
/// Software package (PURL).
Package,
/// Code symbol (function, class, method).
Symbol,
/// Call path from entry point to vulnerable code.
CallPath,
/// VEX claim from vendor or internal team.
VexClaim,
/// Policy rule that affected the verdict.
PolicyRule,
/// External advisory source (NVD, vendor, etc.).
AdvisorySource,
/// Scanner evidence (binary analysis, SBOM, etc.).
ScannerEvidence,
/// Runtime observation (eBPF, traces).
RuntimeObservation,
/// Configuration or environment context.
Configuration,
}
///
/// Status indicator for evidence nodes.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum EvidenceNodeStatus
{
/// Informational, no pass/fail.
Info,
/// Positive indicator (mitigating factor).
Pass,
/// Negative indicator (risk factor).
Fail,
/// Needs review or additional information.
Warning,
/// Unknown or incomplete data.
Unknown,
}
///
/// Edge connecting two evidence nodes.
///
public sealed record EvidenceEdge
{
///
/// Source node identifier.
///
public required string SourceId { get; init; }
///
/// Target node identifier.
///
public required string TargetId { get; init; }
///
/// Type of relationship (contains, calls, claims, references, etc.).
///
public required string Relationship { get; init; }
///
/// Citation linking to source evidence.
///
public required EvidenceCitation Citation { get; init; }
///
/// Whether this edge represents a reachable path.
///
public bool IsReachable { get; init; }
///
/// Strength of the relationship (for visualization).
///
public double? Weight { get; init; }
}
///
/// Citation linking to source evidence.
///
public sealed record EvidenceCitation
{
///
/// Source type (scanner, vex:vendor, advisory:nvd, etc.).
///
public required string Source { get; init; }
///
/// URL to the source evidence (if available).
///
public required string SourceUrl { get; init; }
///
/// When this evidence was observed/collected.
///
public required DateTimeOffset ObservedAt { get; init; }
///
/// Confidence score (0.0-1.0) if applicable.
///
public double? Confidence { get; init; }
///
/// Hash of the evidence (for verification).
///
public string? EvidenceHash { get; init; }
///
/// Whether this citation is verified/signed.
///
public bool IsVerified { get; init; }
}
///
/// Summary verdict for a finding.
///
public sealed record VerdictSummary
{
///
/// Decision outcome (allow, deny, review).
///
public required string Decision { get; init; }
///
/// Human-readable explanation paragraph.
///
public required string Explanation { get; init; }
///
/// Key factors that influenced the decision.
///
public required IReadOnlyList KeyFactors { get; init; }
///
/// Overall confidence score (0.0-1.0).
///
public required double ConfidenceScore { get; init; }
///
/// Policy rule IDs that affected this verdict.
///
public IReadOnlyList? AppliedPolicies { get; init; }
///
/// Timestamp when this verdict was computed.
///
public DateTimeOffset? ComputedAt { get; init; }
}
///
/// Available triage action.
///
public sealed record TriageAction
{
///
/// Unique action identifier.
///
public required string ActionId { get; init; }
///
/// Type of action.
///
public required TriageActionType Type { get; init; }
///
/// Display label.
///
public required string Label { get; init; }
///
/// Optional description of what this action does.
///
public string? Description { get; init; }
///
/// Whether this action requires confirmation dialog.
///
public bool RequiresConfirmation { get; init; }
///
/// Whether this action is currently available.
///
public bool IsEnabled { get; init; } = true;
///
/// Reason if the action is disabled.
///
public string? DisabledReason { get; init; }
///
/// Additional parameters for the action.
///
public IReadOnlyDictionary? Parameters { get; init; }
}
///
/// Type of triage action.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum TriageActionType
{
/// Accept vendor's VEX claim.
AcceptVendorVex,
/// Request additional evidence.
RequestEvidence,
/// Open diff view to previous version.
OpenDiff,
/// Create time-boxed policy exception.
CreateException,
/// Mark as false positive.
MarkFalsePositive,
/// Escalate to security team.
EscalateToSecurityTeam,
/// Apply internal VEX claim.
ApplyInternalVex,
/// Schedule for patching.
SchedulePatch,
/// Suppress finding.
Suppress,
}
///
/// Metadata about evidence collection.
///
public sealed record EvidenceMetadata
{
///
/// When the evidence was collected.
///
public DateTimeOffset CollectedAt { get; init; }
///
/// Number of nodes in the graph.
///
public int NodeCount { get; init; }
///
/// Number of edges in the graph.
///
public int EdgeCount { get; init; }
///
/// Whether the graph is complete or truncated.
///
public bool IsTruncated { get; init; }
///
/// Maximum depth of the tree.
///
public int MaxDepth { get; init; }
///
/// Sources of evidence included.
///
public IReadOnlyList? Sources { get; init; }
}
///
/// Request to execute a triage action.
///
public sealed record ExecuteTriageActionRequest
{
///
/// Finding to apply action to.
///
public required string FindingId { get; init; }
///
/// Action to execute.
///
public required string ActionId { get; init; }
///
/// Optional parameters for the action.
///
public IReadOnlyDictionary? Parameters { get; init; }
///
/// User comment/justification.
///
public string? Comment { get; init; }
}
///
/// Response from triage action execution.
///
public sealed record ExecuteTriageActionResponse
{
///
/// Whether the action succeeded.
///
public required bool Success { get; init; }
///
/// Result message.
///
public required string Message { get; init; }
///
/// Updated verdict after action (if changed).
///
public VerdictSummary? UpdatedVerdict { get; init; }
///
/// Next recommended action (if any).
///
public TriageAction? NextAction { get; init; }
}
///
/// Filters for finding triage.
///
public sealed record TriageFilters
{
///
/// Reachability filter.
///
public ReachabilityFilter Reachability { get; init; } = ReachabilityFilter.Reachable;
///
/// Patch status filter.
///
public PatchStatusFilter PatchStatus { get; init; } = PatchStatusFilter.Unpatched;
///
/// VEX status filter.
///
public VexStatusFilter VexStatus { get; init; } = VexStatusFilter.Unvexed;
///
/// Severity levels to include.
///
public IReadOnlyList Severity { get; init; } = new[] { "critical", "high" };
///
/// Whether to show suppressed findings.
///
public bool ShowSuppressed { get; init; }
}
///
/// Reachability filter options.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ReachabilityFilter
{
/// Show all findings.
All,
/// Show only reachable findings.
Reachable,
/// Show only unreachable findings.
Unreachable,
/// Show findings with unknown reachability.
Unknown,
}
///
/// Patch status filter options.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PatchStatusFilter
{
/// Show all findings.
All,
/// Show only findings with available patches.
Patched,
/// Show only findings without available patches.
Unpatched,
}
///
/// VEX status filter options.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum VexStatusFilter
{
/// Show all findings.
All,
/// Show only findings with VEX claims.
Vexed,
/// Show only findings without VEX claims.
Unvexed,
/// Show findings with conflicting VEX claims.
Conflicting,
}