consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -70,9 +70,9 @@ public static class EnvironmentSettingsEndpoints
Issuer = platform.Authority.Issuer,
ClientId = env.ClientId,
AuthorizeEndpoint = env.AuthorizeEndpoint
?? $"{platform.Authority.Issuer}/connect/authorize",
?? $"{platform.Authority.Issuer.TrimEnd('/')}/connect/authorize",
TokenEndpoint = env.TokenEndpoint
?? $"{platform.Authority.Issuer}/connect/token",
?? $"{platform.Authority.Issuer.TrimEnd('/')}/connect/token",
LogoutEndpoint = env.LogoutEndpoint,
RedirectUri = env.RedirectUri,
SilentRefreshRedirectUri = env.SilentRefreshRedirectUri,

View File

@@ -31,6 +31,7 @@ public static class ScoreEndpoints
.RequireTenant();
MapEvaluateEndpoints(score);
MapExplainEndpoints(score);
MapHistoryEndpoints(score);
MapWeightsEndpoints(score);
MapReplayEndpoints(score);
@@ -162,6 +163,76 @@ public static class ScoreEndpoints
.RequireAuthorization(PlatformPolicies.ScoreRead);
}
private static void MapExplainEndpoints(IEndpointRouteBuilder score)
{
// GET /api/v1/score/explain/{digest} - Get deterministic score explanation
score.MapGet("/explain/{digest}", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
IScoreEvaluationService service,
string digest,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (!TryNormalizeDigest(digest, out var normalizedDigest))
{
return Results.BadRequest(new ScoreExplainErrorResponse
{
Code = "invalid_input",
Message = "Digest must be a non-empty hash with optional algorithm prefix.",
Digest = digest
});
}
try
{
var result = await service.GetExplanationAsync(
requestContext!,
normalizedDigest,
cancellationToken).ConfigureAwait(false);
if (result.Value is null)
{
return Results.NotFound(new ScoreExplainErrorResponse
{
Code = "not_found",
Message = "No score explanation exists for the requested digest.",
Digest = normalizedDigest
});
}
return Results.Ok(new PlatformItemResponse<ScoreExplainResponse>(
requestContext!.TenantId,
requestContext.ActorId,
result.DataAsOf,
result.Cached,
result.CacheTtlSeconds,
result.Value));
}
catch (Exception)
{
return Results.Json(new ScoreExplainErrorResponse
{
Code = "backend_unavailable",
Message = "Score explanation backend is unavailable.",
Digest = normalizedDigest
}, statusCode: StatusCodes.Status503ServiceUnavailable);
}
})
.WithName("GetScoreExplanation")
.WithSummary("Get score explanation by digest")
.WithDescription("Retrieves a deterministic score explanation contract for an existing score digest.")
.Produces<PlatformItemResponse<ScoreExplainResponse>>(StatusCodes.Status200OK)
.Produces<ScoreExplainErrorResponse>(StatusCodes.Status400BadRequest)
.Produces<ScoreExplainErrorResponse>(StatusCodes.Status404NotFound)
.Produces<ScoreExplainErrorResponse>(StatusCodes.Status503ServiceUnavailable)
.RequireAuthorization(PlatformPolicies.ScoreRead);
}
private static void MapWeightsEndpoints(IEndpointRouteBuilder score)
{
var weights = score.MapGroup("/weights").WithTags("Score Weights");
@@ -355,4 +426,28 @@ public static class ScoreEndpoints
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
return false;
}
private static bool TryNormalizeDigest(string rawDigest, out string normalizedDigest)
{
normalizedDigest = string.Empty;
if (string.IsNullOrWhiteSpace(rawDigest))
{
return false;
}
var trimmed = rawDigest.Trim();
if (!trimmed.Contains(':', StringComparison.Ordinal))
{
trimmed = $"sha256:{trimmed}";
}
var parts = trimmed.Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2 || string.IsNullOrWhiteSpace(parts[1]))
{
return false;
}
normalizedDigest = $"{parts[0].ToLowerInvariant()}:{parts[1].ToLowerInvariant()}";
return true;
}
}