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

@@ -19,7 +19,8 @@ public static class AdministrationTrustSigningMutationEndpoints
public static IEndpointRouteBuilder MapAdministrationTrustSigningMutationEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/v1/administration/trust-signing")
.WithTags("Administration");
.WithTags("Administration")
.RequireAuthorization(PlatformPolicies.TrustRead);
group.MapGet("/keys", async Task<IResult>(
HttpContext context,

View File

@@ -17,7 +17,8 @@ public static class AnalyticsEndpoints
public static IEndpointRouteBuilder MapAnalyticsEndpoints(this IEndpointRouteBuilder app)
{
var analytics = app.MapGroup("/api/analytics")
.WithTags("Analytics");
.WithTags("Analytics")
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
analytics.MapGet("/suppliers", async Task<IResult> (
HttpContext context,

View File

@@ -15,7 +15,8 @@ public static class ContextEndpoints
public static IEndpointRouteBuilder MapContextEndpoints(this IEndpointRouteBuilder app)
{
var context = app.MapGroup("/api/v2/context")
.WithTags("Platform Context");
.WithTags("Platform Context")
.RequireAuthorization(PlatformPolicies.ContextRead);
context.MapGet("/regions", async Task<IResult>(
HttpContext httpContext,

View File

@@ -18,7 +18,8 @@ public static class EnvironmentSettingsAdminEndpoints
public static IEndpointRouteBuilder MapEnvironmentSettingsAdminEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/platform/envsettings/db")
.WithTags("Environment Settings Admin");
.WithTags("Environment Settings Admin")
.RequireAuthorization(PlatformPolicies.SetupRead);
group.MapGet("/", async (IEnvironmentSettingsStore store, CancellationToken ct) =>
{

View File

@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Services;
using StellaOps.ReleaseOrchestrator.EvidenceThread.Export;
using StellaOps.ReleaseOrchestrator.EvidenceThread.Models;
@@ -31,13 +32,14 @@ public static class EvidenceThreadEndpoints
public static IEndpointRouteBuilder MapEvidenceThreadEndpoints(this IEndpointRouteBuilder app)
{
var evidence = app.MapGroup("/api/v1/evidence")
.WithTags("Evidence Thread");
.WithTags("Evidence Thread")
.RequireAuthorization(PlatformPolicies.ContextRead);
// GET /api/v1/evidence/{artifactDigest} - Get evidence thread for artifact
evidence.MapGet("/{artifactDigest}", GetEvidenceThread)
.WithName("GetEvidenceThread")
.WithSummary("Get evidence thread for an artifact")
.WithDescription("Retrieves the full evidence thread graph for an artifact by its digest.")
.WithDescription("Retrieves the full evidence thread graph for an artifact by its digest, including node count, link count, verdict, and risk score.")
.Produces<EvidenceThreadResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
@@ -46,7 +48,8 @@ public static class EvidenceThreadEndpoints
evidence.MapPost("/{artifactDigest}/export", ExportEvidenceThread)
.WithName("ExportEvidenceThread")
.WithSummary("Export evidence thread as DSSE bundle")
.WithDescription("Exports the evidence thread as a signed DSSE envelope for offline verification.")
.WithDescription("Exports the evidence thread as a signed DSSE envelope for offline verification. Supports DSSE, JSON, Markdown, and PDF formats. The envelope is optionally signed with the specified key.")
.RequireAuthorization(PlatformPolicies.ContextWrite)
.Produces<EvidenceExportResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
@@ -55,7 +58,8 @@ public static class EvidenceThreadEndpoints
evidence.MapPost("/{artifactDigest}/transcript", GenerateTranscript)
.WithName("GenerateEvidenceTranscript")
.WithSummary("Generate natural language transcript")
.WithDescription("Generates a natural language transcript explaining the evidence thread.")
.WithDescription("Generates a natural language transcript explaining the evidence thread in summary, detailed, or audit format. May invoke an LLM for rationale generation when enabled.")
.RequireAuthorization(PlatformPolicies.ContextWrite)
.Produces<EvidenceTranscriptResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
@@ -64,7 +68,7 @@ public static class EvidenceThreadEndpoints
evidence.MapGet("/{artifactDigest}/nodes", GetEvidenceNodes)
.WithName("GetEvidenceNodes")
.WithSummary("Get evidence nodes for an artifact")
.WithDescription("Retrieves all evidence nodes in the thread.")
.WithDescription("Retrieves all evidence nodes in the thread, optionally filtered by node kind (e.g., sbom, scan, attestation). Returns node summaries, confidence scores, and anchor counts.")
.Produces<EvidenceNodeListResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
@@ -73,7 +77,7 @@ public static class EvidenceThreadEndpoints
evidence.MapGet("/{artifactDigest}/links", GetEvidenceLinks)
.WithName("GetEvidenceLinks")
.WithSummary("Get evidence links for an artifact")
.WithDescription("Retrieves all evidence links in the thread.")
.WithDescription("Retrieves all directed evidence links in the thread, describing provenance and dependency relationships between evidence nodes.")
.Produces<EvidenceLinkListResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
@@ -82,7 +86,8 @@ public static class EvidenceThreadEndpoints
evidence.MapPost("/{artifactDigest}/collect", CollectEvidence)
.WithName("CollectEvidence")
.WithSummary("Collect evidence for an artifact")
.WithDescription("Triggers collection of all available evidence for an artifact.")
.WithDescription("Triggers collection of all available evidence for an artifact: SBOM diff, reachability graph, VEX advisories, and attestations. Returns the count of nodes and links created, plus any collection errors.")
.RequireAuthorization(PlatformPolicies.ContextWrite)
.Produces<EvidenceCollectionResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest);

View File

@@ -20,7 +20,8 @@ public static class FederationTelemetryEndpoints
public static IEndpointRouteBuilder MapFederationTelemetryEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/v1/telemetry/federation")
.WithTags("Federated Telemetry");
.WithTags("Federated Telemetry")
.RequireAuthorization(PlatformPolicies.FederationRead);
// GET /consent — get consent state
group.MapGet("/consent", async Task<IResult>(

View File

@@ -24,7 +24,8 @@ public static class FunctionMapEndpoints
public static IEndpointRouteBuilder MapFunctionMapEndpoints(this IEndpointRouteBuilder app)
{
var maps = app.MapGroup("/api/v1/function-maps")
.WithTags("Function Maps");
.WithTags("Function Maps")
.RequireAuthorization(PlatformPolicies.FunctionMapRead);
MapCrudEndpoints(maps);
MapVerifyEndpoints(maps);

View File

@@ -15,7 +15,8 @@ public static class IntegrationReadModelEndpoints
public static IEndpointRouteBuilder MapIntegrationReadModelEndpoints(this IEndpointRouteBuilder app)
{
var integrations = app.MapGroup("/api/v2/integrations")
.WithTags("Integrations V2");
.WithTags("Integrations V2")
.RequireAuthorization(PlatformPolicies.IntegrationsRead);
integrations.MapGet("/feeds", async Task<IResult>(
HttpContext context,

View File

@@ -16,7 +16,8 @@ public static class LegacyAliasEndpoints
public static IEndpointRouteBuilder MapLegacyAliasEndpoints(this IEndpointRouteBuilder app)
{
var legacy = app.MapGroup("/api/v1")
.WithTags("Pack22 Legacy Aliases");
.WithTags("Pack22 Legacy Aliases")
.RequireAuthorization(PlatformPolicies.ContextRead);
legacy.MapGet("/context/regions", async Task<IResult>(
HttpContext context,

View File

@@ -43,7 +43,8 @@ public static class PackAdapterEndpoints
.RequireAuthorization(PlatformPolicies.HealthRead);
var platform = app.MapGroup("/api/v1/platform")
.WithTags("Platform Ops");
.WithTags("Platform Ops")
.RequireAuthorization(PlatformPolicies.HealthRead);
platform.MapGet("/data-integrity/summary", (
HttpContext context,

View File

@@ -18,7 +18,8 @@ public static class PlatformEndpoints
public static IEndpointRouteBuilder MapPlatformEndpoints(this IEndpointRouteBuilder app)
{
var platform = app.MapGroup("/api/v1/platform")
.WithTags("Platform");
.WithTags("Platform")
.RequireAuthorization(PlatformPolicies.HealthRead);
MapHealthEndpoints(platform);
MapQuotaEndpoints(platform);
@@ -161,12 +162,12 @@ public static class PlatformEndpoints
return failure!;
}
if (string.IsNullOrWhiteSpace(tenantId))
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
{
return Results.BadRequest(new { error = "tenant_missing" });
return tenantFailure!;
}
var result = await service.GetTenantAsync(tenantId.Trim().ToLowerInvariant(), cancellationToken).ConfigureAwait(false);
var result = await service.GetTenantAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse<PlatformQuotaUsage>(
requestContext!.TenantId,
requestContext.ActorId,
@@ -293,12 +294,12 @@ public static class PlatformEndpoints
return failure!;
}
if (string.IsNullOrWhiteSpace(tenantId))
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
{
return Results.BadRequest(new { error = "tenant_missing" });
return tenantFailure!;
}
var status = await service.GetTenantSetupStatusAsync(tenantId.Trim().ToLowerInvariant(), cancellationToken).ConfigureAwait(false);
var status = await service.GetTenantSetupStatusAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
return Results.Ok(status);
}).RequireAuthorization(PlatformPolicies.OnboardingRead);
}
@@ -476,7 +477,8 @@ public static class PlatformEndpoints
private static void MapLegacyQuotaCompatibilityEndpoints(IEndpointRouteBuilder app)
{
var quotas = app.MapGroup("/api/v1/authority/quotas")
.WithTags("Platform Quotas Compatibility");
.WithTags("Platform Quotas Compatibility")
.RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet(string.Empty, async Task<IResult> (
HttpContext context,
@@ -491,7 +493,7 @@ public static class PlatformEndpoints
var summary = await service.GetSummaryAsync(requestContext!, cancellationToken).ConfigureAwait(false);
return Results.Ok(BuildLegacyEntitlement(summary.Value, requestContext!));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/consumption", async Task<IResult> (
HttpContext context,
@@ -506,7 +508,7 @@ public static class PlatformEndpoints
var summary = await service.GetSummaryAsync(requestContext!, cancellationToken).ConfigureAwait(false);
return Results.Ok(BuildLegacyConsumption(summary.Value));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/dashboard", async Task<IResult> (
HttpContext context,
@@ -528,7 +530,7 @@ public static class PlatformEndpoints
activeAlerts = 0,
recentViolations = 0
});
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/history", async Task<IResult> (
HttpContext context,
@@ -570,7 +572,7 @@ public static class PlatformEndpoints
points,
aggregation = string.IsNullOrWhiteSpace(aggregation) ? "daily" : aggregation
});
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/tenants", async Task<IResult> (
HttpContext context,
@@ -612,7 +614,7 @@ public static class PlatformEndpoints
.ToArray();
return Results.Ok(new { items, total = 1 });
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/tenants/{tenantId}", async Task<IResult> (
HttpContext context,
@@ -626,7 +628,12 @@ public static class PlatformEndpoints
return failure!;
}
var result = await service.GetTenantAsync(tenantId, cancellationToken).ConfigureAwait(false);
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
{
return tenantFailure!;
}
var result = await service.GetTenantAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
var consumption = BuildLegacyConsumption(result.Value);
return Results.Ok(new
@@ -655,7 +662,7 @@ public static class PlatformEndpoints
},
forecast = BuildLegacyForecast("api")
});
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/forecast", async Task<IResult> (
HttpContext context,
@@ -673,7 +680,7 @@ public static class PlatformEndpoints
var forecasts = categories.Select(BuildLegacyForecast).ToArray();
return Results.Ok(forecasts);
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapGet("/alerts", (HttpContext context, PlatformRequestContextResolver resolver) =>
{
@@ -694,7 +701,7 @@ public static class PlatformEndpoints
channels = Array.Empty<object>(),
escalationMinutes = 30
}));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
quotas.MapPost("/alerts", (HttpContext context, PlatformRequestContextResolver resolver, [FromBody] object config) =>
{
@@ -704,10 +711,11 @@ public static class PlatformEndpoints
}
return Task.FromResult<IResult>(Results.Ok(config));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaAdmin);
var rateLimits = app.MapGroup("/api/v1/gateway/rate-limits")
.WithTags("Platform Gateway Compatibility");
.WithTags("Platform Gateway Compatibility")
.RequireAuthorization(PlatformPolicies.QuotaRead);
rateLimits.MapGet(string.Empty, (HttpContext context, PlatformRequestContextResolver resolver) =>
{
@@ -729,7 +737,7 @@ public static class PlatformEndpoints
burstRemaining = 119
}
}));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
rateLimits.MapGet("/violations", (HttpContext context, PlatformRequestContextResolver resolver) =>
{
@@ -749,7 +757,7 @@ public static class PlatformEndpoints
end = now.ToString("o")
}
}));
}).RequireAuthorization();
}).RequireAuthorization(PlatformPolicies.QuotaRead);
}
private static LegacyQuotaItem[] BuildLegacyConsumption(IReadOnlyList<PlatformQuotaUsage> usage)
@@ -885,6 +893,37 @@ public static class PlatformEndpoints
return false;
}
private static bool TryResolveRequestedTenant(
PlatformRequestContext requestContext,
string? requestedTenantId,
out string normalizedTenantId,
out IResult? failure)
{
normalizedTenantId = string.Empty;
if (string.IsNullOrWhiteSpace(requestedTenantId))
{
failure = Results.BadRequest(new { error = "tenant_missing" });
return false;
}
normalizedTenantId = requestedTenantId.Trim().ToLowerInvariant();
if (!string.Equals(normalizedTenantId, requestContext.TenantId, StringComparison.Ordinal))
{
failure = Results.Json(
new
{
error = "tenant_forbidden",
requestedTenantId = normalizedTenantId
},
statusCode: StatusCodes.Status403Forbidden);
return false;
}
failure = null;
return true;
}
private sealed record LegacyQuotaItem(
string Category,
decimal Current,

View File

@@ -25,7 +25,8 @@ public static class PolicyInteropEndpoints
public static IEndpointRouteBuilder MapPolicyInteropEndpoints(this IEndpointRouteBuilder app)
{
var interop = app.MapGroup("/api/v1/policy/interop")
.WithTags("PolicyInterop");
.WithTags("PolicyInterop")
.RequireAuthorization(PlatformPolicies.PolicyRead);
MapExportEndpoint(interop);
MapImportEndpoint(interop);

View File

@@ -19,7 +19,8 @@ public static class ReleaseControlEndpoints
public static IEndpointRouteBuilder MapReleaseControlEndpoints(this IEndpointRouteBuilder app)
{
var bundles = app.MapGroup("/api/v1/release-control/bundles")
.WithTags("Release Control");
.WithTags("Release Control")
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
bundles.MapGet(string.Empty, async Task<IResult>(
HttpContext context,

View File

@@ -16,7 +16,8 @@ public static class ReleaseReadModelEndpoints
public static IEndpointRouteBuilder MapReleaseReadModelEndpoints(this IEndpointRouteBuilder app)
{
var releases = app.MapGroup("/api/v2/releases")
.WithTags("Releases V2");
.WithTags("Releases V2")
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
releases.MapGet(string.Empty, async Task<IResult>(
HttpContext context,

View File

@@ -25,7 +25,8 @@ public static class ScoreEndpoints
public static IEndpointRouteBuilder MapScoreEndpoints(this IEndpointRouteBuilder app)
{
var score = app.MapGroup("/api/v1/score")
.WithTags("Score");
.WithTags("Score")
.RequireAuthorization(PlatformPolicies.ScoreRead);
MapEvaluateEndpoints(score);
MapHistoryEndpoints(score);

View File

@@ -15,7 +15,8 @@ public static class SecurityReadModelEndpoints
public static IEndpointRouteBuilder MapSecurityReadModelEndpoints(this IEndpointRouteBuilder app)
{
var security = app.MapGroup("/api/v2/security")
.WithTags("Security V2");
.WithTags("Security V2")
.RequireAuthorization(PlatformPolicies.SecurityRead);
security.MapGet("/findings", async Task<IResult>(
HttpContext context,

View File

@@ -15,7 +15,8 @@ public static class TopologyReadModelEndpoints
public static IEndpointRouteBuilder MapTopologyReadModelEndpoints(this IEndpointRouteBuilder app)
{
var topology = app.MapGroup("/api/v2/topology")
.WithTags("Topology V2");
.WithTags("Topology V2")
.RequireAuthorization(PlatformPolicies.TopologyRead);
topology.MapGet("/regions", async Task<IResult>(
HttpContext context,