using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Signals.Hosting; using StellaOps.Signals.Options; using StellaOps.Signals.Routing; namespace StellaOps.Signals; internal static class CompatibilityApiV1Endpoints { private static readonly CompatibilitySignalRecord[] Signals = [ new( "sig-001", "ci_build", "gitea", "completed", new Dictionary { ["host"] = "build-agent-01", ["runtime"] = "ebpf", ["probeStatus"] = "healthy", ["latencyMs"] = 41 }, "corr-001", "sha256:001", new[] { "update-runtime-health" }, "2026-03-09T08:10:00Z", "2026-03-09T08:10:02Z", null), new( "sig-002", "ci_deploy", "internal", "processing", new Dictionary { ["host"] = "deploy-stage-02", ["runtime"] = "etw", ["probeStatus"] = "degraded", ["latencyMs"] = 84 }, "corr-002", "sha256:002", new[] { "refresh-rollout-state" }, "2026-03-09T08:12:00Z", null, null), new( "sig-003", "registry_push", "harbor", "failed", new Dictionary { ["host"] = "registry-sync-01", ["runtime"] = "dyld", ["probeStatus"] = "failed", ["latencyMs"] = 132 }, "corr-003", "sha256:003", new[] { "retry-mirror" }, "2026-03-09T08:13:00Z", "2026-03-09T08:13:05Z", "Registry callback timed out."), new( "sig-004", "scan_complete", "internal", "completed", new Dictionary { ["host"] = "scanner-03", ["runtime"] = "ebpf", ["probeStatus"] = "healthy", ["latencyMs"] = 58 }, "corr-004", "sha256:004", new[] { "refresh-risk-snapshot" }, "2026-03-09T08:16:00Z", "2026-03-09T08:16:01Z", null), new( "sig-005", "policy_eval", "internal", "received", new Dictionary { ["host"] = "policy-runner-01", ["runtime"] = "unknown", ["probeStatus"] = "degraded", ["latencyMs"] = 73 }, "corr-005", "sha256:005", new[] { "await-policy-evaluation" }, "2026-03-09T08:18:00Z", null, null) ]; public static IEndpointRouteBuilder MapSignalsCompatibilityEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/v1/signals").RequireTenant(); group.MapGet("", ( HttpContext context, SignalsOptions options, SignalsSealedModeMonitor sealedModeMonitor, string? type, string? status, string? provider, int? limit, string? cursor) => { if (!Program.TryAuthorizeAny(context, [SignalsPolicies.Read, StellaOpsScopes.OrchRead], options.Authority.AllowAnonymousFallback, out var authFailure)) { return authFailure ?? Results.Unauthorized(); } if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure)) { return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable); } var filtered = ApplyFilters(type, status, provider); var offset = ParseCursor(cursor); var pageSize = Math.Clamp(limit ?? 50, 1, 200); var items = filtered.Skip(offset).Take(pageSize).ToArray(); var nextCursor = offset + pageSize < filtered.Length ? (offset + pageSize).ToString() : null; return Results.Ok(new { items, total = filtered.Length, cursor = nextCursor }); }); group.MapGet("/stats", ( HttpContext context, SignalsOptions options, SignalsSealedModeMonitor sealedModeMonitor) => { if (!Program.TryAuthorizeAny(context, [SignalsPolicies.Read, StellaOpsScopes.OrchRead], options.Authority.AllowAnonymousFallback, out var authFailure)) { return authFailure ?? Results.Unauthorized(); } if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure)) { return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable); } return Results.Ok(BuildStats(Signals)); }); return app; } private static CompatibilitySignalRecord[] ApplyFilters(string? type, string? status, string? provider) => Signals .Where(signal => string.IsNullOrWhiteSpace(type) || string.Equals(signal.Type, type, StringComparison.OrdinalIgnoreCase)) .Where(signal => string.IsNullOrWhiteSpace(status) || string.Equals(signal.Status, status, StringComparison.OrdinalIgnoreCase)) .Where(signal => string.IsNullOrWhiteSpace(provider) || string.Equals(signal.Provider, provider, StringComparison.OrdinalIgnoreCase)) .ToArray(); private static int ParseCursor(string? cursor) => int.TryParse(cursor, out var offset) && offset >= 0 ? offset : 0; private static object BuildStats(IReadOnlyCollection signals) { var byType = signals .GroupBy(signal => signal.Type, StringComparer.OrdinalIgnoreCase) .ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase); var byStatus = signals .GroupBy(signal => signal.Status, StringComparer.OrdinalIgnoreCase) .ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase); var byProvider = signals .GroupBy(signal => signal.Provider, StringComparer.OrdinalIgnoreCase) .ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase); var successful = signals.Count(signal => string.Equals(signal.Status, "completed", StringComparison.OrdinalIgnoreCase)); var latencySamples = signals .Select(signal => signal.Payload.TryGetValue("latencyMs", out var value) ? value : null) .OfType() .ToArray(); return new { total = signals.Count, byType, byStatus, byProvider, lastHourCount = signals.Count, successRate = signals.Count == 0 ? 100 : Math.Round((successful / (double)signals.Count) * 100, 2), avgProcessingMs = latencySamples.Length == 0 ? 0 : Math.Round(latencySamples.Average(), 2) }; } internal sealed record CompatibilitySignalRecord( string Id, string Type, string Provider, string Status, IReadOnlyDictionary Payload, string? CorrelationId, string? ArtifactRef, IReadOnlyCollection TriggeredActions, string ReceivedAt, string? ProcessedAt, string? Error); }