426 lines
30 KiB
Markdown
426 lines
30 KiB
Markdown
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)
|
||
|
||
---
|