Segment-bound doctor and scheduler frontdoor chunks

This commit is contained in:
master
2026-03-10 12:47:51 +02:00
parent 1b6051662f
commit d881fff387
9 changed files with 138 additions and 15 deletions

View File

@@ -13,3 +13,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| RGH-02 | DONE | 2026-03-04: Expanded approved auth passthrough prefixes (`/authority`, `/doctor`, `/api`) to unblock authenticated gateway routes used by Audit Log UI E2E. |
| RGH-03 | DONE | 2026-03-05: Aligned `/api/v1/search*` and `/api/v1/advisory-ai*` route translations to AdvisoryAI `/v1/*`, added compose/runtime parity safeguards, and verified setup smoke coverage. |
| RGH-04 | DONE | 2026-03-10: Tightened frontdoor route boundaries for `/policy`, restored explicit platform ownership for `/api/v1/aoc*` and `/api/v1/administration*`, and stripped `/doctor`/`/scheduler` shell prefixes before microservice dispatch. |
| RGH-05 | DONE | 2026-03-10: Added segment boundaries to `/doctor` and `/scheduler` frontdoor prefixes so lazy web chunks stay on static hosting while compatibility API prefixes still dispatch to the correct microservice. |

View File

@@ -133,8 +133,8 @@
{ "Type": "Microservice", "Path": "^/api/admin/plans(.*)", "IsRegex": true, "TranslatesTo": "http://registry-token.stella-ops.local/api/admin/plans$1" },
{ "Type": "Microservice", "Path": "^/api/admin(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/admin$1" },
{ "Type": "Microservice", "Path": "^/api/analytics(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/analytics$1" },
{ "Type": "Microservice", "Path": "^/scheduler(.*)", "IsRegex": true, "TranslatesTo": "http://scheduler.stella-ops.local$1" },
{ "Type": "Microservice", "Path": "^/doctor(.*)", "IsRegex": true, "TranslatesTo": "http://doctor.stella-ops.local$1" },
{ "Type": "Microservice", "Path": "^/scheduler(?=/|$)(.*)", "IsRegex": true, "TranslatesTo": "http://scheduler.stella-ops.local$1" },
{ "Type": "Microservice", "Path": "^/doctor(?=/|$)(.*)", "IsRegex": true, "TranslatesTo": "http://doctor.stella-ops.local$1" },
{ "Type": "Microservice", "Path": "^/api/orchestrator(.*)", "IsRegex": true, "TranslatesTo": "http://orchestrator.stella-ops.local/api/orchestrator$1" },
{ "Type": "Microservice", "Path": "^/api/jobengine(.*)", "IsRegex": true, "TranslatesTo": "http://orchestrator.stella-ops.local/api/jobengine$1" },
{ "Type": "Microservice", "Path": "^/api/scheduler(.*)", "IsRegex": true, "TranslatesTo": "http://scheduler.stella-ops.local/api/scheduler$1" },

View File

@@ -19,8 +19,8 @@ public sealed class GatewayRouteSearchMappingsTests
("^/api/v2/security(.*)", "http://platform.stella-ops.local/api/v2/security$1", "Microservice", true),
("^/api/v2/topology(.*)", "http://platform.stella-ops.local/api/v2/topology$1", "Microservice", true),
("^/api/v2/integrations(.*)", "http://platform.stella-ops.local/api/v2/integrations$1", "Microservice", true),
("^/scheduler(.*)", "http://scheduler.stella-ops.local$1", "Microservice", true),
("^/doctor(.*)", "http://doctor.stella-ops.local$1", "Microservice", true),
("^/scheduler(?=/|$)(.*)", "http://scheduler.stella-ops.local$1", "Microservice", true),
("^/doctor(?=/|$)(.*)", "http://doctor.stella-ops.local$1", "Microservice", true),
("^/api/jobengine(.*)", "http://orchestrator.stella-ops.local/api/jobengine$1", "Microservice", true),
("^/api/scheduler(.*)", "http://scheduler.stella-ops.local/api/scheduler$1", "Microservice", true)
];
@@ -90,7 +90,7 @@ public sealed class GatewayRouteSearchMappingsTests
[Theory]
[MemberData(nameof(RouteConfigPaths))]
public void RouteTable_UsesSegmentBoundaryForPolicyRootRoute(string configRelativePath)
public void RouteTable_UsesSegmentBoundariesForFrontdoorRootPrefixes(string configRelativePath)
{
var repoRoot = FindRepositoryRoot();
var configPath = Path.Combine(repoRoot, configRelativePath.Replace('/', Path.DirectorySeparatorChar));
@@ -113,9 +113,32 @@ public sealed class GatewayRouteSearchMappingsTests
route.TryGetProperty("IsRegex", out var isRegex) && isRegex.GetBoolean()))
.ToList();
var route = routes.SingleOrDefault(candidate => string.Equals(candidate.TranslatesTo, "http://policy-gateway.stella-ops.local/policy$1", StringComparison.Ordinal));
Assert.True(route is not null, $"Missing policy root route in {configRelativePath}.");
Assert.Equal("^/policy(?=/|$)(.*)", route!.Path);
AssertSegmentBoundRoute(
routes,
"http://policy-gateway.stella-ops.local/policy$1",
"^/policy(?=/|$)(.*)",
configRelativePath);
AssertSegmentBoundRoute(
routes,
"http://scheduler.stella-ops.local$1",
"^/scheduler(?=/|$)(.*)",
configRelativePath);
AssertSegmentBoundRoute(
routes,
"http://doctor.stella-ops.local$1",
"^/doctor(?=/|$)(.*)",
configRelativePath);
}
private static void AssertSegmentBoundRoute(
IEnumerable<RouteEntry> routes,
string expectedTarget,
string expectedPath,
string configRelativePath)
{
var route = routes.SingleOrDefault(candidate => string.Equals(candidate.TranslatesTo, expectedTarget, StringComparison.Ordinal));
Assert.True(route is not null, $"Missing route for {expectedTarget} in {configRelativePath}.");
Assert.Equal(expectedPath, route!.Path);
}
[Theory]

