// -----------------------------------------------------------------------------
// RuntimeTracesEndpoints.cs
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
// Task: DR-014 — Runtime traces API endpoints
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Findings.Ledger.WebService.Contracts;
namespace StellaOps.Findings.Ledger.WebService.Endpoints;
///
/// API endpoints for runtime traces evidence.
///
public static class RuntimeTracesEndpoints
{
///
/// Maps runtime traces endpoints to the application.
///
public static void MapRuntimeTracesEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/v1/findings")
.WithTags("Runtime Evidence")
.RequireAuthorization();
// GET /api/v1/findings/{findingId}/runtime/traces
group.MapGet("/{findingId:guid}/runtime/traces", GetRuntimeTraces)
.WithName("GetRuntimeTraces")
.WithDescription("Get runtime function traces for a finding")
.Produces(200)
.Produces(404);
// GET /api/v1/findings/{findingId}/runtime/score
group.MapGet("/{findingId:guid}/runtime/score", GetRtsScore)
.WithName("GetRtsScore")
.WithDescription("Get Runtime Trustworthiness Score for a finding")
.Produces(200)
.Produces(404);
}
///
/// Gets runtime function traces for a finding.
///
private static async Task, NotFound>> GetRuntimeTraces(
Guid findingId,
IRuntimeTracesService service,
CancellationToken ct,
[FromQuery] int? limit = null,
[FromQuery] string? sortBy = null)
{
var options = new RuntimeTracesQueryOptions
{
Limit = limit ?? 50,
SortBy = sortBy ?? "hits"
};
var traces = await service.GetTracesAsync(findingId, options, ct);
return traces is not null
? TypedResults.Ok(traces)
: TypedResults.NotFound();
}
///
/// Gets the RTS score for a finding.
///
private static async Task, NotFound>> GetRtsScore(
Guid findingId,
IRuntimeTracesService service,
CancellationToken ct)
{
var score = await service.GetRtsScoreAsync(findingId, ct);
return score is not null
? TypedResults.Ok(score)
: TypedResults.NotFound();
}
}
///
/// Query options for runtime traces.
///
public sealed record RuntimeTracesQueryOptions
{
///
/// Maximum number of traces to return.
///
public int Limit { get; init; } = 50;
///
/// Sort by field (hits, recent).
///
public string SortBy { get; init; } = "hits";
}
///
/// Service for retrieving runtime traces.
///
public interface IRuntimeTracesService
{
///
/// Gets runtime traces for a finding.
///
/// Finding identifier.
/// Query options.
/// Cancellation token.
/// Runtime traces response or null if not found.
Task GetTracesAsync(
Guid findingId,
RuntimeTracesQueryOptions options,
CancellationToken ct);
///
/// Gets RTS score for a finding.
///
/// Finding identifier.
/// Cancellation token.
/// RTS score response or null if not found.
Task GetRtsScoreAsync(Guid findingId, CancellationToken ct);
}