using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using StellaOps.Policy.Engine.Options; using StellaOps.Policy.Engine.Overlay; using StellaOps.Policy.Engine.Streaming; using System.Text; using System.Text.Json; namespace StellaOps.Policy.Engine.Endpoints; public static class PathScopeSimulationEndpoint { public static IEndpointRouteBuilder MapPathScopeSimulation(this IEndpointRouteBuilder routes) { routes.MapPost("/simulation/path-scope", HandleAsync) .RequireRateLimiting(PolicyEngineRateLimitOptions.PolicyName) .WithName("PolicyEngine.PathScopeSimulation") .WithDescription("Stream a what-if path-scope simulation showing how a change in call-graph reachability would alter policy verdicts. Returns NDJSON lines for each simulated path segment with optional deterministic trace output.") .RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicySimulate)); return routes; } private static async Task HandleAsync( [FromBody] PathScopeSimulationRequest request, PathScopeSimulationService service, PathScopeSimulationBridgeService bridge, CancellationToken cancellationToken) { try { var stream = service.StreamAsync(request, cancellationToken); var responseBuilder = new StringBuilder(); await foreach (var line in stream.ConfigureAwait(false)) { responseBuilder.AppendLine(line); } // Emit change event stub when run in what-if mode. if (request.Options.Deterministic && request.Options.IncludeTrace) { var bridgeRequest = new PathScopeSimulationBridgeRequest( Tenant: request.Tenant, Rules: Array.Empty(), Overlays: null, Paths: new[] { request }, Mode: "preview", Seed: null); await bridge.SimulateAsync(bridgeRequest, cancellationToken).ConfigureAwait(false); } return Results.Text(responseBuilder.ToString(), "application/x-ndjson", Encoding.UTF8); } catch (PathScopeSimulationException ex) { var errorLine = JsonSerializer.Serialize(ex.Error); return Results.Text(errorLine + "\n", "application/x-ndjson", Encoding.UTF8, StatusCodes.Status400BadRequest); } } }