# Feature Specification: Spec 341 - Canonical Link / Query Cleanup **Feature Branch**: `341-canonical-link-query-cleanup` **Created**: 2026-05-31 **Status**: Draft **Input**: Candidate `canonical-link-query-cleanup` from `docs/product/spec-candidates.md` + repo inspection of remaining legacy scope query hints in middleware/shell after Specs 338–340 and Spec 339. ## Spec Candidate Check *(mandatory — SPEC-GATE-001)* - **Problem**: The workspace/environment scope contract is now repo-real (Specs 314–322, 338–339), but some remaining URL/query seams still interpret legacy scope query keys (`tenant`, `managed_environment_id`, etc.) as “environment hints”. That reintroduces hidden scope, weakens trust for credential-adjacent and environment-bound surfaces, and makes deep links less predictable. - **Today's failure**: Repo inspection shows remaining legacy query parsing in shared request-scoping seams (e.g. environment/tenant selection middleware and `OperateHubShell`) that can treat `?tenant=` or `?managed_environment_id=` as scope hints outside workspace hubs. This undermines the “explicit or route-owned” scope rule and increases the risk of accidental scope drift resurfacing. - **User-visible improvement**: Operators can share and reload admin links confidently: workspace hubs are workspace-wide by default and only narrow via explicit `environment_id`; environment-bound pages are route-owned and do not accept legacy query aliases as authority; “clear filter” links return to clean canonical URLs; and guard tests block reintroducing legacy query scope behavior. - **Smallest enterprise-capable version**: Inventory remaining legacy scope query parsing, remove it (or make it strictly ignore-only with safe fallback), align canonical link generation across hubs/shell/middleware seams, and add targeted tests to prevent regressions. - **Explicit non-goals**: - No new UI shell/sidebar/topbar redesign. - No new routes or panels. - No new persisted entities, enums, or abstraction frameworks. - No RBAC model or capability registry changes. - No provider/OAuth redesign (Spec 281 family). - No Graph integration changes. - **Permanent complexity imported**: A small, explicit inventory + a small set of guard/contract tests around canonical URL/query semantics. No new runtime taxonomy or UI framework. - **Why now**: Specs 338–340 and Spec 339 hardened scope/authority. The next risk is “scope drift through links and query parsing” reappearing as features continue. This cleanup locks the contract so future work cannot accidentally revive legacy hints. - **Why not local**: Fixing a single page’s link generator is insufficient because the drift sits in shared request-scoping seams. The smallest correct slice must cover the shared parsing/link seams and prove behavior with tests. - **Approval class**: Core Enterprise (scope/authorization safety hardening). - **Red flags triggered**: Cross-surface navigation semantics + shared middleware/shell seams. **Defense**: bounded to removing legacy query hint parsing and aligning canonical URL generation; no new frameworking/persistence; tests make the change reviewable. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 1 | Wiederverwendung: 2 | **Gesamt: 9/12** - **Decision**: approve. ## Summary Canonicalize admin navigation and request scope semantics so that: - Workspace hubs remain workspace-wide unless explicitly filtered by `environment_id`. - Environment-bound pages derive environment context only from route parameters (not legacy query aliases). - Legacy scope query keys do not grant authority, do not silently narrow results, and do not reintroduce “hidden environment context”. - Shared seams (`EnsureWorkspaceSelected`, `EnsureEnvironmentContextSelected`, `OperateHubShell`) do not parse legacy query scope hints for authority. This is a hardening / cleanup slice: it removes legacy scope query hint parsing and aligns link/query semantics with the existing scope contract. ## Completed-Spec Guardrail Result Related specs are context only and must not be rewritten: - Workspace hub contracts and legacy alias guards: Specs 314–322, 317, 338. - Credential-adjacent authority hardening: Spec 339. - Post-scope browser verification gate: Spec 340. No `specs/341-*` package existed before this prep package was created. ## Spec Scope Fields *(mandatory)* - **Scope**: canonical-view (navigation + link/query contract hygiene). - **Primary routes / surfaces affected** (contract-level; exact list is inventory-driven): - Workspace hubs that accept environment narrowing via `environment_id` (e.g. Governance Inbox, Decision Register, Review Register, Evidence Overview, Audit Log, Alerts, Provider Connections index, Customer Review Workspace). - Environment-bound pages under `/admin/workspaces/{workspace}/environments/{environment}/...` that must not accept legacy query scope hints. - Shared request-scoping seams that currently inspect legacy query keys: - `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php` - `apps/platform/app/Support/Middleware/EnsureEnvironmentContextSelected.php` - `apps/platform/app/Support/OperateHub/OperateHubShell.php` - `apps/platform/app/Filament/Pages/EnvironmentRequiredPermissions.php` (query-hint cleanup only) - **Data Ownership**: No schema changes. This slice changes URL/query handling and link generation only. - **RBAC**: - No new capabilities. - Existing 404 vs 403 semantics remain authoritative. - `environment_id` is a filter hint only; it must be validated against current workspace context + actor entitlement. For canonical-view surfaces: - **Default filter behavior when tenant-context is active**: Workspace hub clean URLs are workspace-wide and must not inherit remembered environment context, Filament tenant fallback, session table filters, or legacy query keys. If narrowed, the page must show an explicit `environment_id` filter state. - **Explicit entitlement checks preventing cross-tenant leakage**: Any requested `environment_id` must resolve to an environment in the current workspace that the actor can access; foreign/out-of-scope IDs must fail as not found and must not leak existence. ## UI Surface Impact *(mandatory — UI-COV-001)* Does this spec add, remove, rename, or materially change any reachable UI surface? - [ ] No UI surface impact - [x] Existing page changed - [ ] New page/route added - [x] Navigation changed - [ ] Filament panel/provider surface changed - [ ] New modal/drawer/wizard/action added - [ ] New table/form/state added - [ ] Customer-facing surface changed - [ ] Dangerous action changed - [ ] Status/evidence/review presentation changed - [x] Workspace/environment context presentation changed ## UI/Productization Coverage *(UI-COV-001)* - **Route/page/surface**: Cross-surface link + query semantics for workspace hubs and environment-bound pages (no new UI surface). - **Current page archetype**: scope-contract / navigation semantics (global-context-shell + workspace hub filter contract). - **Design depth**: Domain Pattern Surface (scope/authority hardening) — minimal visible UX change expected. - **Repo-truth level**: repo-verified (existing hub filter contracts + shared middleware/shell code). - **Existing pattern reused**: Spec 314/315/316 hub contracts + Spec 317 legacy alias cleanup + Spec 338 contract tests + existing navigation helpers. - **New pattern required**: none (tighten and converge; do not invent a new link-normalization framework). - **Screenshot required**: no (unless implementation introduces visible copy changes; then add one targeted screenshot in the implementation PR only). - **Page audit required**: no (no new archetype; contract hardening only). - **Customer-safe review required**: no (scope contract applies broadly; not a customer-safe productization slice). - **Dangerous-action review required**: no (no action behavior change; link/query semantics only). - **Coverage files to update (in implementation PR)**: - [ ] `docs/ui-ux-enterprise-audit/route-inventory.md` (only if routes/nav entries change materially; not expected) - [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` (no new surface; expected `no`) - [x] `N/A - no new reachable UI surface added; contract/URL semantics only` ## Cross-Cutting / Shared Pattern Reuse *(mandatory)* - **Cross-cutting feature?**: yes. - **Interaction class(es)**: navigation deep links, scope presentation, URL/query semantics, “clear filter” behavior. - **Systems touched (expected)**: - `apps/platform/app/Support/Navigation/WorkspaceHubNavigation.php` - `apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php` - `apps/platform/app/Filament/Concerns/UsesAdminEnvironmentFilterQueryParameter.php` - `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php` - `apps/platform/app/Support/Middleware/EnsureEnvironmentContextSelected.php` - `apps/platform/app/Support/OperateHub/OperateHubShell.php` - `apps/platform/tests/Feature/Navigation/*` - **Existing pattern(s) to extend**: Explicit `environment_id` filter only for workspace hubs; route-owned environment for environment-bound pages; deny-as-not-found for out-of-scope. - **Allowed deviation and why**: none. This slice removes legacy behavior; it does not add a second link/query interpretation path. - **Consistency impact**: One canonical environment filter key (`environment_id`) for hubs; no legacy query alias authority anywhere; all “clear filter” links return to clean canonical URLs. - **Review focus**: “legacy query scope hints are not authority” + tests that make regressions obvious. ## OperationRun UX Impact N/A — no `OperationRun` start, link, or lifecycle semantics are changed in this slice. ## Provider Boundary / Platform Core Check - **Shared provider/platform boundary touched?**: no. - **N/A**: This is platform scope/navigation hygiene. It must not interpret provider “tenant” identifiers as platform authority. ## Proportionality Review N/A — no new persisted entities, abstraction frameworks, enums/status families, or taxonomy systems are introduced. ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Feature (scope/link/query contract + regression guard). - **Validation lane(s)**: fast-feedback (Feature). Browser smoke is optional and only justified if implementation changes visible navigation/URL behavior that cannot be proven reliably in Feature tests alone. - **Planned validation commands**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation --filter=Canonical` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Workspaces --filter=Scope` - `cd apps/platform && ./vendor/bin/sail pint --dirty` ## Acceptance Criteria - **AC1**: Workspace hubs accept `environment_id` as the only environment-narrowing query key; legacy aliases (`tenant`, `tenant_id`, `managed_environment_id`, `environment`, `tenant_scope`, `tableFilters`) never establish filter authority. - **AC2**: Environment-bound routes derive environment context only from route params and do not parse legacy query aliases as scope hints. - **AC3**: “Clear filter” returns to the clean canonical hub URL and clears any persisted hub filter state as per existing contracts. - **AC4**: Out-of-scope `environment_id` is rejected with deny-as-not-found semantics and does not leak environment existence. - **AC5**: Targeted Feature tests fail loudly on regression and pass after implementation; no new heavy-governance or browser family is introduced without an explicit spec decision. ## User Scenarios & Testing *(mandatory)* ### User Story 1 — Share a canonical workspace hub link (Priority: P1) As a workspace operator, I can share a link to a workspace hub that is either workspace-wide (clean URL) or explicitly narrowed to one environment using `environment_id`, and the page shows scope/filter state correctly after reload/back/forward. **Independent Test**: A Feature test renders each critical hub with clean URL and with `?environment_id=` and asserts filter state, clear-filter behavior, and no legacy query keys. ### User Story 2 — Legacy query aliases never become authority (Priority: P1) As a security reviewer, I can trust that legacy query keys cannot establish environment context or widen results on environment-bound pages or shared scoping middleware. **Independent Test**: Feature tests verify that `?tenant=` and `?managed_environment_id=` are ignored or rejected consistently and never treated as an environment hint for authority. ### User Story 3 — Guard tests prevent reintroduction (Priority: P2) As a developer, I have regression guards that fail CI if new code introduces legacy query parsing or generates links containing legacy scope query keys. **Independent Test**: A guard test scans/validates generated navigation URLs for forbidden query keys. ## Out of Scope - Any feature work that changes business logic, capabilities, policies, or data model. - Any broad UI redesign, new surfaces, or new navigation architecture. - Any provider/OAuth or credential workflow changes. - Any compatibility redirects or preserving legacy scope query keys “just in case”. ## Risks - Some internal/shared surfaces may still rely on legacy query hints for environment selection. Implementation must replace that behavior with explicit route-owned context or explicit `environment_id` on hubs, and prove it with tests. - Over-scoping this cleanup can become a refactor. Tasks must stay inventory-driven and bounded to canonical scope/query semantics only. ## Assumptions - The workspace/environment scope contract tests and hub filter contracts remain authoritative. - This is pre-production: removing legacy scope query keys is allowed and preferred over compatibility layers. ## Open Questions None blocking. Any discovered “needed but unclear” legacy behavior must be recorded as a follow-up candidate rather than silently preserved.