View File

@@ -391,8 +391,8 @@ public sealed class RouteDispatchMiddlewareMicroserviceTests
}
[Theory]
[InlineData("/doctor/api/v1/doctor/checks", @"^/doctor(.*)", "http://doctor.stella-ops.local$1", "doctor", "/api/v1/doctor/checks")]
[InlineData("/scheduler/api/v1/scheduler/runs", @"^/scheduler(.*)", "http://scheduler.stella-ops.local$1", "scheduler", "/api/v1/scheduler/runs")]
[InlineData("/doctor/api/v1/doctor/checks", @"^/doctor(?=/|$)(.*)", "http://doctor.stella-ops.local$1", "doctor", "/api/v1/doctor/checks")]
[InlineData("/scheduler/api/v1/scheduler/runs", @"^/scheduler(?=/|$)(.*)", "http://scheduler.stella-ops.local$1", "scheduler", "/api/v1/scheduler/runs")]
public async Task InvokeAsync_ServiceRootPrefixRoute_StripsFrontdoorPrefixBeforeDispatch(
string requestPath,
string routePath,

View File

@@ -113,6 +113,45 @@ public sealed class StellaOpsRouteResolverTests
Assert.Equal("/overview", policyChildResult.RegexMatch!.Groups[1].Value);
}
[Theory]
[InlineData(@"^/doctor(?=/|$)(.*)", "http://doctor.stella-ops.local$1", "/doctor.routes-JHTKGEQ6.js", StellaOpsRouteType.StaticFiles, null)]
[InlineData(@"^/doctor(?=/|$)(.*)", "http://doctor.stella-ops.local$1", "/doctor/api/v1/doctor/checks", StellaOpsRouteType.Microservice, "/api/v1/doctor/checks")]
[InlineData(@"^/scheduler(?=/|$)(.*)", "http://scheduler.stella-ops.local$1", "/scheduler-ops.routes-5SHXET2O.js", StellaOpsRouteType.StaticFiles, null)]
[InlineData(@"^/scheduler(?=/|$)(.*)", "http://scheduler.stella-ops.local$1", "/scheduler/api/v1/scheduler/runs", StellaOpsRouteType.Microservice, "/api/v1/scheduler/runs")]
public void Resolve_SegmentBoundFrontdoorServiceRoots_DoNotCaptureStaticChunks(
string routePath,
string translatesTo,
string requestPath,
StellaOpsRouteType expectedType,
string? expectedCapture)
{
var resolver = new StellaOpsRouteResolver(
[
MakeRoute(
routePath,
isRegex: true,
translatesTo: translatesTo),
MakeRoute(
"/",
type: StellaOpsRouteType.StaticFiles,
translatesTo: "/app/wwwroot")
]);
var result = resolver.Resolve(new PathString(requestPath));
Assert.NotNull(result.Route);
Assert.Equal(expectedType, result.Route!.Type);
if (expectedCapture is null)
{
Assert.Null(result.RegexMatch);
return;
}
Assert.NotNull(result.RegexMatch);
Assert.Equal(expectedCapture, result.RegexMatch!.Groups[1].Value);
}
[Fact]
public void Resolve_NoMatch_ReturnsNull()
{