consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user