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:
@@ -7,51 +7,67 @@
|
||||
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Policy.Engine.Tenancy;
|
||||
using StellaOps.Policy.Gates;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// API endpoints for risk budget management.
|
||||
/// POL-TEN-03: Tenant enforcement via ITenantContextAccessor.
|
||||
/// </summary>
|
||||
internal static class RiskBudgetEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapRiskBudgets(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/v1/policy/budget")
|
||||
.RequireAuthorization()
|
||||
.WithTags("Risk Budgets");
|
||||
.WithTags("Risk Budgets")
|
||||
.RequireTenantContext();
|
||||
|
||||
group.MapGet("/status/{serviceId}", GetBudgetStatus)
|
||||
.WithName("GetRiskBudgetStatus")
|
||||
.WithSummary("Get current risk budget status for a service.")
|
||||
.WithDescription("Retrieve the current risk budget status for a specific service, including allocated capacity, consumed points, remaining headroom, and enforcement status for the requested or active budget window.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead))
|
||||
.Produces<RiskBudgetStatusResponse>(StatusCodes.Status200OK);
|
||||
|
||||
group.MapPost("/consume", ConsumeBudget)
|
||||
.WithName("ConsumeRiskBudget")
|
||||
.WithSummary("Record budget consumption after a release.")
|
||||
.WithDescription("Record the risk point consumption for a completed release, deducting the specified points from the service's active budget window ledger. Returns the updated budget state including remaining headroom and enforcement status.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyOperate))
|
||||
.Produces<BudgetConsumeResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
|
||||
|
||||
group.MapPost("/check", CheckRelease)
|
||||
.WithName("CheckRelease")
|
||||
.WithSummary("Check if a release can proceed given current budget.")
|
||||
.WithDescription("Evaluate whether a proposed release can proceed given the service's current risk budget, operational context (change freeze, incident state, deployment window), and mitigation factors (feature flags, canary deployment, rollback plan). Returns the required gate level, projected risk points, and pre/post budget state.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead))
|
||||
.Produces<ReleaseCheckResponse>(StatusCodes.Status200OK);
|
||||
|
||||
group.MapGet("/history/{serviceId}", GetBudgetHistory)
|
||||
.WithName("GetBudgetHistory")
|
||||
.WithSummary("Get budget consumption history for a service.")
|
||||
.WithDescription("Retrieve the chronological list of risk budget consumption entries for a service within the specified or current budget window, showing each release's risk point cost and timestamp for audit and trend analysis.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead))
|
||||
.Produces<BudgetHistoryResponse>(StatusCodes.Status200OK);
|
||||
|
||||
group.MapPost("/adjust", AdjustBudget)
|
||||
.WithName("AdjustBudget")
|
||||
.WithSummary("Adjust budget allocation (earned capacity or manual override).")
|
||||
.WithDescription("Apply a positive or negative adjustment to a service's allocated risk budget, supporting earned-capacity rewards and administrative corrections. The adjustment reason is recorded for the audit ledger.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyEdit))
|
||||
.Produces<RiskBudgetStatusResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
|
||||
|
||||
group.MapGet("/list", ListBudgets)
|
||||
.WithName("ListRiskBudgets")
|
||||
.WithSummary("List all risk budgets with optional filtering.")
|
||||
.WithDescription("List risk budgets across services with optional filtering by enforcement status and budget window, returning current allocation, consumption, and remaining headroom for each service to support dashboard and compliance reporting.")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead))
|
||||
.Produces<BudgetListResponse>(StatusCodes.Status200OK);
|
||||
|
||||
return endpoints;
|
||||
@@ -60,9 +76,12 @@ internal static class RiskBudgetEndpoints
|
||||
private static async Task<Ok<RiskBudgetStatusResponse>> GetBudgetStatus(
|
||||
string serviceId,
|
||||
[FromQuery] string? window,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IBudgetLedger ledger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
// POL-TEN-03: tenantId available for downstream scoping when repository layer is wired.
|
||||
var budget = await ledger.GetBudgetAsync(serviceId, window, ct);
|
||||
|
||||
return TypedResults.Ok(new RiskBudgetStatusResponse(
|
||||
@@ -80,9 +99,11 @@ internal static class RiskBudgetEndpoints
|
||||
|
||||
private static async Task<Results<Ok<BudgetConsumeResponse>, ProblemHttpResult>> ConsumeBudget(
|
||||
[FromBody] BudgetConsumeRequest request,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IBudgetLedger ledger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
if (request.RiskPoints <= 0)
|
||||
{
|
||||
return TypedResults.Problem(
|
||||
@@ -114,9 +135,11 @@ internal static class RiskBudgetEndpoints
|
||||
|
||||
private static async Task<Ok<ReleaseCheckResponse>> CheckRelease(
|
||||
[FromBody] ReleaseCheckRequest request,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IBudgetConstraintEnforcer enforcer,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
var input = new ReleaseCheckInput
|
||||
{
|
||||
ServiceId = request.ServiceId,
|
||||
@@ -158,9 +181,11 @@ internal static class RiskBudgetEndpoints
|
||||
private static async Task<Ok<BudgetHistoryResponse>> GetBudgetHistory(
|
||||
string serviceId,
|
||||
[FromQuery] string? window,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IBudgetLedger ledger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
var entries = await ledger.GetHistoryAsync(serviceId, window, ct);
|
||||
|
||||
var items = entries.Select(e => new BudgetEntryDto(
|
||||
@@ -177,9 +202,11 @@ internal static class RiskBudgetEndpoints
|
||||
|
||||
private static async Task<Results<Ok<RiskBudgetStatusResponse>, ProblemHttpResult>> AdjustBudget(
|
||||
[FromBody] BudgetAdjustRequest request,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IBudgetLedger ledger,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
if (request.Adjustment == 0)
|
||||
{
|
||||
return TypedResults.Problem(
|
||||
@@ -209,8 +236,11 @@ internal static class RiskBudgetEndpoints
|
||||
private static Ok<BudgetListResponse> ListBudgets(
|
||||
[FromQuery] string? status,
|
||||
[FromQuery] string? window,
|
||||
[FromQuery] int limit = 50)
|
||||
[FromQuery] int limit = 50,
|
||||
ITenantContextAccessor tenantAccessor = default!)
|
||||
{
|
||||
var tenantId = tenantAccessor.TenantContext!.TenantId;
|
||||
// POL-TEN-03: tenantId available for downstream scoping when repository layer is wired.
|
||||
// This would query from PostgresBudgetStore.GetBudgetsByStatusAsync or GetBudgetsByWindowAsync
|
||||
// For now, return empty list - implementation would need to inject the store
|
||||
return TypedResults.Ok(new BudgetListResponse([], 0));
|
||||
|
||||
Reference in New Issue
Block a user