Skip to content

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=1 always wins over ?v3=1 if both are present (opt-out is conservative).
  • The v1 and v3 query 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:

  1. The map in route-map.ts.
  2. This doc.

A test should be added for the new mapping in route-map.test.ts.

  1. Land the flag (this task) with default off.
  2. Enable on local dev and run the v3 e2e suites.
  3. Enable on kind. Run the v3 cutover smoke (C-V3-CUTOVER-KIND-E2E-SMOKE-001).
  4. Canary on platform-control with a tight rollback bar.
  5. Default-on once the v1 retirement candidates list is at zero.
  6. 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.