diff --git a/docs/implplan/SPRINT_20260310_030_FE_releases_environment_canonical_route_restore.md b/docs/implplan/SPRINT_20260310_030_FE_releases_environment_canonical_route_restore.md index 7d5bb3c28..b2a4a6da0 100644 --- a/docs/implplan/SPRINT_20260310_030_FE_releases_environment_canonical_route_restore.md +++ b/docs/implplan/SPRINT_20260310_030_FE_releases_environment_canonical_route_restore.md @@ -20,7 +20,7 @@ ## Delivery Tracker ### FE-RELEASE-ENV-001 - Restore canonical Releases ownership for environment inventory -Status: DOING +Status: DONE Dependency: none Owners: QA, 3rd Line Support, Product Manager, Architect, Developer Task description: @@ -28,20 +28,23 @@ Task description: - The old release-orchestrator environment pages are not safe to restore: they are placeholder-heavy, contain stale links, and would reintroduce broken actions. The correct fix is to keep the working topology-backed inventory/detail pages and mount them directly under Releases. Completion criteria: -- [ ] `/releases/environments` and `/releases/environments/:environmentId` resolve under `/releases/*` without redirecting to Operations. -- [ ] Legacy release environment aliases redirect to `/releases/environments`. -- [ ] Route ownership specs and live ownership harness match the restored contract. -- [ ] Rebuilt live web passes the canonical route sweep with zero failed routes. +- [x] `/releases/environments` and `/releases/environments/:environmentId` resolve under `/releases/*` without redirecting to Operations. +- [x] Legacy release environment aliases redirect to `/releases/environments`. +- [x] Route ownership specs and live ownership harness match the restored contract. +- [x] Rebuilt live web passes the canonical route sweep with zero failed routes. ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | | 2026-03-10 | Sprint created after the live canonical Playwright sweep dropped to a single failure: `/releases/environments` redirected to `/ops/operations/environments`. Root-cause audit confirmed the redirect was architectural drift, not a component/runtime failure. | Developer | +| 2026-03-10 | Restored `/releases/environments` and `/releases/environments/:environmentId` as Releases-mounted topology surfaces, retargeted the legacy release environment aliases to `/releases/environments`, and updated the route-ownership test/harness expectations to match the canonical contract. | Developer | +| 2026-03-10 | `npx ng test --watch=false --progress=false --ts-config tsconfig.spec.json --include src/app/routes/route-surface-ownership.spec.ts` passed `5/5`; `npm run build` passed; the rebuilt bundle was synced into `compose_console-dist`; `node ./scripts/live-frontdoor-canonical-route-sweep.mjs` passed `111/111`; `node ./scripts/live-route-surface-ownership-check.mjs` passed with `failedActionCount=0` and `runtimeIssueCount=0`. | QA | ## Decisions & Risks - Decision: supersede the earlier Operations-only redirect decision from `SPRINT_20260310_028_FE_route_surface_ownership_alignment.md`; the canonical Releases contract wins because the live route matrix and Pack 22 both depend on `/releases/environments`. - Decision: do not revive `features/release-orchestrator/environments/**` in this slice. Those components remain non-canonical and need separate revival work if they are ever to return. -- Risk: the route-ownership Playwright harness still contains stale expectations for `/setup/notifications` and release environment aliases. It must be updated together with the route change or it will produce false failures. +- Decision: keep the topology-backed environment inventory/detail pages as the shared implementation behind both Releases and Operations rather than forking a second environment inventory surface. +- Decision: hardened `live-route-surface-ownership-check.mjs` to retry watchlist return-label checks when the trust shell briefly reports the blank `StellaOps` transition title; direct Playwright repro proved the underlying product flow was healthy and the prior failure was a harness race. ## Next Checkpoints - Land the Releases route and legacy alias contract update. diff --git a/src/Web/StellaOps.Web/scripts/live-route-surface-ownership-check.mjs b/src/Web/StellaOps.Web/scripts/live-route-surface-ownership-check.mjs index 6652f23d9..62dfb3e88 100644 --- a/src/Web/StellaOps.Web/scripts/live-route-surface-ownership-check.mjs +++ b/src/Web/StellaOps.Web/scripts/live-route-surface-ownership-check.mjs @@ -190,6 +190,16 @@ async function runSidebarCheck(page) { } async function runWatchlistLabelCheck(page, returnTo, expectedLabel) { + const evaluate = async () => { + const labelVisible = await page.getByText(`Return to ${expectedLabel}`, { exact: false }).first().isVisible().catch(() => false); + const snapshot = await captureSnapshot(page, `watchlist-return:${expectedLabel}`); + + return { + ok: labelVisible, + snapshot, + }; + }; + await page.goto( `https://stella-ops.local/setup/trust-signing/watchlist/alerts?${scopeQuery}&alertId=alert-001&scope=tenant&tab=alerts&returnTo=${encodeURIComponent(returnTo)}`, { @@ -199,14 +209,22 @@ async function runWatchlistLabelCheck(page, returnTo, expectedLabel) { ); await settle(page); - const labelVisible = await page.getByText(`Return to ${expectedLabel}`, { exact: false }).first().isVisible().catch(() => false); + let result = await evaluate(); + if ( + !result.ok + && new URL(page.url()).pathname === '/setup/trust-signing/watchlist/alerts' + && (result.snapshot.title === 'StellaOps' || !result.snapshot.heading) + ) { + await page.waitForTimeout(2_000); + result = await evaluate(); + } return { kind: 'watchlist-return', route: page.url(), - ok: labelVisible, + ok: result.ok, expectedLabel, - snapshot: await captureSnapshot(page, `watchlist-return:${expectedLabel}`), + snapshot: result.snapshot, }; }