diff --git a/devops/compose/README.md b/devops/compose/README.md index 0b30010a5..593460606 100644 --- a/devops/compose/README.md +++ b/devops/compose/README.md @@ -143,7 +143,9 @@ docker compose -f docker-compose.stella-ops.yml logs -f scanner-web ### Router Frontdoor Configuration -`router-gateway` uses the microservice-first route table in `router-gateway-local.json`. +`router-gateway` uses the microservice-first route table in `router-gateway-local.json`, +mounted as the container's `appsettings.json` so the local route table replaces the baked-in +gateway route list instead of merging with it. First-party Stella APIs are expected to flow through router transport; reverse proxy remains only for external/bootstrap surfaces that cannot participate in router registration yet (for example OIDC browser flows, Rekor, and static/platform bootstrap assets). diff --git a/devops/compose/docker-compose.stella-ops.legacy.yml b/devops/compose/docker-compose.stella-ops.legacy.yml index 95f4dcb47..9067045b6 100644 --- a/devops/compose/docker-compose.stella-ops.legacy.yml +++ b/devops/compose/docker-compose.stella-ops.legacy.yml @@ -372,7 +372,7 @@ services: volumes: - *cert-volume - console-dist:/app/wwwroot:ro - - ${ROUTER_GATEWAY_CONFIG:-./router-gateway-local.json}:/app/appsettings.local.json:ro + - ${ROUTER_GATEWAY_CONFIG:-./router-gateway-local.json}:/app/appsettings.json:ro - ./envsettings-override.json:/app/envsettings-override.json:ro - ./gateway-ca-bundle.crt:/etc/ssl/certs/ca-certificates.crt:ro ports: diff --git a/devops/compose/docker-compose.stella-services.yml b/devops/compose/docker-compose.stella-services.yml index 8e760ace9..bbcecc55b 100644 --- a/devops/compose/docker-compose.stella-services.yml +++ b/devops/compose/docker-compose.stella-services.yml @@ -209,7 +209,7 @@ services: volumes: - ${STELLAOPS_CERT_VOLUME} - console-dist:/app/wwwroot:ro - - ${ROUTER_GATEWAY_CONFIG:-./router-gateway-local.json}:/app/appsettings.local.json:ro + - ${ROUTER_GATEWAY_CONFIG:-./router-gateway-local.json}:/app/appsettings.json:ro - ./envsettings-override.json:/app/envsettings-override.json:ro - ./gateway-ca-bundle.crt:/etc/ssl/certs/ca-certificates.crt:ro ports: diff --git a/devops/compose/router-gateway-local.json b/devops/compose/router-gateway-local.json index 2f088d3aa..29120623b 100644 --- a/devops/compose/router-gateway-local.json +++ b/devops/compose/router-gateway-local.json @@ -1,5 +1,48 @@ { "Gateway": { + "Node": { + "Region": "local", + "NodeId": "gw-local-01", + "Environment": "dev", + "NeighborRegions": [ + + ] + }, + "Transports": { + "Tcp": { + "Enabled": false, + "BindAddress": "0.0.0.0", + "Port": 9100, + "ReceiveBufferSize": 65536, + "SendBufferSize": 65536, + "MaxFrameSize": 16777216 + }, + "Tls": { + "Enabled": false, + "BindAddress": "0.0.0.0", + "Port": 9443, + "ReceiveBufferSize": 65536, + "SendBufferSize": 65536, + "MaxFrameSize": 16777216, + "CertificatePath": "", + "CertificateKeyPath": "", + "CertificatePassword": "", + "RequireClientCertificate": false, + "AllowSelfSigned": false + } + }, + "Routing": { + "DefaultTimeout": "60s", + "GlobalTimeoutCap": "120s", + "MaxRequestBodySize": "100MB", + "StreamingEnabled": true, + "PreferLocalRegion": true, + "AllowDegradedInstances": true, + "StrictVersionMatching": true, + "NeighborRegions": [ + + ] + }, "Auth": { "DpopEnabled": false, "AllowAnonymous": true, @@ -23,6 +66,15 @@ ] } }, + "OpenApi": { + "Enabled": true, + "CacheTtlSeconds": 300, + "Title": "StellaOps Gateway API", + "Description": "Unified API aggregating all connected microservices.", + "Version": "1.0.0", + "ServerUrl": "/", + "TokenUrl": "/auth/token" + }, "Health": { "StaleThreshold": "30s", "DegradedThreshold": "20s", diff --git a/src/Router/StellaOps.Gateway.WebService/TASKS.md b/src/Router/StellaOps.Gateway.WebService/TASKS.md index ab8e7ce13..e46f99e11 100644 --- a/src/Router/StellaOps.Gateway.WebService/TASKS.md +++ b/src/Router/StellaOps.Gateway.WebService/TASKS.md @@ -15,3 +15,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229 | 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. | | RGH-06 | DONE | 2026-03-10: Restored explicit platform ownership for `/api/v2/evidence*` so environment posture and mission-control evidence read models no longer fall through to the generic v2 service resolver. | +| RGH-07 | DONE | 2026-04-09: Restored local router startup by making `router-gateway-local.json` a standalone gateway config and mounting it as container `appsettings.json`, which removed the duplicate `/platform` merge without weakening validation; source of truth is `SPRINT_20260409_002_Platform_local_stack_regression_retest.md`. | diff --git a/src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayRouteSearchMappingsTests.cs b/src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayRouteSearchMappingsTests.cs index 3ae9fdd27..c2f2528c1 100644 --- a/src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayRouteSearchMappingsTests.cs +++ b/src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayRouteSearchMappingsTests.cs @@ -39,6 +39,32 @@ public sealed class GatewayRouteSearchMappingsTests "devops/compose/router-gateway-local.json" }; + [Fact] + public void LocalComposeRouteTable_IsStandaloneGatewayConfiguration() + { + var repoRoot = FindRepositoryRoot(); + var configPath = Path.Combine( + repoRoot, + "devops", + "compose", + "router-gateway-local.json"); + + Assert.True(File.Exists(configPath), $"Config file not found: {configPath}"); + + using var stream = File.OpenRead(configPath); + using var document = JsonDocument.Parse(stream); + + var gateway = document.RootElement.GetProperty("Gateway"); + Assert.True(gateway.TryGetProperty("Node", out _)); + Assert.True(gateway.TryGetProperty("Transports", out _)); + Assert.True(gateway.TryGetProperty("Routing", out _)); + Assert.True(gateway.TryGetProperty("Auth", out _)); + Assert.True(gateway.TryGetProperty("OpenApi", out _)); + Assert.True(gateway.TryGetProperty("Health", out _)); + Assert.True(gateway.TryGetProperty("Routes", out var routes)); + Assert.NotEmpty(routes.EnumerateArray()); + } + [Theory] [MemberData(nameof(RouteConfigPaths))] public void RouteTable_ContainsUnifiedSearchMappingsAndKeepsThemAheadOfApiCatchAll(string configRelativePath)