Below is a **migration map** from your **current route surface (~120+)** to a **canonical release-control-plane IA**, plus a **redirect/alias strategy** designed to **preserve existing deep links**, keep bookmarks working, and **avoid breaking external references** (docs, emails, Slack links, ticket comments, etc.). I’m assuming the **new canonical IA** is: * **Product flows (what customers do):** `/` (Control Plane), `/releases`, `/approvals`, `/environments`, `/deployments`, `/security/*`, `/evidence/*`, `/witness/*` * **Operations (running the platform):** `/operations/*` * **Settings (configuration + access control):** `/settings/*` * **Policy authoring (still first-class):** `/policy/*` (instead of `/policy-studio/*`) If you want to keep `/ops/*` and `/console/*` as canonical, you can—but then your route taxonomy stays inconsistent. The plan below keeps canonical clean **without breaking anything**, by keeping `/ops/*` and `/console/*` as legacy aliases indefinitely. --- ## 0) Canonical new route taxonomy (what we’re migrating to) ### 0.1 Control plane and release lifecycle * `/` → **Control Plane** (pipeline, pending approvals, drift/risk deltas) * `/releases` → Releases list * `/releases/:releaseId` → Release detail (gates, diff, evidence, proof chain) * `/approvals` → Approvals inbox * `/approvals/:approvalId` → Approval detail (diff-first + decision + evidence) * `/environments` → Environments * `/environments/:envId` → Environment detail * `/deployments` → Deployments list * `/deployments/:deployId` → Deployment detail (workflow DAG + artifacts + evidence) ### 0.2 Security (scanner heritage becomes “gate inputs”) * `/security/overview` → Security overview dashboard (old Home dashboard preserved here) * `/security/findings` → Findings (impact-to-release, reachability chips) * `/security/scans/:scanId` → Scan run detail * `/security/vulnerabilities` → CVE explorer * `/security/vulnerabilities/:cveId` → CVE detail * `/security/sbom/graph` → SBOM graph explorer * `/security/lineage` → Lineage/compare (global) * `/security/reachability` → Reachability center * `/security/vex` → VEX hub (no longer under `/admin`) * `/security/unknowns` → Unknowns tracking * `/security/patch-map` → Patch map ### 0.3 Evidence and verification * `/evidence` → Evidence center (packets, bundles, export, replay, provenance) * `/evidence/:evidenceId` → Evidence packet viewer * `/evidence/packs` and `/evidence/packs/:packId` → Evidence packs * `/evidence/proofs/:subjectDigest` → Proof chain viewer * `/witness/:witnessId` → Witness viewer (reachability slice + replay/verify) ### 0.4 Policy (rename, but keep semantics) * `/policy/packs` (list) * `/policy/packs/:packId/editor` * `/policy/packs/:packId/yaml` * `/policy/packs/:packId/simulate` * `/policy/packs/:packId/approvals` * `/policy/packs/:packId/rules` * `/policy/packs/:packId/explain/:runId` * `/policy/packs/:packId/dashboard` * `/policy/exceptions` (exception queue + approvals) ### 0.5 Operations * `/operations/orchestrator` (+ jobs, quotas) * `/operations/quotas/*` * `/operations/dead-letter/*` * `/operations/slo/*` * `/operations/health/*` * `/operations/feeds/*` * `/operations/offline-kit/*` * `/operations/aoc/*` * `/operations/scheduler/*` * `/operations/doctor` ### 0.6 Settings * `/settings/profile` * `/settings/integrations/*` (hub + detail + activity) * `/settings/admin/*` (tenants/users/roles/clients/tokens/branding) * `/settings/trust/*` (keys/issuers/certs/score-config/audit) * `/settings/registries` (registry token service) * `/settings/notifications/*` * `/settings/policy/governance` * `/settings/sbom-sources` * `/settings/trivy-db` (or fold into feeds) --- # 1) Migration principles (minimize breaking links) **Principle A — Keep old links working forever:** Every old route either: * **Redirects** to the new canonical route, or * Remains as an **alias** that renders the same page/module. **Principle B — Preserve identifiers and semantics:** If `:scanId`, `:packId`, `:subjectDigest` exist today, do not change their format. New routes simply “re-home” them. **Principle C — Use redirects only when mapping is 1:1:** If old route needs **query params** (e.g., “filter type=audit”), use a **guard-based redirect** returning a `UrlTree` (so you can append query parameters safely). **Principle D — Track legacy usage:** Add telemetry: whenever a legacy route is hit, record `{ oldPath, newPath }`. This lets you quantify remaining legacy usage. --- # 2) Old → new route migration map Each entry includes: **Old route → New canonical route** + **strategy**. Legend: * **KEEP** = route stays as-is (canonical already good) * **REDIRECT** = Angular router redirect (1:1 mapping) * **SMART REDIRECT** = redirect via guard/matcher to add query params/open specific view * **ALIAS** = old route still loads same module/component as new (no visible URL change) --- ## 2.1 Home & dashboard routes | Old Route | New Route | Strategy | Notes | | -------------------- | ------------------- | ---------------------- | ------------------------------------------------------------------------------------------ | | `/` | `/` | KEEP (content changes) | Home becomes **Control Plane**. Preserve old “security dashboard” as `/security/overview`. | | `/welcome` | `/welcome` | KEEP | Usually public. Keep stable. | | `/dashboard/sources` | `/operations/feeds` | REDIRECT | Old “sources dashboard” becomes operational view of feeds/mirrors. | Add a prominent navigation link: **Security Overview** → `/security/overview` to avoid “we removed my dashboard” backlash. --- ## 2.2 Analyze routes → Security namespace | Old Route | New Route | Strategy | Notes | | ---------------------------------- | -------------------------------------- | ----------------- | --------------------------------------------------------------------- | | `/findings` | `/security/findings` | REDIRECT | Findings become security impact-to-release view. | | `/findings/:scanId` | `/security/scans/:scanId` | REDIRECT | Preserve deep links; scan detail page remains. | | `/vulnerabilities` | `/security/vulnerabilities` | REDIRECT | CVE explorer moved under security. | | `/vulnerabilities/:vulnId` | `/security/vulnerabilities/:vulnId` | REDIRECT | 1:1 mapping. | | `/graph` | `/security/sbom/graph` | REDIRECT | SBOM graph belongs under Security. | | `/lineage` | `/security/lineage` | REDIRECT | (Or `/releases/lineage`, choose one canonical; I recommend Security.) | | `/lineage/:artifact/compare` | `/security/lineage/:artifact/compare` | ALIAS or REDIRECT | Keep params same. | | `/lineage/compare` | `/security/lineage/compare` | REDIRECT | Stable. | | `/reachability` | `/security/reachability` | REDIRECT | Reachability center is security analysis. | | `/admin/vex-hub` | `/security/vex` | REDIRECT | VEX is not “admin-only”; move. | | `/admin/vex-hub/search` | `/security/vex/search` | REDIRECT | Keep identical subroutes. | | `/admin/vex-hub/search/detail/:id` | `/security/vex/search/detail/:id` | REDIRECT | 1:1. | | `/admin/vex-hub/stats` | `/security/vex/stats` | REDIRECT | 1:1. | | `/admin/vex-hub/consensus` | `/security/vex/consensus` | REDIRECT | 1:1. | | `/admin/vex-hub/explorer` | `/security/vex/explorer` | REDIRECT | 1:1. | | `/analyze/unknowns` | `/security/unknowns` | REDIRECT | 1:1. | | `/analyze/patch-map` | `/security/patch-map` | REDIRECT | 1:1. | | `/scans/:scanId` | `/security/scans/:scanId` | REDIRECT | Consolidate scan detail here. | | `/compare/:currentId` | `/security/lineage/compare/:currentId` | REDIRECT | Preserve compare deep links. | | `/cvss/receipts/:receiptId` | `/evidence/receipts/cvss/:receiptId` | REDIRECT | CVSS receipt is an **evidence artifact**. | --- ## 2.3 Triage routes → split between Security (artifact triage) and Policy/Evidence | Old Route | New Route | Strategy | Notes | | ------------------------------- | ---------------------------------- | -------------- | ------------------------------------------------------------------------------------------- | | `/triage/artifacts` | `/security/artifacts` | REDIRECT | “Artifact workspace” becomes security artifact index (digest-first). | | `/triage/artifacts/:artifactId` | `/security/artifacts/:artifactId` | REDIRECT | Preserve the triage workspace; it becomes “Artifact Detail”. | | `/exceptions` | `/policy/exceptions` | REDIRECT | Exceptions are governance controls for gates. | | `/triage/audit-bundles` | `/evidence?type=audit` | SMART REDIRECT | Needs query param. Alternatively create `/evidence/bundles/audit` to allow simple redirect. | | `/triage/audit-bundles/new` | `/evidence/bundles/new?type=audit` | SMART REDIRECT | Needs query param. | | `/risk` | `/security/risk` | REDIRECT | Risk dashboard becomes security analytics. | **Recommendation to reduce SMART redirects:** create explicit canonical paths: * `/evidence/bundles/audit` * `/evidence/bundles/release` * `/evidence/bundles/scan` Then redirects are trivial and do not require query injection. --- ## 2.4 Policy routes (`/policy-studio/*` → `/policy/*`) | Old Route | New Route | Strategy | Notes | | --------------------------------------------- | -------------------------------------- | -------- | -------------------- | | `/policy-studio/packs` | `/policy/packs` | REDIRECT | Rename for brevity. | | `/policy-studio/packs/:packId/editor` | `/policy/packs/:packId/editor` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/yaml` | `/policy/packs/:packId/yaml` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/simulate` | `/policy/packs/:packId/simulate` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/approvals` | `/policy/packs/:packId/approvals` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/rules` | `/policy/packs/:packId/rules` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/explain/:runId` | `/policy/packs/:packId/explain/:runId` | REDIRECT | 1:1. | | `/policy-studio/packs/:packId/dashboard` | `/policy/packs/:packId/dashboard` | REDIRECT | 1:1. | | `/orchestrator` | `/operations/orchestrator` | REDIRECT | Orchestrator is ops. | | `/orchestrator/jobs` | `/operations/orchestrator/jobs` | REDIRECT | 1:1. | | `/orchestrator/jobs/:jobId` | `/operations/orchestrator/jobs/:jobId` | REDIRECT | 1:1. | | `/orchestrator/quotas` | `/operations/orchestrator/quotas` | REDIRECT | 1:1. | --- ## 2.5 Ops routes (`/ops/*` + `/scheduler/*` → `/operations/*`) | Old Route | New Route | Strategy | Notes | | ------------------------------------- | -------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | | `/sbom-sources` | `/settings/sbom-sources` | REDIRECT | This is configuration, not ops. | | `/ops/quotas` | `/operations/quotas` | REDIRECT | 1:1. | | `/ops/quotas/tenants` | `/operations/quotas/tenants` | REDIRECT | 1:1. | | `/ops/quotas/tenants/:tenantId` | `/operations/quotas/tenants/:tenantId` | REDIRECT | 1:1. | | `/ops/quotas/throttle` | `/operations/quotas/throttle` | REDIRECT | 1:1. | | `/ops/quotas/alerts` | `/operations/quotas/alerts` | REDIRECT | 1:1. | | `/ops/quotas/forecast` | `/operations/quotas/forecast` | REDIRECT | 1:1. | | `/ops/quotas/reports` | `/operations/quotas/reports` | REDIRECT | 1:1. | | `/ops/orchestrator/dead-letter` | `/operations/dead-letter` | REDIRECT | Flatten path; keep subroute for queue. | | `/ops/orchestrator/dead-letter/queue` | `/operations/dead-letter/queue` | REDIRECT | 1:1. | | `/ops/orchestrator/slo` | `/operations/slo` | REDIRECT | 1:1. | | `/ops/orchestrator/slo/alerts` | `/operations/slo/alerts` | REDIRECT | 1:1. | | `/ops/orchestrator/slo/definitions` | `/operations/slo/definitions` | REDIRECT | 1:1. | | `/ops/health` | `/operations/health` | REDIRECT | 1:1. | | `/ops/feeds` | `/operations/feeds` | REDIRECT | 1:1. | | `/ops/feeds/mirror/:mirrorId` | `/operations/feeds/mirror/:mirrorId` | REDIRECT | 1:1. | | `/ops/feeds/airgap/import` | `/operations/feeds/airgap/import` | REDIRECT | 1:1. | | `/ops/feeds/airgap/export` | `/operations/feeds/airgap/export` | REDIRECT | 1:1. | | `/ops/feeds/version-locks` | `/operations/feeds/version-locks` | REDIRECT | 1:1. | | `/ops/offline-kit/*` | `/operations/offline-kit/*` | ALIAS or REDIRECT | Either keep the segment name to avoid churn, or canonicalize to `/operations/offline/*`. | | `/ops/aoc/*` | `/operations/aoc/*` | REDIRECT | Keep short; avoid nested `/compliance/` unless you really need it. | | `/ops/doctor` | `/operations/doctor` | REDIRECT | 1:1. | | `/scheduler/*` | `/operations/scheduler/*` | REDIRECT | Fix inconsistent prefix. | | `/ops/scanner/*` | `/operations/scanner/*` | REDIRECT | Scanner ops is now “security gate engine ops”. | --- ## 2.6 Notify | Old Route | New Route | Strategy | Notes | | --------- | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `/notify` | `/operations/notifications` | REDIRECT | If `/notify` is history/dispatch, it belongs to operations. If it is configuration, redirect to `/settings/notifications`. | --- ## 2.7 Admin + Console routes → Settings namespace | Old Route | New Route | Strategy | Notes | | ------------------------------ | ------------------------------- | -------- | ---------------------------------------------------------- | | `/console/profile` | `/settings/profile` | REDIRECT | Consolidate under settings. | | `/console/status` | `/operations/status` | REDIRECT | Status is ops. | | `/console/configuration` | `/settings/integrations` | REDIRECT | Configuration pane becomes integrations hub. | | `/console/admin/tenants` | `/settings/admin/tenants` | REDIRECT | 1:1. | | `/console/admin/users` | `/settings/admin/users` | REDIRECT | 1:1. | | `/console/admin/roles` | `/settings/admin/roles` | REDIRECT | 1:1. | | `/console/admin/clients` | `/settings/admin/clients` | REDIRECT | 1:1. | | `/console/admin/tokens` | `/settings/admin/tokens` | REDIRECT | 1:1. | | `/console/admin/audit` | `/evidence/audit` | REDIRECT | Audit is evidence. | | `/console/admin/branding` | `/settings/admin/branding` | REDIRECT | 1:1. | | `/admin/audit/*` | `/evidence/audit/*` | REDIRECT | Unified audit log belongs under evidence. | | `/admin/trust/*` | `/settings/trust/*` | REDIRECT | Keys/issuers/certs/score config consolidated. | | `/admin/registries` | `/settings/registries` | REDIRECT | Registry token service is configuration. | | `/admin/issuers` | `/settings/trust/issuers` | REDIRECT | Fold into trust. | | `/admin/notifications` | `/settings/notifications/admin` | REDIRECT | Admin notifications config. | | `/admin/policy/governance` | `/settings/policy/governance` | REDIRECT | Governance is configuration. | | `/admin/policy/simulation` | `/policy/simulation` | REDIRECT | Or keep `/settings/policy/simulation` if truly admin-only. | | `/concelier/trivy-db-settings` | `/settings/trivy-db` | REDIRECT | Or fold into `/operations/feeds/trivy`. | --- ## 2.8 Release Orchestrator routes (`/release-orchestrator/*` → lifecycle roots) | Old Route | New Route | Strategy | Notes | | ------------------------------------ | --------------------------------------- | -------------- | ------------------------------------------------------------------------------- | | `/release-orchestrator` | `/` | REDIRECT | Control plane becomes the orchestrator home. | | `/release-orchestrator/environments` | `/environments` | REDIRECT | 1:1. | | `/release-orchestrator/releases` | `/releases` | REDIRECT | 1:1. | | `/release-orchestrator/workflows` | `/workflows` (or `/settings/workflows`) | REDIRECT | Decide: if workflows are editable config → settings; if used daily → top-level. | | `/release-orchestrator/approvals` | `/approvals` | REDIRECT | 1:1. | | `/release-orchestrator/deployments` | `/deployments` | REDIRECT | 1:1. | | `/release-orchestrator/evidence` | `/evidence?type=release` | SMART REDIRECT | Better to create `/evidence/bundles/release` for simple redirect. | --- ## 2.9 Evidence routes (mostly keep) | Old Route | New Route | Strategy | Notes | | ------------------------- | --------------------------------- | ------------------- | -------------------------------------------------------- | | `/evidence` | `/evidence` | KEEP | Already good. | | `/evidence/bundles` | `/evidence` | ALIAS or REDIRECT | If you keep tabbed routes, you can keep it as alias. | | `/evidence/export` | `/evidence/export` | KEEP | Stable. | | `/evidence/replay` | `/evidence/replay` | KEEP | Stable. | | `/evidence/provenance` | `/evidence/provenance` | KEEP | Stable. | | `/evidence-packs` | `/evidence/packs` | REDIRECT | Normalize under evidence namespace. | | `/evidence-packs/:packId` | `/evidence/packs/:packId` | REDIRECT | 1:1. | | `/proofs/:subjectDigest` | `/evidence/proofs/:subjectDigest` | ALIAS (recommended) | Keep `/proofs/*` forever as a public-friendly shortlink. | --- ## 2.10 Integrations routes → Settings | Old Route | New Route | Strategy | Notes | | ------------------------------ | --------------------------------------- | -------- | ----------------------------------------------------------- | | `/integrations` | `/settings/integrations` | REDIRECT | Canonicalize. | | `/integrations/registries` | `/settings/integrations/registries` | REDIRECT | 1:1. | | `/integrations/scm` | `/settings/integrations/scm` | REDIRECT | 1:1. | | `/integrations/ci` | `/settings/integrations/ci` | REDIRECT | 1:1. | | `/integrations/hosts` | `/settings/integrations/hosts` | REDIRECT | 1:1. | | `/integrations/feeds` | `/settings/integrations/feeds` | REDIRECT | 1:1. | | `/integrations/activity` | `/settings/integrations/activity` | REDIRECT | Or move to `/operations/integrations/activity` if you want. | | `/integrations/:integrationId` | `/settings/integrations/:integrationId` | REDIRECT | 1:1. | --- ## 2.11 Other routes | Old Route | New Route | Strategy | Notes | | ----------------- | ---------------------------- | -------- | ----------------------------------------- | | `/ai-runs` | `/operations/ai-runs` | REDIRECT | AI runs are operational telemetry. | | `/ai-runs/:runId` | `/operations/ai-runs/:runId` | REDIRECT | 1:1. | | `/change-trace` | `/evidence/change-trace` | REDIRECT | Change trace is evidence lineage. | | `/setup` | `/setup` | KEEP | Installation wizard should remain stable. | | `/auth/callback` | `/auth/callback` | KEEP | Must remain stable for OIDC. | --- # 3) Redirect strategy (implementation plan that won’t bite you) ## 3.1 Use a dedicated “Legacy Routes” layer (lowest priority in router) **Order matters.** Put all legacy redirects **after** the new canonical route tree so you don’t accidentally intercept new paths. * `app.routes.ts` 1. New canonical routes 2. Legacy redirect/alias routes 3. `**` fallback ## 3.2 Three redirect mechanisms (use the right one) ### Mechanism 1 — Simple static redirect (`redirectTo`) Use when mapping is clean and 1:1: * `/findings` → `/security/findings` * `/release-orchestrator/releases` → `/releases` ### Mechanism 2 — Param redirect (`redirectTo` with `:param`) Use when it’s still 1:1 but has params: * `/vulnerabilities/:vulnId` → `/security/vulnerabilities/:vulnId` * `/findings/:scanId` → `/security/scans/:scanId` ### Mechanism 3 — SMART redirect (guard/matcher returning a UrlTree) Use when you must: * Add query params (e.g., `type=audit`) * Switch tabs * Open a drawer based on route Examples: * `/triage/audit-bundles` → `/evidence?type=audit` * `/release-orchestrator/evidence` → `/evidence?type=release` **Strong recommendation:** Avoid SMART redirects by giving evidence bundle types **real paths**: * `/evidence/bundles/audit` * `/evidence/bundles/release` Then you can use simple redirects and remove complexity. ## 3.3 Preserve query params and fragments always Legacy URLs in tickets often include query params. Your redirect logic must preserve: * `?tab=...` * `?filters=...` * `#anchor` In Angular, **guard-based UrlTree** redirects are the most reliable way to preserve and augment query params intentionally. ## 3.4 Keep “short links” as permanent aliases Some paths are extremely convenient and should remain: * `/proofs/:subjectDigest` (keep forever, even if canonical is under `/evidence/proofs/...`) * Potentially `/deploy/:id` if you ever add it This reduces friction when humans share links. ## 3.5 Add a “Legacy URL” banner (optional but useful) On legacy-rendered aliases (not redirects), show a slim banner: * “This URL has moved. Update bookmarks.” * Button: “Go to new location” * Include one-click copy of canonical URL This is very effective during the transition without forcing redirects. ## 3.6 Instrument legacy hits Emit a telemetry event: * `legacy_route_hit` * `oldPath` * `newPath` * `tenantId` * `userId` (if available) * `timestamp` This tells you when it’s safe to remove legacy routes (if you ever choose to). --- # 4) Practical redirect coverage checklist (to prevent surprises) Before shipping, test these as **direct loads** (not SPA navigation): 1. `/admin/vex-hub/search/detail/123` loads and lands on `/security/vex/search/detail/123` 2. `/findings/SCAN-123` lands on scan detail 3. `/proofs/sha256:...` still works and lands on proof viewer 4. `/release-orchestrator/environments` lands on `/environments` 5. `/triage/audit-bundles` lands on the correct evidence bundle view (no empty state) ---