# Implementation Plan: Spec 341 - Canonical Link / Query Cleanup **Branch**: `341-canonical-link-query-cleanup` | **Date**: 2026-05-31 | **Spec**: `specs/341-canonical-link-query-cleanup/spec.md` **Input**: Candidate `canonical-link-query-cleanup` from `docs/product/spec-candidates.md` + repo inspection of legacy scope query parsing seams. ## Summary Inventory and remove remaining legacy “scope hint” query parsing (`tenant`, `managed_environment_id`, etc.) in shared request-scoping seams and align canonical link generation so that: - workspace hubs are workspace-wide unless explicitly filtered via `environment_id`; - environment-bound pages are route-owned and do not accept legacy query aliases as authority; and - “clear filter” behavior returns to clean canonical URLs. This is contract hardening; it must not introduce new abstractions, persistence, or UI frameworks. ## Technical Context - **Language/Version**: PHP 8.4.15, Laravel 12.52.x - **Primary Dependencies**: Filament v5, Livewire v4, Pest v4 - **Storage**: PostgreSQL (no schema changes planned) - **Testing**: Pest Feature tests (navigation + scope contracts) - **Validation Lanes**: fast-feedback (Feature); browser only if later proven necessary for visible UX behaviors that cannot be asserted reliably in Feature tests - **Target Platform**: `apps/platform` (Sail locally; Dokploy/container posture unchanged) - **Constraints**: - no compatibility redirects or “legacy alias preservation” - no new packages, migrations, queues, scheduler, storage changes - deny-as-not-found semantics remain authoritative ## UI / Surface Guardrail Plan - **Guardrail scope**: existing reachable surfaces; navigation + scope contract hardening - **Affected routes/pages/actions/states** (inventory-driven): - workspace hubs that accept environment narrowing via `environment_id` and expose “clear filter” behavior - environment-bound pages under `/admin/workspaces/{workspace}/environments/{environment}/...` that must remain route-owned - shared request-scoping seams that must not treat legacy query keys as authority - **State layers in scope**: URL/query + route parameters + session workspace context - **Handling mode**: review-mandatory (scope/authority semantics) - **Required tests or manual smoke**: Feature contract tests for clean vs filtered hub URLs and legacy alias rejection/ignore behavior; browser smoke only if required later - **UI/Productization coverage decision**: existing pages changed / navigation semantics changed; no new reachable surface added; do not update `docs/ui-ux-enterprise-audit/*` unless implementation materially changes navigation entries or routes (not expected) ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes - **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/*` - **Shared abstractions reused**: existing hub filter contract helpers, `WorkspaceContext`, deny-as-not-found semantics, existing guard-test patterns - **New abstraction introduced?**: none - **Spread control**: inventory-driven; limit changes to URL/query scope semantics and canonical link generation only ## OperationRun UX Impact N/A — no `OperationRun` start/completion/link UX changes. ## Implementation Approach ### Phase 1 — Repo truth inventory + failing tests first - inventory every remaining legacy scope query parsing seam (query keys only, not DB naming) - add/adjust Feature tests that fail on today’s behavior and encode the intended contract ### Phase 2 — Remove legacy query scope hint parsing in shared seams - remove or strictly ignore legacy scope hints in: - `EnsureWorkspaceSelected` - `EnsureEnvironmentContextSelected` - `OperateHubShell` - `EnvironmentRequiredPermissions` (query-hint handling only) - ensure out-of-scope requests preserve deny-as-not-found semantics ### Phase 3 — Align canonical link generation - ensure link generation: - never emits legacy scope query keys - uses `environment_id` only for workspace hub narrowing - does not treat `environment_id` as a replacement for route-owned environment pages ### Phase 4 — Regression guards - add/extend guard tests that block: - parsing legacy scope keys as authority - generating navigation URLs containing forbidden scope query keys ### Phase 5 — Validation + close-out - run the narrowest Feature tests - run `pint` on dirty files and `git diff --check` ## Test Governance Check - **Test purpose / classification**: Feature - **Affected validation lanes**: fast-feedback (Feature) - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation --filter=Spec341` - **Fixture / helper cost risks**: none expected; reuse existing workspace/environment factories + navigation harness helpers - **Escalation path**: `document-in-feature` if a bounded exception is proven necessary; otherwise treat legacy alias preservation as a blocker ## Deployment / Ops Impact No migrations, env vars, queues, scheduler, storage, or asset pipeline changes are planned. ## Constitution Check (subset) - Proportionality: no new persistence/abstractions/taxonomy introduced - Compatibility posture: legacy alias preservation is out of scope - Scope & isolation: route-owned environment remains authoritative; hub narrowing is `environment_id` only; deny-as-not-found semantics preserved