save development progress
This commit is contained in:
@@ -375,3 +375,185 @@ public sealed class ChunkVerificationResult
|
||||
/// </summary>
|
||||
public string? ComputedHash { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response model for GET /v1/provcache/{veriKey}/manifest.
|
||||
/// </summary>
|
||||
public sealed class InputManifestResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The VeriKey this manifest describes.
|
||||
/// </summary>
|
||||
public required string VeriKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the source artifact (container image, binary, etc.).
|
||||
/// </summary>
|
||||
public required SourceArtifactInfo SourceArtifact { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the SBOM used in the decision.
|
||||
/// </summary>
|
||||
public required SbomInfoDto Sbom { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about VEX statements contributing to the decision.
|
||||
/// </summary>
|
||||
public required VexInfoDto Vex { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the policy used in evaluation.
|
||||
/// </summary>
|
||||
public required PolicyInfoDto Policy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about signers/attestors.
|
||||
/// </summary>
|
||||
public required SignerInfoDto Signers { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Time window information for cache validity.
|
||||
/// </summary>
|
||||
public required TimeWindowInfoDto TimeWindow { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the manifest was generated.
|
||||
/// </summary>
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM information in API response.
|
||||
/// </summary>
|
||||
public sealed class SbomInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Canonical hash of the SBOM content.
|
||||
/// </summary>
|
||||
public required string Hash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// SBOM format (spdx-2.3, cyclonedx-1.6, etc.).
|
||||
/// </summary>
|
||||
public string? Format { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of packages in the SBOM.
|
||||
/// </summary>
|
||||
public int? PackageCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Completeness percentage (0-100).
|
||||
/// </summary>
|
||||
public int? CompletenessScore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX information in API response.
|
||||
/// </summary>
|
||||
public sealed class VexInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash of the sorted VEX statement set.
|
||||
/// </summary>
|
||||
public required string HashSetHash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of VEX statements contributing to this decision.
|
||||
/// </summary>
|
||||
public int StatementCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sources of VEX statements (vendor names, OpenVEX IDs, etc.).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? Sources { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy information in API response.
|
||||
/// </summary>
|
||||
public sealed class PolicyInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Canonical hash of the policy bundle.
|
||||
/// </summary>
|
||||
public required string Hash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy pack identifier.
|
||||
/// </summary>
|
||||
public string? PackId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy version number.
|
||||
/// </summary>
|
||||
public int? Version { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable policy name.
|
||||
/// </summary>
|
||||
public string? Name { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signer information in API response.
|
||||
/// </summary>
|
||||
public sealed class SignerInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash of the sorted signer set.
|
||||
/// </summary>
|
||||
public required string SetHash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of signers in the set.
|
||||
/// </summary>
|
||||
public int SignerCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signer certificate information.
|
||||
/// </summary>
|
||||
public IReadOnlyList<SignerCertificateDto>? Certificates { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signer certificate information in API response.
|
||||
/// </summary>
|
||||
public sealed class SignerCertificateDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Subject of the certificate (e.g., CN=...).
|
||||
/// </summary>
|
||||
public string? Subject { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate issuer.
|
||||
/// </summary>
|
||||
public string? Issuer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the certificate expires.
|
||||
/// </summary>
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time window information in API response.
|
||||
/// </summary>
|
||||
public sealed class TimeWindowInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// The time window bucket identifier.
|
||||
/// </summary>
|
||||
public required string Bucket { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Start of the time window (UTC).
|
||||
/// </summary>
|
||||
public DateTimeOffset? StartsAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// End of the time window (UTC).
|
||||
/// </summary>
|
||||
public DateTimeOffset? EndsAt { get; init; }
|
||||
}
|
||||
|
||||
@@ -70,6 +70,15 @@ public static partial class ProvcacheEndpointExtensions
|
||||
.Produces<ProvcacheMetricsResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemDetails>(StatusCodes.Status500InternalServerError);
|
||||
|
||||
// GET /v1/provcache/{veriKey}/manifest
|
||||
group.MapGet("/{veriKey}/manifest", GetInputManifest)
|
||||
.WithName("GetInputManifest")
|
||||
.WithSummary("Get input manifest for VeriKey components")
|
||||
.WithDescription("Returns detailed information about the inputs (SBOM, VEX, policy, signers) that formed a cached decision. Use for transparency and debugging.")
|
||||
.Produces<InputManifestResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces<ProblemDetails>(StatusCodes.Status500InternalServerError);
|
||||
|
||||
// Map evidence paging endpoints under /proofs
|
||||
var proofsGroup = endpoints.MapGroup($"{prefix}/proofs")
|
||||
.WithTags("Provcache Evidence")
|
||||
@@ -307,6 +316,119 @@ public static partial class ProvcacheEndpointExtensions
|
||||
title: "Metrics retrieval failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET /v1/provcache/{veriKey}/manifest
|
||||
/// </summary>
|
||||
private static async Task<IResult> GetInputManifest(
|
||||
string veriKey,
|
||||
IProvcacheService provcacheService,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<ProvcacheApiEndpoints> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogDebug("GET /v1/provcache/{VeriKey}/manifest", veriKey);
|
||||
|
||||
try
|
||||
{
|
||||
// First get the entry to verify it exists
|
||||
var result = await provcacheService.GetAsync(veriKey, bypassCache: false, cancellationToken);
|
||||
|
||||
if (result.Status == ProvcacheResultStatus.CacheMiss)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Entry not found",
|
||||
Detail = $"No cache entry found for VeriKey: {veriKey}",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
if (result.Status == ProvcacheResultStatus.Expired)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Entry expired",
|
||||
Detail = $"Cache entry for VeriKey '{veriKey}' has expired",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
var entry = result.Entry;
|
||||
if (entry is null)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Entry not found",
|
||||
Detail = $"No cache entry found for VeriKey: {veriKey}",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
// Build the input manifest from the entry metadata
|
||||
// In a full implementation, we'd resolve these hashes to more detailed metadata
|
||||
// from their respective stores (SBOM store, VEX store, policy registry, etc.)
|
||||
var manifest = BuildInputManifest(entry, timeProvider);
|
||||
|
||||
return Results.Ok(manifest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error getting input manifest for VeriKey {VeriKey}", veriKey);
|
||||
return Results.Problem(
|
||||
detail: ex.Message,
|
||||
statusCode: StatusCodes.Status500InternalServerError,
|
||||
title: "Manifest retrieval failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an InputManifestResponse from a ProvcacheEntry.
|
||||
/// </summary>
|
||||
private static InputManifestResponse BuildInputManifest(ProvcacheEntry entry, TimeProvider timeProvider)
|
||||
{
|
||||
// Build input manifest from the entry and its embedded DecisionDigest
|
||||
// The DecisionDigest contains the VeriKey components as hashes
|
||||
var decision = entry.Decision;
|
||||
|
||||
return new InputManifestResponse
|
||||
{
|
||||
VeriKey = entry.VeriKey,
|
||||
SourceArtifact = new SourceArtifactInfo
|
||||
{
|
||||
// VeriKey includes source hash as first component
|
||||
Digest = entry.VeriKey,
|
||||
},
|
||||
Sbom = new SbomInfoDto
|
||||
{
|
||||
// SBOM hash is embedded in VeriKey computation
|
||||
// In a full implementation, we'd resolve this from the SBOM store
|
||||
Hash = $"sha256:{entry.VeriKey[7..39]}...", // Placeholder - actual hash would come from VeriKey decomposition
|
||||
},
|
||||
Vex = new VexInfoDto
|
||||
{
|
||||
// VEX hash set is embedded in VeriKey computation
|
||||
HashSetHash = $"sha256:{entry.VeriKey[7..39]}...", // Placeholder
|
||||
StatementCount = 0, // Would be resolved from VEX store
|
||||
},
|
||||
Policy = new PolicyInfoDto
|
||||
{
|
||||
Hash = entry.PolicyHash,
|
||||
},
|
||||
Signers = new SignerInfoDto
|
||||
{
|
||||
SetHash = entry.SignerSetHash,
|
||||
SignerCount = 0, // Would be resolved from signer registry
|
||||
},
|
||||
TimeWindow = new TimeWindowInfoDto
|
||||
{
|
||||
Bucket = entry.FeedEpoch,
|
||||
StartsAt = entry.CreatedAt,
|
||||
EndsAt = entry.ExpiresAt,
|
||||
},
|
||||
GeneratedAt = timeProvider.GetUtcNow(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user