UI work to fill SBOM sourcing management gap. UI planning remaining functionality exposure. Work on CI/Tests stabilization
Introduces CGS determinism test runs to CI workflows for Windows, macOS, Linux, Alpine, and Debian, fulfilling CGS-008 cross-platform requirements. Updates local-ci scripts to support new smoke steps, test timeouts, progress intervals, and project slicing for improved test isolation and diagnostics.
This commit is contained in:
@@ -1,7 +1,34 @@
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Verdict.Services;
|
||||
|
||||
namespace StellaOps.Verdict.Api;
|
||||
namespace StellaOps.Verdict.Api
|
||||
{
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// CGS-specific contracts (SPRINT_20251229_001_001_BE_cgs_infrastructure)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Request to build a deterministic verdict.
|
||||
/// </summary>
|
||||
public sealed record BuildVerdictRequest
|
||||
{
|
||||
public required EvidencePack Evidence { get; init; }
|
||||
public required PolicyLock PolicyLock { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to compute verdict delta.
|
||||
/// </summary>
|
||||
public sealed record VerdictDiffRequest
|
||||
{
|
||||
public required string FromCgs { get; init; }
|
||||
public required string ToCgs { get; init; }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Existing contracts
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a new verdict.
|
||||
@@ -157,3 +184,4 @@ public sealed record ExpiredDeleteResponse
|
||||
/// Generic error response.
|
||||
/// </summary>
|
||||
public sealed record ErrorResponse(string Message);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,27 @@ public static class VerdictEndpoints
|
||||
.Produces<VerdictQueryResponse>(StatusCodes.Status200OK)
|
||||
.RequireAuthorization();
|
||||
|
||||
// POST /v1/verdicts/build - Build deterministic verdict with CGS (CGS-003)
|
||||
group.MapPost("/build", HandleBuild)
|
||||
.WithName("verdict.build")
|
||||
.Produces<CgsVerdictResult>(StatusCodes.Status200OK)
|
||||
.Produces<ErrorResponse>(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization();
|
||||
|
||||
// GET /v1/verdicts/cgs/{cgsHash} - Replay verdict by CGS hash (CGS-004)
|
||||
group.MapGet("/cgs/{cgsHash}", HandleReplay)
|
||||
.WithName("verdict.replay")
|
||||
.Produces<CgsVerdictResult>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization();
|
||||
|
||||
// POST /v1/verdicts/diff - Compute verdict delta (CGS-005)
|
||||
group.MapPost("/diff", HandleDiff)
|
||||
.WithName("verdict.diff")
|
||||
.Produces<VerdictDelta>(StatusCodes.Status200OK)
|
||||
.Produces<ErrorResponse>(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization();
|
||||
|
||||
// POST /v1/verdicts/{id}/verify - Verify verdict signature
|
||||
group.MapPost("/{id}/verify", HandleVerify)
|
||||
.WithName("verdict.verify")
|
||||
@@ -318,6 +339,100 @@ public static class VerdictEndpoints
|
||||
return Json(new ExpiredDeleteResponse { DeletedCount = deletedCount }, StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// CGS-specific handlers (SPRINT_20251229_001_001_BE_cgs_infrastructure)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> HandleBuild(
|
||||
BuildVerdictRequest request,
|
||||
IVerdictBuilder verdictBuilder,
|
||||
HttpContext context,
|
||||
ILogger<VerdictEndpointsLogger> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request is null || request.Evidence is null || request.PolicyLock is null)
|
||||
{
|
||||
return Results.BadRequest(new ErrorResponse("Evidence and PolicyLock are required"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await verdictBuilder.BuildAsync(request.Evidence, request.PolicyLock, cancellationToken);
|
||||
|
||||
logger.LogInformation(
|
||||
"Verdict built successfully: cgs={CgsHash}, status={Status}",
|
||||
result.CgsHash,
|
||||
result.Verdict.Status);
|
||||
|
||||
return Json(result, StatusCodes.Status200OK);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to build verdict");
|
||||
return Results.BadRequest(new ErrorResponse($"Failed to build verdict: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleReplay(
|
||||
string cgsHash,
|
||||
IVerdictBuilder verdictBuilder,
|
||||
ILogger<VerdictEndpointsLogger> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cgsHash))
|
||||
{
|
||||
return Results.BadRequest(new ErrorResponse("CGS hash is required"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await verdictBuilder.ReplayAsync(cgsHash, cancellationToken);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
logger.LogWarning("Verdict not found for CGS hash: {CgsHash}", cgsHash);
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Json(result, StatusCodes.Status200OK);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to replay verdict for cgs={CgsHash}", cgsHash);
|
||||
return Results.BadRequest(new ErrorResponse($"Failed to replay verdict: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleDiff(
|
||||
VerdictDiffRequest request,
|
||||
IVerdictBuilder verdictBuilder,
|
||||
ILogger<VerdictEndpointsLogger> logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request is null || string.IsNullOrWhiteSpace(request.FromCgs) || string.IsNullOrWhiteSpace(request.ToCgs))
|
||||
{
|
||||
return Results.BadRequest(new ErrorResponse("FromCgs and ToCgs are required"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var delta = await verdictBuilder.DiffAsync(request.FromCgs, request.ToCgs, cancellationToken);
|
||||
|
||||
logger.LogInformation(
|
||||
"Verdict diff computed: from={From}, to={To}, changes={ChangeCount}",
|
||||
request.FromCgs,
|
||||
request.ToCgs,
|
||||
delta.Changes.Count);
|
||||
|
||||
return Json(delta, StatusCodes.Status200OK);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to diff verdicts: from={From}, to={To}", request.FromCgs, request.ToCgs);
|
||||
return Results.BadRequest(new ErrorResponse($"Failed to diff verdicts: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static Guid GetTenantId(HttpContext context)
|
||||
{
|
||||
// Try to get tenant ID from claims or header
|
||||
|
||||
Reference in New Issue
Block a user