Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -71,6 +71,36 @@ builder.Services.AddSingleton<ISbomLedgerService, SbomLedgerService>();
builder.Services.AddSingleton<ISbomAnalysisTrigger, InMemorySbomAnalysisTrigger>();
builder.Services.AddSingleton<ISbomUploadService, SbomUploadService>();
// Lineage graph services (LIN-BE-013)
builder.Services.AddSingleton<ISbomLineageEdgeRepository, InMemorySbomLineageEdgeRepository>();
// LIN-BE-015: Hover card cache for <150ms response times
// Use distributed cache if configured, otherwise in-memory
var hoverCacheConfig = builder.Configuration.GetSection("SbomService:HoverCache");
if (hoverCacheConfig.GetValue<bool>("UseDistributed"))
{
// Expects IDistributedCache to be registered (e.g., Valkey/Redis)
builder.Services.AddSingleton<ILineageHoverCache, DistributedLineageHoverCache>();
}
else
{
builder.Services.AddSingleton<ILineageHoverCache, InMemoryLineageHoverCache>();
}
builder.Services.AddSingleton<ISbomLineageGraphService, SbomLineageGraphService>();
// LIN-BE-028: Lineage compare service
builder.Services.AddSingleton<ILineageCompareService, LineageCompareService>();
// LIN-BE-023: Replay hash service
builder.Services.AddSingleton<IReplayHashService, ReplayHashService>();
// LIN-BE-033: Replay verification service
builder.Services.AddSingleton<IReplayVerificationService, ReplayVerificationService>();
// LIN-BE-034: Compare cache with TTL and VEX invalidation
builder.Services.Configure<CompareCacheOptions>(builder.Configuration.GetSection("SbomService:CompareCache"));
builder.Services.AddSingleton<ILineageCompareCache, InMemoryLineageCompareCache>();
builder.Services.AddSingleton<IProjectionRepository>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
@@ -618,6 +648,307 @@ app.MapGet("/sbom/ledger/lineage", async Task<IResult> (
return Results.Ok(lineage);
});
// -----------------------------------------------------------------------------
// Lineage Graph API Endpoints (LIN-BE-013/014)
// Sprint: SPRINT_20251228_005_BE_sbom_lineage_graph_i
// -----------------------------------------------------------------------------
app.MapGet("/api/v1/lineage/{artifactDigest}", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
[FromRoute] string artifactDigest,
[FromQuery] string? tenant,
[FromQuery] int? maxDepth,
[FromQuery] bool? includeBadges,
[FromQuery] bool? includeReplayHash,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(artifactDigest))
{
return Results.BadRequest(new { error = "artifactDigest is required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
var options = new SbomLineageQueryOptions
{
MaxDepth = maxDepth ?? 10,
IncludeBadges = includeBadges ?? true,
IncludeReplayHash = includeReplayHash ?? false
};
using var activity = SbomTracing.Source.StartActivity("lineage.graph", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
activity?.SetTag("artifact_digest", artifactDigest);
var graph = await lineageService.GetLineageGraphAsync(
artifactDigest.Trim(),
tenant.Trim(),
options,
cancellationToken);
if (graph is null)
{
return Results.NotFound(new { error = "lineage graph not found" });
}
return Results.Ok(graph);
});
app.MapGet("/api/v1/lineage/diff", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
[FromQuery] string? from,
[FromQuery] string? to,
[FromQuery] string? tenant,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
{
return Results.BadRequest(new { error = "from and to digests are required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.diff", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
activity?.SetTag("from_digest", from);
activity?.SetTag("to_digest", to);
var diff = await lineageService.GetLineageDiffAsync(
from.Trim(),
to.Trim(),
tenant.Trim(),
cancellationToken);
if (diff is null)
{
return Results.NotFound(new { error = "lineage diff not found" });
}
SbomMetrics.LedgerDiffsTotal.Add(1);
return Results.Ok(diff);
});
app.MapGet("/api/v1/lineage/hover", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
[FromQuery] string? from,
[FromQuery] string? to,
[FromQuery] string? tenant,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
{
return Results.BadRequest(new { error = "from and to digests are required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.hover", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
var hoverCard = await lineageService.GetHoverCardAsync(
from.Trim(),
to.Trim(),
tenant.Trim(),
cancellationToken);
if (hoverCard is null)
{
return Results.NotFound(new { error = "hover card data not found" });
}
return Results.Ok(hoverCard);
});
app.MapGet("/api/v1/lineage/{artifactDigest}/children", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
[FromRoute] string artifactDigest,
[FromQuery] string? tenant,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(artifactDigest))
{
return Results.BadRequest(new { error = "artifactDigest is required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.children", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
activity?.SetTag("artifact_digest", artifactDigest);
var children = await lineageService.GetChildrenAsync(
artifactDigest.Trim(),
tenant.Trim(),
cancellationToken);
return Results.Ok(new { parentDigest = artifactDigest.Trim(), children });
});
app.MapGet("/api/v1/lineage/{artifactDigest}/parents", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
[FromRoute] string artifactDigest,
[FromQuery] string? tenant,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(artifactDigest))
{
return Results.BadRequest(new { error = "artifactDigest is required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.parents", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
activity?.SetTag("artifact_digest", artifactDigest);
var parents = await lineageService.GetParentsAsync(
artifactDigest.Trim(),
tenant.Trim(),
cancellationToken);
return Results.Ok(new { childDigest = artifactDigest.Trim(), parents });
});
// -----------------------------------------------------------------------------
// Lineage Compare API (LIN-BE-028)
// Sprint: SPRINT_20251228_007_BE_sbom_lineage_graph_ii
// -----------------------------------------------------------------------------
app.MapGet("/api/v1/lineage/compare", async Task<IResult> (
[FromServices] ILineageCompareService compareService,
[FromQuery(Name = "a")] string? fromDigest,
[FromQuery(Name = "b")] string? toDigest,
[FromQuery] string? tenant,
[FromQuery] bool? includeSbomDiff,
[FromQuery] bool? includeVexDeltas,
[FromQuery] bool? includeReachabilityDeltas,
[FromQuery] bool? includeAttestations,
[FromQuery] bool? includeReplayHashes,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(fromDigest) || string.IsNullOrWhiteSpace(toDigest))
{
return Results.BadRequest(new { error = "a (from digest) and b (to digest) query parameters are required" });
}
if (string.IsNullOrWhiteSpace(tenant))
{
return Results.BadRequest(new { error = "tenant is required" });
}
var options = new LineageCompareOptions
{
IncludeSbomDiff = includeSbomDiff ?? true,
IncludeVexDeltas = includeVexDeltas ?? true,
IncludeReachabilityDeltas = includeReachabilityDeltas ?? true,
IncludeAttestations = includeAttestations ?? true,
IncludeReplayHashes = includeReplayHashes ?? true
};
using var activity = SbomTracing.Source.StartActivity("lineage.compare", ActivityKind.Server);
activity?.SetTag("tenant", tenant);
activity?.SetTag("from_digest", fromDigest);
activity?.SetTag("to_digest", toDigest);
var result = await compareService.CompareAsync(
fromDigest.Trim(),
toDigest.Trim(),
tenant.Trim(),
options,
cancellationToken);
if (result is null)
{
return Results.NotFound(new { error = "comparison data not found for the specified artifacts" });
}
return Results.Ok(result);
});
// -----------------------------------------------------------------------------
// Replay Verification API (LIN-BE-033)
// Sprint: SPRINT_20251228_007_BE_sbom_lineage_graph_ii
// -----------------------------------------------------------------------------
app.MapPost("/api/v1/lineage/verify", async Task<IResult> (
[FromServices] IReplayVerificationService verificationService,
[FromBody] ReplayVerifyRequest request,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(request.ReplayHash))
{
return Results.BadRequest(new { error = "replayHash is required" });
}
if (string.IsNullOrWhiteSpace(request.TenantId))
{
return Results.BadRequest(new { error = "tenantId is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.verify", ActivityKind.Server);
activity?.SetTag("tenant", request.TenantId);
activity?.SetTag("replay_hash", request.ReplayHash.Length > 16 ? request.ReplayHash[..16] + "..." : request.ReplayHash);
var verifyRequest = new ReplayVerificationRequest
{
ReplayHash = request.ReplayHash,
TenantId = request.TenantId,
SbomDigest = request.SbomDigest,
FeedsSnapshotDigest = request.FeedsSnapshotDigest,
PolicyVersion = request.PolicyVersion,
VexVerdictsDigest = request.VexVerdictsDigest,
Timestamp = request.Timestamp,
FreezeTime = request.FreezeTime ?? true,
ReEvaluatePolicy = request.ReEvaluatePolicy ?? false
};
var result = await verificationService.VerifyAsync(verifyRequest, cancellationToken);
return Results.Ok(result);
});
app.MapPost("/api/v1/lineage/compare-drift", async Task<IResult> (
[FromServices] IReplayVerificationService verificationService,
[FromBody] CompareDriftRequest request,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(request.HashA) || string.IsNullOrWhiteSpace(request.HashB))
{
return Results.BadRequest(new { error = "hashA and hashB are required" });
}
if (string.IsNullOrWhiteSpace(request.TenantId))
{
return Results.BadRequest(new { error = "tenantId is required" });
}
using var activity = SbomTracing.Source.StartActivity("lineage.compare-drift", ActivityKind.Server);
activity?.SetTag("tenant", request.TenantId);
var result = await verificationService.CompareDriftAsync(
request.HashA,
request.HashB,
request.TenantId,
cancellationToken);
return Results.Ok(result);
});
app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
[FromServices] ISbomQueryService service,
[FromRoute] string? snapshotId,
@@ -932,4 +1263,5 @@ app.MapPost("/internal/orchestrator/watermarks", async Task<IResult> (
app.Run();
// Program class public for WebApplicationFactory<Program>
public partial class Program;