Add integration tests for Proof Chain and Reachability workflows
- Implement ProofChainTestFixture for PostgreSQL-backed integration tests. - Create StellaOps.Integration.ProofChain project with necessary dependencies. - Add ReachabilityIntegrationTests to validate call graph extraction and reachability analysis. - Introduce ReachabilityTestFixture for managing corpus and fixture paths. - Establish StellaOps.Integration.Reachability project with required references. - Develop UnknownsWorkflowTests to cover the unknowns lifecycle: detection, ranking, escalation, and resolution. - Create StellaOps.Integration.Unknowns project with dependencies for unknowns workflow.
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ProofCommandGroup.cs
|
||||
// Sprint: SPRINT_3500_0004_0001_cli_verbs
|
||||
// Task: T4 - Complete Proof Verify
|
||||
// Description: CLI commands for proof chain verification
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Cli.Services;
|
||||
using StellaOps.Cli.Services.Models;
|
||||
|
||||
namespace StellaOps.Cli.Commands.Proof;
|
||||
|
||||
@@ -8,248 +19,390 @@ namespace StellaOps.Cli.Commands.Proof;
|
||||
/// Command group for proof chain operations.
|
||||
/// Implements advisory §15 CLI commands.
|
||||
/// </summary>
|
||||
public class ProofCommandGroup
|
||||
public static class ProofCommandGroup
|
||||
{
|
||||
private readonly ILogger<ProofCommandGroup> _logger;
|
||||
|
||||
public ProofCommandGroup(ILogger<ProofCommandGroup> logger)
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Build the proof command tree.
|
||||
/// </summary>
|
||||
public Command BuildCommand()
|
||||
public static Command BuildProofCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var proofCommand = new Command("proof", "Proof chain operations");
|
||||
var proofCommand = new Command("proof", "Proof chain verification and operations");
|
||||
|
||||
proofCommand.AddCommand(BuildVerifyCommand());
|
||||
proofCommand.AddCommand(BuildSpineCommand());
|
||||
proofCommand.Add(BuildVerifyCommand(services, verboseOption, cancellationToken));
|
||||
proofCommand.Add(BuildSpineCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
return proofCommand;
|
||||
}
|
||||
|
||||
private Command BuildVerifyCommand()
|
||||
private static Command BuildVerifyCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var artifactArg = new Argument<string>(
|
||||
name: "artifact",
|
||||
description: "Artifact digest (sha256:...) or PURL");
|
||||
|
||||
var sbomOption = new Option<FileInfo?>(
|
||||
aliases: ["-s", "--sbom"],
|
||||
description: "Path to SBOM file");
|
||||
|
||||
var vexOption = new Option<FileInfo?>(
|
||||
aliases: ["--vex"],
|
||||
description: "Path to VEX file");
|
||||
|
||||
var anchorOption = new Option<Guid?>(
|
||||
aliases: ["-a", "--anchor"],
|
||||
description: "Trust anchor ID");
|
||||
|
||||
var offlineOption = new Option<bool>(
|
||||
name: "--offline",
|
||||
description: "Offline mode (skip Rekor verification)");
|
||||
|
||||
var outputOption = new Option<string>(
|
||||
name: "--output",
|
||||
getDefaultValue: () => "text",
|
||||
description: "Output format: text, json");
|
||||
|
||||
var verboseOption = new Option<int>(
|
||||
aliases: ["-v", "--verbose"],
|
||||
getDefaultValue: () => 0,
|
||||
description: "Verbose output level (use -vv for very verbose)");
|
||||
|
||||
var verifyCommand = new Command("verify", "Verify an artifact's proof chain")
|
||||
var bundleOption = new Option<string>("--bundle", "-b")
|
||||
{
|
||||
artifactArg,
|
||||
sbomOption,
|
||||
vexOption,
|
||||
anchorOption,
|
||||
offlineOption,
|
||||
outputOption,
|
||||
verboseOption
|
||||
Description = "Path to attestation bundle file (.tar.gz)",
|
||||
Required = true
|
||||
};
|
||||
|
||||
verifyCommand.SetHandler(async (context) =>
|
||||
var offlineOption = new Option<bool>("--offline")
|
||||
{
|
||||
var artifact = context.ParseResult.GetValueForArgument(artifactArg);
|
||||
var sbomFile = context.ParseResult.GetValueForOption(sbomOption);
|
||||
var vexFile = context.ParseResult.GetValueForOption(vexOption);
|
||||
var anchorId = context.ParseResult.GetValueForOption(anchorOption);
|
||||
var offline = context.ParseResult.GetValueForOption(offlineOption);
|
||||
var output = context.ParseResult.GetValueForOption(outputOption) ?? "text";
|
||||
var verbose = context.ParseResult.GetValueForOption(verboseOption);
|
||||
Description = "Offline mode (skip Rekor verification)"
|
||||
};
|
||||
|
||||
context.ExitCode = await VerifyAsync(
|
||||
artifact,
|
||||
sbomFile,
|
||||
vexFile,
|
||||
anchorId,
|
||||
var outputOption = new Option<string>("--output", "-o")
|
||||
{
|
||||
Description = "Output format: text, json"
|
||||
};
|
||||
|
||||
var verifyCommand = new Command("verify", "Verify an attestation bundle's proof chain");
|
||||
verifyCommand.Add(bundleOption);
|
||||
verifyCommand.Add(offlineOption);
|
||||
verifyCommand.Add(outputOption);
|
||||
verifyCommand.Add(verboseOption);
|
||||
|
||||
verifyCommand.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var bundlePath = parseResult.GetValue(bundleOption) ?? string.Empty;
|
||||
var offline = parseResult.GetValue(offlineOption);
|
||||
var output = parseResult.GetValue(outputOption) ?? "text";
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return await HandleVerifyAsync(
|
||||
services,
|
||||
bundlePath,
|
||||
offline,
|
||||
output,
|
||||
verbose,
|
||||
context.GetCancellationToken());
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return verifyCommand;
|
||||
}
|
||||
|
||||
private Command BuildSpineCommand()
|
||||
private static Command BuildSpineCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var spineCommand = new Command("spine", "Proof spine operations");
|
||||
|
||||
// stellaops proof spine create
|
||||
var createCommand = new Command("create", "Create a proof spine for an artifact");
|
||||
var artifactArg = new Argument<string>("artifact", "Artifact digest or PURL");
|
||||
createCommand.AddArgument(artifactArg);
|
||||
createCommand.SetHandler(async (context) =>
|
||||
// proof spine show
|
||||
var bundleIdArg = new Argument<string>("bundle-id")
|
||||
{
|
||||
var artifact = context.ParseResult.GetValueForArgument(artifactArg);
|
||||
context.ExitCode = await CreateSpineAsync(artifact, context.GetCancellationToken());
|
||||
});
|
||||
Description = "Proof bundle ID"
|
||||
};
|
||||
|
||||
// stellaops proof spine show
|
||||
var showCommand = new Command("show", "Show proof spine details");
|
||||
var bundleArg = new Argument<string>("bundleId", "Proof bundle ID");
|
||||
showCommand.AddArgument(bundleArg);
|
||||
showCommand.SetHandler(async (context) =>
|
||||
showCommand.Add(bundleIdArg);
|
||||
showCommand.Add(verboseOption);
|
||||
|
||||
showCommand.SetAction(async (parseResult, ct) =>
|
||||
{
|
||||
var bundleId = context.ParseResult.GetValueForArgument(bundleArg);
|
||||
context.ExitCode = await ShowSpineAsync(bundleId, context.GetCancellationToken());
|
||||
var bundleId = parseResult.GetValue(bundleIdArg) ?? string.Empty;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return await HandleSpineShowAsync(
|
||||
services,
|
||||
bundleId,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
spineCommand.AddCommand(createCommand);
|
||||
spineCommand.AddCommand(showCommand);
|
||||
spineCommand.Add(showCommand);
|
||||
|
||||
return spineCommand;
|
||||
}
|
||||
|
||||
private async Task<int> VerifyAsync(
|
||||
string artifact,
|
||||
FileInfo? sbomFile,
|
||||
FileInfo? vexFile,
|
||||
Guid? anchorId,
|
||||
private static async Task<int> HandleVerifyAsync(
|
||||
IServiceProvider services,
|
||||
string bundlePath,
|
||||
bool offline,
|
||||
string output,
|
||||
int verbose,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var loggerFactory = services.GetService<ILoggerFactory>();
|
||||
var logger = loggerFactory?.CreateLogger(typeof(ProofCommandGroup));
|
||||
|
||||
try
|
||||
{
|
||||
if (verbose > 0)
|
||||
if (verbose)
|
||||
{
|
||||
_logger.LogDebug("Starting proof verification for {Artifact}", artifact);
|
||||
logger?.LogDebug("Verifying attestation bundle: {BundlePath}", bundlePath);
|
||||
}
|
||||
|
||||
// Validate artifact format
|
||||
if (!IsValidArtifactId(artifact))
|
||||
// Check file exists
|
||||
if (!File.Exists(bundlePath))
|
||||
{
|
||||
_logger.LogError("Invalid artifact format: {Artifact}", artifact);
|
||||
return ProofExitCodes.SystemError;
|
||||
var errorMsg = $"Bundle file not found: {bundlePath}";
|
||||
logger?.LogError(errorMsg);
|
||||
if (output == "json")
|
||||
{
|
||||
PrintJsonResult(new ProofVerifyResult(
|
||||
Valid: false,
|
||||
Status: "error",
|
||||
BundlePath: bundlePath,
|
||||
ErrorMessage: errorMsg));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Error: {errorMsg}");
|
||||
}
|
||||
return AttestationBundleExitCodes.FileNotFound;
|
||||
}
|
||||
|
||||
if (verbose > 0)
|
||||
// Get the attestation bundle verifier
|
||||
var verifier = services.GetService<IAttestationBundleVerifier>();
|
||||
if (verifier is null)
|
||||
{
|
||||
_logger.LogDebug("Artifact format valid: {Artifact}", artifact);
|
||||
logger?.LogWarning("IAttestationBundleVerifier not available, using built-in verifier");
|
||||
verifier = new AttestationBundleVerifier(
|
||||
services.GetService<ILogger<AttestationBundleVerifier>>()
|
||||
?? Microsoft.Extensions.Logging.Abstractions.NullLogger<AttestationBundleVerifier>.Instance);
|
||||
}
|
||||
|
||||
// TODO: Implement actual verification using IVerificationPipeline
|
||||
// 1. Load SBOM if provided
|
||||
// 2. Load VEX if provided
|
||||
// 3. Find or use specified trust anchor
|
||||
// 4. Run verification pipeline
|
||||
// 5. Check Rekor inclusion (unless offline)
|
||||
// 6. Generate receipt
|
||||
// Configure verification options
|
||||
var options = new AttestationBundleVerifyOptions(
|
||||
FilePath: bundlePath,
|
||||
Offline: offline,
|
||||
VerifyTransparency: !offline);
|
||||
|
||||
if (verbose > 0)
|
||||
if (verbose)
|
||||
{
|
||||
_logger.LogDebug("Verification pipeline not yet implemented");
|
||||
logger?.LogDebug("Verification options: offline={Offline}, verifyTransparency={VerifyTransparency}",
|
||||
options.Offline, options.VerifyTransparency);
|
||||
}
|
||||
|
||||
// Run verification
|
||||
var result = await verifier.VerifyAsync(options, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
logger?.LogDebug("Verification result: success={Success}, status={Status}",
|
||||
result.Success, result.Status);
|
||||
}
|
||||
|
||||
// Output result
|
||||
if (output == "json")
|
||||
{
|
||||
Console.WriteLine("{");
|
||||
Console.WriteLine($" \"artifact\": \"{artifact}\",");
|
||||
Console.WriteLine(" \"status\": \"pass\",");
|
||||
Console.WriteLine(" \"message\": \"Verification successful (stub)\"");
|
||||
Console.WriteLine("}");
|
||||
PrintJsonResult(new ProofVerifyResult(
|
||||
Valid: result.Success,
|
||||
Status: result.Status,
|
||||
BundlePath: bundlePath,
|
||||
RootHash: result.RootHash,
|
||||
AttestationId: result.AttestationId,
|
||||
ExportId: result.ExportId,
|
||||
Subjects: result.Subjects,
|
||||
PredicateType: result.PredicateType,
|
||||
Checks: BuildVerificationChecks(result),
|
||||
ErrorMessage: result.ErrorMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("StellaOps Scan Summary");
|
||||
Console.WriteLine("══════════════════════");
|
||||
Console.WriteLine($"Artifact: {artifact}");
|
||||
Console.WriteLine("Status: PASS (stub - verification not yet implemented)");
|
||||
PrintTextResult(result, offline);
|
||||
}
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
return result.ExitCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Verification failed for {Artifact}", artifact);
|
||||
logger?.LogError(ex, "Verification failed for {BundlePath}", bundlePath);
|
||||
if (output == "json")
|
||||
{
|
||||
PrintJsonResult(new ProofVerifyResult(
|
||||
Valid: false,
|
||||
Status: "error",
|
||||
BundlePath: bundlePath,
|
||||
ErrorMessage: ex.Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
}
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> CreateSpineAsync(string artifact, CancellationToken ct)
|
||||
private static async Task<int> HandleSpineShowAsync(
|
||||
IServiceProvider services,
|
||||
string bundleId,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var loggerFactory = services.GetService<ILoggerFactory>();
|
||||
var logger = loggerFactory?.CreateLogger(typeof(ProofCommandGroup));
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Creating proof spine for {Artifact}", artifact);
|
||||
if (verbose)
|
||||
{
|
||||
logger?.LogDebug("Showing proof spine {BundleId}", bundleId);
|
||||
}
|
||||
|
||||
// TODO: Implement spine creation using IProofSpineAssembler
|
||||
Console.WriteLine($"Creating proof spine for: {artifact}");
|
||||
Console.WriteLine("Spine creation not yet implemented");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create spine for {Artifact}", artifact);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> ShowSpineAsync(string bundleId, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Showing proof spine {BundleId}", bundleId);
|
||||
|
||||
// TODO: Implement spine retrieval
|
||||
// TODO: Implement spine retrieval from backend
|
||||
Console.WriteLine($"Proof spine: {bundleId}");
|
||||
Console.WriteLine("Spine display not yet implemented");
|
||||
Console.WriteLine("Use 'stella proof verify --bundle <path>' for local bundle verification.");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to show spine {BundleId}", bundleId);
|
||||
logger?.LogError(ex, "Failed to show spine {BundleId}", bundleId);
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidArtifactId(string artifact)
|
||||
private static IReadOnlyList<ProofVerifyCheck>? BuildVerificationChecks(AttestationBundleVerifyResult result)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(artifact))
|
||||
return false;
|
||||
var checks = new List<ProofVerifyCheck>();
|
||||
|
||||
// sha256:<64-hex>
|
||||
if (artifact.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase))
|
||||
// File integrity check
|
||||
checks.Add(new ProofVerifyCheck(
|
||||
Check: "file_integrity",
|
||||
Status: result.ExitCode != AttestationBundleExitCodes.ChecksumMismatch ? "pass" : "fail",
|
||||
Details: result.ExitCode == AttestationBundleExitCodes.ChecksumMismatch
|
||||
? result.ErrorMessage
|
||||
: "Bundle checksums verified"));
|
||||
|
||||
// DSSE signature check
|
||||
checks.Add(new ProofVerifyCheck(
|
||||
Check: "dsse_signature",
|
||||
Status: result.ExitCode != AttestationBundleExitCodes.SignatureFailure ? "pass" : "fail",
|
||||
Details: result.ExitCode == AttestationBundleExitCodes.SignatureFailure
|
||||
? result.ErrorMessage
|
||||
: "DSSE envelope signature valid"));
|
||||
|
||||
// Transparency check (if not offline)
|
||||
if (result.ExitCode == AttestationBundleExitCodes.MissingTransparency)
|
||||
{
|
||||
var hash = artifact[7..];
|
||||
return hash.Length == 64 && hash.All(c => "0123456789abcdef".Contains(char.ToLowerInvariant(c)));
|
||||
checks.Add(new ProofVerifyCheck(
|
||||
Check: "transparency_log",
|
||||
Status: "fail",
|
||||
Details: result.ErrorMessage));
|
||||
}
|
||||
else if (result.Success)
|
||||
{
|
||||
checks.Add(new ProofVerifyCheck(
|
||||
Check: "transparency_log",
|
||||
Status: "pass",
|
||||
Details: "Transparency entry verified or skipped (offline)"));
|
||||
}
|
||||
|
||||
// pkg:type/...
|
||||
if (artifact.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return artifact.Length > 5; // Minimal PURL validation
|
||||
}
|
||||
|
||||
return false;
|
||||
return checks;
|
||||
}
|
||||
|
||||
private static void PrintTextResult(AttestationBundleVerifyResult result, bool offline)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Proof Verification Result");
|
||||
Console.WriteLine(new string('=', 40));
|
||||
|
||||
var statusDisplay = result.Success ? "PASS" : "FAIL";
|
||||
|
||||
Console.WriteLine($"Status: {statusDisplay}");
|
||||
Console.WriteLine($"Bundle: {result.BundlePath}");
|
||||
|
||||
if (!string.IsNullOrEmpty(result.RootHash))
|
||||
{
|
||||
Console.WriteLine($"Root Hash: {result.RootHash}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.AttestationId))
|
||||
{
|
||||
Console.WriteLine($"Attestation ID: {result.AttestationId}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.ExportId))
|
||||
{
|
||||
Console.WriteLine($"Export ID: {result.ExportId}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.PredicateType))
|
||||
{
|
||||
Console.WriteLine($"Predicate: {result.PredicateType}");
|
||||
}
|
||||
|
||||
if (result.Subjects is { Count: > 0 })
|
||||
{
|
||||
Console.WriteLine($"Subjects: {result.Subjects.Count}");
|
||||
foreach (var subject in result.Subjects.Take(5))
|
||||
{
|
||||
Console.WriteLine($" - {subject}");
|
||||
}
|
||||
if (result.Subjects.Count > 5)
|
||||
{
|
||||
Console.WriteLine($" ... and {result.Subjects.Count - 5} more");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Verification Checks:");
|
||||
Console.WriteLine(new string('-', 40));
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
Console.WriteLine($" [PASS] File integrity");
|
||||
Console.WriteLine($" [PASS] DSSE envelope format");
|
||||
Console.WriteLine($" [PASS] Signature validation");
|
||||
if (offline)
|
||||
{
|
||||
Console.WriteLine($" [SKIP] Transparency log (offline mode)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" [PASS] Transparency log");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" [FAIL] {result.ErrorMessage}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void PrintJsonResult(ProofVerifyResult result)
|
||||
{
|
||||
Console.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
}
|
||||
|
||||
#region DTOs
|
||||
|
||||
/// <summary>
|
||||
/// Result of proof verification.
|
||||
/// </summary>
|
||||
private sealed record ProofVerifyResult(
|
||||
bool Valid,
|
||||
string Status,
|
||||
string? BundlePath = null,
|
||||
string? RootHash = null,
|
||||
string? AttestationId = null,
|
||||
string? ExportId = null,
|
||||
IReadOnlyList<string>? Subjects = null,
|
||||
string? PredicateType = null,
|
||||
IReadOnlyList<ProofVerifyCheck>? Checks = null,
|
||||
string? ErrorMessage = null);
|
||||
|
||||
/// <summary>
|
||||
/// Individual verification check result.
|
||||
/// </summary>
|
||||
private sealed record ProofVerifyCheck(
|
||||
string Check,
|
||||
string Status,
|
||||
string? Details = null);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ public static class ProofExitCodes
|
||||
/// </summary>
|
||||
public const int OfflineModeError = 7;
|
||||
|
||||
/// <summary>
|
||||
/// Input error - invalid arguments or missing required parameters.
|
||||
/// </summary>
|
||||
public const int InputError = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Get a human-readable description for an exit code.
|
||||
/// </summary>
|
||||
@@ -62,6 +67,7 @@ public static class ProofExitCodes
|
||||
RekorVerificationFailed => "Rekor verification failed",
|
||||
KeyRevoked => "Signing key revoked",
|
||||
OfflineModeError => "Offline mode error",
|
||||
InputError => "Invalid input or missing required parameters",
|
||||
_ => $"Unknown exit code: {exitCode}"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user