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

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Tenancy;
using StellaOps.Policy.RiskProfile.Lifecycle;
using StellaOps.Policy.RiskProfile.Models;
using System.Security.Claims;
@@ -11,45 +12,55 @@ using System.Text.Json;
namespace StellaOps.Policy.Engine.Endpoints;
/// <summary>
/// POL-TEN-03: Tenant enforcement via ITenantContextAccessor.
/// </summary>
internal static class RiskProfileEndpoints
{
public static IEndpointRouteBuilder MapRiskProfiles(this IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/risk/profiles")
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })))
.WithTags("Risk Profiles");
.WithTags("Risk Profiles")
.RequireTenantContext();
group.MapGet(string.Empty, ListProfiles)
.WithName("ListRiskProfiles")
.WithSummary("List all available risk profiles.")
.WithDescription("List all registered risk profiles for the current tenant, returning each profile's identifier, current version, and description. Used by the policy console and advisory pipeline to discover which profiles are available for evaluation binding.")
.Produces<RiskProfileListResponse>(StatusCodes.Status200OK);
group.MapGet("/{profileId}", GetProfile)
.WithName("GetRiskProfile")
.WithSummary("Get a risk profile by ID.")
.WithDescription("Retrieve the full definition of a risk profile by identifier, including signal weights, severity override rules, and the deterministic profile hash used for reproducible evaluation runs.")
.Produces<RiskProfileResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/{profileId}/versions", ListVersions)
.WithName("ListRiskProfileVersions")
.WithSummary("List all versions of a risk profile.")
.WithDescription("List the full version history of a risk profile, including lifecycle status (Draft, Active, Deprecated, Archived), activation timestamps, and actor identities for each version, supporting compliance audit trails.")
.Produces<RiskProfileVersionListResponse>(StatusCodes.Status200OK);
group.MapGet("/{profileId}/versions/{version}", GetVersion)
.WithName("GetRiskProfileVersion")
.WithSummary("Get a specific version of a risk profile.")
.WithDescription("Retrieve the complete definition and lifecycle metadata for a specific versioned risk profile, including its deterministic hash and version info record, enabling exact-version lookups during policy replay and audit verification.")
.Produces<RiskProfileResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapPost(string.Empty, CreateProfile)
.WithName("CreateRiskProfile")
.WithSummary("Create a new risk profile version in draft status.")
.WithDescription("Register a new risk profile version in Draft lifecycle status, recording the authoring actor and creation timestamp. The profile must be activated before it can be used in live policy evaluations.")
.Produces<RiskProfileResponse>(StatusCodes.Status201Created)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
group.MapPost("/{profileId}/versions/{version}:activate", ActivateProfile)
.WithName("ActivateRiskProfile")
.WithSummary("Activate a draft risk profile, making it available for use.")
.WithDescription("Transition a Draft risk profile version to Active status, making it available for binding to policy evaluation runs. Records the activating actor and timestamps the transition for the lifecycle audit log.")
.Produces<RiskProfileVersionInfoResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
@@ -57,6 +68,7 @@ internal static class RiskProfileEndpoints
group.MapPost("/{profileId}/versions/{version}:deprecate", DeprecateProfile)
.WithName("DeprecateRiskProfile")
.WithSummary("Deprecate an active risk profile.")
.WithDescription("Mark an active risk profile version as Deprecated, optionally specifying a successor version and a human-readable reason. Deprecated profiles remain queryable for audit but are excluded from new evaluation bindings.")
.Produces<RiskProfileVersionInfoResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
@@ -64,29 +76,34 @@ internal static class RiskProfileEndpoints
group.MapPost("/{profileId}/versions/{version}:archive", ArchiveProfile)
.WithName("ArchiveRiskProfile")
.WithSummary("Archive a risk profile, removing it from active use.")
.WithDescription("Transition a risk profile version to Archived status, permanently removing it from active evaluation use while preserving the definition and lifecycle record for historical audit queries.")
.Produces<RiskProfileVersionInfoResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/{profileId}/events", GetProfileEvents)
.WithName("GetRiskProfileEvents")
.WithSummary("Get lifecycle events for a risk profile.")
.WithDescription("Retrieve the ordered lifecycle event log for a risk profile, including creation, activation, deprecation, and archival events with actor and timestamp information, supporting compliance reporting and change history review.")
.Produces<RiskProfileEventListResponse>(StatusCodes.Status200OK);
group.MapPost("/compare", CompareProfiles)
.WithName("CompareRiskProfiles")
.WithSummary("Compare two risk profile versions and list differences.")
.WithDescription("Compute a structured diff between two risk profile versions, listing added, removed, and modified signal weights and override rules. Used by policy authors to validate the impact of profile changes before promoting to active.")
.Produces<RiskProfileComparisonResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
group.MapGet("/{profileId}/hash", GetProfileHash)
.WithName("GetRiskProfileHash")
.WithSummary("Get the deterministic hash of a risk profile.")
.WithDescription("Compute and return the deterministic hash of a risk profile, optionally restricted to content-only hashing (excluding metadata). Used to verify profile identity across environments and ensure reproducible evaluation inputs.")
.Produces<RiskProfileHashResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/{profileId}/metadata", GetProfileMetadata)
.WithName("GetRiskProfileMetadata")
.WithSummary("Export risk profile metadata for notification enrichment (POLICY-RISK-40-002).")
.WithDescription("Export a compact metadata summary of a risk profile including signal names, severity thresholds, active version info, and custom metadata fields. Used by the notification enrichment pipeline to annotate policy-triggered alerts with human-readable risk context.")
.Produces<RiskProfileMetadataExportResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
@@ -95,6 +112,7 @@ internal static class RiskProfileEndpoints
private static IResult ListProfiles(
HttpContext context,
ITenantContextAccessor tenantAccessor,
RiskProfileConfigurationService profileService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
@@ -103,6 +121,8 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
// POL-TEN-03: tenantId available for downstream scoping when repository layer is wired.
var ids = profileService.GetProfileIds();
var profiles = ids
.Select(id => profileService.GetProfile(id))
@@ -116,6 +136,7 @@ internal static class RiskProfileEndpoints
private static IResult GetProfile(
HttpContext context,
[FromRoute] string profileId,
ITenantContextAccessor tenantAccessor,
RiskProfileConfigurationService profileService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
@@ -124,6 +145,7 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
var profile = profileService.GetProfile(profileId);
if (profile == null)
{
@@ -196,6 +218,7 @@ internal static class RiskProfileEndpoints
private static IResult CreateProfile(
HttpContext context,
[FromBody] CreateRiskProfileRequest request,
ITenantContextAccessor tenantAccessor,
RiskProfileConfigurationService profileService,
RiskProfileLifecycleService lifecycleService)
{
@@ -205,6 +228,8 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
if (request?.Profile == null)
{
return Results.BadRequest(new ProblemDetails
@@ -244,6 +269,7 @@ internal static class RiskProfileEndpoints
HttpContext context,
[FromRoute] string profileId,
[FromRoute] string version,
ITenantContextAccessor tenantAccessor,
RiskProfileLifecycleService lifecycleService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyActivate);
@@ -252,6 +278,7 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
var actorId = ResolveActorId(context);
try
@@ -285,6 +312,7 @@ internal static class RiskProfileEndpoints
[FromRoute] string profileId,
[FromRoute] string version,
[FromBody] DeprecateRiskProfileRequest? request,
ITenantContextAccessor tenantAccessor,
RiskProfileLifecycleService lifecycleService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
@@ -293,6 +321,7 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
var actorId = ResolveActorId(context);
try
@@ -331,6 +360,7 @@ internal static class RiskProfileEndpoints
HttpContext context,
[FromRoute] string profileId,
[FromRoute] string version,
ITenantContextAccessor tenantAccessor,
RiskProfileLifecycleService lifecycleService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
@@ -339,6 +369,7 @@ internal static class RiskProfileEndpoints
return scopeResult;
}
var tenantId = tenantAccessor.TenantContext!.TenantId;
var actorId = ResolveActorId(context);
try