// SPDX-License-Identifier: BUSL-1.1 // Copyright (c) 2025 StellaOps // Sprint: SPRINT_20260122_039_Scanner_runtime_linkage_verification // Task: RLV-009 - Platform API: Function Map Endpoints using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; using StellaOps.Platform.WebService.Services; namespace StellaOps.Platform.WebService.Endpoints; /// /// Function map management API endpoints. /// public static class FunctionMapEndpoints { /// /// Maps function-map-related endpoints. /// public static IEndpointRouteBuilder MapFunctionMapEndpoints(this IEndpointRouteBuilder app) { var maps = app.MapGroup("/api/v1/function-maps") .WithTags("Function Maps") .RequireAuthorization(PlatformPolicies.FunctionMapRead) .RequireTenant(); MapCrudEndpoints(maps); MapVerifyEndpoints(maps); return app; } private static void MapCrudEndpoints(IEndpointRouteBuilder maps) { // POST /api/v1/function-maps - Create function map maps.MapPost("/", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, [FromBody] CreateFunctionMapRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.CreateAsync( requestContext!, request, cancellationToken).ConfigureAwait(false); return Results.Created( $"/api/v1/function-maps/{result.Value.Id}", new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, result.DataAsOf, result.Cached, result.CacheTtlSeconds, result.Value)); }) .WithName("CreateFunctionMap") .WithSummary("Create function map") .WithDescription("Creates a new function map from an SBOM reference and hot function patterns.") .RequireAuthorization(PlatformPolicies.FunctionMapWrite); // GET /api/v1/function-maps - List function maps maps.MapGet("/", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, [FromQuery] int? limit, [FromQuery] int? offset, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.ListAsync( requestContext!, limit ?? 100, offset ?? 0, cancellationToken).ConfigureAwait(false); return Results.Ok(new PlatformListResponse( requestContext!.TenantId, requestContext.ActorId, result.DataAsOf, result.Cached, result.CacheTtlSeconds, result.Value, result.Value.Count, limit ?? 100, offset ?? 0)); }) .WithName("ListFunctionMaps") .WithSummary("List function maps") .WithDescription("Lists all function maps for the current tenant.") .RequireAuthorization(PlatformPolicies.FunctionMapRead); // GET /api/v1/function-maps/{id} - Get function map by ID maps.MapGet("/{id}", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, string id, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.GetByIdAsync( requestContext!, id, cancellationToken).ConfigureAwait(false); if (result.Value is null) { return Results.NotFound(new { error = "Function map not found", id }); } return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, result.DataAsOf, result.Cached, result.CacheTtlSeconds, result.Value)); }) .WithName("GetFunctionMap") .WithSummary("Get function map") .WithDescription("Retrieves a function map by its unique identifier.") .RequireAuthorization(PlatformPolicies.FunctionMapRead); // DELETE /api/v1/function-maps/{id} - Delete function map maps.MapDelete("/{id}", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, string id, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.DeleteAsync( requestContext!, id, cancellationToken).ConfigureAwait(false); if (!result.Value) { return Results.NotFound(new { error = "Function map not found", id }); } return Results.NoContent(); }) .WithName("DeleteFunctionMap") .WithSummary("Delete function map") .WithDescription("Deletes a function map by its unique identifier.") .RequireAuthorization(PlatformPolicies.FunctionMapWrite); } private static void MapVerifyEndpoints(IEndpointRouteBuilder maps) { // POST /api/v1/function-maps/{id}/verify - Verify observations against map maps.MapPost("/{id}/verify", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, string id, [FromBody] VerifyFunctionMapRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.VerifyAsync( requestContext!, id, request, cancellationToken).ConfigureAwait(false); return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, result.DataAsOf, result.Cached, result.CacheTtlSeconds, result.Value)); }) .WithName("VerifyFunctionMap") .WithSummary("Verify function map") .WithDescription("Verifies runtime observations against a declared function map.") .RequireAuthorization(PlatformPolicies.FunctionMapVerify); // GET /api/v1/function-maps/{id}/coverage - Get coverage statistics maps.MapGet("/{id}/coverage", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IFunctionMapService service, string id, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.GetCoverageAsync( requestContext!, id, cancellationToken).ConfigureAwait(false); return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, result.DataAsOf, result.Cached, result.CacheTtlSeconds, result.Value)); }) .WithName("GetFunctionMapCoverage") .WithSummary("Get function map coverage") .WithDescription("Returns current coverage statistics for a function map.") .RequireAuthorization(PlatformPolicies.FunctionMapRead); } private static bool TryResolveContext( HttpContext context, PlatformRequestContextResolver resolver, out PlatformRequestContext? requestContext, out IResult? failure) { if (resolver.TryResolve(context, out requestContext, out var error)) { failure = null; return true; } failure = Results.BadRequest(new { error = error ?? "tenant_missing" }); return false; } }