save development progress

This commit is contained in:
StellaOps Bot
2025-12-25 23:09:58 +02:00
parent d71853ad7e
commit aa70af062e
351 changed files with 37683 additions and 150156 deletions

View File

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

View File

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