wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10

This commit is contained in:
master
2026-02-23 15:30:50 +02:00
parent bd8fee6ed8
commit e746577380
1424 changed files with 81225 additions and 25251 deletions

View File

@@ -5,6 +5,7 @@ using StellaOps.Scanner.SmartDiff.Detection;
using StellaOps.Scanner.SmartDiff.Output;
using StellaOps.Scanner.WebService.Security;
using StellaOps.Scanner.WebService.Services;
using StellaOps.Scanner.WebService.Tenancy;
using System.Collections.Immutable;
namespace StellaOps.Scanner.WebService.Endpoints;
@@ -87,10 +88,16 @@ internal static class SmartDiffEndpoints
IVexCandidateStore candidateStore,
IScanMetadataRepository? metadataRepo = null,
bool? pretty = null,
HttpContext? context = null,
CancellationToken ct = default)
{
if (!TryResolveTenant(context, out var tenantId, out var failure))
{
return failure!;
}
// Gather all data for the scan
var changes = await changeRepo.GetChangesForScanAsync(scanId, ct);
var changes = await changeRepo.GetChangesForScanAsync(scanId, ct, tenantId: tenantId);
// Get scan metadata if available
string? baseDigest = null;
@@ -111,7 +118,7 @@ internal static class SmartDiffEndpoints
IReadOnlyList<StellaOps.Scanner.SmartDiff.Output.VexCandidate> vexCandidates = [];
if (!string.IsNullOrWhiteSpace(targetDigest))
{
var candidates = await candidateStore.GetCandidatesAsync(targetDigest, ct).ConfigureAwait(false);
var candidates = await candidateStore.GetCandidatesAsync(targetDigest, ct, tenantId: tenantId).ConfigureAwait(false);
vexCandidates = candidates.Select(ToSarifVexCandidate).ToList();
}
@@ -164,8 +171,14 @@ internal static class SmartDiffEndpoints
IVexCandidateStore store,
double? minConfidence = null,
bool? pendingOnly = null,
HttpContext? context = null,
CancellationToken ct = default)
{
if (!TryResolveTenant(context, out var tenantId, out var failure))
{
return failure!;
}
var metadata = await metadataRepository.GetScanMetadataAsync(scanId, ct).ConfigureAwait(false);
var targetDigest = NormalizeDigest(metadata?.TargetDigest);
if (string.IsNullOrWhiteSpace(targetDigest))
@@ -173,7 +186,7 @@ internal static class SmartDiffEndpoints
return Results.NotFound(new { error = "Scan metadata not found", scanId });
}
return await HandleGetCandidatesAsync(targetDigest, store, minConfidence, pendingOnly, ct).ConfigureAwait(false);
return await HandleGetCandidatesAsync(targetDigest, store, minConfidence, pendingOnly, context, ct).ConfigureAwait(false);
}
private static StellaOps.Scanner.SmartDiff.Output.RiskDirection ToSarifRiskDirection(MaterialRiskChangeResult change)
@@ -230,9 +243,15 @@ internal static class SmartDiffEndpoints
string scanId,
IMaterialRiskChangeRepository repository,
double? minPriority = null,
HttpContext? context = null,
CancellationToken ct = default)
{
var changes = await repository.GetChangesForScanAsync(scanId, ct);
if (!TryResolveTenant(context, out var tenantId, out var failure))
{
return failure!;
}
var changes = await repository.GetChangesForScanAsync(scanId, ct, tenantId: tenantId);
if (minPriority.HasValue)
{
@@ -257,15 +276,21 @@ internal static class SmartDiffEndpoints
IVexCandidateStore store,
double? minConfidence = null,
bool? pendingOnly = null,
HttpContext? context = null,
CancellationToken ct = default)
{
if (!TryResolveTenant(context, out var tenantId, out var failure))
{
return failure!;
}
var normalizedDigest = NormalizeDigest(digest);
if (string.IsNullOrWhiteSpace(normalizedDigest))
{
return Results.BadRequest(new { error = "Invalid image digest" });
}
var candidates = await store.GetCandidatesAsync(normalizedDigest, ct);
var candidates = await store.GetCandidatesAsync(normalizedDigest, ct, tenantId: tenantId);
if (minConfidence.HasValue)
{
@@ -293,9 +318,15 @@ internal static class SmartDiffEndpoints
private static async Task<IResult> HandleGetCandidateAsync(
string candidateId,
IVexCandidateStore store,
HttpContext? context = null,
CancellationToken ct = default)
{
var candidate = await store.GetCandidateAsync(candidateId, ct);
if (!TryResolveTenant(context, out var tenantId, out var failure))
{
return failure!;
}
var candidate = await store.GetCandidateAsync(candidateId, ct, tenantId: tenantId);
if (candidate is null)
{
@@ -325,6 +356,10 @@ internal static class SmartDiffEndpoints
{
return Results.BadRequest(new { error = "Invalid action", validActions = new[] { "accept", "reject", "defer" } });
}
if (!TryResolveTenant(httpContext, out var tenantId, out var failure))
{
return failure!;
}
var reviewer = httpContext.User.Identity?.Name ?? "anonymous";
var review = new VexCandidateReview(
@@ -333,7 +368,7 @@ internal static class SmartDiffEndpoints
ReviewedAt: timeProvider.GetUtcNow(),
Comment: request.Comment);
var success = await store.ReviewCandidateAsync(candidateId, review, ct);
var success = await store.ReviewCandidateAsync(candidateId, review, ct, tenantId: tenantId);
if (!success)
{
@@ -365,6 +400,10 @@ internal static class SmartDiffEndpoints
{
return Results.BadRequest(new { error = "CandidateId is required" });
}
if (!TryResolveTenant(httpContext, out var tenantId, out var failure))
{
return failure!;
}
var metadata = await metadataRepository.GetScanMetadataAsync(scanId, ct).ConfigureAwait(false);
var targetDigest = NormalizeDigest(metadata?.TargetDigest);
@@ -373,7 +412,7 @@ internal static class SmartDiffEndpoints
return Results.NotFound(new { error = "Scan metadata not found", scanId });
}
var candidate = await store.GetCandidateAsync(request.CandidateId, ct).ConfigureAwait(false);
var candidate = await store.GetCandidateAsync(request.CandidateId, ct, tenantId: tenantId).ConfigureAwait(false);
if (candidate is null || !string.Equals(candidate.ImageDigest, targetDigest, StringComparison.OrdinalIgnoreCase))
{
return Results.NotFound(new { error = "Candidate not found for scan", scanId, candidateId = request.CandidateId });
@@ -482,6 +521,40 @@ internal static class SmartDiffEndpoints
};
}
private static bool TryResolveTenant(HttpContext? context, out string tenantId, out IResult? failure)
{
tenantId = string.Empty;
failure = null;
if (context is null)
{
failure = Results.BadRequest(new
{
type = "validation-error",
title = "Invalid tenant context",
detail = "tenant_missing"
});
return false;
}
if (ScannerRequestContextResolver.TryResolveTenant(
context,
out tenantId,
out var tenantError,
allowDefaultTenant: true))
{
return true;
}
failure = Results.BadRequest(new
{
type = "validation-error",
title = "Invalid tenant context",
detail = tenantError ?? "tenant_conflict"
});
return false;
}
#endregion
}