using System.CommandLine; using StellaOps.Cli.Extensions; namespace StellaOps.Cli.Commands; internal static class VerifyCommandGroup { internal static Command BuildVerifyCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var verify = new Command("verify", "Verification commands (offline-first)."); verify.Add(BuildVerifyOfflineCommand(services, verboseOption, cancellationToken)); verify.Add(BuildVerifyImageCommand(services, verboseOption, cancellationToken)); return verify; } private static Command BuildVerifyOfflineCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var evidenceDirOption = new Option("--evidence-dir") { Description = "Path to offline evidence directory (contains keys/, policy/, sboms/, attestations/, tlog/).", Required = true }; var artifactOption = new Option("--artifact") { Description = "Artifact digest to verify (sha256:).", Required = true }; var policyOption = new Option("--policy") { Description = "Policy file path (YAML or JSON). If relative, resolves under evidence-dir.", Required = true }; var outputDirOption = new Option("--output-dir") { Description = "Directory to write deterministic reconciliation outputs." }; var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var command = new Command("offline", "Verify offline evidence for a specific artifact.") { evidenceDirOption, artifactOption, policyOption, outputDirOption, outputOption, verboseOption }; command.SetAction(parseResult => { var evidenceDir = parseResult.GetValue(evidenceDirOption) ?? string.Empty; var artifact = parseResult.GetValue(artifactOption) ?? string.Empty; var policy = parseResult.GetValue(policyOption) ?? string.Empty; var outputDir = parseResult.GetValue(outputDirOption); var outputFormat = parseResult.GetValue(outputOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleVerifyOfflineAsync( services, evidenceDir, artifact, policy, outputDir, outputFormat, verbose, cancellationToken); }); return command; } private static Command BuildVerifyImageCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var referenceArg = new Argument("reference") { Description = "Image reference (registry/repo@sha256:digest or registry/repo:tag)" }; var requireOption = new Option("--require", "-r") { Description = "Required attestation types: sbom, vex, decision, approval", AllowMultipleArgumentsPerToken = true }; requireOption.SetDefaultValue(new[] { "sbom", "vex", "decision" }); var trustPolicyOption = new Option("--trust-policy") { Description = "Path to trust policy file (YAML or JSON)" }; var outputOption = new Option("--output", "-o") { Description = "Output format: table, json, sarif" }.SetDefaultValue("table").FromAmong("table", "json", "sarif"); var strictOption = new Option("--strict") { Description = "Fail if any required attestation is missing" }; var command = new Command("image", "Verify attestation chain for a container image") { referenceArg, requireOption, trustPolicyOption, outputOption, strictOption, verboseOption }; command.SetAction(parseResult => { var reference = parseResult.GetValue(referenceArg) ?? string.Empty; var require = parseResult.GetValue(requireOption) ?? Array.Empty(); var trustPolicy = parseResult.GetValue(trustPolicyOption); var output = parseResult.GetValue(outputOption) ?? "table"; var strict = parseResult.GetValue(strictOption); var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleVerifyImageAsync( services, reference, require, trustPolicy, output, strict, verbose, cancellationToken); }); return command; } }