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

This commit is contained in:
master
2025-11-28 18:21:46 +02:00
parent 05da719048
commit d1cbb905f8
103 changed files with 49604 additions and 105 deletions

View 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
}

View 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; }
}

View 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>();
}

View 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>();
}

View 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>();
}

View 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";
}

View File

@@ -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; }
}

View 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; }
}

View 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>();
}

View 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; }
}

View 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>();
}

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View 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; }
}

View 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; }
}

View 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>();
}

View 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; }
}

View 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; }
}

View 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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View 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;
}