V3 Cutover Route Map v1¶
Decision¶
The v3 product surfaces under /v3-prod/* are now feature-complete enough
for an opt-in internal cutover. Rather than telling internal users to
manually navigate to /v3-prod/... URLs forever, this task introduces a
deliberate environment-driven cutover so kind, platform-control, and
production can each test the new defaults independently.
The cutover is a Next.js middleware that issues 307 redirects from selected v1 product routes to their v3-prod equivalents, gated by an env flag and a sticky per-user opt-out cookie.
Configuration¶
| Surface | Value |
|---|---|
| Env flag | NEXT_PUBLIC_V3_DEFAULT |
| Enabled value | 1 (any other value, including unset, leaves cutover off) |
| Default | off |
| Opt-out query | ?v1=1 — stays on v1 and sets the opt-out cookie |
| Opt-in query | ?v3=1 — clears the opt-out cookie and redirects to v3 |
| Cookie name | gpuaas_v3_opt_out |
| Cookie lifetime | 30 days |
Behavior summary¶
flag off → never redirect
flag on, opt-out → never redirect (sticky for 30 days)
flag on, opt-in → redirect mapped routes to /v3-prod/* (clear opt-out cookie)
flag on, default → redirect mapped routes to /v3-prod/*
The middleware only redirects routes in the table below. Unmapped routes fall through untouched. This is deliberate: silently redirecting routes we didn't think about is worse than leaving a v1 page in place during cutover.
Pass-through allowlist¶
The middleware never touches these paths, regardless of flag state. The matchers handle most of these via Next.js matcher config; the route-map also enforces them at the application layer for defense in depth:
/api/*— backend proxy/_next/*— framework internals- Static asset extensions (
.png,.css,.js, etc.) /v3-prod/*and/v3/*— already on the v3 surface/auth/*— login and OIDC callbacks/proxy-launch/*— workload proxy entry/terminal/*— terminal session/healthz,/docs/api,/docs/openapi
Route map (v1 → v3-prod)¶
Top-level¶
| v1 path | → v3-prod path |
|---|---|
/ |
/v3-prod (authenticated persona dispatcher; sends user → workloads, tenant/project admin → overview, platform admin → platform overview, ops → platform ops) |
/workloads |
/v3-prod/workloads |
/marketplace |
/v3-prod/compute |
/apps |
/v3-prod/apps |
/storage |
/v3-prod/storage |
/access |
/v3-prod/access |
/iam |
/v3-prod/access/memberships |
/billing |
/v3-prod/account/billing |
/settings |
/v3-prod/account/profile |
/audit-logs |
/v3-prod/evidence |
/docs |
/v3-prod/docs |
/developer |
/v3-prod/developer/downloads |
/developer/downloads |
/v3-prod/developer/downloads |
/developer/api-docs |
/v3-prod/developer/api-docs/swagger |
/developer/api-docs/swagger |
/v3-prod/developer/api-docs/swagger |
/developer/api-docs/redoc |
/v3-prod/developer/api-docs/redoc |
/admin |
/v3-prod/platform/overview |
/notifications |
/v3-prod/notifications |
/launch |
/v3-prod/launch/compute |
/schedulers |
/v3-prod/schedulers |
/schedulers/tenant-shared |
/v3-prod/schedulers |
Detail / nested¶
| v1 path | → v3-prod path |
|---|---|
/workloads/{id} |
/v3-prod/workloads/{id} |
/storage/{id} |
/v3-prod/storage/{id} |
/apps/{slug} |
/v3-prod/apps/{slug} |
/apps/catalog |
/v3-prod/apps |
/apps/instances |
/v3-prod/workloads (app instances are workloads in v3) |
/apps/instances/{id} |
/v3-prod/apps/instances/{id} (resolver redirects to the owning workload detail when visible) |
/apps/artifacts |
/v3-prod/apps/artifacts |
/launch/compute |
/v3-prod/launch/compute |
/launch/app/{slug} |
/v3-prod/launch/app/{slug} |
/allocations |
/v3-prod/workloads |
/allocations/{id} |
/v3-prod/allocations/{id} (resolver redirects to the owning workload detail when visible) |
/settings/profile |
/v3-prod/account/profile |
/settings/ssh-keys |
/v3-prod/account/security |
/settings/team |
/v3-prod/access/memberships |
/iam/app-entitlements |
/v3-prod/access/entitlements |
/iam/service-accounts |
/v3-prod/access/service-accounts |
/iam/platform-roles |
/v3-prod/platform/iam |
/access/quotas |
/v3-prod/access/quotas |
/access/connectivity |
/v3-prod/access/connectivity |
/access/identity |
/v3-prod/access/identity |
/access/audit |
/v3-prod/access/audit |
/admin/overview |
/v3-prod/platform/overview |
/admin/allocations |
/v3-prod/platform/lifecycle |
/admin/nodes |
/v3-prod/platform/lifecycle |
/admin/nodes/decommissions/{id} |
/v3-prod/platform/lifecycle/decommissions/{id} |
/admin/nodes/onboardings/{id} |
/v3-prod/platform/lifecycle/onboardings/{id} |
/admin/maas |
/v3-prod/platform/lifecycle/maas |
/admin/maas/new |
/v3-prod/platform/lifecycle/maas/new |
/admin/maas/{siteId} |
/v3-prod/platform/lifecycle/maas/{siteId} |
/admin/users |
/v3-prod/platform/iam |
/admin/users/{userId} |
/v3-prod/platform/iam/users/{userId} |
/admin/payments |
/v3-prod/platform/finance |
/admin/payments/sessions |
/v3-prod/platform/finance |
/admin/audit-logs |
/v3-prod/platform/evidence |
/admin/ops |
/v3-prod/platform/ops |
/admin/telemetry |
/v3-prod/platform/ops |
/admin/runbooks |
/v3-prod/platform/ops |
/admin/skus |
/v3-prod/platform/config/skus |
/admin/os-images |
/v3-prod/platform/config/os-images |
/admin/quotas |
/v3-prod/platform/config/quotas |
Deliberately not mapped (fall through)¶
| v1 path | Reason |
|---|---|
/docs/* other than /docs, /docs/api, and /docs/openapi |
Static/raw docs stay outside middleware migration |
/admin/{anything-not-listed} |
Map only what we know maps; unknown admin routes stay on v1 |
Admin/Ops retirement coverage¶
The admin and ops cutover is intentionally workflow-based. V1 admin pages often exposed entities directly; V3 groups those entities under the operator intent they support. This table is the current retirement checklist for known V1 admin routes.
| V1 route | V3 workflow | Classification | Retirement note |
|---|---|---|---|
/admin/overview |
/v3-prod/platform/overview |
migrated | Platform landing summarizes fleet, active allocations, API health, workers, and DLQ posture. |
/admin/ops |
/v3-prod/platform/ops |
migrated | Live triage and runbook entry point replace the generic ops entity/debug page. |
/admin/telemetry |
/v3-prod/platform/ops |
migrated | Operational telemetry belongs with live triage until a dedicated observability workbench is needed. |
/admin/nodes |
/v3-prod/platform/lifecycle |
migrated | Node inventory is part of lifecycle intervention, not a standalone table dump. |
/admin/nodes/onboardings/{id} |
/v3-prod/platform/lifecycle/onboardings/{id} |
migrated | Onboarding workflow detail now keeps timeline, current failure, and resume/rerun controls in the V3 lifecycle family. |
/admin/nodes/decommissions/{id} |
/v3-prod/platform/lifecycle/decommissions/{id} |
migrated | Decommission/reimage workflow detail now keeps timeline, current failure, and resume/rerun controls in the V3 lifecycle family. |
/admin/allocations |
/v3-prod/platform/lifecycle |
migrated | Operator allocation work is lifecycle/recovery-oriented; user allocation work is /v3-prod/workloads. |
/admin/maas |
/v3-prod/platform/lifecycle/maas |
migrated | MAAS control center covers site posture, active provisioning workflows, reconciliation, and drift. |
/admin/maas/new |
/v3-prod/platform/lifecycle/maas/new |
migrated | Site registration is a V3 lifecycle setup workflow. |
/admin/maas/{siteId} |
/v3-prod/platform/lifecycle/maas/{siteId} |
migrated | Site detail keeps profiles, discovery candidates, RoCE assignments, and site-scoped workflows. |
/admin/skus |
/v3-prod/platform/config/skus |
migrated | SKU catalog setup is a configuration-publishing workflow with activate/retire controls. |
/admin/os-images |
/v3-prod/platform/config/os-images |
migrated | OS image compatibility belongs beside SKU/config publishing and now has a dedicated V3 matrix. |
/admin/quotas |
/v3-prod/platform/config/quotas |
migrated | Platform quota policy values stay in config; scoped project/user quota UX belongs under Access/tenant/project surfaces. |
/admin/audit-logs |
/v3-prod/platform/evidence |
migrated | Evidence page is the investigation workbench; raw audit export can remain an API/tool action. |
/admin/payments/sessions |
/v3-prod/platform/finance |
migrated | Payment sessions are finance interventions, not a standalone ledger table. |
/admin/users |
/v3-prod/platform/iam |
migrated | User directory is platform IAM/role governance. |
/admin/users/{userId} |
/v3-prod/platform/iam/users/{userId} |
migrated | User detail preserves per-user role binding, tenant/project membership, balance credit, refund, and evidence/finance pivots. |
Compatibility-only pages should not block V3 default-on if their remaining use is narrow investigation, export, or recovery. Any repeated operator need found there should become a V3 workflow/read-model task rather than another V1 patch.
Platform workflow read-model sections¶
The A-side V3 platform workflow read models expose explicit sections for the operator workflows that replace V1 admin tables:
| Family | Read-model section | Purpose |
|---|---|---|
| Ops | workflows |
Live triage, control-plane health, and investigation entry points. |
| Lifecycle | intervention_queues |
Lifecycle intervention queues for blocked nodes, active onboarding/reimage/decommission work, and agent repair/rollout. |
| Config | publish_workflows |
Config publish workflows for SKUs, OS images, MAAS profiles, and policy values. |
| Evidence | pivots |
Evidence pivot cards for correlation search, target history, and scoped exports. |
| Finance | interventions |
Finance intervention cards for failed reconciliation, stuck checkout, and payment-event lag. |
| IAM | governance |
IAM governance cards for platform admins, role bindings, stale/offboarding review, and identity continuity. |
These sections are intentionally additive to the existing KPI/table fields so the UI can move from skeletal tables to workflow cards without changing the route inventory.
Query-param semantics¶
?v1=1always wins over?v3=1if both are present (opt-out is conservative).- The
v1andv3query params are stripped from the destination URL — no opt-out token leaks to the v3 surface or back to the v1 page after redirect. - All other query params are preserved on the redirect target.
Operating procedures¶
| Environment | How to flip |
|---|---|
| Local dev | NEXT_PUBLIC_V3_DEFAULT=1 pnpm --dir packages/web dev |
| kind | Set NEXT_PUBLIC_V3_DEFAULT=1 on the web deployment env |
| platform-control | Trigger release with WEB_NEXT_PUBLIC_V3_DEFAULT=1; canary first |
| Rollback | Unset the env var (or set to anything other than 1); next request flow returns to v1 |
Tests¶
The decision logic is unit-tested at
packages/web/src/lib/cutover/route-map.test.ts. The Next.js middleware
adapter (packages/web/middleware.ts) is intentionally a thin wrapper.
When adding a new route mapping, update both:
- The map in
route-map.ts. - This doc.
A test should be added for the new mapping in route-map.test.ts.
Cutover rollout sequence (recommended)¶
- Land the flag (this task) with default off.
- Enable on local dev and run the v3 e2e suites.
- Enable on kind. Run the v3 cutover smoke (C-V3-CUTOVER-KIND-E2E-SMOKE-001).
- Canary on platform-control with a tight rollback bar.
- Default-on once the v1 retirement candidates list is at zero.
- Remove the flag and the v1 routes themselves once the cutover is final.
V1 retirement guardrails¶
The route map is a cutover mechanism, not a reason to keep V1 alive forever. Once a workflow is covered by a V3 production page and a V3 E2E suite, the V1 route is compatibility-only. Broad V1 frontend E2E should not remain the default CI signal for migrated workflows.
See doc/product/V3_V1_Retirement_Guardrails_v1.md for the compatibility-only
route list, frontend e2e policy, and removal conditions.