up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
503
src/Cli/StellaOps.Cli/Services/Models/AdvisoryLinksetModels.cs
Normal file
503
src/Cli/StellaOps.Cli/Services/Models/AdvisoryLinksetModels.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-LNM-22-001: Advisory linkset models for obs get/linkset show/export commands
|
||||
|
||||
/// <summary>
|
||||
/// Extended advisory linkset query with additional filters for CLI-LNM-22-001.
|
||||
/// </summary>
|
||||
internal sealed record AdvisoryLinksetQuery(
|
||||
string Tenant,
|
||||
IReadOnlyList<string> ObservationIds,
|
||||
IReadOnlyList<string> Aliases,
|
||||
IReadOnlyList<string> Purls,
|
||||
IReadOnlyList<string> Cpes,
|
||||
IReadOnlyList<string> Sources,
|
||||
string? Severity,
|
||||
bool? KevOnly,
|
||||
bool? HasFix,
|
||||
int? Limit,
|
||||
string? Cursor)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates from a basic AdvisoryObservationsQuery.
|
||||
/// </summary>
|
||||
public static AdvisoryLinksetQuery FromBasicQuery(AdvisoryObservationsQuery query)
|
||||
{
|
||||
return new AdvisoryLinksetQuery(
|
||||
query.Tenant,
|
||||
query.ObservationIds,
|
||||
query.Aliases,
|
||||
query.Purls,
|
||||
query.Cpes,
|
||||
Sources: Array.Empty<string>(),
|
||||
Severity: null,
|
||||
KevOnly: null,
|
||||
HasFix: null,
|
||||
query.Limit,
|
||||
query.Cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extended advisory linkset response with conflict information.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetResponse
|
||||
{
|
||||
[JsonPropertyName("observations")]
|
||||
public IReadOnlyList<AdvisoryLinksetObservation> Observations { get; init; } =
|
||||
Array.Empty<AdvisoryLinksetObservation>();
|
||||
|
||||
[JsonPropertyName("linkset")]
|
||||
public AdvisoryLinksetAggregate Linkset { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("conflicts")]
|
||||
public IReadOnlyList<AdvisoryLinksetConflict> Conflicts { get; init; } =
|
||||
Array.Empty<AdvisoryLinksetConflict>();
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public int? TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extended advisory observation with severity, KEV, and fix information.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetObservation
|
||||
{
|
||||
[JsonPropertyName("observationId")]
|
||||
public string ObservationId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public AdvisoryObservationSource Source { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("upstream")]
|
||||
public AdvisoryObservationUpstream Upstream { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("linkset")]
|
||||
public AdvisoryObservationLinkset Linkset { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public AdvisoryLinksetSeverity? Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("kev")]
|
||||
public AdvisoryLinksetKev? Kev { get; init; }
|
||||
|
||||
[JsonPropertyName("fix")]
|
||||
public AdvisoryLinksetFix? Fix { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset? UpdatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advisory severity information.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetSeverity
|
||||
{
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("cvssV3")]
|
||||
public AdvisoryLinksetCvss? CvssV3 { get; init; }
|
||||
|
||||
[JsonPropertyName("cvssV2")]
|
||||
public AdvisoryLinksetCvss? CvssV2 { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CVSS score details.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetCvss
|
||||
{
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; init; }
|
||||
|
||||
[JsonPropertyName("vector")]
|
||||
public string? Vector { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string? Severity { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KEV (Known Exploited Vulnerabilities) information.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetKev
|
||||
{
|
||||
[JsonPropertyName("listed")]
|
||||
public bool Listed { get; init; }
|
||||
|
||||
[JsonPropertyName("addedDate")]
|
||||
public DateTimeOffset? AddedDate { get; init; }
|
||||
|
||||
[JsonPropertyName("dueDate")]
|
||||
public DateTimeOffset? DueDate { get; init; }
|
||||
|
||||
[JsonPropertyName("knownRansomwareCampaignUse")]
|
||||
public bool? KnownRansomwareCampaignUse { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix availability information.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetFix
|
||||
{
|
||||
[JsonPropertyName("available")]
|
||||
public bool Available { get; init; }
|
||||
|
||||
[JsonPropertyName("versions")]
|
||||
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
[JsonPropertyName("advisoryLinks")]
|
||||
public IReadOnlyList<string> AdvisoryLinks { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated linkset with conflict summary.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetAggregate
|
||||
{
|
||||
[JsonPropertyName("aliases")]
|
||||
public IReadOnlyList<string> Aliases { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("cpes")]
|
||||
public IReadOnlyList<string> Cpes { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("references")]
|
||||
public IReadOnlyList<AdvisoryObservationReference> References { get; init; } =
|
||||
Array.Empty<AdvisoryObservationReference>();
|
||||
|
||||
[JsonPropertyName("sourceCoverage")]
|
||||
public AdvisorySourceCoverageSummary? SourceCoverage { get; init; }
|
||||
|
||||
[JsonPropertyName("conflictSummary")]
|
||||
public AdvisoryConflictSummary? ConflictSummary { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source coverage summary across observations.
|
||||
/// </summary>
|
||||
internal sealed class AdvisorySourceCoverageSummary
|
||||
{
|
||||
[JsonPropertyName("totalSources")]
|
||||
public int TotalSources { get; init; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public IReadOnlyList<string> Sources { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("coveragePercent")]
|
||||
public double CoveragePercent { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conflict summary for the linkset.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryConflictSummary
|
||||
{
|
||||
[JsonPropertyName("hasConflicts")]
|
||||
public bool HasConflicts { get; init; }
|
||||
|
||||
[JsonPropertyName("totalConflicts")]
|
||||
public int TotalConflicts { get; init; }
|
||||
|
||||
[JsonPropertyName("severityConflicts")]
|
||||
public int SeverityConflicts { get; init; }
|
||||
|
||||
[JsonPropertyName("kevConflicts")]
|
||||
public int KevConflicts { get; init; }
|
||||
|
||||
[JsonPropertyName("fixConflicts")]
|
||||
public int FixConflicts { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed conflict information between observations.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryLinksetConflict
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("field")]
|
||||
public string Field { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public IReadOnlyList<AdvisoryConflictSource> Sources { get; init; } =
|
||||
Array.Empty<AdvisoryConflictSource>();
|
||||
|
||||
[JsonPropertyName("resolution")]
|
||||
public string? Resolution { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source contribution to a conflict.
|
||||
/// </summary>
|
||||
internal sealed class AdvisoryConflictSource
|
||||
{
|
||||
[JsonPropertyName("observationId")]
|
||||
public string ObservationId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV (Open Source Vulnerability) format output.
|
||||
/// Per CLI-LNM-22-001, supports JSON/OSV output format.
|
||||
/// </summary>
|
||||
internal sealed class OsvVulnerability
|
||||
{
|
||||
[JsonPropertyName("schema_version")]
|
||||
public string SchemaVersion { get; init; } = "1.6.0";
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("modified")]
|
||||
public string Modified { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("published")]
|
||||
public string? Published { get; init; }
|
||||
|
||||
[JsonPropertyName("withdrawn")]
|
||||
public string? Withdrawn { get; init; }
|
||||
|
||||
[JsonPropertyName("aliases")]
|
||||
public IReadOnlyList<string> Aliases { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("related")]
|
||||
public IReadOnlyList<string> Related { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public string? Summary { get; init; }
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public string? Details { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public IReadOnlyList<OsvSeverity> Severity { get; init; } = Array.Empty<OsvSeverity>();
|
||||
|
||||
[JsonPropertyName("affected")]
|
||||
public IReadOnlyList<OsvAffected> Affected { get; init; } = Array.Empty<OsvAffected>();
|
||||
|
||||
[JsonPropertyName("references")]
|
||||
public IReadOnlyList<OsvReference> References { get; init; } = Array.Empty<OsvReference>();
|
||||
|
||||
[JsonPropertyName("credits")]
|
||||
public IReadOnlyList<OsvCredit> Credits { get; init; } = Array.Empty<OsvCredit>();
|
||||
|
||||
[JsonPropertyName("database_specific")]
|
||||
public OsvDatabaseSpecific? DatabaseSpecific { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV severity entry.
|
||||
/// </summary>
|
||||
internal sealed class OsvSeverity
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public string Score { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV affected package entry.
|
||||
/// </summary>
|
||||
internal sealed class OsvAffected
|
||||
{
|
||||
[JsonPropertyName("package")]
|
||||
public OsvPackage Package { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public IReadOnlyList<OsvSeverity> Severity { get; init; } = Array.Empty<OsvSeverity>();
|
||||
|
||||
[JsonPropertyName("ranges")]
|
||||
public IReadOnlyList<OsvRange> Ranges { get; init; } = Array.Empty<OsvRange>();
|
||||
|
||||
[JsonPropertyName("versions")]
|
||||
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("ecosystem_specific")]
|
||||
public Dictionary<string, object>? EcosystemSpecific { get; init; }
|
||||
|
||||
[JsonPropertyName("database_specific")]
|
||||
public Dictionary<string, object>? DatabaseSpecific { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV package identifier.
|
||||
/// </summary>
|
||||
internal sealed class OsvPackage
|
||||
{
|
||||
[JsonPropertyName("ecosystem")]
|
||||
public string Ecosystem { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV version range.
|
||||
/// </summary>
|
||||
internal sealed class OsvRange
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("repo")]
|
||||
public string? Repo { get; init; }
|
||||
|
||||
[JsonPropertyName("events")]
|
||||
public IReadOnlyList<OsvEvent> Events { get; init; } = Array.Empty<OsvEvent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV range event.
|
||||
/// </summary>
|
||||
internal sealed class OsvEvent
|
||||
{
|
||||
[JsonPropertyName("introduced")]
|
||||
public string? Introduced { get; init; }
|
||||
|
||||
[JsonPropertyName("fixed")]
|
||||
public string? Fixed { get; init; }
|
||||
|
||||
[JsonPropertyName("last_affected")]
|
||||
public string? LastAffected { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public string? Limit { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV reference entry.
|
||||
/// </summary>
|
||||
internal sealed class OsvReference
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV credit entry.
|
||||
/// </summary>
|
||||
internal sealed class OsvCredit
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("contact")]
|
||||
public IReadOnlyList<string> Contact { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV database-specific metadata.
|
||||
/// </summary>
|
||||
internal sealed class OsvDatabaseSpecific
|
||||
{
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; init; }
|
||||
|
||||
[JsonPropertyName("kev")]
|
||||
public OsvKevInfo? Kev { get; init; }
|
||||
|
||||
[JsonPropertyName("stellaops")]
|
||||
public OsvStellaOpsInfo? StellaOps { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OSV KEV information.
|
||||
/// </summary>
|
||||
internal sealed class OsvKevInfo
|
||||
{
|
||||
[JsonPropertyName("listed")]
|
||||
public bool Listed { get; init; }
|
||||
|
||||
[JsonPropertyName("added_date")]
|
||||
public string? AddedDate { get; init; }
|
||||
|
||||
[JsonPropertyName("due_date")]
|
||||
public string? DueDate { get; init; }
|
||||
|
||||
[JsonPropertyName("ransomware")]
|
||||
public bool? Ransomware { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// StellaOps-specific OSV metadata.
|
||||
/// </summary>
|
||||
internal sealed class OsvStellaOpsInfo
|
||||
{
|
||||
[JsonPropertyName("observation_ids")]
|
||||
public IReadOnlyList<string> ObservationIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public IReadOnlyList<string> Sources { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("has_conflicts")]
|
||||
public bool HasConflicts { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export format enumeration for advisory exports.
|
||||
/// </summary>
|
||||
internal enum AdvisoryExportFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON format (native StellaOps).
|
||||
/// </summary>
|
||||
Json,
|
||||
|
||||
/// <summary>
|
||||
/// OSV (Open Source Vulnerability) format.
|
||||
/// </summary>
|
||||
Osv,
|
||||
|
||||
/// <summary>
|
||||
/// NDJSON (newline-delimited JSON) format.
|
||||
/// </summary>
|
||||
Ndjson,
|
||||
|
||||
/// <summary>
|
||||
/// CSV format for spreadsheet imports.
|
||||
/// </summary>
|
||||
Csv
|
||||
}
|
||||
223
src/Cli/StellaOps.Cli/Services/Models/ApiSpecModels.cs
Normal file
223
src/Cli/StellaOps.Cli/Services/Models/ApiSpecModels.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Request for downloading API specification.
|
||||
/// CLI-SDK-63-001: Exposes stella api spec download command.
|
||||
/// </summary>
|
||||
internal sealed class ApiSpecDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tenant context for the operation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Output directory for the downloaded spec.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required string OutputPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Spec format to download (openapi-json, openapi-yaml).
|
||||
/// </summary>
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = "openapi-json";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to overwrite existing files.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool Overwrite { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional service filter (e.g., "concelier", "scanner", "policy").
|
||||
/// When null, downloads the aggregate/combined spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("service")]
|
||||
public string? Service { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Expected ETag for conditional download (If-None-Match).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ExpectedETag { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Expected checksum for verification after download.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string? ExpectedChecksum { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Checksum algorithm (sha256, sha384, sha512).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ChecksumAlgorithm { get; init; } = "sha256";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of API spec download operation.
|
||||
/// </summary>
|
||||
internal sealed class ApiSpecDownloadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the operation was successful.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Path where the spec was downloaded.
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string? Path { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the downloaded spec in bytes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sizeBytes")]
|
||||
public long SizeBytes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the result was served from cache (304 Not Modified).
|
||||
/// </summary>
|
||||
[JsonPropertyName("fromCache")]
|
||||
public bool FromCache { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// ETag of the downloaded spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("etag")]
|
||||
public string? ETag { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Computed checksum of the downloaded spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("checksum")]
|
||||
public string? Checksum { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Checksum algorithm used.
|
||||
/// </summary>
|
||||
[JsonPropertyName("checksumAlgorithm")]
|
||||
public string? ChecksumAlgorithm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether checksum verification passed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("checksumVerified")]
|
||||
public bool? ChecksumVerified { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// API version extracted from the spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("apiVersion")]
|
||||
public string? ApiVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp of when the spec was generated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset? GeneratedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error code if the operation failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("errorCode")]
|
||||
public string? ErrorCode { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about available API specifications.
|
||||
/// </summary>
|
||||
internal sealed class ApiSpecInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Service name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("service")]
|
||||
public required string Service { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// API version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// OpenAPI spec version (e.g., "3.1.0").
|
||||
/// </summary>
|
||||
[JsonPropertyName("openApiVersion")]
|
||||
public string? OpenApiVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Available formats.
|
||||
/// </summary>
|
||||
[JsonPropertyName("formats")]
|
||||
public IReadOnlyList<string> Formats { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// ETag for the spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("etag")]
|
||||
public string? ETag { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 checksum of the JSON format.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sha256")]
|
||||
public string? Sha256 { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Last modified timestamp.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lastModified")]
|
||||
public DateTimeOffset? LastModified { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Download URL for the spec.
|
||||
/// </summary>
|
||||
[JsonPropertyName("downloadUrl")]
|
||||
public string? DownloadUrl { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for listing available API specifications.
|
||||
/// </summary>
|
||||
internal sealed class ApiSpecListResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the operation was successful.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Available API specifications.
|
||||
/// </summary>
|
||||
[JsonPropertyName("specs")]
|
||||
public IReadOnlyList<ApiSpecInfo> Specs { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate spec info (combined all services).
|
||||
/// </summary>
|
||||
[JsonPropertyName("aggregate")]
|
||||
public ApiSpecInfo? Aggregate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
230
src/Cli/StellaOps.Cli/Services/Models/AttestationModels.cs
Normal file
230
src/Cli/StellaOps.Cli/Services/Models/AttestationModels.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-FORENSICS-54-002: Attestation models for forensic attest show command
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope from attestation file.
|
||||
/// </summary>
|
||||
internal sealed class AttestationEnvelope
|
||||
{
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public string Payload { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<AttestationSignature> Signatures { get; init; } = Array.Empty<AttestationSignature>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature in attestation envelope.
|
||||
/// </summary>
|
||||
internal sealed class AttestationSignature
|
||||
{
|
||||
[JsonPropertyName("keyid")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("sig")]
|
||||
public string Signature { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In-toto statement from attestation payload.
|
||||
/// </summary>
|
||||
internal sealed class InTotoStatement
|
||||
{
|
||||
[JsonPropertyName("_type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public IReadOnlyList<InTotoSubject> Subject { get; init; } = Array.Empty<InTotoSubject>();
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public string PredicateType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("predicate")]
|
||||
public object? Predicate { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subject in in-toto statement.
|
||||
/// </summary>
|
||||
internal sealed class InTotoSubject
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public IReadOnlyDictionary<string, string> Digest { get; init; } = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of attestation show operation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationShowResult
|
||||
{
|
||||
[JsonPropertyName("filePath")]
|
||||
public string FilePath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("statementType")]
|
||||
public string StatementType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public string PredicateType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subjects")]
|
||||
public IReadOnlyList<AttestationSubjectInfo> Subjects { get; init; } = Array.Empty<AttestationSubjectInfo>();
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<AttestationSignatureInfo> Signatures { get; init; } = Array.Empty<AttestationSignatureInfo>();
|
||||
|
||||
[JsonPropertyName("predicateSummary")]
|
||||
public AttestationPredicateSummary? PredicateSummary { get; init; }
|
||||
|
||||
[JsonPropertyName("verificationResult")]
|
||||
public AttestationVerificationResult? VerificationResult { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subject information for display.
|
||||
/// </summary>
|
||||
internal sealed class AttestationSubjectInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string DigestAlgorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestValue")]
|
||||
public string DigestValue { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature information for display.
|
||||
/// </summary>
|
||||
internal sealed class AttestationSignatureInfo
|
||||
{
|
||||
[JsonPropertyName("keyId")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool? IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("isTrusted")]
|
||||
public bool? IsTrusted { get; init; }
|
||||
|
||||
[JsonPropertyName("signerInfo")]
|
||||
public AttestationSignerInfo? SignerInfo { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signer information extracted from signature or certificate.
|
||||
/// </summary>
|
||||
internal sealed class AttestationSignerInfo
|
||||
{
|
||||
[JsonPropertyName("commonName")]
|
||||
public string? CommonName { get; init; }
|
||||
|
||||
[JsonPropertyName("organization")]
|
||||
public string? Organization { get; init; }
|
||||
|
||||
[JsonPropertyName("email")]
|
||||
public string? Email { get; init; }
|
||||
|
||||
[JsonPropertyName("issuer")]
|
||||
public string? Issuer { get; init; }
|
||||
|
||||
[JsonPropertyName("notBefore")]
|
||||
public DateTimeOffset? NotBefore { get; init; }
|
||||
|
||||
[JsonPropertyName("notAfter")]
|
||||
public DateTimeOffset? NotAfter { get; init; }
|
||||
|
||||
[JsonPropertyName("fingerprint")]
|
||||
public string? Fingerprint { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of the predicate for display.
|
||||
/// </summary>
|
||||
internal sealed class AttestationPredicateSummary
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset? Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("buildType")]
|
||||
public string? BuildType { get; init; }
|
||||
|
||||
[JsonPropertyName("builder")]
|
||||
public string? Builder { get; init; }
|
||||
|
||||
[JsonPropertyName("invocationId")]
|
||||
public string? InvocationId { get; init; }
|
||||
|
||||
[JsonPropertyName("materials")]
|
||||
public IReadOnlyList<AttestationMaterial>? Materials { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Material in attestation predicate.
|
||||
/// </summary>
|
||||
internal sealed class AttestationMaterial
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public string Uri { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public IReadOnlyDictionary<string, string>? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verification result for attestation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationVerificationResult
|
||||
{
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureCount")]
|
||||
public int SignatureCount { get; init; }
|
||||
|
||||
[JsonPropertyName("validSignatures")]
|
||||
public int ValidSignatures { get; init; }
|
||||
|
||||
[JsonPropertyName("trustedSignatures")]
|
||||
public int TrustedSignatures { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for attestation show operation.
|
||||
/// </summary>
|
||||
internal sealed class AttestationShowOptions
|
||||
{
|
||||
public bool VerifySignatures { get; init; } = true;
|
||||
public string? TrustRootPath { get; init; }
|
||||
public IReadOnlyList<ForensicTrustRoot> TrustRoots { get; init; } = Array.Empty<ForensicTrustRoot>();
|
||||
}
|
||||
420
src/Cli/StellaOps.Cli/Services/Models/DeterminismModels.cs
Normal file
420
src/Cli/StellaOps.Cli/Services/Models/DeterminismModels.cs
Normal file
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-DETER-70-003: Determinism score models (docs/modules/scanner/determinism-score.md)
|
||||
|
||||
/// <summary>
|
||||
/// Request for running determinism harness.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismRunRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Image digests to test.
|
||||
/// </summary>
|
||||
[JsonPropertyName("images")]
|
||||
public IReadOnlyList<string> Images { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Scanner container image reference.
|
||||
/// </summary>
|
||||
[JsonPropertyName("scanner")]
|
||||
public string Scanner { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Policy bundle path or SHA.
|
||||
/// </summary>
|
||||
[JsonPropertyName("policyBundle")]
|
||||
public string? PolicyBundle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Feeds bundle path or SHA.
|
||||
/// </summary>
|
||||
[JsonPropertyName("feedsBundle")]
|
||||
public string? FeedsBundle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of runs per image (default 10).
|
||||
/// </summary>
|
||||
[JsonPropertyName("runs")]
|
||||
public int Runs { get; init; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed clock timestamp for deterministic execution.
|
||||
/// </summary>
|
||||
[JsonPropertyName("fixedClock")]
|
||||
public DateTimeOffset? FixedClock { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// RNG seed for deterministic execution.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rngSeed")]
|
||||
public int RngSeed { get; init; } = 1337;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum concurrency (default 1 for determinism).
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxConcurrency")]
|
||||
public int MaxConcurrency { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Memory limit for container (default 2G).
|
||||
/// </summary>
|
||||
[JsonPropertyName("memoryLimit")]
|
||||
public string MemoryLimit { get; init; } = "2G";
|
||||
|
||||
/// <summary>
|
||||
/// CPU set for container (default 0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("cpuSet")]
|
||||
public string CpuSet { get; init; } = "0";
|
||||
|
||||
/// <summary>
|
||||
/// Platform (default linux/amd64).
|
||||
/// </summary>
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; init; } = "linux/amd64";
|
||||
|
||||
/// <summary>
|
||||
/// Minimum threshold for individual image scores.
|
||||
/// </summary>
|
||||
[JsonPropertyName("imageThreshold")]
|
||||
public double ImageThreshold { get; init; } = 0.90;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum threshold for overall score.
|
||||
/// </summary>
|
||||
[JsonPropertyName("overallThreshold")]
|
||||
public double OverallThreshold { get; init; } = 0.95;
|
||||
|
||||
/// <summary>
|
||||
/// Output directory for determinism.json and run artifacts.
|
||||
/// </summary>
|
||||
[JsonPropertyName("outputDir")]
|
||||
public string? OutputDir { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Release version string for the manifest.
|
||||
/// </summary>
|
||||
[JsonPropertyName("release")]
|
||||
public string? Release { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determinism score manifest (determinism.json schema per SCAN-DETER-186-010).
|
||||
/// </summary>
|
||||
internal sealed class DeterminismManifest
|
||||
{
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = "1";
|
||||
|
||||
[JsonPropertyName("release")]
|
||||
public string Release { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; init; } = "linux/amd64";
|
||||
|
||||
[JsonPropertyName("policy_sha")]
|
||||
public string PolicySha { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("feeds_sha")]
|
||||
public string FeedsSha { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scanner_sha")]
|
||||
public string ScannerSha { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("images")]
|
||||
public IReadOnlyList<DeterminismImageResult> Images { get; init; } = Array.Empty<DeterminismImageResult>();
|
||||
|
||||
[JsonPropertyName("overall_score")]
|
||||
public double OverallScore { get; init; }
|
||||
|
||||
[JsonPropertyName("thresholds")]
|
||||
public DeterminismThresholds Thresholds { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("generated_at")]
|
||||
public DateTimeOffset GeneratedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
[JsonPropertyName("execution")]
|
||||
public DeterminismExecutionInfo? Execution { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-image determinism result.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismImageResult
|
||||
{
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("runs")]
|
||||
public int Runs { get; init; }
|
||||
|
||||
[JsonPropertyName("identical")]
|
||||
public int Identical { get; init; }
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; init; }
|
||||
|
||||
[JsonPropertyName("artifact_hashes")]
|
||||
public IReadOnlyDictionary<string, string> ArtifactHashes { get; init; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("non_deterministic")]
|
||||
public IReadOnlyList<string> NonDeterministic { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
|
||||
[JsonPropertyName("run_details")]
|
||||
public IReadOnlyList<DeterminismRunDetail>? RunDetails { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details of a single determinism run.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismRunDetail
|
||||
{
|
||||
[JsonPropertyName("run_number")]
|
||||
public int RunNumber { get; init; }
|
||||
|
||||
[JsonPropertyName("identical")]
|
||||
public bool Identical { get; init; }
|
||||
|
||||
[JsonPropertyName("artifact_hashes")]
|
||||
public IReadOnlyDictionary<string, string> ArtifactHashes { get; init; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("duration_ms")]
|
||||
public long DurationMs { get; init; }
|
||||
|
||||
[JsonPropertyName("exit_code")]
|
||||
public int ExitCode { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thresholds for determinism scoring.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismThresholds
|
||||
{
|
||||
[JsonPropertyName("image_min")]
|
||||
public double ImageMin { get; init; } = 0.90;
|
||||
|
||||
[JsonPropertyName("overall_min")]
|
||||
public double OverallMin { get; init; } = 0.95;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execution information for determinism harness.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismExecutionInfo
|
||||
{
|
||||
[JsonPropertyName("fixed_clock")]
|
||||
public DateTimeOffset? FixedClock { get; init; }
|
||||
|
||||
[JsonPropertyName("rng_seed")]
|
||||
public int RngSeed { get; init; }
|
||||
|
||||
[JsonPropertyName("max_concurrency")]
|
||||
public int MaxConcurrency { get; init; }
|
||||
|
||||
[JsonPropertyName("memory_limit")]
|
||||
public string MemoryLimit { get; init; } = "2G";
|
||||
|
||||
[JsonPropertyName("cpu_set")]
|
||||
public string CpuSet { get; init; } = "0";
|
||||
|
||||
[JsonPropertyName("network_mode")]
|
||||
public string NetworkMode { get; init; } = "none";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of running the determinism harness.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismRunResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("manifest")]
|
||||
public DeterminismManifest? Manifest { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("passedThreshold")]
|
||||
public bool PassedThreshold { get; init; }
|
||||
|
||||
[JsonPropertyName("failedImages")]
|
||||
public IReadOnlyList<string> FailedImages { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("durationMs")]
|
||||
public long DurationMs { get; init; }
|
||||
}
|
||||
|
||||
// CLI-DETER-70-004: Determinism report models
|
||||
|
||||
/// <summary>
|
||||
/// Request for generating a determinism report.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismReportRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Paths to determinism.json files to include in report.
|
||||
/// </summary>
|
||||
[JsonPropertyName("manifestPaths")]
|
||||
public IReadOnlyList<string> ManifestPaths { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Output format (markdown, json, csv).
|
||||
/// </summary>
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = "markdown";
|
||||
|
||||
/// <summary>
|
||||
/// Output path for the report.
|
||||
/// </summary>
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Include detailed per-run information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("includeDetails")]
|
||||
public bool IncludeDetails { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Title for the report.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated determinism report across multiple manifests.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismReport
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; init; } = "Determinism Score Report";
|
||||
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset GeneratedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public DeterminismReportSummary Summary { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("releases")]
|
||||
public IReadOnlyList<DeterminismReleaseEntry> Releases { get; init; } = Array.Empty<DeterminismReleaseEntry>();
|
||||
|
||||
[JsonPropertyName("imageMatrix")]
|
||||
public IReadOnlyList<DeterminismImageMatrixEntry> ImageMatrix { get; init; } = Array.Empty<DeterminismImageMatrixEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary statistics for the report.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismReportSummary
|
||||
{
|
||||
[JsonPropertyName("totalReleases")]
|
||||
public int TotalReleases { get; init; }
|
||||
|
||||
[JsonPropertyName("totalImages")]
|
||||
public int TotalImages { get; init; }
|
||||
|
||||
[JsonPropertyName("averageScore")]
|
||||
public double AverageScore { get; init; }
|
||||
|
||||
[JsonPropertyName("minScore")]
|
||||
public double MinScore { get; init; }
|
||||
|
||||
[JsonPropertyName("maxScore")]
|
||||
public double MaxScore { get; init; }
|
||||
|
||||
[JsonPropertyName("passedCount")]
|
||||
public int PassedCount { get; init; }
|
||||
|
||||
[JsonPropertyName("failedCount")]
|
||||
public int FailedCount { get; init; }
|
||||
|
||||
[JsonPropertyName("nonDeterministicArtifacts")]
|
||||
public IReadOnlyList<string> NonDeterministicArtifacts { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry for a single release in the report.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismReleaseEntry
|
||||
{
|
||||
[JsonPropertyName("release")]
|
||||
public string Release { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("overallScore")]
|
||||
public double OverallScore { get; init; }
|
||||
|
||||
[JsonPropertyName("passed")]
|
||||
public bool Passed { get; init; }
|
||||
|
||||
[JsonPropertyName("imageCount")]
|
||||
public int ImageCount { get; init; }
|
||||
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("scannerSha")]
|
||||
public string ScannerSha { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("manifestPath")]
|
||||
public string ManifestPath { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-image matrix entry showing scores across releases.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismImageMatrixEntry
|
||||
{
|
||||
[JsonPropertyName("imageDigest")]
|
||||
public string ImageDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scores")]
|
||||
public IReadOnlyDictionary<string, double> Scores { get; init; } = new Dictionary<string, double>();
|
||||
|
||||
[JsonPropertyName("averageScore")]
|
||||
public double AverageScore { get; init; }
|
||||
|
||||
[JsonPropertyName("nonDeterministicArtifacts")]
|
||||
public IReadOnlyList<string> NonDeterministicArtifacts { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of generating a determinism report.
|
||||
/// </summary>
|
||||
internal sealed class DeterminismReportResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("report")]
|
||||
public DeterminismReport? Report { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = "markdown";
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
422
src/Cli/StellaOps.Cli/Services/Models/ExceptionModels.cs
Normal file
422
src/Cli/StellaOps.Cli/Services/Models/ExceptionModels.cs
Normal file
@@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-EXC-25-001: Exception governance models for stella exceptions commands
|
||||
|
||||
/// <summary>
|
||||
/// Exception scope types.
|
||||
/// </summary>
|
||||
internal static class ExceptionScopeTypes
|
||||
{
|
||||
public const string Purl = "purl";
|
||||
public const string Image = "image";
|
||||
public const string Component = "component";
|
||||
public const string TenantWide = "tenant";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception status values following lifecycle: draft -> staged -> active -> expired.
|
||||
/// </summary>
|
||||
internal static class ExceptionStatuses
|
||||
{
|
||||
public const string Draft = "draft";
|
||||
public const string Staged = "staged";
|
||||
public const string Active = "active";
|
||||
public const string Expired = "expired";
|
||||
public const string Revoked = "revoked";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception effect types.
|
||||
/// </summary>
|
||||
internal static class ExceptionEffectTypes
|
||||
{
|
||||
public const string Suppress = "suppress";
|
||||
public const string Defer = "defer";
|
||||
public const string Downgrade = "downgrade";
|
||||
public const string RequireControl = "requireControl";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception scope definition.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionScope
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = ExceptionScopeTypes.Purl;
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("ruleNames")]
|
||||
public IReadOnlyList<string>? RuleNames { get; init; }
|
||||
|
||||
[JsonPropertyName("severities")]
|
||||
public IReadOnlyList<string>? Severities { get; init; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public IReadOnlyList<string>? Sources { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyList<string>? Tags { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evidence reference for an exception.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionEvidenceRef
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty; // ticket, vex_claim, scan_report, attestation
|
||||
|
||||
[JsonPropertyName("uri")]
|
||||
public string Uri { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception effect definition.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionEffect
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("effectType")]
|
||||
public string EffectType { get; init; } = ExceptionEffectTypes.Suppress;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("downgradeSeverity")]
|
||||
public string? DowngradeSeverity { get; init; }
|
||||
|
||||
[JsonPropertyName("requiredControlId")]
|
||||
public string? RequiredControlId { get; init; }
|
||||
|
||||
[JsonPropertyName("routingTemplate")]
|
||||
public string? RoutingTemplate { get; init; }
|
||||
|
||||
[JsonPropertyName("maxDurationDays")]
|
||||
public int? MaxDurationDays { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception instance representing a governed waiver.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionInstance
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vuln")]
|
||||
public string Vuln { get; init; } = string.Empty; // CVE ID or alias
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public ExceptionScope Scope { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("effectId")]
|
||||
public string EffectId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("effect")]
|
||||
public ExceptionEffect? Effect { get; init; }
|
||||
|
||||
[JsonPropertyName("justification")]
|
||||
public string Justification { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public string Owner { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = ExceptionStatuses.Draft;
|
||||
|
||||
[JsonPropertyName("expiration")]
|
||||
public DateTimeOffset? Expiration { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("createdBy")]
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("approvedBy")]
|
||||
public string? ApprovedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("approvedAt")]
|
||||
public DateTimeOffset? ApprovedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("evidenceRefs")]
|
||||
public IReadOnlyList<ExceptionEvidenceRef> EvidenceRefs { get; init; } = Array.Empty<ExceptionEvidenceRef>();
|
||||
|
||||
[JsonPropertyName("policyBinding")]
|
||||
public string? PolicyBinding { get; init; }
|
||||
|
||||
[JsonPropertyName("supersedes")]
|
||||
public string? Supersedes { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to list exceptions.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("vuln")]
|
||||
public string? Vuln { get; init; }
|
||||
|
||||
[JsonPropertyName("scopeType")]
|
||||
public string? ScopeType { get; init; }
|
||||
|
||||
[JsonPropertyName("scopeValue")]
|
||||
public string? ScopeValue { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public IReadOnlyList<string>? Statuses { get; init; }
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public string? Owner { get; init; }
|
||||
|
||||
[JsonPropertyName("effectType")]
|
||||
public string? EffectType { get; init; }
|
||||
|
||||
[JsonPropertyName("expiringBefore")]
|
||||
public DateTimeOffset? ExpiringBefore { get; init; }
|
||||
|
||||
[JsonPropertyName("includeExpired")]
|
||||
public bool IncludeExpired { get; init; }
|
||||
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; init; } = 50;
|
||||
|
||||
[JsonPropertyName("pageToken")]
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from listing exceptions.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionListResponse
|
||||
{
|
||||
[JsonPropertyName("exceptions")]
|
||||
public IReadOnlyList<ExceptionInstance> Exceptions { get; init; } = Array.Empty<ExceptionInstance>();
|
||||
|
||||
[JsonPropertyName("nextPageToken")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public long? TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create an exception.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionCreateRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vuln")]
|
||||
public string Vuln { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public ExceptionScope Scope { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("effectId")]
|
||||
public string EffectId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("justification")]
|
||||
public string Justification { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public string Owner { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("expiration")]
|
||||
public DateTimeOffset? Expiration { get; init; }
|
||||
|
||||
[JsonPropertyName("evidenceRefs")]
|
||||
public IReadOnlyList<ExceptionEvidenceRef>? EvidenceRefs { get; init; }
|
||||
|
||||
[JsonPropertyName("policyBinding")]
|
||||
public string? PolicyBinding { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("stage")]
|
||||
public bool Stage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to promote an exception (draft -> staged -> active).
|
||||
/// </summary>
|
||||
internal sealed class ExceptionPromoteRequest
|
||||
{
|
||||
[JsonPropertyName("exceptionId")]
|
||||
public string ExceptionId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("targetStatus")]
|
||||
public string TargetStatus { get; init; } = ExceptionStatuses.Active;
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to revoke an exception.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionRevokeRequest
|
||||
{
|
||||
[JsonPropertyName("exceptionId")]
|
||||
public string ExceptionId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to import exceptions from NDJSON.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionImportRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("stage")]
|
||||
public bool Stage { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of exception import.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionImportResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("imported")]
|
||||
public int Imported { get; init; }
|
||||
|
||||
[JsonPropertyName("skipped")]
|
||||
public int Skipped { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<ExceptionImportError> Errors { get; init; } = Array.Empty<ExceptionImportError>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import error detail.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionImportError
|
||||
{
|
||||
[JsonPropertyName("line")]
|
||||
public int Line { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("field")]
|
||||
public string? Field { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to export exceptions.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionExportRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("statuses")]
|
||||
public IReadOnlyList<string>? Statuses { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = "ndjson"; // ndjson, json
|
||||
|
||||
[JsonPropertyName("includeManifest")]
|
||||
public bool IncludeManifest { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("signed")]
|
||||
public bool Signed { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export manifest for exception bundle.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionExportManifest
|
||||
{
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; init; }
|
||||
|
||||
[JsonPropertyName("sha256")]
|
||||
public string Sha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("aocEnforced")]
|
||||
public bool AocEnforced { get; init; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureUri")]
|
||||
public string? SignatureUri { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of exception operation.
|
||||
/// </summary>
|
||||
internal sealed class ExceptionOperationResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public ExceptionInstance? Exception { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
279
src/Cli/StellaOps.Cli/Services/Models/ForensicSnapshotModels.cs
Normal file
279
src/Cli/StellaOps.Cli/Services/Models/ForensicSnapshotModels.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-FORENSICS-53-001: Forensic snapshot models for evidence locker integration
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a forensic snapshot.
|
||||
/// </summary>
|
||||
internal sealed record ForensicSnapshotCreateRequest(
|
||||
[property: JsonPropertyName("caseId")] string CaseId,
|
||||
[property: JsonPropertyName("description")] string? Description = null,
|
||||
[property: JsonPropertyName("tags")] IReadOnlyList<string>? Tags = null,
|
||||
[property: JsonPropertyName("scope")] ForensicSnapshotScope? Scope = null,
|
||||
[property: JsonPropertyName("retentionDays")] int? RetentionDays = null);
|
||||
|
||||
/// <summary>
|
||||
/// Scope configuration for forensic snapshot.
|
||||
/// </summary>
|
||||
internal sealed record ForensicSnapshotScope(
|
||||
[property: JsonPropertyName("sbomIds")] IReadOnlyList<string>? SbomIds = null,
|
||||
[property: JsonPropertyName("scanIds")] IReadOnlyList<string>? ScanIds = null,
|
||||
[property: JsonPropertyName("policyIds")] IReadOnlyList<string>? PolicyIds = null,
|
||||
[property: JsonPropertyName("vulnerabilityIds")] IReadOnlyList<string>? VulnerabilityIds = null,
|
||||
[property: JsonPropertyName("timeRange")] ForensicTimeRange? TimeRange = null);
|
||||
|
||||
/// <summary>
|
||||
/// Time range for forensic snapshot scope.
|
||||
/// </summary>
|
||||
internal sealed record ForensicTimeRange(
|
||||
[property: JsonPropertyName("from")] DateTimeOffset? From = null,
|
||||
[property: JsonPropertyName("to")] DateTimeOffset? To = null);
|
||||
|
||||
/// <summary>
|
||||
/// Forensic snapshot document from the evidence locker.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSnapshotDocument
|
||||
{
|
||||
[JsonPropertyName("snapshotId")]
|
||||
public string SnapshotId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("caseId")]
|
||||
public string CaseId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("manifest")]
|
||||
public ForensicSnapshotManifest? Manifest { get; init; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public ForensicSnapshotScope? Scope { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyList<string> Tags { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("expiresAt")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
[JsonPropertyName("createdBy")]
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("sizeBytes")]
|
||||
public long? SizeBytes { get; init; }
|
||||
|
||||
[JsonPropertyName("artifactCount")]
|
||||
public int? ArtifactCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manifest for a forensic snapshot.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSnapshotManifest
|
||||
{
|
||||
[JsonPropertyName("manifestId")]
|
||||
public string ManifestId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = "1.0";
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string DigestAlgorithm { get; init; } = "sha256";
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public ForensicManifestSignature? Signature { get; init; }
|
||||
|
||||
[JsonPropertyName("artifacts")]
|
||||
public IReadOnlyList<ForensicSnapshotArtifact> Artifacts { get; init; } =
|
||||
Array.Empty<ForensicSnapshotArtifact>();
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public ForensicManifestMetadata? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature information for the manifest.
|
||||
/// </summary>
|
||||
internal sealed class ForensicManifestSignature
|
||||
{
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signedAt")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("certificate")]
|
||||
public string? Certificate { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual artifact in a forensic snapshot.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSnapshotArtifact
|
||||
{
|
||||
[JsonPropertyName("artifactId")]
|
||||
public string ArtifactId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string DigestAlgorithm { get; init; } = "sha256";
|
||||
|
||||
[JsonPropertyName("sizeBytes")]
|
||||
public long SizeBytes { get; init; }
|
||||
|
||||
[JsonPropertyName("mediaType")]
|
||||
public string? MediaType { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public Dictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata for the forensic manifest.
|
||||
/// </summary>
|
||||
internal sealed class ForensicManifestMetadata
|
||||
{
|
||||
[JsonPropertyName("capturedAt")]
|
||||
public DateTimeOffset CapturedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("capturedBy")]
|
||||
public string? CapturedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("toolVersion")]
|
||||
public string? ToolVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("stellaOpsVersion")]
|
||||
public string? StellaOpsVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("chainOfCustody")]
|
||||
public IReadOnlyList<ForensicChainOfCustodyEntry> ChainOfCustody { get; init; } =
|
||||
Array.Empty<ForensicChainOfCustodyEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chain of custody entry.
|
||||
/// </summary>
|
||||
internal sealed class ForensicChainOfCustodyEntry
|
||||
{
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string Actor { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public string? Signature { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for listing forensic snapshots.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSnapshotListResponse
|
||||
{
|
||||
[JsonPropertyName("snapshots")]
|
||||
public IReadOnlyList<ForensicSnapshotDocument> Snapshots { get; init; } =
|
||||
Array.Empty<ForensicSnapshotDocument>();
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query parameters for listing forensic snapshots.
|
||||
/// </summary>
|
||||
internal sealed record ForensicSnapshotListQuery(
|
||||
string Tenant,
|
||||
string? CaseId = null,
|
||||
string? Status = null,
|
||||
IReadOnlyList<string>? Tags = null,
|
||||
DateTimeOffset? CreatedAfter = null,
|
||||
DateTimeOffset? CreatedBefore = null,
|
||||
int? Limit = null,
|
||||
int? Offset = null);
|
||||
|
||||
/// <summary>
|
||||
/// Local cache metadata for forensic snapshots.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSnapshotCacheEntry
|
||||
{
|
||||
[JsonPropertyName("snapshotId")]
|
||||
public string SnapshotId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("caseId")]
|
||||
public string CaseId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("localPath")]
|
||||
public string LocalPath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("manifestDigest")]
|
||||
public string ManifestDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("downloadedAt")]
|
||||
public DateTimeOffset DownloadedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("sizeBytes")]
|
||||
public long SizeBytes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot status enumeration.
|
||||
/// </summary>
|
||||
internal static class ForensicSnapshotStatus
|
||||
{
|
||||
public const string Pending = "pending";
|
||||
public const string Creating = "creating";
|
||||
public const string Ready = "ready";
|
||||
public const string Failed = "failed";
|
||||
public const string Expired = "expired";
|
||||
public const string Archived = "archived";
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-FORENSICS-54-001: Forensic bundle verification models
|
||||
|
||||
/// <summary>
|
||||
/// Represents a forensic bundle for local verification.
|
||||
/// </summary>
|
||||
internal sealed class ForensicBundle
|
||||
{
|
||||
[JsonPropertyName("manifestPath")]
|
||||
public string ManifestPath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("manifest")]
|
||||
public ForensicSnapshotManifest? Manifest { get; init; }
|
||||
|
||||
[JsonPropertyName("artifacts")]
|
||||
public IReadOnlyList<ForensicBundleArtifact> Artifacts { get; init; } = Array.Empty<ForensicBundleArtifact>();
|
||||
|
||||
[JsonPropertyName("dsseEnvelopes")]
|
||||
public IReadOnlyList<ForensicDsseEnvelope> DsseEnvelopes { get; init; } = Array.Empty<ForensicDsseEnvelope>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Artifact in a forensic bundle with local file reference.
|
||||
/// </summary>
|
||||
internal sealed class ForensicBundleArtifact
|
||||
{
|
||||
[JsonPropertyName("artifactId")]
|
||||
public string ArtifactId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("localPath")]
|
||||
public string LocalPath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("expectedDigest")]
|
||||
public string ExpectedDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string DigestAlgorithm { get; init; } = "sha256";
|
||||
|
||||
[JsonPropertyName("sizeBytes")]
|
||||
public long SizeBytes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope for signature verification.
|
||||
/// </summary>
|
||||
internal sealed class ForensicDsseEnvelope
|
||||
{
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public string Payload { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<ForensicDsseSignature> Signatures { get; init; } = Array.Empty<ForensicDsseSignature>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature entry.
|
||||
/// </summary>
|
||||
internal sealed class ForensicDsseSignature
|
||||
{
|
||||
[JsonPropertyName("keyid")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sig")]
|
||||
public string Signature { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of forensic bundle verification.
|
||||
/// </summary>
|
||||
internal sealed class ForensicVerificationResult
|
||||
{
|
||||
[JsonPropertyName("bundlePath")]
|
||||
public string BundlePath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiedAt")]
|
||||
public DateTimeOffset VerifiedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
[JsonPropertyName("manifestVerification")]
|
||||
public ForensicManifestVerification? ManifestVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("checksumVerification")]
|
||||
public ForensicChecksumVerification? ChecksumVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureVerification")]
|
||||
public ForensicSignatureVerification? SignatureVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("chainOfCustodyVerification")]
|
||||
public ForensicChainOfCustodyVerification? ChainOfCustodyVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<ForensicVerificationError> Errors { get; init; } = Array.Empty<ForensicVerificationError>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manifest verification result.
|
||||
/// </summary>
|
||||
internal sealed class ForensicManifestVerification
|
||||
{
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("manifestId")]
|
||||
public string ManifestId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string DigestAlgorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("computedDigest")]
|
||||
public string ComputedDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("artifactCount")]
|
||||
public int ArtifactCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checksum verification result.
|
||||
/// </summary>
|
||||
internal sealed class ForensicChecksumVerification
|
||||
{
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("totalArtifacts")]
|
||||
public int TotalArtifacts { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiedArtifacts")]
|
||||
public int VerifiedArtifacts { get; init; }
|
||||
|
||||
[JsonPropertyName("failedArtifacts")]
|
||||
public IReadOnlyList<ForensicArtifactChecksumFailure> FailedArtifacts { get; init; } =
|
||||
Array.Empty<ForensicArtifactChecksumFailure>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual artifact checksum failure.
|
||||
/// </summary>
|
||||
internal sealed class ForensicArtifactChecksumFailure
|
||||
{
|
||||
[JsonPropertyName("artifactId")]
|
||||
public string ArtifactId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("expectedDigest")]
|
||||
public string ExpectedDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("actualDigest")]
|
||||
public string ActualDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature verification result.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSignatureVerification
|
||||
{
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureCount")]
|
||||
public int SignatureCount { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiedSignatures")]
|
||||
public int VerifiedSignatures { get; init; }
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<ForensicSignatureDetail> Signatures { get; init; } =
|
||||
Array.Empty<ForensicSignatureDetail>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual signature verification detail.
|
||||
/// </summary>
|
||||
internal sealed class ForensicSignatureDetail
|
||||
{
|
||||
[JsonPropertyName("keyId")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("isTrusted")]
|
||||
public bool IsTrusted { get; init; }
|
||||
|
||||
[JsonPropertyName("signedAt")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("fingerprint")]
|
||||
public string? Fingerprint { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chain of custody verification result.
|
||||
/// </summary>
|
||||
internal sealed class ForensicChainOfCustodyVerification
|
||||
{
|
||||
[JsonPropertyName("isValid")]
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("entryCount")]
|
||||
public int EntryCount { get; init; }
|
||||
|
||||
[JsonPropertyName("timelineValid")]
|
||||
public bool TimelineValid { get; init; }
|
||||
|
||||
[JsonPropertyName("signaturesValid")]
|
||||
public bool SignaturesValid { get; init; }
|
||||
|
||||
[JsonPropertyName("entries")]
|
||||
public IReadOnlyList<ForensicChainOfCustodyEntryVerification> Entries { get; init; } =
|
||||
Array.Empty<ForensicChainOfCustodyEntryVerification>();
|
||||
|
||||
[JsonPropertyName("gaps")]
|
||||
public IReadOnlyList<ForensicTimelineGap> Gaps { get; init; } = Array.Empty<ForensicTimelineGap>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual chain of custody entry verification.
|
||||
/// </summary>
|
||||
internal sealed class ForensicChainOfCustodyEntryVerification
|
||||
{
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; init; }
|
||||
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string Actor { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureValid")]
|
||||
public bool? SignatureValid { get; init; }
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeline gap in chain of custody.
|
||||
/// </summary>
|
||||
internal sealed class ForensicTimelineGap
|
||||
{
|
||||
[JsonPropertyName("fromIndex")]
|
||||
public int FromIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("toIndex")]
|
||||
public int ToIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("fromTimestamp")]
|
||||
public DateTimeOffset FromTimestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("toTimestamp")]
|
||||
public DateTimeOffset ToTimestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("gapDuration")]
|
||||
public TimeSpan GapDuration { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verification error detail.
|
||||
/// </summary>
|
||||
internal sealed class ForensicVerificationError
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; init; }
|
||||
|
||||
[JsonPropertyName("artifactId")]
|
||||
public string? ArtifactId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trust root configuration for forensic verification.
|
||||
/// </summary>
|
||||
internal sealed class ForensicTrustRoot
|
||||
{
|
||||
[JsonPropertyName("keyId")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fingerprint")]
|
||||
public string Fingerprint { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("publicKey")]
|
||||
public string PublicKey { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = "rsa-pss-sha256";
|
||||
|
||||
[JsonPropertyName("notBefore")]
|
||||
public DateTimeOffset? NotBefore { get; init; }
|
||||
|
||||
[JsonPropertyName("notAfter")]
|
||||
public DateTimeOffset? NotAfter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verification options for forensic bundle.
|
||||
/// </summary>
|
||||
internal sealed class ForensicVerificationOptions
|
||||
{
|
||||
public bool VerifyChecksums { get; init; } = true;
|
||||
public bool VerifySignatures { get; init; } = true;
|
||||
public bool VerifyChainOfCustody { get; init; } = true;
|
||||
public bool StrictTimeline { get; init; } = false;
|
||||
public IReadOnlyList<ForensicTrustRoot> TrustRoots { get; init; } = Array.Empty<ForensicTrustRoot>();
|
||||
public string? TrustRootPath { get; init; }
|
||||
}
|
||||
612
src/Cli/StellaOps.Cli/Services/Models/NotifyModels.cs
Normal file
612
src/Cli/StellaOps.Cli/Services/Models/NotifyModels.cs
Normal file
@@ -0,0 +1,612 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-PARITY-41-002: Notify command models for CLI
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel types.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
internal enum NotifyChannelType
|
||||
{
|
||||
Slack,
|
||||
Teams,
|
||||
Email,
|
||||
Webhook,
|
||||
Custom,
|
||||
PagerDuty,
|
||||
OpsGenie,
|
||||
Cli,
|
||||
InAppInbox,
|
||||
InApp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery status.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
internal enum NotifyDeliveryStatus
|
||||
{
|
||||
Pending,
|
||||
Sent,
|
||||
Failed,
|
||||
Throttled,
|
||||
Digested,
|
||||
Dropped
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel list request.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool? Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("cursor")]
|
||||
public string? Cursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel list response.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelListResponse
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public IReadOnlyList<NotifyChannelSummary> Items { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel summary for list view.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelSummary
|
||||
{
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("displayName")]
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("deliveryCount")]
|
||||
public int DeliveryCount { get; init; }
|
||||
|
||||
[JsonPropertyName("failureRate")]
|
||||
public double? FailureRate { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed notify channel response.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelDetail
|
||||
{
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenantId")]
|
||||
public string TenantId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("displayName")]
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("config")]
|
||||
public NotifyChannelConfigInfo? Config { get; init; }
|
||||
|
||||
[JsonPropertyName("labels")]
|
||||
public IReadOnlyDictionary<string, string>? Labels { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("createdBy")]
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedBy")]
|
||||
public string? UpdatedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("stats")]
|
||||
public NotifyChannelStats? Stats { get; init; }
|
||||
|
||||
[JsonPropertyName("health")]
|
||||
public NotifyChannelHealth? Health { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel configuration info (redacted secrets).
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelConfigInfo
|
||||
{
|
||||
[JsonPropertyName("secretRef")]
|
||||
public string SecretRef { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("target")]
|
||||
public string? Target { get; init; }
|
||||
|
||||
[JsonPropertyName("endpoint")]
|
||||
public string? Endpoint { get; init; }
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public IReadOnlyDictionary<string, string>? Properties { get; init; }
|
||||
|
||||
[JsonPropertyName("limits")]
|
||||
public NotifyChannelLimitsInfo? Limits { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel limits.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelLimitsInfo
|
||||
{
|
||||
[JsonPropertyName("concurrency")]
|
||||
public int? Concurrency { get; init; }
|
||||
|
||||
[JsonPropertyName("requestsPerMinute")]
|
||||
public int? RequestsPerMinute { get; init; }
|
||||
|
||||
[JsonPropertyName("timeoutSeconds")]
|
||||
public int? TimeoutSeconds { get; init; }
|
||||
|
||||
[JsonPropertyName("maxBatchSize")]
|
||||
public int? MaxBatchSize { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel statistics.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelStats
|
||||
{
|
||||
[JsonPropertyName("totalDeliveries")]
|
||||
public long TotalDeliveries { get; init; }
|
||||
|
||||
[JsonPropertyName("successfulDeliveries")]
|
||||
public long SuccessfulDeliveries { get; init; }
|
||||
|
||||
[JsonPropertyName("failedDeliveries")]
|
||||
public long FailedDeliveries { get; init; }
|
||||
|
||||
[JsonPropertyName("throttledDeliveries")]
|
||||
public long ThrottledDeliveries { get; init; }
|
||||
|
||||
[JsonPropertyName("lastDeliveryAt")]
|
||||
public DateTimeOffset? LastDeliveryAt { get; init; }
|
||||
|
||||
[JsonPropertyName("avgLatencyMs")]
|
||||
public double? AvgLatencyMs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify channel health status.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelHealth
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("lastCheckAt")]
|
||||
public DateTimeOffset? LastCheckAt { get; init; }
|
||||
|
||||
[JsonPropertyName("consecutiveFailures")]
|
||||
public int ConsecutiveFailures { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Channel test request.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelTestRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Channel test result.
|
||||
/// </summary>
|
||||
internal sealed class NotifyChannelTestResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("latencyMs")]
|
||||
public long? LatencyMs { get; init; }
|
||||
|
||||
[JsonPropertyName("responseCode")]
|
||||
public int? ResponseCode { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
[JsonPropertyName("deliveryId")]
|
||||
public string? DeliveryId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify rule list request.
|
||||
/// </summary>
|
||||
internal sealed class NotifyRuleListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool? Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("eventType")]
|
||||
public string? EventType { get; init; }
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string? ChannelId { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify rule list response.
|
||||
/// </summary>
|
||||
internal sealed class NotifyRuleListResponse
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public IReadOnlyList<NotifyRuleSummary> Items { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify rule summary.
|
||||
/// </summary>
|
||||
internal sealed class NotifyRuleSummary
|
||||
{
|
||||
[JsonPropertyName("ruleId")]
|
||||
public string RuleId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("eventTypes")]
|
||||
public IReadOnlyList<string> EventTypes { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("channelIds")]
|
||||
public IReadOnlyList<string> ChannelIds { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int Priority { get; init; }
|
||||
|
||||
[JsonPropertyName("matchCount")]
|
||||
public long MatchCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery list request.
|
||||
/// </summary>
|
||||
internal sealed class NotifyDeliveryListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string? ChannelId { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("eventType")]
|
||||
public string? EventType { get; init; }
|
||||
|
||||
[JsonPropertyName("since")]
|
||||
public DateTimeOffset? Since { get; init; }
|
||||
|
||||
[JsonPropertyName("until")]
|
||||
public DateTimeOffset? Until { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("cursor")]
|
||||
public string? Cursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery list response.
|
||||
/// </summary>
|
||||
internal sealed class NotifyDeliveryListResponse
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public IReadOnlyList<NotifyDeliverySummary> Items { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery summary.
|
||||
/// </summary>
|
||||
internal sealed class NotifyDeliverySummary
|
||||
{
|
||||
[JsonPropertyName("deliveryId")]
|
||||
public string DeliveryId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("channelName")]
|
||||
public string? ChannelName { get; init; }
|
||||
|
||||
[JsonPropertyName("channelType")]
|
||||
public string ChannelType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("eventType")]
|
||||
public string EventType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("attemptCount")]
|
||||
public int AttemptCount { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("sentAt")]
|
||||
public DateTimeOffset? SentAt { get; init; }
|
||||
|
||||
[JsonPropertyName("latencyMs")]
|
||||
public long? LatencyMs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery detail.
|
||||
/// </summary>
|
||||
internal sealed class NotifyDeliveryDetail
|
||||
{
|
||||
[JsonPropertyName("deliveryId")]
|
||||
public string DeliveryId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenantId")]
|
||||
public string TenantId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string ChannelId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("channelName")]
|
||||
public string? ChannelName { get; init; }
|
||||
|
||||
[JsonPropertyName("channelType")]
|
||||
public string ChannelType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("ruleId")]
|
||||
public string? RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("eventId")]
|
||||
public string? EventId { get; init; }
|
||||
|
||||
[JsonPropertyName("eventType")]
|
||||
public string EventType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public string? Subject { get; init; }
|
||||
|
||||
[JsonPropertyName("attemptCount")]
|
||||
public int AttemptCount { get; init; }
|
||||
|
||||
[JsonPropertyName("attempts")]
|
||||
public IReadOnlyList<NotifyDeliveryAttempt>? Attempts { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("sentAt")]
|
||||
public DateTimeOffset? SentAt { get; init; }
|
||||
|
||||
[JsonPropertyName("failedAt")]
|
||||
public DateTimeOffset? FailedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
[JsonPropertyName("idempotencyKey")]
|
||||
public string? IdempotencyKey { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify delivery attempt.
|
||||
/// </summary>
|
||||
internal sealed class NotifyDeliveryAttempt
|
||||
{
|
||||
[JsonPropertyName("attemptNumber")]
|
||||
public int AttemptNumber { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("attemptedAt")]
|
||||
public DateTimeOffset AttemptedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("latencyMs")]
|
||||
public long? LatencyMs { get; init; }
|
||||
|
||||
[JsonPropertyName("responseCode")]
|
||||
public int? ResponseCode { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retry delivery request.
|
||||
/// </summary>
|
||||
internal sealed class NotifyRetryRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("deliveryId")]
|
||||
public string DeliveryId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("idempotencyKey")]
|
||||
public string? IdempotencyKey { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retry delivery result.
|
||||
/// </summary>
|
||||
internal sealed class NotifyRetryResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("deliveryId")]
|
||||
public string DeliveryId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("newStatus")]
|
||||
public string? NewStatus { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send notification request.
|
||||
/// </summary>
|
||||
internal sealed class NotifySendRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("channelId")]
|
||||
public string? ChannelId { get; init; }
|
||||
|
||||
[JsonPropertyName("eventType")]
|
||||
public string EventType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public string? Subject { get; init; }
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
public string Body { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string? Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("idempotencyKey")]
|
||||
public string? IdempotencyKey { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send notification result.
|
||||
/// </summary>
|
||||
internal sealed class NotifySendResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("eventId")]
|
||||
public string? EventId { get; init; }
|
||||
|
||||
[JsonPropertyName("deliveryIds")]
|
||||
public IReadOnlyList<string>? DeliveryIds { get; init; }
|
||||
|
||||
[JsonPropertyName("channelsMatched")]
|
||||
public int ChannelsMatched { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("idempotencyKey")]
|
||||
public string? IdempotencyKey { get; init; }
|
||||
}
|
||||
542
src/Cli/StellaOps.Cli/Services/Models/ObservabilityModels.cs
Normal file
542
src/Cli/StellaOps.Cli/Services/Models/ObservabilityModels.cs
Normal file
@@ -0,0 +1,542 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-OBS-51-001: Observability models for stella obs commands
|
||||
|
||||
/// <summary>
|
||||
/// Service health status from the platform.
|
||||
/// </summary>
|
||||
internal sealed class ServiceHealthStatus
|
||||
{
|
||||
[JsonPropertyName("service")]
|
||||
public string Service { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = "unknown"; // healthy, degraded, unhealthy, unknown
|
||||
|
||||
[JsonPropertyName("availability")]
|
||||
public double Availability { get; init; }
|
||||
|
||||
[JsonPropertyName("sloTarget")]
|
||||
public double SloTarget { get; init; } = 0.999;
|
||||
|
||||
[JsonPropertyName("errorBudgetRemaining")]
|
||||
public double ErrorBudgetRemaining { get; init; }
|
||||
|
||||
[JsonPropertyName("burnRate")]
|
||||
public BurnRateInfo? BurnRate { get; init; }
|
||||
|
||||
[JsonPropertyName("latency")]
|
||||
public LatencyInfo? Latency { get; init; }
|
||||
|
||||
[JsonPropertyName("traffic")]
|
||||
public TrafficInfo? Traffic { get; init; }
|
||||
|
||||
[JsonPropertyName("queues")]
|
||||
public IReadOnlyList<QueueHealth> Queues { get; init; } = Array.Empty<QueueHealth>();
|
||||
|
||||
[JsonPropertyName("lastUpdated")]
|
||||
public DateTimeOffset LastUpdated { get; init; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Burn rate alert information.
|
||||
/// </summary>
|
||||
internal sealed class BurnRateInfo
|
||||
{
|
||||
[JsonPropertyName("current")]
|
||||
public double Current { get; init; }
|
||||
|
||||
[JsonPropertyName("shortWindow")]
|
||||
public double ShortWindow { get; init; } // 5m or 1h window
|
||||
|
||||
[JsonPropertyName("longWindow")]
|
||||
public double LongWindow { get; init; } // 6h or 3d window
|
||||
|
||||
[JsonPropertyName("alertLevel")]
|
||||
public string AlertLevel { get; init; } = "none"; // none, warning, critical
|
||||
|
||||
[JsonPropertyName("threshold2x")]
|
||||
public double Threshold2x { get; init; } = 2.0;
|
||||
|
||||
[JsonPropertyName("threshold14x")]
|
||||
public double Threshold14x { get; init; } = 14.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Latency percentile information.
|
||||
/// </summary>
|
||||
internal sealed class LatencyInfo
|
||||
{
|
||||
[JsonPropertyName("p50")]
|
||||
public double P50Ms { get; init; }
|
||||
|
||||
[JsonPropertyName("p95")]
|
||||
public double P95Ms { get; init; }
|
||||
|
||||
[JsonPropertyName("p99")]
|
||||
public double P99Ms { get; init; }
|
||||
|
||||
[JsonPropertyName("p95Target")]
|
||||
public double P95TargetMs { get; init; } = 300;
|
||||
|
||||
[JsonPropertyName("breaching")]
|
||||
public bool Breaching { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traffic/throughput information.
|
||||
/// </summary>
|
||||
internal sealed class TrafficInfo
|
||||
{
|
||||
[JsonPropertyName("requestsPerSecond")]
|
||||
public double RequestsPerSecond { get; init; }
|
||||
|
||||
[JsonPropertyName("successRate")]
|
||||
public double SuccessRate { get; init; }
|
||||
|
||||
[JsonPropertyName("errorRate")]
|
||||
public double ErrorRate { get; init; }
|
||||
|
||||
[JsonPropertyName("totalRequests")]
|
||||
public long TotalRequests { get; init; }
|
||||
|
||||
[JsonPropertyName("totalErrors")]
|
||||
public long TotalErrors { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue health information.
|
||||
/// </summary>
|
||||
internal sealed class QueueHealth
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("depth")]
|
||||
public long Depth { get; init; }
|
||||
|
||||
[JsonPropertyName("depthThreshold")]
|
||||
public long DepthThreshold { get; init; } = 1000;
|
||||
|
||||
[JsonPropertyName("oldestMessageAge")]
|
||||
public TimeSpan OldestMessageAge { get; init; }
|
||||
|
||||
[JsonPropertyName("throughput")]
|
||||
public double Throughput { get; init; }
|
||||
|
||||
[JsonPropertyName("successRate")]
|
||||
public double SuccessRate { get; init; }
|
||||
|
||||
[JsonPropertyName("alerting")]
|
||||
public bool Alerting { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Platform-wide health summary.
|
||||
/// </summary>
|
||||
internal sealed class PlatformHealthSummary
|
||||
{
|
||||
[JsonPropertyName("overallStatus")]
|
||||
public string OverallStatus { get; init; } = "unknown";
|
||||
|
||||
[JsonPropertyName("services")]
|
||||
public IReadOnlyList<ServiceHealthStatus> Services { get; init; } = Array.Empty<ServiceHealthStatus>();
|
||||
|
||||
[JsonPropertyName("activeAlerts")]
|
||||
public IReadOnlyList<ActiveAlert> ActiveAlerts { get; init; } = Array.Empty<ActiveAlert>();
|
||||
|
||||
[JsonPropertyName("globalErrorBudget")]
|
||||
public double GlobalErrorBudget { get; init; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Active alert information.
|
||||
/// </summary>
|
||||
internal sealed class ActiveAlert
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("service")]
|
||||
public string Service { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty; // burn_rate, latency, error_rate, queue_depth
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string Severity { get; init; } = "warning"; // warning, critical
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public DateTimeOffset StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public double Value { get; init; }
|
||||
|
||||
[JsonPropertyName("threshold")]
|
||||
public double Threshold { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for obs top command.
|
||||
/// </summary>
|
||||
internal sealed class ObsTopRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter by service names.
|
||||
/// </summary>
|
||||
[JsonPropertyName("services")]
|
||||
public IReadOnlyList<string> Services { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Filter by tenant.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Include queue details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("includeQueues")]
|
||||
public bool IncludeQueues { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh interval in seconds for streaming mode (0 = single fetch).
|
||||
/// </summary>
|
||||
[JsonPropertyName("refreshInterval")]
|
||||
public int RefreshInterval { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum alerts to return.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxAlerts")]
|
||||
public int MaxAlerts { get; init; } = 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of obs top command.
|
||||
/// </summary>
|
||||
internal sealed class ObsTopResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public PlatformHealthSummary? Summary { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
// CLI-OBS-52-001: Trace and logs models
|
||||
|
||||
/// <summary>
|
||||
/// Request for fetching a trace by ID.
|
||||
/// </summary>
|
||||
internal sealed class ObsTraceRequest
|
||||
{
|
||||
[JsonPropertyName("traceId")]
|
||||
public string TraceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("includeEvidence")]
|
||||
public bool IncludeEvidence { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distributed trace with spans.
|
||||
/// </summary>
|
||||
internal sealed class DistributedTrace
|
||||
{
|
||||
[JsonPropertyName("traceId")]
|
||||
public string TraceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("rootSpan")]
|
||||
public TraceSpan? RootSpan { get; init; }
|
||||
|
||||
[JsonPropertyName("spans")]
|
||||
public IReadOnlyList<TraceSpan> Spans { get; init; } = Array.Empty<TraceSpan>();
|
||||
|
||||
[JsonPropertyName("services")]
|
||||
public IReadOnlyList<string> Services { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("duration")]
|
||||
public TimeSpan Duration { get; init; }
|
||||
|
||||
[JsonPropertyName("startTime")]
|
||||
public DateTimeOffset StartTime { get; init; }
|
||||
|
||||
[JsonPropertyName("endTime")]
|
||||
public DateTimeOffset EndTime { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = "ok"; // ok, error
|
||||
|
||||
[JsonPropertyName("evidenceLinks")]
|
||||
public IReadOnlyList<EvidenceLink> EvidenceLinks { get; init; } = Array.Empty<EvidenceLink>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual span within a trace.
|
||||
/// </summary>
|
||||
internal sealed class TraceSpan
|
||||
{
|
||||
[JsonPropertyName("spanId")]
|
||||
public string SpanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("parentSpanId")]
|
||||
public string? ParentSpanId { get; init; }
|
||||
|
||||
[JsonPropertyName("operationName")]
|
||||
public string OperationName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("serviceName")]
|
||||
public string ServiceName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("startTime")]
|
||||
public DateTimeOffset StartTime { get; init; }
|
||||
|
||||
[JsonPropertyName("duration")]
|
||||
public TimeSpan Duration { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = "ok"; // ok, error
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyDictionary<string, string> Tags { get; init; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("logs")]
|
||||
public IReadOnlyList<SpanLog> Logs { get; init; } = Array.Empty<SpanLog>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log entry within a span.
|
||||
/// </summary>
|
||||
internal sealed class SpanLog
|
||||
{
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; init; } = "info"; // debug, info, warn, error
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public IReadOnlyDictionary<string, string> Fields { get; init; } = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link to evidence artifact (SBOM, VEX, attestation, etc.).
|
||||
/// </summary>
|
||||
internal sealed class EvidenceLink
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty; // sbom, vex, attestation, scan_result
|
||||
|
||||
[JsonPropertyName("uri")]
|
||||
public string Uri { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of fetching a trace.
|
||||
/// </summary>
|
||||
internal sealed class ObsTraceResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("trace")]
|
||||
public DistributedTrace? Trace { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for fetching logs.
|
||||
/// </summary>
|
||||
internal sealed class ObsLogsRequest
|
||||
{
|
||||
[JsonPropertyName("from")]
|
||||
public DateTimeOffset From { get; init; }
|
||||
|
||||
[JsonPropertyName("to")]
|
||||
public DateTimeOffset To { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("services")]
|
||||
public IReadOnlyList<string> Services { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("levels")]
|
||||
public IReadOnlyList<string> Levels { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("query")]
|
||||
public string? Query { get; init; }
|
||||
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; init; } = 100;
|
||||
|
||||
[JsonPropertyName("pageToken")]
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log entry from the platform.
|
||||
/// </summary>
|
||||
internal sealed class LogEntry
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; init; } = "info";
|
||||
|
||||
[JsonPropertyName("service")]
|
||||
public string Service { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("traceId")]
|
||||
public string? TraceId { get; init; }
|
||||
|
||||
[JsonPropertyName("spanId")]
|
||||
public string? SpanId { get; init; }
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public IReadOnlyDictionary<string, string> Fields { get; init; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("evidenceLinks")]
|
||||
public IReadOnlyList<EvidenceLink> EvidenceLinks { get; init; } = Array.Empty<EvidenceLink>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of fetching logs.
|
||||
/// </summary>
|
||||
internal sealed class ObsLogsResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("logs")]
|
||||
public IReadOnlyList<LogEntry> Logs { get; init; } = Array.Empty<LogEntry>();
|
||||
|
||||
[JsonPropertyName("nextPageToken")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public long? TotalCount { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
// CLI-OBS-55-001: Incident mode models
|
||||
|
||||
/// <summary>
|
||||
/// Incident mode state.
|
||||
/// </summary>
|
||||
internal sealed class IncidentModeState
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("setAt")]
|
||||
public DateTimeOffset? SetAt { get; init; }
|
||||
|
||||
[JsonPropertyName("expiresAt")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string? Actor { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("retentionExtensionDays")]
|
||||
public int RetentionExtensionDays { get; init; } = 60;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; init; } = "cli"; // cli, config, api
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to enable incident mode.
|
||||
/// </summary>
|
||||
internal sealed class IncidentModeEnableRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("ttlMinutes")]
|
||||
public int TtlMinutes { get; init; } = 30;
|
||||
|
||||
[JsonPropertyName("retentionExtensionDays")]
|
||||
public int RetentionExtensionDays { get; init; } = 60;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to disable incident mode.
|
||||
/// </summary>
|
||||
internal sealed class IncidentModeDisableRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of incident mode operation.
|
||||
/// </summary>
|
||||
internal sealed class IncidentModeResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public IncidentModeState? State { get; init; }
|
||||
|
||||
[JsonPropertyName("previousState")]
|
||||
public IncidentModeState? PreviousState { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
671
src/Cli/StellaOps.Cli/Services/Models/OrchestratorModels.cs
Normal file
671
src/Cli/StellaOps.Cli/Services/Models/OrchestratorModels.cs
Normal file
@@ -0,0 +1,671 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-ORCH-32-001: Orchestrator source and job models for stella orch commands
|
||||
|
||||
/// <summary>
|
||||
/// Source status values.
|
||||
/// </summary>
|
||||
internal static class SourceStatuses
|
||||
{
|
||||
public const string Active = "active";
|
||||
public const string Paused = "paused";
|
||||
public const string Disabled = "disabled";
|
||||
public const string Throttled = "throttled";
|
||||
public const string Error = "error";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source type values representing data feed categories.
|
||||
/// </summary>
|
||||
internal static class SourceTypes
|
||||
{
|
||||
public const string Advisory = "advisory";
|
||||
public const string Vex = "vex";
|
||||
public const string Sbom = "sbom";
|
||||
public const string Package = "package";
|
||||
public const string Registry = "registry";
|
||||
public const string Custom = "custom";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orchestrator source definition representing a data feed.
|
||||
/// </summary>
|
||||
internal sealed class OrchestratorSource
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = SourceTypes.Advisory;
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
public string Host { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = SourceStatuses.Active;
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int Priority { get; init; }
|
||||
|
||||
[JsonPropertyName("schedule")]
|
||||
public SourceSchedule? Schedule { get; init; }
|
||||
|
||||
[JsonPropertyName("rateLimit")]
|
||||
public SourceRateLimit? RateLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("lastRun")]
|
||||
public SourceLastRun? LastRun { get; init; }
|
||||
|
||||
[JsonPropertyName("metrics")]
|
||||
public SourceMetrics? Metrics { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("pausedAt")]
|
||||
public DateTimeOffset? PausedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("pausedBy")]
|
||||
public string? PausedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("pauseReason")]
|
||||
public string? PauseReason { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyList<string> Tags { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source schedule configuration.
|
||||
/// </summary>
|
||||
internal sealed class SourceSchedule
|
||||
{
|
||||
[JsonPropertyName("cron")]
|
||||
public string? Cron { get; init; }
|
||||
|
||||
[JsonPropertyName("intervalMinutes")]
|
||||
public int? IntervalMinutes { get; init; }
|
||||
|
||||
[JsonPropertyName("nextRunAt")]
|
||||
public DateTimeOffset? NextRunAt { get; init; }
|
||||
|
||||
[JsonPropertyName("timezone")]
|
||||
public string Timezone { get; init; } = "UTC";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source rate limit configuration.
|
||||
/// </summary>
|
||||
internal sealed class SourceRateLimit
|
||||
{
|
||||
[JsonPropertyName("maxRequestsPerMinute")]
|
||||
public int MaxRequestsPerMinute { get; init; }
|
||||
|
||||
[JsonPropertyName("maxRequestsPerHour")]
|
||||
public int? MaxRequestsPerHour { get; init; }
|
||||
|
||||
[JsonPropertyName("burstSize")]
|
||||
public int BurstSize { get; init; } = 1;
|
||||
|
||||
[JsonPropertyName("currentTokens")]
|
||||
public double? CurrentTokens { get; init; }
|
||||
|
||||
[JsonPropertyName("refillRatePerSecond")]
|
||||
public double? RefillRatePerSecond { get; init; }
|
||||
|
||||
[JsonPropertyName("throttledUntil")]
|
||||
public DateTimeOffset? ThrottledUntil { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source last run information.
|
||||
/// </summary>
|
||||
internal sealed class SourceLastRun
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string? RunId { get; init; }
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public DateTimeOffset? StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("itemsProcessed")]
|
||||
public long? ItemsProcessed { get; init; }
|
||||
|
||||
[JsonPropertyName("itemsFailed")]
|
||||
public long? ItemsFailed { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
[JsonPropertyName("durationMs")]
|
||||
public long? DurationMs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source metrics summary.
|
||||
/// </summary>
|
||||
internal sealed class SourceMetrics
|
||||
{
|
||||
[JsonPropertyName("totalRuns")]
|
||||
public long TotalRuns { get; init; }
|
||||
|
||||
[JsonPropertyName("successfulRuns")]
|
||||
public long SuccessfulRuns { get; init; }
|
||||
|
||||
[JsonPropertyName("failedRuns")]
|
||||
public long FailedRuns { get; init; }
|
||||
|
||||
[JsonPropertyName("averageDurationMs")]
|
||||
public double? AverageDurationMs { get; init; }
|
||||
|
||||
[JsonPropertyName("totalItemsProcessed")]
|
||||
public long TotalItemsProcessed { get; init; }
|
||||
|
||||
[JsonPropertyName("totalItemsFailed")]
|
||||
public long TotalItemsFailed { get; init; }
|
||||
|
||||
[JsonPropertyName("lastSuccessAt")]
|
||||
public DateTimeOffset? LastSuccessAt { get; init; }
|
||||
|
||||
[JsonPropertyName("lastFailureAt")]
|
||||
public DateTimeOffset? LastFailureAt { get; init; }
|
||||
|
||||
[JsonPropertyName("uptimePercent")]
|
||||
public double? UptimePercent { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to list sources.
|
||||
/// </summary>
|
||||
internal sealed class SourceListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool? Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
public string? Host { get; init; }
|
||||
|
||||
[JsonPropertyName("tag")]
|
||||
public string? Tag { get; init; }
|
||||
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; init; } = 50;
|
||||
|
||||
[JsonPropertyName("pageToken")]
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from listing sources.
|
||||
/// </summary>
|
||||
internal sealed class SourceListResponse
|
||||
{
|
||||
[JsonPropertyName("sources")]
|
||||
public IReadOnlyList<OrchestratorSource> Sources { get; init; } = Array.Empty<OrchestratorSource>();
|
||||
|
||||
[JsonPropertyName("nextPageToken")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public long? TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to pause a source.
|
||||
/// </summary>
|
||||
internal sealed class SourcePauseRequest
|
||||
{
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("durationMinutes")]
|
||||
public int? DurationMinutes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to resume a source.
|
||||
/// </summary>
|
||||
internal sealed class SourceResumeRequest
|
||||
{
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to test a source connection.
|
||||
/// </summary>
|
||||
internal sealed class SourceTestRequest
|
||||
{
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("timeout")]
|
||||
public int TimeoutSeconds { get; init; } = 30;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of source operation.
|
||||
/// </summary>
|
||||
internal sealed class SourceOperationResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public OrchestratorSource? Source { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of source test operation.
|
||||
/// </summary>
|
||||
internal sealed class SourceTestResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reachable")]
|
||||
public bool Reachable { get; init; }
|
||||
|
||||
[JsonPropertyName("latencyMs")]
|
||||
public long? LatencyMs { get; init; }
|
||||
|
||||
[JsonPropertyName("statusCode")]
|
||||
public int? StatusCode { get; init; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
[JsonPropertyName("tlsValid")]
|
||||
public bool? TlsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("tlsExpiry")]
|
||||
public DateTimeOffset? TlsExpiry { get; init; }
|
||||
|
||||
[JsonPropertyName("testedAt")]
|
||||
public DateTimeOffset TestedAt { get; init; }
|
||||
}
|
||||
|
||||
// CLI-ORCH-34-001: Backfill wizard and quota management models
|
||||
|
||||
/// <summary>
|
||||
/// Request to start a backfill operation for a source.
|
||||
/// </summary>
|
||||
internal sealed class BackfillRequest
|
||||
{
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("from")]
|
||||
public DateTimeOffset From { get; init; }
|
||||
|
||||
[JsonPropertyName("to")]
|
||||
public DateTimeOffset To { get; init; }
|
||||
|
||||
[JsonPropertyName("dryRun")]
|
||||
public bool DryRun { get; init; }
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int Priority { get; init; } = 5;
|
||||
|
||||
[JsonPropertyName("concurrency")]
|
||||
public int Concurrency { get; init; } = 1;
|
||||
|
||||
[JsonPropertyName("batchSize")]
|
||||
public int BatchSize { get; init; } = 100;
|
||||
|
||||
[JsonPropertyName("resume")]
|
||||
public bool Resume { get; init; }
|
||||
|
||||
[JsonPropertyName("filter")]
|
||||
public string? Filter { get; init; }
|
||||
|
||||
[JsonPropertyName("force")]
|
||||
public bool Force { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a backfill operation.
|
||||
/// </summary>
|
||||
internal sealed class BackfillResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("backfillId")]
|
||||
public string? BackfillId { get; init; }
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("from")]
|
||||
public DateTimeOffset From { get; init; }
|
||||
|
||||
[JsonPropertyName("to")]
|
||||
public DateTimeOffset To { get; init; }
|
||||
|
||||
[JsonPropertyName("dryRun")]
|
||||
public bool DryRun { get; init; }
|
||||
|
||||
[JsonPropertyName("estimatedItems")]
|
||||
public long? EstimatedItems { get; init; }
|
||||
|
||||
[JsonPropertyName("processedItems")]
|
||||
public long ProcessedItems { get; init; }
|
||||
|
||||
[JsonPropertyName("failedItems")]
|
||||
public long FailedItems { get; init; }
|
||||
|
||||
[JsonPropertyName("skippedItems")]
|
||||
public long SkippedItems { get; init; }
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public DateTimeOffset? StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("estimatedDurationMs")]
|
||||
public long? EstimatedDurationMs { get; init; }
|
||||
|
||||
[JsonPropertyName("actualDurationMs")]
|
||||
public long? ActualDurationMs { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status values for backfill operations.
|
||||
/// </summary>
|
||||
internal static class BackfillStatuses
|
||||
{
|
||||
public const string Pending = "pending";
|
||||
public const string Running = "running";
|
||||
public const string Completed = "completed";
|
||||
public const string Failed = "failed";
|
||||
public const string Cancelled = "cancelled";
|
||||
public const string DryRun = "dry_run";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to list backfill operations.
|
||||
/// </summary>
|
||||
internal sealed class BackfillListRequest
|
||||
{
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string? SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; init; } = 20;
|
||||
|
||||
[JsonPropertyName("pageToken")]
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from listing backfill operations.
|
||||
/// </summary>
|
||||
internal sealed class BackfillListResponse
|
||||
{
|
||||
[JsonPropertyName("backfills")]
|
||||
public IReadOnlyList<BackfillResult> Backfills { get; init; } = Array.Empty<BackfillResult>();
|
||||
|
||||
[JsonPropertyName("nextPageToken")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public long? TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to cancel a backfill operation.
|
||||
/// </summary>
|
||||
internal sealed class BackfillCancelRequest
|
||||
{
|
||||
[JsonPropertyName("backfillId")]
|
||||
public string BackfillId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quota resource representing usage limits for a tenant/source.
|
||||
/// </summary>
|
||||
internal sealed class OrchestratorQuota
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string? SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("resourceType")]
|
||||
public string ResourceType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public long Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("used")]
|
||||
public long Used { get; init; }
|
||||
|
||||
[JsonPropertyName("remaining")]
|
||||
public long Remaining { get; init; }
|
||||
|
||||
[JsonPropertyName("period")]
|
||||
public string Period { get; init; } = "monthly";
|
||||
|
||||
[JsonPropertyName("periodStart")]
|
||||
public DateTimeOffset PeriodStart { get; init; }
|
||||
|
||||
[JsonPropertyName("periodEnd")]
|
||||
public DateTimeOffset PeriodEnd { get; init; }
|
||||
|
||||
[JsonPropertyName("resetAt")]
|
||||
public DateTimeOffset ResetAt { get; init; }
|
||||
|
||||
[JsonPropertyName("warningThreshold")]
|
||||
public double WarningThreshold { get; init; } = 0.8;
|
||||
|
||||
[JsonPropertyName("isWarning")]
|
||||
public bool IsWarning { get; init; }
|
||||
|
||||
[JsonPropertyName("isExceeded")]
|
||||
public bool IsExceeded { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quota resource types.
|
||||
/// </summary>
|
||||
internal static class QuotaResourceTypes
|
||||
{
|
||||
public const string ApiCalls = "api_calls";
|
||||
public const string DataIngested = "data_ingested_bytes";
|
||||
public const string ItemsProcessed = "items_processed";
|
||||
public const string Backfills = "backfills";
|
||||
public const string ConcurrentJobs = "concurrent_jobs";
|
||||
public const string Storage = "storage_bytes";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quota period types.
|
||||
/// </summary>
|
||||
internal static class QuotaPeriods
|
||||
{
|
||||
public const string Hourly = "hourly";
|
||||
public const string Daily = "daily";
|
||||
public const string Weekly = "weekly";
|
||||
public const string Monthly = "monthly";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to get quotas.
|
||||
/// </summary>
|
||||
internal sealed class QuotaGetRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string? SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("resourceType")]
|
||||
public string? ResourceType { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from getting quotas.
|
||||
/// </summary>
|
||||
internal sealed class QuotaGetResponse
|
||||
{
|
||||
[JsonPropertyName("quotas")]
|
||||
public IReadOnlyList<OrchestratorQuota> Quotas { get; init; } = Array.Empty<OrchestratorQuota>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to set a quota limit.
|
||||
/// </summary>
|
||||
internal sealed class QuotaSetRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string? SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("resourceType")]
|
||||
public string ResourceType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public long Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("period")]
|
||||
public string Period { get; init; } = QuotaPeriods.Monthly;
|
||||
|
||||
[JsonPropertyName("warningThreshold")]
|
||||
public double WarningThreshold { get; init; } = 0.8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a quota operation.
|
||||
/// </summary>
|
||||
internal sealed class QuotaOperationResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("quota")]
|
||||
public OrchestratorQuota? Quota { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to reset a quota's usage counter.
|
||||
/// </summary>
|
||||
internal sealed class QuotaResetRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sourceId")]
|
||||
public string? SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("resourceType")]
|
||||
public string ResourceType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
915
src/Cli/StellaOps.Cli/Services/Models/PackModels.cs
Normal file
915
src/Cli/StellaOps.Cli/Services/Models/PackModels.cs
Normal file
@@ -0,0 +1,915 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-PACKS-42-001: Task Pack models for stella pack commands
|
||||
|
||||
/// <summary>
|
||||
/// Task pack metadata from the registry.
|
||||
/// </summary>
|
||||
internal sealed class TaskPackInfo
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("author")]
|
||||
public string? Author { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public PackSignature? Signature { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("labels")]
|
||||
public IReadOnlyDictionary<string, string> Labels { get; init; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyList<PackInputSchema> Inputs { get; init; } = Array.Empty<PackInputSchema>();
|
||||
|
||||
[JsonPropertyName("outputs")]
|
||||
public IReadOnlyList<PackOutputSchema> Outputs { get; init; } = Array.Empty<PackOutputSchema>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack signature information.
|
||||
/// </summary>
|
||||
internal sealed class PackSignature
|
||||
{
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = string.Empty; // ecdsa-p256, rsa-pkcs1-sha256, etc.
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("certificate")]
|
||||
public string? Certificate { get; init; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset? Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorLogId")]
|
||||
public string? RekorLogId { get; init; }
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack input parameter schema.
|
||||
/// </summary>
|
||||
internal sealed class PackInputSchema
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = "string"; // string, number, boolean, array, object
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
public bool Required { get; init; }
|
||||
|
||||
[JsonPropertyName("default")]
|
||||
public object? Default { get; init; }
|
||||
|
||||
[JsonPropertyName("enum")]
|
||||
public IReadOnlyList<string>? Enum { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack output schema.
|
||||
/// </summary>
|
||||
internal sealed class PackOutputSchema
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = "string";
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to plan a pack execution.
|
||||
/// </summary>
|
||||
internal sealed class PackPlanRequest
|
||||
{
|
||||
[JsonPropertyName("packId")]
|
||||
public string PackId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyDictionary<string, object>? Inputs { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("dryRun")]
|
||||
public bool DryRun { get; init; }
|
||||
|
||||
[JsonPropertyName("validateOnly")]
|
||||
public bool ValidateOnly { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execution plan step.
|
||||
/// </summary>
|
||||
internal sealed class PackPlanStep
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("dependsOn")]
|
||||
public IReadOnlyList<string> DependsOn { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("condition")]
|
||||
public string? Condition { get; init; }
|
||||
|
||||
[JsonPropertyName("timeout")]
|
||||
public TimeSpan? Timeout { get; init; }
|
||||
|
||||
[JsonPropertyName("retryPolicy")]
|
||||
public PackRetryPolicy? RetryPolicy { get; init; }
|
||||
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyDictionary<string, object>? Inputs { get; init; }
|
||||
|
||||
[JsonPropertyName("requiresApproval")]
|
||||
public bool RequiresApproval { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retry policy for pack steps.
|
||||
/// </summary>
|
||||
internal sealed class PackRetryPolicy
|
||||
{
|
||||
[JsonPropertyName("maxAttempts")]
|
||||
public int MaxAttempts { get; init; } = 1;
|
||||
|
||||
[JsonPropertyName("backoffMultiplier")]
|
||||
public double BackoffMultiplier { get; init; } = 2.0;
|
||||
|
||||
[JsonPropertyName("initialDelayMs")]
|
||||
public int InitialDelayMs { get; init; } = 1000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack plan operation.
|
||||
/// </summary>
|
||||
internal sealed class PackPlanResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("planId")]
|
||||
public string? PlanId { get; init; }
|
||||
|
||||
[JsonPropertyName("planHash")]
|
||||
public string? PlanHash { get; init; }
|
||||
|
||||
[JsonPropertyName("steps")]
|
||||
public IReadOnlyList<PackPlanStep> Steps { get; init; } = Array.Empty<PackPlanStep>();
|
||||
|
||||
[JsonPropertyName("requiresApproval")]
|
||||
public bool RequiresApproval { get; init; }
|
||||
|
||||
[JsonPropertyName("approvalGates")]
|
||||
public IReadOnlyList<string> ApprovalGates { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("estimatedDuration")]
|
||||
public TimeSpan? EstimatedDuration { get; init; }
|
||||
|
||||
[JsonPropertyName("validationErrors")]
|
||||
public IReadOnlyList<PackValidationError> ValidationErrors { get; init; } = Array.Empty<PackValidationError>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation error from pack plan/verify.
|
||||
/// </summary>
|
||||
internal sealed class PackValidationError
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string? Path { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string Severity { get; init; } = "error"; // error, warning
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to run a pack.
|
||||
/// </summary>
|
||||
internal sealed class PackRunRequest
|
||||
{
|
||||
[JsonPropertyName("packId")]
|
||||
public string PackId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("planId")]
|
||||
public string? PlanId { get; init; }
|
||||
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyDictionary<string, object>? Inputs { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("labels")]
|
||||
public IReadOnlyDictionary<string, string>? Labels { get; init; }
|
||||
|
||||
[JsonPropertyName("waitForCompletion")]
|
||||
public bool WaitForCompletion { get; init; }
|
||||
|
||||
[JsonPropertyName("timeoutMinutes")]
|
||||
public int TimeoutMinutes { get; init; } = 60;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack run status.
|
||||
/// </summary>
|
||||
internal sealed class PackRunStatus
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("packId")]
|
||||
public string PackId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = "pending"; // pending, running, succeeded, failed, cancelled, waiting_approval
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public DateTimeOffset? StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("duration")]
|
||||
public TimeSpan? Duration { get; init; }
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string? Actor { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("currentStep")]
|
||||
public string? CurrentStep { get; init; }
|
||||
|
||||
[JsonPropertyName("stepStatuses")]
|
||||
public IReadOnlyList<PackStepStatus> StepStatuses { get; init; } = Array.Empty<PackStepStatus>();
|
||||
|
||||
[JsonPropertyName("outputs")]
|
||||
public IReadOnlyDictionary<string, object>? Outputs { get; init; }
|
||||
|
||||
[JsonPropertyName("artifacts")]
|
||||
public IReadOnlyList<PackArtifact> Artifacts { get; init; } = Array.Empty<PackArtifact>();
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status of individual pack step.
|
||||
/// </summary>
|
||||
internal sealed class PackStepStatus
|
||||
{
|
||||
[JsonPropertyName("stepId")]
|
||||
public string StepId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = "pending"; // pending, running, succeeded, failed, skipped
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public DateTimeOffset? StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("duration")]
|
||||
public TimeSpan? Duration { get; init; }
|
||||
|
||||
[JsonPropertyName("attempt")]
|
||||
public int Attempt { get; init; } = 1;
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
|
||||
[JsonPropertyName("outputs")]
|
||||
public IReadOnlyDictionary<string, object>? Outputs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Artifact produced by pack run.
|
||||
/// </summary>
|
||||
internal sealed class PackArtifact
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty; // log, sbom, report, attestation
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long Size { get; init; }
|
||||
|
||||
[JsonPropertyName("uri")]
|
||||
public string? Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack run operation.
|
||||
/// </summary>
|
||||
internal sealed class PackRunResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("run")]
|
||||
public PackRunStatus? Run { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to push a pack to the registry.
|
||||
/// </summary>
|
||||
internal sealed class PackPushRequest
|
||||
{
|
||||
[JsonPropertyName("packPath")]
|
||||
public string PackPath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("sign")]
|
||||
public bool Sign { get; init; }
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("force")]
|
||||
public bool Force { get; init; }
|
||||
|
||||
[JsonPropertyName("labels")]
|
||||
public IReadOnlyDictionary<string, string>? Labels { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack push operation.
|
||||
/// </summary>
|
||||
internal sealed class PackPushResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("pack")]
|
||||
public TaskPackInfo? Pack { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorLogId")]
|
||||
public string? RekorLogId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to pull a pack from the registry.
|
||||
/// </summary>
|
||||
internal sealed class PackPullRequest
|
||||
{
|
||||
[JsonPropertyName("packId")]
|
||||
public string PackId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("verify")]
|
||||
public bool Verify { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack pull operation.
|
||||
/// </summary>
|
||||
internal sealed class PackPullResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("pack")]
|
||||
public TaskPackInfo? Pack { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to verify a pack.
|
||||
/// </summary>
|
||||
internal sealed class PackVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("packPath")]
|
||||
public string? PackPath { get; init; }
|
||||
|
||||
[JsonPropertyName("packId")]
|
||||
public string? PackId { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("checkRekor")]
|
||||
public bool CheckRekor { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("checkExpiry")]
|
||||
public bool CheckExpiry { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack verify operation.
|
||||
/// </summary>
|
||||
internal sealed class PackVerifyResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("pack")]
|
||||
public TaskPackInfo? Pack { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureValid")]
|
||||
public bool SignatureValid { get; init; }
|
||||
|
||||
[JsonPropertyName("digestMatch")]
|
||||
public bool DigestMatch { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorVerified")]
|
||||
public bool? RekorVerified { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateValid")]
|
||||
public bool? CertificateValid { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateExpiry")]
|
||||
public DateTimeOffset? CertificateExpiry { get; init; }
|
||||
|
||||
[JsonPropertyName("schemaValid")]
|
||||
public bool SchemaValid { get; init; }
|
||||
|
||||
[JsonPropertyName("validationErrors")]
|
||||
public IReadOnlyList<PackValidationError> ValidationErrors { get; init; } = Array.Empty<PackValidationError>();
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
// CLI-PACKS-43-001: Advanced pack features
|
||||
|
||||
/// <summary>
|
||||
/// Request to pause a pack run waiting for approval.
|
||||
/// </summary>
|
||||
internal sealed class PackApprovalPauseRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("stepId")]
|
||||
public string? StepId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to resume a paused pack run with approval.
|
||||
/// </summary>
|
||||
internal sealed class PackApprovalResumeRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("approved")]
|
||||
public bool Approved { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("stepId")]
|
||||
public string? StepId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of an approval operation.
|
||||
/// </summary>
|
||||
internal sealed class PackApprovalResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("run")]
|
||||
public PackRunStatus? Run { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to inject a secret into a pack run.
|
||||
/// </summary>
|
||||
internal sealed class PackSecretInjectRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("secretRef")]
|
||||
public string SecretRef { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("secretProvider")]
|
||||
public string SecretProvider { get; init; } = "vault"; // vault, aws-ssm, azure-keyvault, k8s-secret
|
||||
|
||||
[JsonPropertyName("targetEnvVar")]
|
||||
public string? TargetEnvVar { get; init; }
|
||||
|
||||
[JsonPropertyName("targetPath")]
|
||||
public string? TargetPath { get; init; }
|
||||
|
||||
[JsonPropertyName("stepId")]
|
||||
public string? StepId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a secret injection operation.
|
||||
/// </summary>
|
||||
internal sealed class PackSecretInjectResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("secretRef")]
|
||||
public string SecretRef { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("injectedAt")]
|
||||
public DateTimeOffset InjectedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("auditEventId")]
|
||||
public string? AuditEventId { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to list pack runs.
|
||||
/// </summary>
|
||||
internal sealed class PackRunListRequest
|
||||
{
|
||||
[JsonPropertyName("packId")]
|
||||
public string? PackId { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string? Actor { get; init; }
|
||||
|
||||
[JsonPropertyName("since")]
|
||||
public DateTimeOffset? Since { get; init; }
|
||||
|
||||
[JsonPropertyName("until")]
|
||||
public DateTimeOffset? Until { get; init; }
|
||||
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; init; } = 20;
|
||||
|
||||
[JsonPropertyName("pageToken")]
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from listing pack runs.
|
||||
/// </summary>
|
||||
internal sealed class PackRunListResponse
|
||||
{
|
||||
[JsonPropertyName("runs")]
|
||||
public IReadOnlyList<PackRunStatus> Runs { get; init; } = Array.Empty<PackRunStatus>();
|
||||
|
||||
[JsonPropertyName("nextPageToken")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("totalCount")]
|
||||
public long? TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to cancel a pack run.
|
||||
/// </summary>
|
||||
internal sealed class PackCancelRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("force")]
|
||||
public bool Force { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to get pack run logs.
|
||||
/// </summary>
|
||||
internal sealed class PackLogsRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("stepId")]
|
||||
public string? StepId { get; init; }
|
||||
|
||||
[JsonPropertyName("follow")]
|
||||
public bool Follow { get; init; }
|
||||
|
||||
[JsonPropertyName("tail")]
|
||||
public int? Tail { get; init; }
|
||||
|
||||
[JsonPropertyName("since")]
|
||||
public DateTimeOffset? Since { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack log entry.
|
||||
/// </summary>
|
||||
internal sealed class PackLogEntry
|
||||
{
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("stepId")]
|
||||
public string? StepId { get; init; }
|
||||
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; init; } = "info"; // debug, info, warn, error
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("stream")]
|
||||
public string Stream { get; init; } = "stdout"; // stdout, stderr
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of pack logs request.
|
||||
/// </summary>
|
||||
internal sealed class PackLogsResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("logs")]
|
||||
public IReadOnlyList<PackLogEntry> Logs { get; init; } = Array.Empty<PackLogEntry>();
|
||||
|
||||
[JsonPropertyName("nextToken")]
|
||||
public string? NextToken { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to download a pack artifact.
|
||||
/// </summary>
|
||||
internal sealed class PackArtifactDownloadRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public string RunId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("artifactName")]
|
||||
public string ArtifactName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of artifact download.
|
||||
/// </summary>
|
||||
internal sealed class PackArtifactDownloadResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("artifact")]
|
||||
public PackArtifact? Artifact { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offline cache entry for packs.
|
||||
/// </summary>
|
||||
internal sealed class PackCacheEntry
|
||||
{
|
||||
[JsonPropertyName("packId")]
|
||||
public string PackId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("cachedAt")]
|
||||
public DateTimeOffset CachedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("expiresAt")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long Size { get; init; }
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to manage offline cache.
|
||||
/// </summary>
|
||||
internal sealed class PackCacheRequest
|
||||
{
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; init; } = "list"; // list, add, remove, sync, prune
|
||||
|
||||
[JsonPropertyName("packId")]
|
||||
public string? PackId { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("cacheDir")]
|
||||
public string? CacheDir { get; init; }
|
||||
|
||||
[JsonPropertyName("maxAge")]
|
||||
public TimeSpan? MaxAge { get; init; }
|
||||
|
||||
[JsonPropertyName("maxSize")]
|
||||
public long? MaxSize { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of cache operation.
|
||||
/// </summary>
|
||||
internal sealed class PackCacheResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("entries")]
|
||||
public IReadOnlyList<PackCacheEntry> Entries { get; init; } = Array.Empty<PackCacheEntry>();
|
||||
|
||||
[JsonPropertyName("totalSize")]
|
||||
public long TotalSize { get; init; }
|
||||
|
||||
[JsonPropertyName("prunedCount")]
|
||||
public int PrunedCount { get; init; }
|
||||
|
||||
[JsonPropertyName("prunedSize")]
|
||||
public long PrunedSize { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -2,16 +2,39 @@ using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-POLICY-27-003: Enhanced simulation modes
|
||||
internal enum PolicySimulationMode
|
||||
{
|
||||
Quick,
|
||||
Batch
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input for policy simulation.
|
||||
/// Per CLI-EXC-25-002, supports exception preview via WithExceptions/WithoutExceptions.
|
||||
/// Per CLI-POLICY-27-003, supports mode (quick/batch), SBOM selectors, heatmap, and manifest download.
|
||||
/// Per CLI-SIG-26-002, supports reachability overrides for vulnerability/package state and score.
|
||||
/// </summary>
|
||||
internal sealed record PolicySimulationInput(
|
||||
int? BaseVersion,
|
||||
int? CandidateVersion,
|
||||
IReadOnlyList<string> SbomSet,
|
||||
IReadOnlyDictionary<string, object?> Environment,
|
||||
bool Explain);
|
||||
bool Explain,
|
||||
IReadOnlyList<string>? WithExceptions = null,
|
||||
IReadOnlyList<string>? WithoutExceptions = null,
|
||||
PolicySimulationMode? Mode = null,
|
||||
IReadOnlyList<string>? SbomSelectors = null,
|
||||
bool IncludeHeatmap = false,
|
||||
bool IncludeManifest = false,
|
||||
IReadOnlyList<ReachabilityOverride>? ReachabilityOverrides = null);
|
||||
|
||||
internal sealed record PolicySimulationResult(
|
||||
PolicySimulationDiff Diff,
|
||||
string? ExplainUri);
|
||||
string? ExplainUri,
|
||||
PolicySimulationHeatmap? Heatmap = null,
|
||||
string? ManifestDownloadUri = null,
|
||||
string? ManifestDigest = null);
|
||||
|
||||
internal sealed record PolicySimulationDiff(
|
||||
string? SchemaVersion,
|
||||
@@ -24,3 +47,17 @@ internal sealed record PolicySimulationDiff(
|
||||
internal sealed record PolicySimulationSeverityDelta(int? Up, int? Down);
|
||||
|
||||
internal sealed record PolicySimulationRuleDelta(string RuleId, string RuleName, int? Up, int? Down);
|
||||
|
||||
// CLI-POLICY-27-003: Heatmap summary for quick severity visualization
|
||||
internal sealed record PolicySimulationHeatmap(
|
||||
int Critical,
|
||||
int High,
|
||||
int Medium,
|
||||
int Low,
|
||||
int Info,
|
||||
IReadOnlyList<PolicySimulationHeatmapBucket> Buckets);
|
||||
|
||||
internal sealed record PolicySimulationHeatmapBucket(
|
||||
string Label,
|
||||
int Count,
|
||||
string? Color);
|
||||
|
||||
1211
src/Cli/StellaOps.Cli/Services/Models/PolicyWorkspaceModels.cs
Normal file
1211
src/Cli/StellaOps.Cli/Services/Models/PolicyWorkspaceModels.cs
Normal file
File diff suppressed because it is too large
Load Diff
468
src/Cli/StellaOps.Cli/Services/Models/PromotionModels.cs
Normal file
468
src/Cli/StellaOps.Cli/Services/Models/PromotionModels.cs
Normal file
@@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-PROMO-70-001: Promotion attestation models
|
||||
|
||||
/// <summary>
|
||||
/// Request for assembling a promotion attestation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionAssembleRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("vexPath")]
|
||||
public string? VexPath { get; init; }
|
||||
|
||||
[JsonPropertyName("fromEnvironment")]
|
||||
public string FromEnvironment { get; init; } = "staging";
|
||||
|
||||
[JsonPropertyName("toEnvironment")]
|
||||
public string ToEnvironment { get; init; } = "prod";
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string? Actor { get; init; }
|
||||
|
||||
[JsonPropertyName("pipeline")]
|
||||
public string? Pipeline { get; init; }
|
||||
|
||||
[JsonPropertyName("ticket")]
|
||||
public string? Ticket { get; init; }
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
|
||||
[JsonPropertyName("skipRekor")]
|
||||
public bool SkipRekor { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Promotion attestation predicate following stella.ops/promotion@v1 schema.
|
||||
/// </summary>
|
||||
internal sealed class PromotionPredicate
|
||||
{
|
||||
[JsonPropertyName("_type")]
|
||||
public string Type { get; init; } = "stella.ops/promotion@v1";
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public IReadOnlyList<PromotionSubject> Subject { get; init; } = Array.Empty<PromotionSubject>();
|
||||
|
||||
[JsonPropertyName("materials")]
|
||||
public IReadOnlyList<PromotionMaterial> Materials { get; init; } = Array.Empty<PromotionMaterial>();
|
||||
|
||||
[JsonPropertyName("promotion")]
|
||||
public PromotionMetadata Promotion { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("rekor")]
|
||||
public PromotionRekorEntry? Rekor { get; init; }
|
||||
|
||||
[JsonPropertyName("attestation")]
|
||||
public PromotionAttestationMetadata? Attestation { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subject in promotion attestation (image reference).
|
||||
/// </summary>
|
||||
internal sealed class PromotionSubject
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public IReadOnlyDictionary<string, string> Digest { get; init; } = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Material in promotion attestation (SBOM, VEX, etc.).
|
||||
/// </summary>
|
||||
internal sealed class PromotionMaterial
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("algo")]
|
||||
public string Algo { get; init; } = "sha256";
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; init; }
|
||||
|
||||
[JsonPropertyName("uri")]
|
||||
public string? Uri { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Promotion metadata.
|
||||
/// </summary>
|
||||
internal sealed class PromotionMetadata
|
||||
{
|
||||
[JsonPropertyName("from")]
|
||||
public string From { get; init; } = "staging";
|
||||
|
||||
[JsonPropertyName("to")]
|
||||
public string To { get; init; } = "prod";
|
||||
|
||||
[JsonPropertyName("actor")]
|
||||
public string? Actor { get; init; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
[JsonPropertyName("pipeline")]
|
||||
public string? Pipeline { get; init; }
|
||||
|
||||
[JsonPropertyName("ticket")]
|
||||
public string? Ticket { get; init; }
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rekor entry in promotion attestation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionRekorEntry
|
||||
{
|
||||
[JsonPropertyName("uuid")]
|
||||
public string Uuid { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("logIndex")]
|
||||
public long LogIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("inclusionProof")]
|
||||
public PromotionInclusionProof? InclusionProof { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkle inclusion proof.
|
||||
/// </summary>
|
||||
internal sealed class PromotionInclusionProof
|
||||
{
|
||||
[JsonPropertyName("rootHash")]
|
||||
public string RootHash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("hashes")]
|
||||
public IReadOnlyList<string> Hashes { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("treeSize")]
|
||||
public long TreeSize { get; init; }
|
||||
|
||||
[JsonPropertyName("checkpoint")]
|
||||
public PromotionCheckpoint? Checkpoint { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rekor checkpoint.
|
||||
/// </summary>
|
||||
internal sealed class PromotionCheckpoint
|
||||
{
|
||||
[JsonPropertyName("origin")]
|
||||
public string Origin { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long Size { get; init; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signedNote")]
|
||||
public string? SignedNote { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attestation metadata.
|
||||
/// </summary>
|
||||
internal sealed class PromotionAttestationMetadata
|
||||
{
|
||||
[JsonPropertyName("bundle_sha256")]
|
||||
public string BundleSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("witness")]
|
||||
public string? Witness { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of promotion assemble operation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionAssembleResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate")]
|
||||
public PromotionPredicate? Predicate { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("imageDigest")]
|
||||
public string ImageDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("materials")]
|
||||
public IReadOnlyList<PromotionMaterial> Materials { get; init; } = Array.Empty<PromotionMaterial>();
|
||||
|
||||
[JsonPropertyName("rekorEntry")]
|
||||
public PromotionRekorEntry? RekorEntry { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
// CLI-PROMO-70-002: Promotion attest/verify models
|
||||
|
||||
/// <summary>
|
||||
/// Request for attesting a promotion predicate.
|
||||
/// </summary>
|
||||
internal sealed class PromotionAttestRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("predicatePath")]
|
||||
public string? PredicatePath { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate")]
|
||||
public PromotionPredicate? Predicate { get; init; }
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("useKeyless")]
|
||||
public bool UseKeyless { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("uploadToRekor")]
|
||||
public bool UploadToRekor { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of promotion attest operation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionAttestResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("bundlePath")]
|
||||
public string? BundlePath { get; init; }
|
||||
|
||||
[JsonPropertyName("dsseEnvelope")]
|
||||
public DsseEnvelope? DsseEnvelope { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorEntry")]
|
||||
public PromotionRekorEntry? RekorEntry { get; init; }
|
||||
|
||||
[JsonPropertyName("auditId")]
|
||||
public string? AuditId { get; init; }
|
||||
|
||||
[JsonPropertyName("signerKeyId")]
|
||||
public string? SignerKeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("signedAt")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope for promotion attestation.
|
||||
/// </summary>
|
||||
internal sealed class DsseEnvelope
|
||||
{
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = "application/vnd.in-toto+json";
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public string Payload { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<DsseSignature> Signatures { get; init; } = Array.Empty<DsseSignature>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature.
|
||||
/// </summary>
|
||||
internal sealed class DsseSignature
|
||||
{
|
||||
[JsonPropertyName("keyid")]
|
||||
public string KeyId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sig")]
|
||||
public string Sig { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("cert")]
|
||||
public string? Cert { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for verifying a promotion attestation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("bundlePath")]
|
||||
public string? BundlePath { get; init; }
|
||||
|
||||
[JsonPropertyName("predicatePath")]
|
||||
public string? PredicatePath { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("vexPath")]
|
||||
public string? VexPath { get; init; }
|
||||
|
||||
[JsonPropertyName("trustRootPath")]
|
||||
public string? TrustRootPath { get; init; }
|
||||
|
||||
[JsonPropertyName("checkpointPath")]
|
||||
public string? CheckpointPath { get; init; }
|
||||
|
||||
[JsonPropertyName("skipRekorVerification")]
|
||||
public bool SkipRekorVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("skipSignatureVerification")]
|
||||
public bool SkipSignatureVerification { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of promotion verify operation.
|
||||
/// </summary>
|
||||
internal sealed class PromotionVerifyResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureVerification")]
|
||||
public PromotionSignatureVerification? SignatureVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("materialVerification")]
|
||||
public PromotionMaterialVerification? MaterialVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorVerification")]
|
||||
public PromotionRekorVerification? RekorVerification { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate")]
|
||||
public PromotionPredicate? Predicate { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature verification result.
|
||||
/// </summary>
|
||||
internal sealed class PromotionSignatureVerification
|
||||
{
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string? Algorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("certSubject")]
|
||||
public string? CertSubject { get; init; }
|
||||
|
||||
[JsonPropertyName("certIssuer")]
|
||||
public string? CertIssuer { get; init; }
|
||||
|
||||
[JsonPropertyName("validFrom")]
|
||||
public DateTimeOffset? ValidFrom { get; init; }
|
||||
|
||||
[JsonPropertyName("validTo")]
|
||||
public DateTimeOffset? ValidTo { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Material verification result.
|
||||
/// </summary>
|
||||
internal sealed class PromotionMaterialVerification
|
||||
{
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("materials")]
|
||||
public IReadOnlyList<PromotionMaterialVerificationEntry> Materials { get; init; } = Array.Empty<PromotionMaterialVerificationEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual material verification entry.
|
||||
/// </summary>
|
||||
internal sealed class PromotionMaterialVerificationEntry
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("expectedDigest")]
|
||||
public string ExpectedDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("actualDigest")]
|
||||
public string? ActualDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rekor verification result.
|
||||
/// </summary>
|
||||
internal sealed class PromotionRekorVerification
|
||||
{
|
||||
[JsonPropertyName("verified")]
|
||||
public bool Verified { get; init; }
|
||||
|
||||
[JsonPropertyName("uuid")]
|
||||
public string? Uuid { get; init; }
|
||||
|
||||
[JsonPropertyName("logIndex")]
|
||||
public long? LogIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("inclusionProofVerified")]
|
||||
public bool InclusionProofVerified { get; init; }
|
||||
|
||||
[JsonPropertyName("checkpointVerified")]
|
||||
public bool CheckpointVerified { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
252
src/Cli/StellaOps.Cli/Services/Models/ReachabilityModels.cs
Normal file
252
src/Cli/StellaOps.Cli/Services/Models/ReachabilityModels.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-SIG-26-001: Reachability command models
|
||||
|
||||
/// <summary>
|
||||
/// Request to upload a call graph for reachability analysis.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityUploadCallGraphRequest
|
||||
{
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string? AssetId { get; init; }
|
||||
|
||||
[JsonPropertyName("callGraphPath")]
|
||||
public string CallGraphPath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of uploading a call graph.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityUploadCallGraphResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("callGraphId")]
|
||||
public string CallGraphId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("entriesProcessed")]
|
||||
public int EntriesProcessed { get; init; }
|
||||
|
||||
[JsonPropertyName("errorsCount")]
|
||||
public int ErrorsCount { get; init; }
|
||||
|
||||
[JsonPropertyName("uploadedAt")]
|
||||
public DateTimeOffset UploadedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to list reachability analyses.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityListRequest
|
||||
{
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string? AssetId { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response containing reachability analyses.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityListResponse
|
||||
{
|
||||
[JsonPropertyName("analyses")]
|
||||
public IReadOnlyList<ReachabilityAnalysisSummary> Analyses { get; init; } = Array.Empty<ReachabilityAnalysisSummary>();
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of a reachability analysis.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityAnalysisSummary
|
||||
{
|
||||
[JsonPropertyName("analysisId")]
|
||||
public string AnalysisId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string? AssetId { get; init; }
|
||||
|
||||
[JsonPropertyName("assetName")]
|
||||
public string? AssetName { get; init; }
|
||||
|
||||
[JsonPropertyName("callGraphId")]
|
||||
public string CallGraphId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reachableCount")]
|
||||
public int ReachableCount { get; init; }
|
||||
|
||||
[JsonPropertyName("unreachableCount")]
|
||||
public int UnreachableCount { get; init; }
|
||||
|
||||
[JsonPropertyName("unknownCount")]
|
||||
public int UnknownCount { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to explain reachability for a specific vulnerability or package.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityExplainRequest
|
||||
{
|
||||
[JsonPropertyName("analysisId")]
|
||||
public string AnalysisId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string? VulnerabilityId { get; init; }
|
||||
|
||||
[JsonPropertyName("packagePurl")]
|
||||
public string? PackagePurl { get; init; }
|
||||
|
||||
[JsonPropertyName("includeCallPaths")]
|
||||
public bool IncludeCallPaths { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of reachability explanation.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityExplainResult
|
||||
{
|
||||
[JsonPropertyName("analysisId")]
|
||||
public string AnalysisId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string? VulnerabilityId { get; init; }
|
||||
|
||||
[JsonPropertyName("packagePurl")]
|
||||
public string? PackagePurl { get; init; }
|
||||
|
||||
[JsonPropertyName("reachabilityState")]
|
||||
public string ReachabilityState { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reachabilityScore")]
|
||||
public double? ReachabilityScore { get; init; }
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public string Confidence { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reasoning")]
|
||||
public string? Reasoning { get; init; }
|
||||
|
||||
[JsonPropertyName("callPaths")]
|
||||
public IReadOnlyList<ReachabilityCallPath> CallPaths { get; init; } = Array.Empty<ReachabilityCallPath>();
|
||||
|
||||
[JsonPropertyName("affectedFunctions")]
|
||||
public IReadOnlyList<ReachabilityFunction> AffectedFunctions { get; init; } = Array.Empty<ReachabilityFunction>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call path demonstrating reachability.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityCallPath
|
||||
{
|
||||
[JsonPropertyName("pathId")]
|
||||
public string PathId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("depth")]
|
||||
public int Depth { get; init; }
|
||||
|
||||
[JsonPropertyName("entryPoint")]
|
||||
public ReachabilityFunction EntryPoint { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("frames")]
|
||||
public IReadOnlyList<ReachabilityFunction> Frames { get; init; } = Array.Empty<ReachabilityFunction>();
|
||||
|
||||
[JsonPropertyName("vulnerableFunction")]
|
||||
public ReachabilityFunction VulnerableFunction { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function in the call graph.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityFunction
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public string? Signature { get; init; }
|
||||
|
||||
[JsonPropertyName("className")]
|
||||
public string? ClassName { get; init; }
|
||||
|
||||
[JsonPropertyName("packageName")]
|
||||
public string? PackageName { get; init; }
|
||||
|
||||
[JsonPropertyName("filePath")]
|
||||
public string? FilePath { get; init; }
|
||||
|
||||
[JsonPropertyName("lineNumber")]
|
||||
public int? LineNumber { get; init; }
|
||||
}
|
||||
|
||||
// CLI-SIG-26-002: Policy simulate reachability override models (extends PolicySimulationInput)
|
||||
|
||||
/// <summary>
|
||||
/// Reachability override for policy simulation.
|
||||
/// </summary>
|
||||
internal sealed class ReachabilityOverride
|
||||
{
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string? VulnerabilityId { get; init; }
|
||||
|
||||
[JsonPropertyName("packagePurl")]
|
||||
public string? PackagePurl { get; init; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public string State { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double? Score { get; init; }
|
||||
}
|
||||
448
src/Cli/StellaOps.Cli/Services/Models/RiskModels.cs
Normal file
448
src/Cli/StellaOps.Cli/Services/Models/RiskModels.cs
Normal file
@@ -0,0 +1,448 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-RISK-66-001: Risk profile list models
|
||||
|
||||
/// <summary>
|
||||
/// Request to list risk profiles.
|
||||
/// </summary>
|
||||
internal sealed class RiskProfileListRequest
|
||||
{
|
||||
[JsonPropertyName("includeDisabled")]
|
||||
public bool IncludeDisabled { get; init; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string? Category { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response containing a list of risk profiles.
|
||||
/// </summary>
|
||||
internal sealed class RiskProfileListResponse
|
||||
{
|
||||
[JsonPropertyName("profiles")]
|
||||
public IReadOnlyList<RiskProfileSummary> Profiles { get; init; } = Array.Empty<RiskProfileSummary>();
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of a risk profile.
|
||||
/// </summary>
|
||||
internal sealed class RiskProfileSummary
|
||||
{
|
||||
[JsonPropertyName("profileId")]
|
||||
public string ProfileId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string Category { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public int Version { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("builtIn")]
|
||||
public bool BuiltIn { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset? UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("ruleCount")]
|
||||
public int RuleCount { get; init; }
|
||||
|
||||
[JsonPropertyName("severityWeights")]
|
||||
public RiskSeverityWeights? SeverityWeights { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Severity weights for risk scoring.
|
||||
/// </summary>
|
||||
internal sealed class RiskSeverityWeights
|
||||
{
|
||||
[JsonPropertyName("critical")]
|
||||
public double Critical { get; init; }
|
||||
|
||||
[JsonPropertyName("high")]
|
||||
public double High { get; init; }
|
||||
|
||||
[JsonPropertyName("medium")]
|
||||
public double Medium { get; init; }
|
||||
|
||||
[JsonPropertyName("low")]
|
||||
public double Low { get; init; }
|
||||
|
||||
[JsonPropertyName("info")]
|
||||
public double Info { get; init; }
|
||||
}
|
||||
|
||||
// CLI-RISK-66-002: Risk simulate models
|
||||
|
||||
/// <summary>
|
||||
/// Request to simulate risk scoring.
|
||||
/// </summary>
|
||||
internal sealed class RiskSimulateRequest
|
||||
{
|
||||
[JsonPropertyName("profileId")]
|
||||
public string? ProfileId { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomId")]
|
||||
public string? SbomId { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string? AssetId { get; init; }
|
||||
|
||||
[JsonPropertyName("diffMode")]
|
||||
public bool DiffMode { get; init; }
|
||||
|
||||
[JsonPropertyName("baselineProfileId")]
|
||||
public string? BaselineProfileId { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of risk simulation.
|
||||
/// </summary>
|
||||
internal sealed class RiskSimulateResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("profileId")]
|
||||
public string ProfileId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("profileName")]
|
||||
public string ProfileName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("overallScore")]
|
||||
public double OverallScore { get; init; }
|
||||
|
||||
[JsonPropertyName("grade")]
|
||||
public string Grade { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("findings")]
|
||||
public RiskSimulateFindingsSummary Findings { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("componentScores")]
|
||||
public IReadOnlyList<RiskComponentScore> ComponentScores { get; init; } = Array.Empty<RiskComponentScore>();
|
||||
|
||||
[JsonPropertyName("diff")]
|
||||
public RiskSimulateDiff? Diff { get; init; }
|
||||
|
||||
[JsonPropertyName("simulatedAt")]
|
||||
public DateTimeOffset SimulatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of findings from risk simulation.
|
||||
/// </summary>
|
||||
internal sealed class RiskSimulateFindingsSummary
|
||||
{
|
||||
[JsonPropertyName("critical")]
|
||||
public int Critical { get; init; }
|
||||
|
||||
[JsonPropertyName("high")]
|
||||
public int High { get; init; }
|
||||
|
||||
[JsonPropertyName("medium")]
|
||||
public int Medium { get; init; }
|
||||
|
||||
[JsonPropertyName("low")]
|
||||
public int Low { get; init; }
|
||||
|
||||
[JsonPropertyName("info")]
|
||||
public int Info { get; init; }
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component-level risk score.
|
||||
/// </summary>
|
||||
internal sealed class RiskComponentScore
|
||||
{
|
||||
[JsonPropertyName("componentId")]
|
||||
public string ComponentId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("componentName")]
|
||||
public string ComponentName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; init; }
|
||||
|
||||
[JsonPropertyName("grade")]
|
||||
public string Grade { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("findingCount")]
|
||||
public int FindingCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diff between baseline and candidate risk scores.
|
||||
/// </summary>
|
||||
internal sealed class RiskSimulateDiff
|
||||
{
|
||||
[JsonPropertyName("baselineScore")]
|
||||
public double BaselineScore { get; init; }
|
||||
|
||||
[JsonPropertyName("candidateScore")]
|
||||
public double CandidateScore { get; init; }
|
||||
|
||||
[JsonPropertyName("delta")]
|
||||
public double Delta { get; init; }
|
||||
|
||||
[JsonPropertyName("improved")]
|
||||
public bool Improved { get; init; }
|
||||
|
||||
[JsonPropertyName("findingsAdded")]
|
||||
public int FindingsAdded { get; init; }
|
||||
|
||||
[JsonPropertyName("findingsRemoved")]
|
||||
public int FindingsRemoved { get; init; }
|
||||
}
|
||||
|
||||
// CLI-RISK-67-001: Risk results models
|
||||
|
||||
/// <summary>
|
||||
/// Request to get risk results.
|
||||
/// </summary>
|
||||
internal sealed class RiskResultsRequest
|
||||
{
|
||||
[JsonPropertyName("assetId")]
|
||||
public string? AssetId { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomId")]
|
||||
public string? SbomId { get; init; }
|
||||
|
||||
[JsonPropertyName("profileId")]
|
||||
public string? ProfileId { get; init; }
|
||||
|
||||
[JsonPropertyName("minSeverity")]
|
||||
public string? MinSeverity { get; init; }
|
||||
|
||||
[JsonPropertyName("maxScore")]
|
||||
public double? MaxScore { get; init; }
|
||||
|
||||
[JsonPropertyName("includeExplain")]
|
||||
public bool IncludeExplain { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response containing risk results.
|
||||
/// </summary>
|
||||
internal sealed class RiskResultsResponse
|
||||
{
|
||||
[JsonPropertyName("results")]
|
||||
public IReadOnlyList<RiskResult> Results { get; init; } = Array.Empty<RiskResult>();
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public RiskResultsSummary Summary { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual risk result.
|
||||
/// </summary>
|
||||
internal sealed class RiskResult
|
||||
{
|
||||
[JsonPropertyName("resultId")]
|
||||
public string ResultId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string AssetId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("assetName")]
|
||||
public string? AssetName { get; init; }
|
||||
|
||||
[JsonPropertyName("profileId")]
|
||||
public string ProfileId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("profileName")]
|
||||
public string? ProfileName { get; init; }
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; init; }
|
||||
|
||||
[JsonPropertyName("grade")]
|
||||
public string Grade { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string Severity { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("findingCount")]
|
||||
public int FindingCount { get; init; }
|
||||
|
||||
[JsonPropertyName("evaluatedAt")]
|
||||
public DateTimeOffset EvaluatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("explain")]
|
||||
public RiskResultExplain? Explain { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explanation for a risk result.
|
||||
/// </summary>
|
||||
internal sealed class RiskResultExplain
|
||||
{
|
||||
[JsonPropertyName("factors")]
|
||||
public IReadOnlyList<RiskFactor> Factors { get; init; } = Array.Empty<RiskFactor>();
|
||||
|
||||
[JsonPropertyName("recommendations")]
|
||||
public IReadOnlyList<string> Recommendations { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factor contributing to risk score.
|
||||
/// </summary>
|
||||
internal sealed class RiskFactor
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("weight")]
|
||||
public double Weight { get; init; }
|
||||
|
||||
[JsonPropertyName("contribution")]
|
||||
public double Contribution { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of risk results.
|
||||
/// </summary>
|
||||
internal sealed class RiskResultsSummary
|
||||
{
|
||||
[JsonPropertyName("averageScore")]
|
||||
public double AverageScore { get; init; }
|
||||
|
||||
[JsonPropertyName("minScore")]
|
||||
public double MinScore { get; init; }
|
||||
|
||||
[JsonPropertyName("maxScore")]
|
||||
public double MaxScore { get; init; }
|
||||
|
||||
[JsonPropertyName("assetCount")]
|
||||
public int AssetCount { get; init; }
|
||||
|
||||
[JsonPropertyName("bySeverity")]
|
||||
public RiskSimulateFindingsSummary BySeverity { get; init; } = new();
|
||||
}
|
||||
|
||||
// CLI-RISK-68-001: Risk bundle verify models
|
||||
|
||||
/// <summary>
|
||||
/// Request to verify a risk bundle.
|
||||
/// </summary>
|
||||
internal sealed class RiskBundleVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("bundlePath")]
|
||||
public string BundlePath { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signaturePath")]
|
||||
public string? SignaturePath { get; init; }
|
||||
|
||||
[JsonPropertyName("checkRekor")]
|
||||
public bool CheckRekor { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of verifying a risk bundle.
|
||||
/// </summary>
|
||||
internal sealed class RiskBundleVerifyResult
|
||||
{
|
||||
[JsonPropertyName("valid")]
|
||||
public bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("bundleId")]
|
||||
public string BundleId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("bundleVersion")]
|
||||
public string BundleVersion { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("profileCount")]
|
||||
public int ProfileCount { get; init; }
|
||||
|
||||
[JsonPropertyName("ruleCount")]
|
||||
public int RuleCount { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureValid")]
|
||||
public bool? SignatureValid { get; init; }
|
||||
|
||||
[JsonPropertyName("signedAt")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("signedBy")]
|
||||
public string? SignedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorVerified")]
|
||||
public bool? RekorVerified { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorLogIndex")]
|
||||
public string? RekorLogIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
633
src/Cli/StellaOps.Cli/Services/Models/SbomModels.cs
Normal file
633
src/Cli/StellaOps.Cli/Services/Models/SbomModels.cs
Normal file
@@ -0,0 +1,633 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-PARITY-41-001: SBOM Explorer models for CLI
|
||||
|
||||
/// <summary>
|
||||
/// SBOM list request parameters.
|
||||
/// </summary>
|
||||
internal sealed class SbomListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAfter")]
|
||||
public DateTimeOffset? CreatedAfter { get; init; }
|
||||
|
||||
[JsonPropertyName("createdBefore")]
|
||||
public DateTimeOffset? CreatedBefore { get; init; }
|
||||
|
||||
[JsonPropertyName("hasVulnerabilities")]
|
||||
public bool? HasVulnerabilities { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("cursor")]
|
||||
public string? Cursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paginated SBOM list response.
|
||||
/// </summary>
|
||||
internal sealed class SbomListResponse
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public IReadOnlyList<SbomSummary> Items { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary view of an SBOM.
|
||||
/// </summary>
|
||||
internal sealed class SbomSummary
|
||||
{
|
||||
[JsonPropertyName("sbomId")]
|
||||
public string SbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("formatVersion")]
|
||||
public string? FormatVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilityCount")]
|
||||
public int VulnerabilityCount { get; init; }
|
||||
|
||||
[JsonPropertyName("licensesDetected")]
|
||||
public int LicensesDetected { get; init; }
|
||||
|
||||
[JsonPropertyName("determinismScore")]
|
||||
public double? DeterminismScore { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyList<string>? Tags { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed SBOM response including components, vulnerabilities, and metadata.
|
||||
/// </summary>
|
||||
internal sealed class SbomDetailResponse
|
||||
{
|
||||
[JsonPropertyName("sbomId")]
|
||||
public string SbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("formatVersion")]
|
||||
public string? FormatVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilityCount")]
|
||||
public int VulnerabilityCount { get; init; }
|
||||
|
||||
[JsonPropertyName("licensesDetected")]
|
||||
public int LicensesDetected { get; init; }
|
||||
|
||||
[JsonPropertyName("determinismScore")]
|
||||
public double? DeterminismScore { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IReadOnlyList<string>? Tags { get; init; }
|
||||
|
||||
[JsonPropertyName("components")]
|
||||
public IReadOnlyList<SbomComponent>? Components { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public SbomMetadata? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilities")]
|
||||
public IReadOnlyList<SbomVulnerability>? Vulnerabilities { get; init; }
|
||||
|
||||
[JsonPropertyName("licenses")]
|
||||
public IReadOnlyList<SbomLicense>? Licenses { get; init; }
|
||||
|
||||
[JsonPropertyName("attestation")]
|
||||
public SbomAttestation? Attestation { get; init; }
|
||||
|
||||
[JsonPropertyName("explain")]
|
||||
public SbomExplainInfo? Explain { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM component information.
|
||||
/// </summary>
|
||||
internal sealed class SbomComponent
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("cpe")]
|
||||
public string? Cpe { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
[JsonPropertyName("supplier")]
|
||||
public string? Supplier { get; init; }
|
||||
|
||||
[JsonPropertyName("licenses")]
|
||||
public IReadOnlyList<string>? Licenses { get; init; }
|
||||
|
||||
[JsonPropertyName("hashes")]
|
||||
public IReadOnlyDictionary<string, string>? Hashes { get; init; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public string? Scope { get; init; }
|
||||
|
||||
[JsonPropertyName("dependencies")]
|
||||
public IReadOnlyList<string>? Dependencies { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM creation metadata.
|
||||
/// </summary>
|
||||
internal sealed class SbomMetadata
|
||||
{
|
||||
[JsonPropertyName("toolName")]
|
||||
public string? ToolName { get; init; }
|
||||
|
||||
[JsonPropertyName("toolVersion")]
|
||||
public string? ToolVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("scannerVersion")]
|
||||
public string? ScannerVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("serialNumber")]
|
||||
public string? SerialNumber { get; init; }
|
||||
|
||||
[JsonPropertyName("documentNamespace")]
|
||||
public string? DocumentNamespace { get; init; }
|
||||
|
||||
[JsonPropertyName("creators")]
|
||||
public IReadOnlyList<string>? Creators { get; init; }
|
||||
|
||||
[JsonPropertyName("annotations")]
|
||||
public IReadOnlyDictionary<string, string>? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vulnerability found in SBOM.
|
||||
/// </summary>
|
||||
internal sealed class SbomVulnerability
|
||||
{
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string? Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double? Score { get; init; }
|
||||
|
||||
[JsonPropertyName("affectedComponent")]
|
||||
public string? AffectedComponent { get; init; }
|
||||
|
||||
[JsonPropertyName("fixedIn")]
|
||||
public string? FixedIn { get; init; }
|
||||
|
||||
[JsonPropertyName("vexStatus")]
|
||||
public string? VexStatus { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// License information in SBOM.
|
||||
/// </summary>
|
||||
internal sealed class SbomLicense
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string? Id { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string? Url { get; init; }
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("components")]
|
||||
public IReadOnlyList<string>? Components { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attestation information for SBOM.
|
||||
/// </summary>
|
||||
internal sealed class SbomAttestation
|
||||
{
|
||||
[JsonPropertyName("signed")]
|
||||
public bool Signed { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureAlgorithm")]
|
||||
public string? SignatureAlgorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureKeyId")]
|
||||
public string? SignatureKeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("signedAt")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorLogIndex")]
|
||||
public long? RekorLogIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("rekorLogId")]
|
||||
public string? RekorLogId { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateIssuer")]
|
||||
public string? CertificateIssuer { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateSubject")]
|
||||
public string? CertificateSubject { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explain information for SBOM generation (determinism debugging).
|
||||
/// </summary>
|
||||
internal sealed class SbomExplainInfo
|
||||
{
|
||||
[JsonPropertyName("determinismFactors")]
|
||||
public IReadOnlyList<SbomDeterminismFactor>? DeterminismFactors { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionPath")]
|
||||
public IReadOnlyList<SbomCompositionStep>? CompositionPath { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factor affecting SBOM determinism score.
|
||||
/// </summary>
|
||||
internal sealed class SbomDeterminismFactor
|
||||
{
|
||||
[JsonPropertyName("factor")]
|
||||
public string Factor { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("impact")]
|
||||
public string Impact { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; init; }
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public string? Details { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step in SBOM composition chain.
|
||||
/// </summary>
|
||||
internal sealed class SbomCompositionStep
|
||||
{
|
||||
[JsonPropertyName("step")]
|
||||
public int Step { get; init; }
|
||||
|
||||
[JsonPropertyName("operation")]
|
||||
public string Operation { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("input")]
|
||||
public string? Input { get; init; }
|
||||
|
||||
[JsonPropertyName("output")]
|
||||
public string? Output { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM compare request parameters.
|
||||
/// </summary>
|
||||
internal sealed class SbomCompareRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("baseSbomId")]
|
||||
public string BaseSbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("targetSbomId")]
|
||||
public string TargetSbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("includeUnchanged")]
|
||||
public bool IncludeUnchanged { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM comparison result.
|
||||
/// </summary>
|
||||
internal sealed class SbomCompareResponse
|
||||
{
|
||||
[JsonPropertyName("baseSbomId")]
|
||||
public string BaseSbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("targetSbomId")]
|
||||
public string TargetSbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public SbomCompareSummary Summary { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("componentChanges")]
|
||||
public IReadOnlyList<SbomComponentChange>? ComponentChanges { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilityChanges")]
|
||||
public IReadOnlyList<SbomVulnerabilityChange>? VulnerabilityChanges { get; init; }
|
||||
|
||||
[JsonPropertyName("licenseChanges")]
|
||||
public IReadOnlyList<SbomLicenseChange>? LicenseChanges { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of SBOM comparison.
|
||||
/// </summary>
|
||||
internal sealed class SbomCompareSummary
|
||||
{
|
||||
[JsonPropertyName("componentsAdded")]
|
||||
public int ComponentsAdded { get; init; }
|
||||
|
||||
[JsonPropertyName("componentsRemoved")]
|
||||
public int ComponentsRemoved { get; init; }
|
||||
|
||||
[JsonPropertyName("componentsModified")]
|
||||
public int ComponentsModified { get; init; }
|
||||
|
||||
[JsonPropertyName("componentsUnchanged")]
|
||||
public int ComponentsUnchanged { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilitiesAdded")]
|
||||
public int VulnerabilitiesAdded { get; init; }
|
||||
|
||||
[JsonPropertyName("vulnerabilitiesRemoved")]
|
||||
public int VulnerabilitiesRemoved { get; init; }
|
||||
|
||||
[JsonPropertyName("licensesAdded")]
|
||||
public int LicensesAdded { get; init; }
|
||||
|
||||
[JsonPropertyName("licensesRemoved")]
|
||||
public int LicensesRemoved { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component change in comparison.
|
||||
/// </summary>
|
||||
internal sealed class SbomComponentChange
|
||||
{
|
||||
[JsonPropertyName("changeType")]
|
||||
public string ChangeType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("componentName")]
|
||||
public string ComponentName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("baseVersion")]
|
||||
public string? BaseVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("targetVersion")]
|
||||
public string? TargetVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("basePurl")]
|
||||
public string? BasePurl { get; init; }
|
||||
|
||||
[JsonPropertyName("targetPurl")]
|
||||
public string? TargetPurl { get; init; }
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public IReadOnlyList<string>? Details { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vulnerability change in comparison.
|
||||
/// </summary>
|
||||
internal sealed class SbomVulnerabilityChange
|
||||
{
|
||||
[JsonPropertyName("changeType")]
|
||||
public string ChangeType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string? Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("affectedComponent")]
|
||||
public string? AffectedComponent { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// License change in comparison.
|
||||
/// </summary>
|
||||
internal sealed class SbomLicenseChange
|
||||
{
|
||||
[JsonPropertyName("changeType")]
|
||||
public string ChangeType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("licenseId")]
|
||||
public string? LicenseId { get; init; }
|
||||
|
||||
[JsonPropertyName("licenseName")]
|
||||
public string LicenseName { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM export request parameters.
|
||||
/// </summary>
|
||||
internal sealed class SbomExportRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomId")]
|
||||
public string SbomId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = "spdx";
|
||||
|
||||
[JsonPropertyName("formatVersion")]
|
||||
public string? FormatVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("signed")]
|
||||
public bool Signed { get; init; }
|
||||
|
||||
[JsonPropertyName("includeVex")]
|
||||
public bool IncludeVex { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM export result.
|
||||
/// </summary>
|
||||
internal sealed class SbomExportResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("exportId")]
|
||||
public string? ExportId { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("downloadUrl")]
|
||||
public string? DownloadUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("digestAlgorithm")]
|
||||
public string? DigestAlgorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("signed")]
|
||||
public bool Signed { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureKeyId")]
|
||||
public string? SignatureKeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("expiresAt")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
}
|
||||
|
||||
// CLI-PARITY-41-001: Parity matrix models
|
||||
|
||||
/// <summary>
|
||||
/// Parity matrix entry showing CLI command coverage.
|
||||
/// </summary>
|
||||
internal sealed class ParityMatrixEntry
|
||||
{
|
||||
[JsonPropertyName("commandGroup")]
|
||||
public string CommandGroup { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("command")]
|
||||
public string Command { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("cliSupport")]
|
||||
public string CliSupport { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("apiEndpoint")]
|
||||
public string? ApiEndpoint { get; init; }
|
||||
|
||||
[JsonPropertyName("uiEquivalent")]
|
||||
public string? UiEquivalent { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministic")]
|
||||
public bool Deterministic { get; init; }
|
||||
|
||||
[JsonPropertyName("explainSupport")]
|
||||
public bool ExplainSupport { get; init; }
|
||||
|
||||
[JsonPropertyName("offlineSupport")]
|
||||
public bool OfflineSupport { get; init; }
|
||||
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parity matrix summary response.
|
||||
/// </summary>
|
||||
internal sealed class ParityMatrixResponse
|
||||
{
|
||||
[JsonPropertyName("entries")]
|
||||
public IReadOnlyList<ParityMatrixEntry> Entries { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public ParityMatrixSummary Summary { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("cliVersion")]
|
||||
public string? CliVersion { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of parity matrix coverage.
|
||||
/// </summary>
|
||||
internal sealed class ParityMatrixSummary
|
||||
{
|
||||
[JsonPropertyName("totalCommands")]
|
||||
public int TotalCommands { get; init; }
|
||||
|
||||
[JsonPropertyName("fullParity")]
|
||||
public int FullParity { get; init; }
|
||||
|
||||
[JsonPropertyName("partialParity")]
|
||||
public int PartialParity { get; init; }
|
||||
|
||||
[JsonPropertyName("noParity")]
|
||||
public int NoParity { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministicCommands")]
|
||||
public int DeterministicCommands { get; init; }
|
||||
|
||||
[JsonPropertyName("explainEnabledCommands")]
|
||||
public int ExplainEnabledCommands { get; init; }
|
||||
|
||||
[JsonPropertyName("offlineCapableCommands")]
|
||||
public int OfflineCapableCommands { get; init; }
|
||||
}
|
||||
834
src/Cli/StellaOps.Cli/Services/Models/SbomerModels.cs
Normal file
834
src/Cli/StellaOps.Cli/Services/Models/SbomerModels.cs
Normal file
@@ -0,0 +1,834 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-SBOM-60-001: Sbomer command models for layer/compose operations
|
||||
|
||||
/// <summary>
|
||||
/// SBOM fragment from a container layer.
|
||||
/// </summary>
|
||||
internal sealed class SbomFragment
|
||||
{
|
||||
[JsonPropertyName("fragmentId")]
|
||||
public string FragmentId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fragmentSha256")]
|
||||
public string FragmentSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("dsseEnvelopeSha256")]
|
||||
public string? DsseEnvelopeSha256 { get; init; }
|
||||
|
||||
[JsonPropertyName("dsseEnvelopeUri")]
|
||||
public string? DsseEnvelopeUri { get; init; }
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureAlgorithm")]
|
||||
public string? SignatureAlgorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureValid")]
|
||||
public bool? SignatureValid { get; init; }
|
||||
|
||||
[JsonPropertyName("components")]
|
||||
public IReadOnlyList<SbomFragmentComponent>? Components { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component within an SBOM fragment.
|
||||
/// </summary>
|
||||
internal sealed class SbomFragmentComponent
|
||||
{
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
[JsonPropertyName("identityKey")]
|
||||
public string? IdentityKey { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer list request for sbomer layer list.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerListRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("cursor")]
|
||||
public string? Cursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer list response.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerListResponse
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public IReadOnlyList<SbomFragment> Items { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer show request.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerShowRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("includeComponents")]
|
||||
public bool IncludeComponents { get; init; }
|
||||
|
||||
[JsonPropertyName("includeDsse")]
|
||||
public bool IncludeDsse { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer detail response.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerDetail
|
||||
{
|
||||
[JsonPropertyName("fragment")]
|
||||
public SbomFragment Fragment { get; init; } = new();
|
||||
|
||||
[JsonPropertyName("dsseEnvelope")]
|
||||
public DsseEnvelopeInfo? DsseEnvelope { get; init; }
|
||||
|
||||
[JsonPropertyName("canonicalJson")]
|
||||
public string? CanonicalJson { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleProof")]
|
||||
public MerkleProofInfo? MerkleProof { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope information.
|
||||
/// </summary>
|
||||
internal sealed class DsseEnvelopeInfo
|
||||
{
|
||||
[JsonPropertyName("payloadType")]
|
||||
public string PayloadType { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("payloadSha256")]
|
||||
public string PayloadSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public IReadOnlyList<DsseSignatureInfo> Signatures { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("envelopeSha256")]
|
||||
public string EnvelopeSha256 { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature information.
|
||||
/// </summary>
|
||||
internal sealed class DsseSignatureInfo
|
||||
{
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("algorithm")]
|
||||
public string Algorithm { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("signatureSha256")]
|
||||
public string SignatureSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("valid")]
|
||||
public bool? Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateSubject")]
|
||||
public string? CertificateSubject { get; init; }
|
||||
|
||||
[JsonPropertyName("certificateExpiry")]
|
||||
public DateTimeOffset? CertificateExpiry { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkle proof information.
|
||||
/// </summary>
|
||||
internal sealed class MerkleProofInfo
|
||||
{
|
||||
[JsonPropertyName("leafHash")]
|
||||
public string LeafHash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("rootHash")]
|
||||
public string RootHash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("proofHashes")]
|
||||
public IReadOnlyList<string> ProofHashes { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("leafIndex")]
|
||||
public int LeafIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("treeSize")]
|
||||
public int TreeSize { get; init; }
|
||||
|
||||
[JsonPropertyName("valid")]
|
||||
public bool? Valid { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer verify request.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiersPath")]
|
||||
public string? VerifiersPath { get; init; }
|
||||
|
||||
[JsonPropertyName("offline")]
|
||||
public bool Offline { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer verify result.
|
||||
/// </summary>
|
||||
internal sealed class SbomerLayerVerifyResult
|
||||
{
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("valid")]
|
||||
public bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("dsseValid")]
|
||||
public bool DsseValid { get; init; }
|
||||
|
||||
[JsonPropertyName("contentHashMatch")]
|
||||
public bool ContentHashMatch { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleProofValid")]
|
||||
public bool? MerkleProofValid { get; init; }
|
||||
|
||||
[JsonPropertyName("signatureAlgorithm")]
|
||||
public string? SignatureAlgorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composition manifest (_composition.json).
|
||||
/// </summary>
|
||||
internal sealed class CompositionManifest
|
||||
{
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = "1.0";
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleRoot")]
|
||||
public string MerkleRoot { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("composedSha256")]
|
||||
public string ComposedSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fragments")]
|
||||
public IReadOnlyList<CompositionFragmentEntry> Fragments { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("canonicalOrder")]
|
||||
public IReadOnlyList<string> CanonicalOrder { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public IReadOnlyDictionary<string, string>? Properties { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fragment entry in composition manifest.
|
||||
/// </summary>
|
||||
internal sealed class CompositionFragmentEntry
|
||||
{
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fragmentSha256")]
|
||||
public string FragmentSha256 { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("dsseEnvelopeSha256")]
|
||||
public string? DsseEnvelopeSha256 { get; init; }
|
||||
|
||||
[JsonPropertyName("componentCount")]
|
||||
public int ComponentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("order")]
|
||||
public int Order { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compose request for sbomer compose.
|
||||
/// </summary>
|
||||
internal sealed class SbomerComposeRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("imageRef")]
|
||||
public string? ImageRef { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; init; }
|
||||
|
||||
[JsonPropertyName("verifyFragments")]
|
||||
public bool VerifyFragments { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiersPath")]
|
||||
public string? VerifiersPath { get; init; }
|
||||
|
||||
[JsonPropertyName("offline")]
|
||||
public bool Offline { get; init; }
|
||||
|
||||
[JsonPropertyName("emitCompositionManifest")]
|
||||
public bool EmitCompositionManifest { get; init; } = true;
|
||||
|
||||
[JsonPropertyName("emitMerkleDiagnostics")]
|
||||
public bool EmitMerkleDiagnostics { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compose result.
|
||||
/// </summary>
|
||||
internal sealed class SbomerComposeResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("composedSha256")]
|
||||
public string? ComposedSha256 { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleRoot")]
|
||||
public string? MerkleRoot { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentCount")]
|
||||
public int FragmentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("totalComponents")]
|
||||
public int TotalComponents { get; init; }
|
||||
|
||||
[JsonPropertyName("outputPath")]
|
||||
public string? OutputPath { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionManifestPath")]
|
||||
public string? CompositionManifestPath { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleDiagnosticsPath")]
|
||||
public string? MerkleDiagnosticsPath { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentVerifications")]
|
||||
public IReadOnlyList<SbomerLayerVerifyResult>? FragmentVerifications { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministic")]
|
||||
public bool Deterministic { get; init; }
|
||||
|
||||
[JsonPropertyName("duration")]
|
||||
public TimeSpan? Duration { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composition show request.
|
||||
/// </summary>
|
||||
internal sealed class SbomerCompositionShowRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionPath")]
|
||||
public string? CompositionPath { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkle diagnostics for composition.
|
||||
/// </summary>
|
||||
internal sealed class MerkleDiagnostics
|
||||
{
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("rootHash")]
|
||||
public string RootHash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("treeSize")]
|
||||
public int TreeSize { get; init; }
|
||||
|
||||
[JsonPropertyName("leaves")]
|
||||
public IReadOnlyList<MerkleLeafInfo> Leaves { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("intermediateNodes")]
|
||||
public IReadOnlyList<MerkleNodeInfo>? IntermediateNodes { get; init; }
|
||||
|
||||
[JsonPropertyName("valid")]
|
||||
public bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkle leaf information.
|
||||
/// </summary>
|
||||
internal sealed class MerkleLeafInfo
|
||||
{
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; init; }
|
||||
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string LayerDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fragmentSha256")]
|
||||
public string FragmentSha256 { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkle intermediate node information.
|
||||
/// </summary>
|
||||
internal sealed class MerkleNodeInfo
|
||||
{
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; init; }
|
||||
|
||||
[JsonPropertyName("index")]
|
||||
public int Index { get; init; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("leftChild")]
|
||||
public string? LeftChild { get; init; }
|
||||
|
||||
[JsonPropertyName("rightChild")]
|
||||
public string? RightChild { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composition verify request.
|
||||
/// </summary>
|
||||
internal sealed class SbomerCompositionVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionPath")]
|
||||
public string? CompositionPath { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiersPath")]
|
||||
public string? VerifiersPath { get; init; }
|
||||
|
||||
[JsonPropertyName("offline")]
|
||||
public bool Offline { get; init; }
|
||||
|
||||
[JsonPropertyName("recompose")]
|
||||
public bool Recompose { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composition verify result.
|
||||
/// </summary>
|
||||
internal sealed class SbomerCompositionVerifyResult
|
||||
{
|
||||
[JsonPropertyName("valid")]
|
||||
public bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("merkleRootMatch")]
|
||||
public bool MerkleRootMatch { get; init; }
|
||||
|
||||
[JsonPropertyName("composedHashMatch")]
|
||||
public bool ComposedHashMatch { get; init; }
|
||||
|
||||
[JsonPropertyName("allFragmentsValid")]
|
||||
public bool AllFragmentsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentCount")]
|
||||
public int FragmentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentVerifications")]
|
||||
public IReadOnlyList<SbomerLayerVerifyResult>? FragmentVerifications { get; init; }
|
||||
|
||||
[JsonPropertyName("recomposedHash")]
|
||||
public string? RecomposedHash { get; init; }
|
||||
|
||||
[JsonPropertyName("expectedHash")]
|
||||
public string? ExpectedHash { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministic")]
|
||||
public bool Deterministic { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
}
|
||||
|
||||
// CLI-SBOM-60-002: Drift detection and explain models
|
||||
|
||||
/// <summary>
|
||||
/// Drift analysis request.
|
||||
/// </summary>
|
||||
internal sealed class SbomerDriftRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("baselineScanId")]
|
||||
public string? BaselineScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("baselinePath")]
|
||||
public string? BaselinePath { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionPath")]
|
||||
public string? CompositionPath { get; init; }
|
||||
|
||||
[JsonPropertyName("explain")]
|
||||
public bool Explain { get; init; }
|
||||
|
||||
[JsonPropertyName("offline")]
|
||||
public bool Offline { get; init; }
|
||||
|
||||
[JsonPropertyName("offlineKitPath")]
|
||||
public string? OfflineKitPath { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drift analysis result.
|
||||
/// </summary>
|
||||
internal sealed class SbomerDriftResult
|
||||
{
|
||||
[JsonPropertyName("hasDrift")]
|
||||
public bool HasDrift { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministic")]
|
||||
public bool Deterministic { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("baselineScanId")]
|
||||
public string? BaselineScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("currentHash")]
|
||||
public string? CurrentHash { get; init; }
|
||||
|
||||
[JsonPropertyName("baselineHash")]
|
||||
public string? BaselineHash { get; init; }
|
||||
|
||||
[JsonPropertyName("driftSummary")]
|
||||
public DriftSummary? Summary { get; init; }
|
||||
|
||||
[JsonPropertyName("driftDetails")]
|
||||
public IReadOnlyList<DriftDetail>? Details { get; init; }
|
||||
|
||||
[JsonPropertyName("explanations")]
|
||||
public IReadOnlyList<DriftExplanation>? Explanations { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of drift between two SBOMs.
|
||||
/// </summary>
|
||||
internal sealed class DriftSummary
|
||||
{
|
||||
[JsonPropertyName("componentsAdded")]
|
||||
public int ComponentsAdded { get; init; }
|
||||
|
||||
[JsonPropertyName("componentsRemoved")]
|
||||
public int ComponentsRemoved { get; init; }
|
||||
|
||||
[JsonPropertyName("componentsModified")]
|
||||
public int ComponentsModified { get; init; }
|
||||
|
||||
[JsonPropertyName("arrayOrderingDrifts")]
|
||||
public int ArrayOrderingDrifts { get; init; }
|
||||
|
||||
[JsonPropertyName("timestampDrifts")]
|
||||
public int TimestampDrifts { get; init; }
|
||||
|
||||
[JsonPropertyName("keyOrderingDrifts")]
|
||||
public int KeyOrderingDrifts { get; init; }
|
||||
|
||||
[JsonPropertyName("whitespaceDrifts")]
|
||||
public int WhitespaceDrifts { get; init; }
|
||||
|
||||
[JsonPropertyName("totalDrifts")]
|
||||
public int TotalDrifts { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed drift information.
|
||||
/// </summary>
|
||||
internal sealed class DriftDetail
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("currentValue")]
|
||||
public string? CurrentValue { get; init; }
|
||||
|
||||
[JsonPropertyName("baselineValue")]
|
||||
public string? BaselineValue { get; init; }
|
||||
|
||||
[JsonPropertyName("layerDigest")]
|
||||
public string? LayerDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public string Severity { get; init; } = "info";
|
||||
|
||||
[JsonPropertyName("breaksDeterminism")]
|
||||
public bool BreaksDeterminism { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explanation for drift occurrence.
|
||||
/// </summary>
|
||||
internal sealed class DriftExplanation
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("expectedBehavior")]
|
||||
public string? ExpectedBehavior { get; init; }
|
||||
|
||||
[JsonPropertyName("actualBehavior")]
|
||||
public string? ActualBehavior { get; init; }
|
||||
|
||||
[JsonPropertyName("rootCause")]
|
||||
public string? RootCause { get; init; }
|
||||
|
||||
[JsonPropertyName("remediation")]
|
||||
public string? Remediation { get; init; }
|
||||
|
||||
[JsonPropertyName("documentationUrl")]
|
||||
public string? DocumentationUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("affectedLayers")]
|
||||
public IReadOnlyList<string>? AffectedLayers { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drift verify request for offline verification.
|
||||
/// </summary>
|
||||
internal sealed class SbomerDriftVerifyRequest
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomPath")]
|
||||
public string? SbomPath { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionPath")]
|
||||
public string? CompositionPath { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiersPath")]
|
||||
public string? VerifiersPath { get; init; }
|
||||
|
||||
[JsonPropertyName("offlineKitPath")]
|
||||
public string? OfflineKitPath { get; init; }
|
||||
|
||||
[JsonPropertyName("recomposeLocally")]
|
||||
public bool RecomposeLocally { get; init; }
|
||||
|
||||
[JsonPropertyName("validateFragments")]
|
||||
public bool ValidateFragments { get; init; }
|
||||
|
||||
[JsonPropertyName("checkMerkleProofs")]
|
||||
public bool CheckMerkleProofs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drift verify result.
|
||||
/// </summary>
|
||||
internal sealed class SbomerDriftVerifyResult
|
||||
{
|
||||
[JsonPropertyName("valid")]
|
||||
public bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("deterministic")]
|
||||
public bool Deterministic { get; init; }
|
||||
|
||||
[JsonPropertyName("scanId")]
|
||||
public string ScanId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("compositionValid")]
|
||||
public bool CompositionValid { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentsValid")]
|
||||
public bool FragmentsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleProofsValid")]
|
||||
public bool MerkleProofsValid { get; init; }
|
||||
|
||||
[JsonPropertyName("recomposedHashMatch")]
|
||||
public bool? RecomposedHashMatch { get; init; }
|
||||
|
||||
[JsonPropertyName("currentHash")]
|
||||
public string? CurrentHash { get; init; }
|
||||
|
||||
[JsonPropertyName("recomposedHash")]
|
||||
public string? RecomposedHash { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentVerifications")]
|
||||
public IReadOnlyList<SbomerLayerVerifyResult>? FragmentVerifications { get; init; }
|
||||
|
||||
[JsonPropertyName("driftResult")]
|
||||
public SbomerDriftResult? DriftResult { get; init; }
|
||||
|
||||
[JsonPropertyName("offlineKitInfo")]
|
||||
public OfflineKitInfo? OfflineKitInfo { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<string>? Warnings { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about offline kit used for verification.
|
||||
/// </summary>
|
||||
internal sealed class OfflineKitInfo
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset? CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("fragmentCount")]
|
||||
public int FragmentCount { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiersPresent")]
|
||||
public bool VerifiersPresent { get; init; }
|
||||
|
||||
[JsonPropertyName("compositionManifestPresent")]
|
||||
public bool CompositionManifestPresent { get; init; }
|
||||
}
|
||||
282
src/Cli/StellaOps.Cli/Services/Models/SdkModels.cs
Normal file
282
src/Cli/StellaOps.Cli/Services/Models/SdkModels.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Request for checking SDK updates.
|
||||
/// CLI-SDK-64-001: Supports stella sdk update command.
|
||||
/// </summary>
|
||||
internal sealed class SdkUpdateRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tenant context for the operation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Language filter (typescript, go, csharp, python, java).
|
||||
/// </summary>
|
||||
[JsonPropertyName("language")]
|
||||
public string? Language { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to only check for updates without downloading.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool CheckOnly { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include changelog information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("includeChangelog")]
|
||||
public bool IncludeChangelog { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include deprecation notices.
|
||||
/// </summary>
|
||||
[JsonPropertyName("includeDeprecations")]
|
||||
public bool IncludeDeprecations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for SDK update check.
|
||||
/// </summary>
|
||||
internal sealed class SdkUpdateResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the operation was successful.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Available SDK updates.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updates")]
|
||||
public IReadOnlyList<SdkVersionInfo> Updates { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Deprecation notices.
|
||||
/// </summary>
|
||||
[JsonPropertyName("deprecations")]
|
||||
public IReadOnlyList<SdkDeprecation> Deprecations { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when updates were last checked.
|
||||
/// </summary>
|
||||
[JsonPropertyName("checkedAt")]
|
||||
public DateTimeOffset CheckedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SDK version information.
|
||||
/// </summary>
|
||||
internal sealed class SdkVersionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// SDK language (typescript, go, csharp, python, java).
|
||||
/// </summary>
|
||||
[JsonPropertyName("language")]
|
||||
public required string Language { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the SDK.
|
||||
/// </summary>
|
||||
[JsonPropertyName("displayName")]
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Package name (e.g., @stellaops/sdk, stellaops-sdk).
|
||||
/// </summary>
|
||||
[JsonPropertyName("packageName")]
|
||||
public required string PackageName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Current installed version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("installedVersion")]
|
||||
public string? InstalledVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Latest available version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("latestVersion")]
|
||||
public required string LatestVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether an update is available.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updateAvailable")]
|
||||
public bool UpdateAvailable { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum supported API version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("minApiVersion")]
|
||||
public string? MinApiVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum supported API version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxApiVersion")]
|
||||
public string? MaxApiVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Release date of the latest version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("releaseDate")]
|
||||
public DateTimeOffset? ReleaseDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Changelog for recent versions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("changelog")]
|
||||
public IReadOnlyList<SdkChangelogEntry>? Changelog { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Download URL for the package.
|
||||
/// </summary>
|
||||
[JsonPropertyName("downloadUrl")]
|
||||
public string? DownloadUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Package registry URL.
|
||||
/// </summary>
|
||||
[JsonPropertyName("registryUrl")]
|
||||
public string? RegistryUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Documentation URL.
|
||||
/// </summary>
|
||||
[JsonPropertyName("docsUrl")]
|
||||
public string? DocsUrl { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SDK changelog entry.
|
||||
/// </summary>
|
||||
internal sealed class SdkChangelogEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Version number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Release date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("releaseDate")]
|
||||
public DateTimeOffset? ReleaseDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Change type (feature, fix, breaking, deprecation).
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Change description.
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public required string Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is a breaking change.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isBreaking")]
|
||||
public bool IsBreaking { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Link to more details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("link")]
|
||||
public string? Link { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SDK deprecation notice.
|
||||
/// </summary>
|
||||
internal sealed class SdkDeprecation
|
||||
{
|
||||
/// <summary>
|
||||
/// SDK language affected.
|
||||
/// </summary>
|
||||
[JsonPropertyName("language")]
|
||||
public required string Language { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated feature or API.
|
||||
/// </summary>
|
||||
[JsonPropertyName("feature")]
|
||||
public required string Feature { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Deprecation message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Version when deprecation was introduced.
|
||||
/// </summary>
|
||||
[JsonPropertyName("deprecatedInVersion")]
|
||||
public string? DeprecatedInVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Version when feature will be removed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("removedInVersion")]
|
||||
public string? RemovedInVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Replacement or migration path.
|
||||
/// </summary>
|
||||
[JsonPropertyName("replacement")]
|
||||
public string? Replacement { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Link to migration guide.
|
||||
/// </summary>
|
||||
[JsonPropertyName("migrationGuide")]
|
||||
public string? MigrationGuide { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the deprecation (info, warning, critical).
|
||||
/// </summary>
|
||||
[JsonPropertyName("severity")]
|
||||
public string Severity { get; init; } = "warning";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for listing installed SDKs.
|
||||
/// </summary>
|
||||
internal sealed class SdkListResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the operation was successful.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Installed SDK versions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sdks")]
|
||||
public IReadOnlyList<SdkVersionInfo> Sdks { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
@@ -14,6 +14,15 @@ internal sealed class PolicySimulationRequestDocument
|
||||
public Dictionary<string, JsonElement>? Env { get; set; }
|
||||
|
||||
public bool? Explain { get; set; }
|
||||
|
||||
// CLI-POLICY-27-003: Enhanced simulation options
|
||||
public string? Mode { get; set; }
|
||||
|
||||
public IReadOnlyList<string>? SbomSelectors { get; set; }
|
||||
|
||||
public bool? IncludeHeatmap { get; set; }
|
||||
|
||||
public bool? IncludeManifest { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicySimulationResponseDocument
|
||||
@@ -21,6 +30,13 @@ internal sealed class PolicySimulationResponseDocument
|
||||
public PolicySimulationDiffDocument? Diff { get; set; }
|
||||
|
||||
public string? ExplainUri { get; set; }
|
||||
|
||||
// CLI-POLICY-27-003: Enhanced response fields
|
||||
public PolicySimulationHeatmapDocument? Heatmap { get; set; }
|
||||
|
||||
public string? ManifestDownloadUri { get; set; }
|
||||
|
||||
public string? ManifestDigest { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicySimulationDiffDocument
|
||||
@@ -55,3 +71,28 @@ internal sealed class PolicySimulationRuleDeltaDocument
|
||||
|
||||
public int? Down { get; set; }
|
||||
}
|
||||
|
||||
// CLI-POLICY-27-003: Heatmap response documents
|
||||
internal sealed class PolicySimulationHeatmapDocument
|
||||
{
|
||||
public int? Critical { get; set; }
|
||||
|
||||
public int? High { get; set; }
|
||||
|
||||
public int? Medium { get; set; }
|
||||
|
||||
public int? Low { get; set; }
|
||||
|
||||
public int? Info { get; set; }
|
||||
|
||||
public List<PolicySimulationHeatmapBucketDocument>? Buckets { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicySimulationHeatmapBucketDocument
|
||||
{
|
||||
public string? Label { get; set; }
|
||||
|
||||
public int? Count { get; set; }
|
||||
|
||||
public string? Color { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,18 +1,184 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models.Transport;
|
||||
|
||||
/// <summary>
|
||||
/// RFC 7807 Problem Details response.
|
||||
/// </summary>
|
||||
internal sealed class ProblemDocument
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public int? Status { get; set; }
|
||||
|
||||
[JsonPropertyName("instance")]
|
||||
public string? Instance { get; set; }
|
||||
|
||||
[JsonPropertyName("extensions")]
|
||||
public Dictionary<string, object?>? Extensions { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standardized API error envelope with error.code and trace_id.
|
||||
/// CLI-SDK-62-002: Supports surfacing structured error information.
|
||||
/// </summary>
|
||||
internal sealed class ApiErrorEnvelope
|
||||
{
|
||||
/// <summary>
|
||||
/// Error details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public ApiErrorDetail? Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Distributed trace identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("trace_id")]
|
||||
public string? TraceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("request_id")]
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string? Timestamp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error detail within the standardized envelope.
|
||||
/// </summary>
|
||||
internal sealed class ApiErrorDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// Machine-readable error code (e.g., "ERR_AUTH_INVALID_SCOPE").
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable error message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Detailed description of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Target of the error (field name, resource identifier).
|
||||
/// </summary>
|
||||
[JsonPropertyName("target")]
|
||||
public string? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inner errors for nested error details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("inner_errors")]
|
||||
public IReadOnlyList<ApiErrorDetail>? InnerErrors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional metadata about the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("metadata")]
|
||||
public Dictionary<string, object?>? Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Help URL for more information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("help_url")]
|
||||
public string? HelpUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retry-after hint in seconds (for rate limiting).
|
||||
/// </summary>
|
||||
[JsonPropertyName("retry_after")]
|
||||
public int? RetryAfter { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parsed API error result combining multiple error formats.
|
||||
/// </summary>
|
||||
internal sealed class ParsedApiError
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code (from envelope, problem, or HTTP status).
|
||||
/// </summary>
|
||||
public required string Code { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message.
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Detailed error description.
|
||||
/// </summary>
|
||||
public string? Detail { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Trace ID for distributed tracing.
|
||||
/// </summary>
|
||||
public string? TraceId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Request ID.
|
||||
/// </summary>
|
||||
public string? RequestId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP status code.
|
||||
/// </summary>
|
||||
public int HttpStatus { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Target of the error.
|
||||
/// </summary>
|
||||
public string? Target { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Help URL for more information.
|
||||
/// </summary>
|
||||
public string? HelpUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Retry-after hint in seconds.
|
||||
/// </summary>
|
||||
public int? RetryAfter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Inner errors.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ApiErrorDetail>? InnerErrors { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional metadata.
|
||||
/// </summary>
|
||||
public Dictionary<string, object?>? Metadata { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Original problem document if parsed.
|
||||
/// </summary>
|
||||
public ProblemDocument? ProblemDocument { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Original error envelope if parsed.
|
||||
/// </summary>
|
||||
public ApiErrorEnvelope? ErrorEnvelope { get; init; }
|
||||
}
|
||||
|
||||
292
src/Cli/StellaOps.Cli/Services/Models/VexObservationModels.cs
Normal file
292
src/Cli/StellaOps.Cli/Services/Models/VexObservationModels.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
|
||||
// CLI-LNM-22-002: VEX observation models for CLI commands
|
||||
|
||||
/// <summary>
|
||||
/// Query options for VEX observations.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationQuery
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityIds")]
|
||||
public IReadOnlyList<string> VulnerabilityIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("productKeys")]
|
||||
public IReadOnlyList<string> ProductKeys { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("cpes")]
|
||||
public IReadOnlyList<string> Cpes { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("statuses")]
|
||||
public IReadOnlyList<string> Statuses { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("providerIds")]
|
||||
public IReadOnlyList<string> ProviderIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; init; }
|
||||
|
||||
[JsonPropertyName("cursor")]
|
||||
public string? Cursor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from VEX observation query.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationResponse
|
||||
{
|
||||
[JsonPropertyName("observations")]
|
||||
public IReadOnlyList<VexObservation> Observations { get; init; } = Array.Empty<VexObservation>();
|
||||
|
||||
[JsonPropertyName("aggregate")]
|
||||
public VexObservationAggregate? Aggregate { get; init; }
|
||||
|
||||
[JsonPropertyName("nextCursor")]
|
||||
public string? NextCursor { get; init; }
|
||||
|
||||
[JsonPropertyName("hasMore")]
|
||||
public bool HasMore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX observation document.
|
||||
/// </summary>
|
||||
internal sealed class VexObservation
|
||||
{
|
||||
[JsonPropertyName("observationId")]
|
||||
public string ObservationId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("providerId")]
|
||||
public string ProviderId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("product")]
|
||||
public VexObservationProduct? Product { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("justification")]
|
||||
public string? Justification { get; init; }
|
||||
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; init; }
|
||||
|
||||
[JsonPropertyName("document")]
|
||||
public VexObservationDocument? Document { get; init; }
|
||||
|
||||
[JsonPropertyName("firstSeen")]
|
||||
public DateTimeOffset FirstSeen { get; init; }
|
||||
|
||||
[JsonPropertyName("lastSeen")]
|
||||
public DateTimeOffset LastSeen { get; init; }
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public VexObservationConfidence? Confidence { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTimeOffset? UpdatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product information in VEX observation.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationProduct
|
||||
{
|
||||
[JsonPropertyName("key")]
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("cpe")]
|
||||
public string? Cpe { get; init; }
|
||||
|
||||
[JsonPropertyName("componentIdentifiers")]
|
||||
public IReadOnlyList<string> ComponentIdentifiers { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Document reference in VEX observation.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationDocument
|
||||
{
|
||||
[JsonPropertyName("format")]
|
||||
public string Format { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string Digest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sourceUri")]
|
||||
public string SourceUri { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("revision")]
|
||||
public string? Revision { get; init; }
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public VexObservationSignature? Signature { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signature metadata for VEX document.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationSignature
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public string? Subject { get; init; }
|
||||
|
||||
[JsonPropertyName("issuer")]
|
||||
public string? Issuer { get; init; }
|
||||
|
||||
[JsonPropertyName("keyId")]
|
||||
public string? KeyId { get; init; }
|
||||
|
||||
[JsonPropertyName("verifiedAt")]
|
||||
public DateTimeOffset? VerifiedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("transparencyLogReference")]
|
||||
public string? TransparencyLogReference { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confidence level in VEX observation.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationConfidence
|
||||
{
|
||||
[JsonPropertyName("level")]
|
||||
public string Level { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double? Score { get; init; }
|
||||
|
||||
[JsonPropertyName("method")]
|
||||
public string? Method { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate data from VEX observation query.
|
||||
/// </summary>
|
||||
internal sealed class VexObservationAggregate
|
||||
{
|
||||
[JsonPropertyName("vulnerabilityIds")]
|
||||
public IReadOnlyList<string> VulnerabilityIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("productKeys")]
|
||||
public IReadOnlyList<string> ProductKeys { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("cpes")]
|
||||
public IReadOnlyList<string> Cpes { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("providerIds")]
|
||||
public IReadOnlyList<string> ProviderIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("statusCounts")]
|
||||
public IReadOnlyDictionary<string, int> StatusCounts { get; init; } = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX linkset query options.
|
||||
/// </summary>
|
||||
internal sealed class VexLinksetQuery
|
||||
{
|
||||
[JsonPropertyName("tenant")]
|
||||
public string Tenant { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("productKeys")]
|
||||
public IReadOnlyList<string> ProductKeys { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("statuses")]
|
||||
public IReadOnlyList<string> Statuses { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX linkset response showing linked observations.
|
||||
/// </summary>
|
||||
internal sealed class VexLinksetResponse
|
||||
{
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("observations")]
|
||||
public IReadOnlyList<VexObservation> Observations { get; init; } = Array.Empty<VexObservation>();
|
||||
|
||||
[JsonPropertyName("summary")]
|
||||
public VexLinksetSummary? Summary { get; init; }
|
||||
|
||||
[JsonPropertyName("conflicts")]
|
||||
public IReadOnlyList<VexLinksetConflict> Conflicts { get; init; } = Array.Empty<VexLinksetConflict>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of VEX linkset.
|
||||
/// </summary>
|
||||
internal sealed class VexLinksetSummary
|
||||
{
|
||||
[JsonPropertyName("totalObservations")]
|
||||
public int TotalObservations { get; init; }
|
||||
|
||||
[JsonPropertyName("providers")]
|
||||
public IReadOnlyList<string> Providers { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("products")]
|
||||
public IReadOnlyList<string> Products { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("statusCounts")]
|
||||
public IReadOnlyDictionary<string, int> StatusCounts { get; init; } = new Dictionary<string, int>();
|
||||
|
||||
[JsonPropertyName("hasConflicts")]
|
||||
public bool HasConflicts { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conflict between VEX observations.
|
||||
/// </summary>
|
||||
internal sealed class VexLinksetConflict
|
||||
{
|
||||
[JsonPropertyName("productKey")]
|
||||
public string ProductKey { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("conflictingStatuses")]
|
||||
public IReadOnlyList<string> ConflictingStatuses { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("observations")]
|
||||
public IReadOnlyList<string> ObservationIds { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; init; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user