Restore live platform compatibility contracts

This commit is contained in:
master
2026-03-10 01:37:24 +02:00
parent 6b7168ca3c
commit afb9711e61
15 changed files with 1790 additions and 27 deletions

View File

@@ -0,0 +1,215 @@
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<string, object?>
{
["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<string, object?>
{
["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<string, object?>
{
["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<string, object?>
{
["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<string, object?>
{
["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<CompatibilitySignalRecord> 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<int>()
.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<string, object?> Payload,
string? CorrelationId,
string? ArtifactRef,
IReadOnlyCollection<string> TriggeredActions,
string ReceivedAt,
string? ProcessedAt,
string? Error);
}

View File

@@ -1024,6 +1024,8 @@ signalsGroup.MapPost("/reachability/recompute", async Task<IResult> (
}
}).WithName("SignalsReachabilityRecompute");
StellaOps.Signals.CompatibilityApiV1Endpoints.MapSignalsCompatibilityEndpoints(app);
await app.LoadTranslationsAsync();
@@ -1072,6 +1074,59 @@ internal partial class Program
return false;
}
internal static bool TryAuthorizeAny(HttpContext httpContext, IReadOnlyCollection<string> requiredScopes, bool fallbackAllowed, out IResult? failure)
{
ArgumentNullException.ThrowIfNull(httpContext);
ArgumentNullException.ThrowIfNull(requiredScopes);
var scopes = requiredScopes
.Where(static scope => !string.IsNullOrWhiteSpace(scope))
.Select(static scope => scope.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (scopes.Length == 0)
{
failure = Results.StatusCode(StatusCodes.Status403Forbidden);
return false;
}
if (httpContext.User?.Identity?.IsAuthenticated == true)
{
if (scopes.Any(scope => TokenScopeAuthorizer.HasScope(httpContext.User, scope)))
{
failure = null;
return true;
}
failure = Results.StatusCode(StatusCodes.Status403Forbidden);
return false;
}
if (!fallbackAllowed)
{
failure = Results.Unauthorized();
return false;
}
if (!httpContext.Request.Headers.TryGetValue("X-Scopes", out var scopesHeader) ||
string.IsNullOrWhiteSpace(scopesHeader.ToString()))
{
failure = Results.Unauthorized();
return false;
}
var principal = HeaderScopeAuthorizer.CreatePrincipal(scopesHeader.ToString());
if (scopes.Any(scope => HeaderScopeAuthorizer.HasScope(principal, scope)))
{
failure = null;
return true;
}
failure = Results.StatusCode(StatusCodes.Status403Forbidden);
return false;
}
internal static bool TryEnsureSealedMode(SignalsSealedModeMonitor monitor, out IResult? failure)
{
if (!monitor.EnforcementEnabled)

View File

@@ -0,0 +1,73 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using StellaOps.Auth.Abstractions;
using StellaOps.Signals.Routing;
using Xunit;
namespace StellaOps.Signals.Tests;
public sealed class ProgramCompatibilityTests
{
[Fact]
public void TryAuthorizeAny_AllowsLegacyOrchReadTokenScope()
{
var context = new DefaultHttpContext
{
User = CreatePrincipal(StellaOpsScopes.OrchRead)
};
var authorized = Program.TryAuthorizeAny(
context,
[SignalsPolicies.Read, StellaOpsScopes.OrchRead],
fallbackAllowed: false,
out var failure);
Assert.True(authorized);
Assert.Null(failure);
}
[Fact]
public void TryAuthorizeAny_AllowsHeaderFallbackForLegacyScope()
{
var context = new DefaultHttpContext();
context.Request.Headers["X-Scopes"] = StellaOpsScopes.OrchRead;
var authorized = Program.TryAuthorizeAny(
context,
[SignalsPolicies.Read, StellaOpsScopes.OrchRead],
fallbackAllowed: true,
out var failure);
Assert.True(authorized);
Assert.Null(failure);
}
[Fact]
public void TryAuthorizeAny_RejectsWhenNoCompatibleScopeExists()
{
var context = new DefaultHttpContext
{
User = CreatePrincipal(StellaOpsScopes.PolicyRead)
};
var authorized = Program.TryAuthorizeAny(
context,
[SignalsPolicies.Read, StellaOpsScopes.OrchRead],
fallbackAllowed: false,
out var failure);
Assert.False(authorized);
Assert.NotNull(failure);
}
private static ClaimsPrincipal CreatePrincipal(string scope)
{
var identity = new ClaimsIdentity(
[
new Claim(StellaOpsClaimTypes.Scope, scope),
new Claim(StellaOpsClaimTypes.ScopeItem, scope)
], "Bearer");
return new ClaimsPrincipal(identity);
}
}