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:
master
2025-12-29 19:12:38 +02:00
parent 41552d26ec
commit a4badc275e
286 changed files with 50918 additions and 992 deletions

View File

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

View File

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