B7 window — old vulnerability-plane route swap

NO LONGER APPLICABLE — 2026-09-04. This procedure removes gateway routes that point at old-plane hosts. There are none left: RAR-5/RAR-7 (81e276c2a3, 83bfc9359f, f4b1774cf6) executed the 2026-08-28 owner ruling (“all microservices needs to autopublish no router configuration for microservices endpoints”) and both route tables now hold 22 routes with no old-plane target host among them. Measured 2026-09-04 at HEAD: pwsh tools/scripts/b7-remove-old-vuln-plane-routes.ps1 -Check exits 0 reporting 0 route(s) selected for removalin both configs and confirming the post-state — no old-plane route, and the hub group absent because the plane is auto-published. The “35 routes per config” figure below is historical, not a current expectation.

A bare run is now safe but pointless. It was not: until ced8a2d286 the write was guarded only by if (-not $Check) with no count condition, so a run without -Check rewrote BOTH route config files on disk regardless of whether anything was selected, then printed written per file, Swap applied. Total routes removed: 0, and exited 0 — reporting an apply it had not performed. Under SPRINT_20260904_001 RRS-1 the write is now gated on a count and a zero-work bare run exits 3 (“zero work, target state positively confirmed”) without touching either file. Exit 3 rather than 0 so a caller keyed on the exit code cannot confuse confirmation with a completed cutover.

Do instead: confirm the auto-published layer serves the Vulnerabilities surfaces through the gateway.

Owner sprint: SPRINT_20260722_006 ROUTE-E3 · Executes inside: the SPRINT_20260722_008 VULN-G1 window (step 5), together with the Console release and CLI/mirror/Policy reader swaps — owner D8: routes are REPLACED in the window, never aliased early.

The change is a script, not a patch. The gateway configs drift daily; a frozen diff would rot before the window. The reviewed artifact is tools/scripts/b7-remove-old-vuln-plane-routes.ps1, which removes every route targeting an old-plane host from BOTH route configs (src/Router/StellaOps.Gateway.WebService/appsettings.json and devops/compose/router-gateway-local.json), selected by target host — never by path cosmetics — and refuses to write anything that no longer parses as JSON.

What gets removed (rehearsed 2026-08-02; 0 selected as of 2026-09-04 — see banner)

35 routes per config, symmetric across both files — historical; none of these rows survives in the current 22-route tables:

Target hostRoutesCapabilities riding it
concelier.stella-ops.local20advisories / advisory-sources / issues, concelier jobs, feeds, canonical, connectors, federation, diagnostics, raw-advisory ingest — and two NON-vuln surfaces: /api/v1/learn/sbom + /api/v1/sboms
excititor-web.stella-ops.local12excititor ingest/reconcile/statements/providers/oci-trust-policies, VEX observations/candidates/attestations, obs
vexhub.stella-ops.local2/api/v1/vex, /api/vex
vexlens.stella-ops.local1/api/v1/vexlens

Conscious acknowledgement required at review: the two SBOM surfaces on the Concelier host die with it. Their successor (SbomService-owned or hub-owned) must either already serve replacements by the window or their loss must be an explicitly accepted window outcome — do not let them disappear silently.

Window steps

  1. pwsh tools/scripts/b7-remove-old-vuln-plane-routes.ps1 -Check — confirm the removal set still matches the table above; investigate any drift.
  2. pwsh tools/scripts/b7-remove-old-vuln-plane-routes.ps1 — apply to both configs (the script JSON-validates before writing).
  3. Recreate the gateway (docker compose … up -d --force-recreate --no-deps gateway) so the deployed config reloads; the compose overlay copy is the one the live gateway actually reads.
  4. Verify:
    • VulnerabilitiesHubRouteConfigTests — stays green untouched (rehearsed 2026-08-02: 6/6 against swapped configs; it asserts the hub pattern’s own behavior, not the legacy routes’ existence);

    • through-gateway forcing call on /api/vulnerabilities/v1/... with Bearer intact (the ROUTE-E1 open criterion — same assembly as the B5 gate);

    • an old prefix no longer reaches a half-dead service. Do not expect a single status code — measured live 2026-08-04, the removed surface answers three different ways depending on what it falls through to, and all three are correct:

      ProbeResultWhy
      /api/v1/advisories/...404generic v1 catch-all substitutes host advisories.stella-ops.local, which is not a registered microservice
      /api/v1/concelier/jobs, /api/v1/vex503 urn:stellaops:router:target-microservice-unavailablethe substituted service name is known to the registry but has no instances
      /obs/excititor/health200 text/htmlno API route matches, so it reaches the SPA static fallback and gets the console shell

      The 200 is the trap: an operator probing /obs/excititor/* with a browser or a naive curl sees success. Check the content type, not the status.

  5. Update the route suites that assert old-plane routes exist — expected RED after the swap, updated in the SAME window change. Measured 2026-08-04: four suites actually go red (9 of 59 cases), not six. Red, and updated to absence guards: ConcelierJobsRouteConfigTests, FederationRouteConfigurationTests, GatewayRouteSearchMappingsTests, OperationalApiOwnershipRouteConfigTests. Predicted red but in fact green — do not go hunting for failures here: TopologyOwnershipRouteConfigTests asserts topology routes are release-orchestrator-owned (removing Concelier routes cannot break a “does not contain Concelier” assertion), and RouteDispatchMiddlewareMicroserviceTests builds in-memory route tables and never reads either config.
  6. Commit configs + test updates as one reviewed change referencing the window’s checklist entry.

Rollback

Valid ONLY before VULN-G1 step 3 (schema drop): git checkout of the two configs + gateway recreate restores the old routing. After the drop the old plane no longer exists and fix-forward applies (D8/T11).

Rehearsal record (2026-08-02)

-Check and a full apply were rehearsed on the working tree: 70 routes removed (35 per config, counts as in the table), both files re-parsed as JSON, hub route tests 6/6, configs then restored. The scratch-stack end-to-end rehearsal of the swap rides the B5 gate assembly (gateway + Authority + hub), same as ROUTE-E1’s forcing check.