using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Text.Json.Serialization; namespace StellaOps.Scanner.WebService.Contracts; internal static class OrchestratorEventKinds { public const string ScannerReportReady = "scanner.event.report.ready"; public const string ScannerScanCompleted = "scanner.event.scan.completed"; } internal sealed record OrchestratorEvent { [JsonPropertyName("eventId")] [JsonPropertyOrder(0)] public Guid EventId { get; init; } [JsonPropertyName("kind")] [JsonPropertyOrder(1)] public string Kind { get; init; } = string.Empty; [JsonPropertyName("version")] [JsonPropertyOrder(2)] public int Version { get; init; } = 1; [JsonPropertyName("tenant")] [JsonPropertyOrder(3)] public string Tenant { get; init; } = string.Empty; [JsonPropertyName("occurredAt")] [JsonPropertyOrder(4)] public DateTimeOffset OccurredAt { get; init; } [JsonPropertyName("recordedAt")] [JsonPropertyOrder(5)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTimeOffset? RecordedAt { get; init; } [JsonPropertyName("source")] [JsonPropertyOrder(6)] public string Source { get; init; } = string.Empty; [JsonPropertyName("idempotencyKey")] [JsonPropertyOrder(7)] public string IdempotencyKey { get; init; } = string.Empty; [JsonPropertyName("correlationId")] [JsonPropertyOrder(8)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? CorrelationId { get; init; } [JsonPropertyName("traceId")] [JsonPropertyOrder(9)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? TraceId { get; init; } [JsonPropertyName("spanId")] [JsonPropertyOrder(10)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? SpanId { get; init; } [JsonPropertyName("scope")] [JsonPropertyOrder(11)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public OrchestratorEventScope? Scope { get; init; } [JsonPropertyName("payload")] [JsonPropertyOrder(12)] public OrchestratorEventPayload Payload { get; init; } = default!; [JsonPropertyName("attributes")] [JsonPropertyOrder(13)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ImmutableSortedDictionary? Attributes { get; init; } } internal sealed record OrchestratorEventScope { [JsonPropertyName("namespace")] [JsonPropertyOrder(0)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Namespace { get; init; } [JsonPropertyName("repo")] [JsonPropertyOrder(1)] public string Repo { get; init; } = string.Empty; [JsonPropertyName("digest")] [JsonPropertyOrder(2)] public string Digest { get; init; } = string.Empty; [JsonPropertyName("component")] [JsonPropertyOrder(3)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Component { get; init; } [JsonPropertyName("image")] [JsonPropertyOrder(4)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Image { get; init; } } internal abstract record OrchestratorEventPayload; internal sealed record ReportReadyEventPayload : OrchestratorEventPayload { [JsonPropertyName("reportId")] [JsonPropertyOrder(0)] public string ReportId { get; init; } = string.Empty; [JsonPropertyName("scanId")] [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ScanId { get; init; } [JsonPropertyName("imageDigest")] [JsonPropertyOrder(2)] public string ImageDigest { get; init; } = string.Empty; [JsonPropertyName("generatedAt")] [JsonPropertyOrder(3)] public DateTimeOffset GeneratedAt { get; init; } [JsonPropertyName("verdict")] [JsonPropertyOrder(4)] public string Verdict { get; init; } = string.Empty; [JsonPropertyName("summary")] [JsonPropertyOrder(5)] public ReportSummaryDto Summary { get; init; } = new(); [JsonPropertyName("delta")] [JsonPropertyOrder(6)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ReportDeltaPayload? Delta { get; init; } [JsonPropertyName("quietedFindingCount")] [JsonPropertyOrder(7)] public int QuietedFindingCount { get; init; } [JsonPropertyName("policy")] [JsonPropertyOrder(8)] public ReportPolicyDto Policy { get; init; } = new(); [JsonPropertyName("links")] [JsonPropertyOrder(9)] public ReportLinksPayload Links { get; init; } = new(); [JsonPropertyName("dsse")] [JsonPropertyOrder(10)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DsseEnvelopeDto? Dsse { get; init; } [JsonPropertyName("report")] [JsonPropertyOrder(11)] public ReportDocumentDto Report { get; init; } = new(); } internal sealed record ScanCompletedEventPayload : OrchestratorEventPayload { [JsonPropertyName("reportId")] [JsonPropertyOrder(0)] public string ReportId { get; init; } = string.Empty; [JsonPropertyName("scanId")] [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ScanId { get; init; } [JsonPropertyName("imageDigest")] [JsonPropertyOrder(2)] public string ImageDigest { get; init; } = string.Empty; [JsonPropertyName("verdict")] [JsonPropertyOrder(3)] public string Verdict { get; init; } = string.Empty; [JsonPropertyName("summary")] [JsonPropertyOrder(4)] public ReportSummaryDto Summary { get; init; } = new(); [JsonPropertyName("delta")] [JsonPropertyOrder(5)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ReportDeltaPayload? Delta { get; init; } [JsonPropertyName("policy")] [JsonPropertyOrder(6)] public ReportPolicyDto Policy { get; init; } = new(); [JsonPropertyName("findings")] [JsonPropertyOrder(7)] public IReadOnlyList Findings { get; init; } = Array.Empty(); [JsonPropertyName("links")] [JsonPropertyOrder(8)] public ReportLinksPayload Links { get; init; } = new(); [JsonPropertyName("dsse")] [JsonPropertyOrder(9)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DsseEnvelopeDto? Dsse { get; init; } [JsonPropertyName("report")] [JsonPropertyOrder(10)] public ReportDocumentDto Report { get; init; } = new(); } internal sealed record ReportDeltaPayload { [JsonPropertyName("newCritical")] [JsonPropertyOrder(0)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? NewCritical { get; init; } [JsonPropertyName("newHigh")] [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? NewHigh { get; init; } [JsonPropertyName("kev")] [JsonPropertyOrder(2)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList? Kev { get; init; } } internal sealed record ReportLinksPayload { [JsonPropertyName("report")] [JsonPropertyOrder(0)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public LinkTarget? Report { get; init; } [JsonPropertyName("policy")] [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public LinkTarget? Policy { get; init; } [JsonPropertyName("attestation")] [JsonPropertyOrder(2)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public LinkTarget? Attestation { get; init; } } internal sealed record LinkTarget( [property: JsonPropertyName("ui"), JsonPropertyOrder(0), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Ui, [property: JsonPropertyName("api"), JsonPropertyOrder(1), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Api) { public static LinkTarget? Create(string? ui, string? api) { if (string.IsNullOrWhiteSpace(ui) && string.IsNullOrWhiteSpace(api)) { return null; } return new LinkTarget( string.IsNullOrWhiteSpace(ui) ? null : ui, string.IsNullOrWhiteSpace(api) ? null : api); } } internal sealed record FindingSummaryPayload { [JsonPropertyName("id")] [JsonPropertyOrder(0)] public string Id { get; init; } = string.Empty; [JsonPropertyName("severity")] [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Severity { get; init; } [JsonPropertyName("cve")] [JsonPropertyOrder(2)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Cve { get; init; } [JsonPropertyName("purl")] [JsonPropertyOrder(3)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Purl { get; init; } [JsonPropertyName("reachability")] [JsonPropertyOrder(4)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Reachability { get; init; } }