using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using StellaOps.Auth.ServerIntegration.Tenancy; using System.Globalization; namespace StellaOps.Platform.WebService.Endpoints; public static class AocCompatibilityEndpoints { public static IEndpointRouteBuilder MapAocCompatibilityEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/v1/aoc") .WithTags("AOC Compatibility") .RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.AocVerify)) .RequireTenant(); group.MapGet("/metrics", ( HttpContext httpContext, [FromQuery] string? tenantId, [FromQuery] int? windowMinutes, TimeProvider timeProvider) => { var tenant = ResolveTenant(httpContext, tenantId); if (string.IsNullOrWhiteSpace(tenant)) { return Results.BadRequest(new { error = "tenant_required" }); } var effectiveWindowMinutes = windowMinutes is > 0 ? windowMinutes.Value : 1440; var now = timeProvider.GetUtcNow(); return Results.Ok(new { passCount = 12847, failCount = 23, totalCount = 12870, passRate = 0.9982, recentViolations = BuildViolationSummaries(now), ingestThroughput = new { docsPerMinute = 8.9, avgLatencyMs = 145, p95LatencyMs = 312, queueDepth = 3, errorRate = 0.18 }, timeWindow = new { start = now.AddMinutes(-effectiveWindowMinutes).ToString("O", CultureInfo.InvariantCulture), end = now.ToString("O", CultureInfo.InvariantCulture), durationMinutes = effectiveWindowMinutes } }); }) .WithName("AocCompatibility.GetMetrics"); group.MapPost("/verify", ( HttpContext httpContext, AocVerifyRequest request, TimeProvider timeProvider) => { var tenant = ResolveTenant(httpContext, request.TenantId); if (string.IsNullOrWhiteSpace(tenant)) { return Results.BadRequest(new { error = "tenant_required" }); } var now = timeProvider.GetUtcNow(); return Results.Ok(new { verificationId = $"verify-{tenant}-{now:yyyyMMddHHmmss}", status = "partial", checkedCount = Math.Clamp(request.Limit ?? 250, 1, 1000), passedCount = 247, failedCount = 3, violations = new[] { new { documentId = "sbom-nginx-prod", violationCode = "AOC-PROV-001", field = "provenance.digest", expected = "signed", actual = "missing", provenance = new { sourceId = "docker-hub", ingestedAt = now.AddMinutes(-20).ToString("O", CultureInfo.InvariantCulture), digest = "sha256:4fdb5e6a31a80f0d", sourceType = "registry", sourceUrl = "docker.io/library/nginx:1.27.4", submitter = "scanner-agent-01" } } }, completedAt = now.ToString("O", CultureInfo.InvariantCulture) }); }) .WithName("AocCompatibility.Verify"); group.MapGet("/compliance/dashboard", ( HttpContext httpContext, [FromQuery] string? tenantId, TimeProvider timeProvider) => { var tenant = ResolveTenant(httpContext, tenantId); if (string.IsNullOrWhiteSpace(tenant)) { return Results.BadRequest(new { error = "tenant_required" }); } var now = timeProvider.GetUtcNow(); return Results.Ok(new { metrics = new { guardViolations = new { count = 23, percentage = 0.18, byReason = new Dictionary(StringComparer.Ordinal) { ["schema_invalid"] = 9, ["missing_required_fields"] = 8, ["hash_mismatch"] = 6 }, trend = "stable" }, provenanceCompleteness = new { percentage = 99.1, recordsWithValidHash = 12755, totalRecords = 12870, trend = "up" }, deduplicationRate = new { percentage = 12.4, duplicatesDetected = 1595, totalIngested = 12870, trend = "stable" }, ingestionLatency = new { p50Ms = 122, p95Ms = 301, p99Ms = 410, meetsSla = true, slaTargetP95Ms = 500 }, supersedesDepth = new { maxDepth = 4, avgDepth = 1.6, distribution = new[] { new { depth = 1, count = 1180 }, new { depth = 2, count = 242 }, new { depth = 3, count = 44 }, new { depth = 4, count = 6 } } }, periodStart = now.AddDays(-7).ToString("O", CultureInfo.InvariantCulture), periodEnd = now.ToString("O", CultureInfo.InvariantCulture) }, recentViolations = BuildGuardViolations(now, page: 1, pageSize: 5).Items, ingestionFlow = BuildIngestionFlow(now) }); }) .WithName("AocCompatibility.GetComplianceDashboard"); group.MapGet("/compliance/violations", ( [FromQuery] int? page, [FromQuery] int? pageSize, TimeProvider timeProvider) => { var effectivePage = page is > 0 ? page.Value : 1; var effectivePageSize = pageSize is > 0 ? pageSize.Value : 20; var response = BuildGuardViolations(timeProvider.GetUtcNow(), effectivePage, effectivePageSize); return Results.Ok(response); }) .WithName("AocCompatibility.GetViolations"); group.MapPost("/compliance/violations/{violationId}/retry", (string violationId) => Results.Ok(new { success = true, message = $"Retry scheduled for {violationId}." })) .WithName("AocCompatibility.RetryViolation"); group.MapGet("/ingestion/flow", ([FromServices] TimeProvider timeProvider) => Results.Ok(BuildIngestionFlow(timeProvider.GetUtcNow()))) .WithName("AocCompatibility.GetIngestionFlow"); group.MapPost("/provenance/validate", ( AocProvenanceValidateRequest request, TimeProvider timeProvider) => { var now = timeProvider.GetUtcNow(); var inputType = string.IsNullOrWhiteSpace(request.InputType) ? "finding_id" : request.InputType!; var inputValue = string.IsNullOrWhiteSpace(request.InputValue) ? "finding-001" : request.InputValue!; return Results.Ok(new { inputType, inputValue, steps = new[] { new AocProvenanceStep( "source", "Registry intake", now.AddMinutes(-40).ToString("O", CultureInfo.InvariantCulture), "sha256:1f7d98a2bf54c390", null, "valid", new Dictionary(StringComparer.Ordinal) { ["source"] = "docker-hub", ["artifact"] = "nginx:1.27.4" }), new AocProvenanceStep( "normalized", "Concelier normalization", now.AddMinutes(-33).ToString("O", CultureInfo.InvariantCulture), "sha256:4fdb5e6a31a80f0d", "sha256:1f7d98a2bf54c390", "valid", new Dictionary(StringComparer.Ordinal) { ["pipeline"] = "concelier", ["tenant"] = "demo-prod" }), new AocProvenanceStep( "finding", "Finding materialized", now.AddMinutes(-22).ToString("O", CultureInfo.InvariantCulture), "sha256:0a52b69d9f9c72c0", "sha256:4fdb5e6a31a80f0d", "valid", new Dictionary(StringComparer.Ordinal) { ["findingId"] = inputValue, ["decision"] = "warn" }) }, isComplete = true, validationErrors = Array.Empty(), validatedAt = now.ToString("O", CultureInfo.InvariantCulture) }); }) .WithName("AocCompatibility.ValidateProvenance"); group.MapPost("/compliance/reports", ( AocComplianceReportRequest request, TimeProvider timeProvider) => { var now = timeProvider.GetUtcNow(); return Results.Ok(new { reportId = $"aoc-report-{now:yyyyMMddHHmmss}", generatedAt = now.ToString("O", CultureInfo.InvariantCulture), period = new { start = request.StartDate ?? now.AddDays(-7).ToString("O", CultureInfo.InvariantCulture), end = request.EndDate ?? now.ToString("O", CultureInfo.InvariantCulture) }, guardViolationSummary = new { total = 23, bySource = new Dictionary(StringComparer.Ordinal) { ["docker-hub"] = 12, ["github-packages"] = 11 }, byReason = new Dictionary(StringComparer.Ordinal) { ["schema_invalid"] = 9, ["missing_required_fields"] = 8, ["hash_mismatch"] = 6 } }, provenanceCompliance = new { percentage = 99.1, bySource = new Dictionary(StringComparer.Ordinal) { ["docker-hub"] = 99.5, ["github-packages"] = 98.7 } }, deduplicationMetrics = new { rate = 12.4, bySource = new Dictionary(StringComparer.Ordinal) { ["docker-hub"] = 10.8, ["github-packages"] = 14.1 } }, latencyMetrics = new { p50Ms = 122, p95Ms = 301, p99Ms = 410, bySource = new Dictionary(StringComparer.Ordinal) { ["docker-hub"] = new { p50 = 118, p95 = 292, p99 = 401 }, ["github-packages"] = new { p50 = 126, p95 = 312, p99 = 420 } } } }); }) .WithName("AocCompatibility.GenerateComplianceReport"); return app; } private static object[] BuildViolationSummaries(DateTimeOffset now) => [ new { code = "AOC-PROV-001", description = "Missing provenance attestation", count = 12, severity = "high", lastSeen = now.AddMinutes(-15).ToString("O", CultureInfo.InvariantCulture) }, new { code = "AOC-DIGEST-002", description = "Digest mismatch in manifest", count = 7, severity = "critical", lastSeen = now.AddMinutes(-42).ToString("O", CultureInfo.InvariantCulture) }, new { code = "AOC-SCHEMA-003", description = "Schema validation failed", count = 4, severity = "medium", lastSeen = now.AddHours(-2).ToString("O", CultureInfo.InvariantCulture) } ]; private static object BuildIngestionFlow(DateTimeOffset now) => new { sources = new[] { new { sourceId = "docker-hub", sourceName = "Docker Hub", module = "concelier", throughputPerMinute = 5.2, latencyP50Ms = 112, latencyP95Ms = 284, latencyP99Ms = 365, errorRate = 0.12, backlogDepth = 2, lastIngestionAt = now.AddMinutes(-3).ToString("O", CultureInfo.InvariantCulture), status = "healthy" }, new { sourceId = "github-packages", sourceName = "GitHub Packages", module = "excititor", throughputPerMinute = 3.7, latencyP50Ms = 133, latencyP95Ms = 318, latencyP99Ms = 411, errorRate = 0.24, backlogDepth = 1, lastIngestionAt = now.AddMinutes(-6).ToString("O", CultureInfo.InvariantCulture), status = "degraded" } }, totalThroughput = 8.9, avgLatencyP95Ms = 301, overallErrorRate = 0.18, lastUpdatedAt = now.ToString("O", CultureInfo.InvariantCulture) }; private static AocGuardViolationResponse BuildGuardViolations(DateTimeOffset now, int page, int pageSize) { var all = new[] { new AocGuardViolation( "viol-001", now.AddMinutes(-16).ToString("O", CultureInfo.InvariantCulture), "docker-hub", "missing_required_fields", "Provenance digest missing from normalized advisory.", "{\"digest\":null}", "concelier", true), new AocGuardViolation( "viol-002", now.AddMinutes(-44).ToString("O", CultureInfo.InvariantCulture), "github-packages", "hash_mismatch", "Manifest digest did not match DSSE payload.", "{\"expected\":\"sha256:a\",\"actual\":\"sha256:b\"}", "excititor", true), new AocGuardViolation( "viol-003", now.AddHours(-2).ToString("O", CultureInfo.InvariantCulture), "docker-hub", "schema_invalid", "Document failed schema validation for SPDX 2.3.", "{\"schema\":\"spdx-2.3\"}", "concelier", false) }; var effectivePage = page > 0 ? page : 1; var effectivePageSize = pageSize > 0 ? pageSize : 20; var skip = (effectivePage - 1) * effectivePageSize; var items = all.Skip(skip).Take(effectivePageSize).ToArray(); return new AocGuardViolationResponse( items, all.Length, effectivePage, effectivePageSize, skip + items.Length < all.Length); } private static string? ResolveTenant(HttpContext httpContext, string? tenantId) => tenantId?.Trim() ?? httpContext.Request.Headers["X-StellaOps-Tenant"].FirstOrDefault() ?? httpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? httpContext.User.Claims.FirstOrDefault(static claim => claim.Type is "stellaops:tenant" or "tenant_id")?.Value; private sealed record AocVerifyRequest(string? TenantId, string? Since, int? Limit); private sealed record AocProvenanceValidateRequest(string? InputType, string? InputValue); private sealed record AocComplianceReportRequest( string? StartDate, string? EndDate, IReadOnlyList? Sources, string? Format, bool IncludeViolationDetails); private sealed record AocGuardViolation( string Id, string Timestamp, string Source, string Reason, string Message, string PayloadSample, string Module, bool CanRetry); private sealed record AocGuardViolationResponse( IReadOnlyList Items, int TotalCount, int Page, int PageSize, bool HasMore); private sealed record AocProvenanceStep( string StepType, string Label, string Timestamp, string Hash, string? LinkedFromHash, string Status, IReadOnlyDictionary Details); }