# Implementation Plan: Managed Environment Canonical Route Cutover & Legacy Tenant Surface Retirement **Branch**: `297-managed-environment-canonical-route-cutover` | **Date**: 2026-05-12 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/297-managed-environment-canonical-route-cutover/spec.md) **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/297-managed-environment-canonical-route-cutover/spec.md` ## Summary Spec 297 completes the hard product cutover from legacy tenant surfaces to canonical workspace-managed-environment routes. The implementation retires active `/admin/tenants...` product routes, keeps `/admin/t...` dead, removes or permanently neutralizes `TenantPanelProvider`, replaces runtime link generation with one canonical managed-environment link contract, rejects legacy intended URLs, renames the old tenant-panel test helper with no alias, and adds guard tests that prevent backsliding. This plan is preparation only. It does not implement application code. ## Technical Context **Language/Version**: PHP 8.4.15 **Primary Dependencies**: Laravel 12.52.0, Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, Laravel Sail 1.52.0 **Storage**: PostgreSQL through Laravel/Sail for tests; no new storage planned **Testing**: Pest via `./vendor/bin/sail artisan test --compact`; Browser tests only if visible navigation is touched **Validation Lanes**: targeted Feature guards, Workspaces, ProviderConnections, RequiredPermissions, Filament, Spec 288 guard pack, Spec 293 cutover lane, optional Browser smoke **Target Platform**: Laravel Sail local runtime and Gitea-compatible CI runners **Project Type**: Laravel web application under `apps/platform` **Performance Goals**: Route/link guards stay deterministic and focused; no new heavy or browser defaults **Constraints**: no `/admin/t...` restoration, no `/admin/tenants...` compatibility surface, no TenantPanelProvider reactivation, no old helper alias, no DB/model rename, no broad localization or RBAC refactor **Scale/Scope**: Route, link, intended URL, Filament resource registration, and test-helper cutover only ## Initial Repo Baseline Preparation audit on 2026-05-12 found: - Current branch before Spec Kit execution: `platform-dev`; Spec Kit switched to `297-managed-environment-canonical-route-cutover`. - Working tree was clean before creating the spec package. - `TenantPanelProvider` still exists at `apps/platform/app/Providers/Filament/TenantPanelProvider.php`. - `apps/platform/bootstrap/providers.php` is already guarded by existing tests against registering `TenantPanelProvider`. - `route:list --path=admin/tenants` currently shows four active Filament tenant resource routes: index, view, edit, memberships. - `route:list --path=admin/workspaces` currently shows canonical environment routes under `/admin/workspaces/{workspace}/environments...` and workspace operations under `/admin/workspaces/{workspace}/operations...`. - `rg` currently finds many active tests/runtime references to `TenantResource::getUrl(...)`, `TenantDashboard::getUrl(...)`, `TenantRequiredPermissions::getUrl(...)`, `/admin/t/...`, `/admin/tenants...`, and `setTenantPanelContext()`. - The attempted `route:list --columns=...` option is unsupported in this Laravel version; retry without `--columns`. The implementation must refresh `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/297-managed-environment-canonical-route-cutover/legacy-surface-audit.md` before editing runtime code. ## UI / Surface Guardrail Plan - **Guardrail scope**: changed route/link/navigation contract for existing surfaces; no new product workflow. - **Native vs custom classification summary**: Native Filament/resources/pages and shared link helpers. No custom Blade/Tailwind surface is planned. - **Shared-family relevance**: navigation entry points, action links, notifications/toast actions, OperationRun links, provider/permission links, test context helpers. - **State layers in scope**: route registration, URL helper, intended URL/session, Filament panel/resource registration, test panel/workspace/environment context. - **Audience modes in scope**: operator-MSP and support-platform only through existing surfaces. - **Decision/diagnostic/raw hierarchy plan**: existing environment/readiness/operations surfaces keep their hierarchy; the cutover only changes canonical route truth. - **Raw/support gating plan**: unchanged; raw provider detail remains where existing policies allow it. - **One-primary-action / duplicate-truth control**: do not add parallel actions to preserve legacy paths. Replace old destinations with canonical ones. - **Handling modes by drift class or surface**: retire, replace, or document allowed technical reference. Unsafe or ambiguous legacy URL resolution falls back or 404s. - **Repository-signal treatment**: review-mandatory for route-list output, source-scan allowlists, intended URL fallback, helper rename, and any remaining `Tenant` product copy in touched files. - **Special surface test profiles**: `route-contract`, `standard-native-filament`, `global-context-shell`, `browser-smoke` if visible navigation changes. - **Required tests or manual smoke**: targeted Pest guard tests first; Browser only when implementation touches visible navigation flows. - **Exception path and spread control**: Allowed remaining technical `Tenant` references must be listed in `legacy-surface-audit.md` or final summary. - **Active feature PR close-out entry**: Guardrail / Route Cutover / Smoke Coverage if browser proof was run. ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes. - **Systems touched**: Filament panel providers, TenantResource/TenantDashboard/TenantRequiredPermissions routes or links, WorkspaceRedirectResolver/intended URL support, OperationRunLinks, WorkspaceOverviewBuilder, provider/required-permissions link emitters, `tests/Pest.php`, guard tests, browser tests when route navigation is visible. - **Shared abstractions reused**: existing workspace/environment routes, `WorkspaceContext`, `OperationRunLinks`, existing admin panel context helper, existing Spec 288/293 guard style. - **New abstraction introduced? why?**: Only a bounded `ManagedEnvironmentLinks` helper if no existing repo-real helper owns canonical environment URLs. It exists to remove scattered route-name literals and prevent legacy URL generation. - **Why the existing abstraction was sufficient or insufficient**: The canonical routes exist, but runtime link generation remains scattered and some helpers still emit old destinations. - **Bounded deviation / spread control**: Technical `Tenant` model names and Microsoft tenant ID copy remain only where non-product or provider-specific. ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, route/link safety only. - **Central contract reused**: `OperationRunLinks` and `admin.operations.index` / `admin.operations.view` with explicit workspace context. - **Delegated UX behaviors**: preserve existing `View operation` / `Open operation` behavior. - **Surface-owned behavior kept local**: environment/provider surfaces own only initiation inputs and page-local copy. - **Queued DB-notification policy**: N/A. - **Terminal notification path**: unchanged. - **Exception path**: none. ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes. - **Provider-owned seams**: Microsoft Entra tenant ID copy, Graph permission names, provider diagnostics payloads. - **Platform-core seams**: route family, link generation, workspace/environment context, operations links, RBAC and access-scope semantics. - **Neutral platform terms / contracts preserved**: workspace, managed environment, provider connection, target scope, required permissions, diagnostics, access scope, operation. - **Retained provider-specific semantics and why**: Microsoft-specific identity/permission terms remain only when they identify external provider truth. - **Bounded extraction or follow-up path**: No multi-provider framework. Follow-up only for DB/model rename or broader provider-boundary cleanup beyond route cutover. ## Constitution Check *GATE: Must pass before runtime implementation and re-check before close-out.* - Inventory-first: no new inventory or snapshot truth. - Read/write separation: no new write workflow. Existing destructive actions touched by route/resource work keep confirmation, authorization, and audit behavior. - Single Graph contract path: no new Graph calls. - Deterministic capabilities: capability-first RBAC remains authoritative; no role-string checks. - Proportionality / no premature abstraction: use existing helper if possible; any new link helper is bounded to current route generation. - No new persisted truth: no migrations, tables, compatibility shims, or dual-read paths. - Workspace isolation: all environment and operations links carry explicit workspace context or validate current workspace context. - Tenant isolation: tenant-owned records exposed through canonical environment routes still enforce managed-environment entitlement. - RBAC-UX: non-member/out-of-scope remains 404; established member missing capability remains 403; UI hiding is not security. - Provider boundary: tenant-first platform route language is retired; provider-specific tenant terms remain only provider-owned. - Test governance: guard tests are allowed and focused; no full-suite repair or new lane framework. - Filament-native UI: Filament remains v5 on Livewire v4, no v3/v4 API usage, no ad-hoc UI redesign. - Deployment/ops: no asset registration is planned. If assets are unexpectedly registered, deploy notes include `cd apps/platform && php artisan filament:assets`. ## Filament v5 Output Contract - **Livewire compliance**: Filament v5 targets Livewire v4.0+; current app has Livewire 4.1.4. - **Provider registration location**: Laravel 12 provider registration must remain in `apps/platform/bootstrap/providers.php`. `TenantPanelProvider` must not be registered there. - **Globally searchable resources**: If `TenantResource` is retired or moved out of active discovery, global search must be disabled for it or it must no longer register. Any managed-environment resource that remains globally searchable must have Edit or View pages. - **Destructive actions**: This spec does not add destructive actions. Any touched existing destructive action must still execute through `->action(...)`, use `->requiresConfirmation()`, and enforce server-side authorization. - **Asset strategy**: No new Filament assets are planned. If implementation unexpectedly registers assets, deployment must include `cd apps/platform && php artisan filament:assets`. - **Testing plan**: Pages/actions/helpers changed by the cutover are covered with Pest/Filament tests; guard tests cover route resurrection, helper resurrection, intended URL rejection, legacy URL generation, and managed-environment canonical links. ## Test Governance Check - **Test purpose / classification by changed surface**: Feature guard tests for route/link/intended URL contracts; Unit tests for pure helper logic; Feature/Filament tests for pages/resources; Browser only for visible navigation smoke. - **Affected validation lanes**: Feature/Guards, Feature/Workspaces, Feature/ProviderConnections, Feature/RequiredPermissions, Feature/Filament, Spec 288 guard pack, Spec 293 cutover lane, optional Browser lane. - **Why this lane mix is the narrowest sufficient proof**: The risk is route/link resurrection, not complete product behavior. Focused guards plus existing domain test directories prove the changed contracts. - **Narrowest proving commands**: ```bash cd apps/platform ./vendor/bin/sail artisan test --compact tests/Feature/Guards/NoLegacyTenantPanelRuntimeTest.php ./vendor/bin/sail artisan test --compact tests/Feature/Guards/NoActiveTenantResourceRoutesTest.php ./vendor/bin/sail artisan test --compact tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php ./vendor/bin/sail artisan test --compact tests/Feature/Workspaces/WorkspaceIntendedUrlLegacyRejectionTest.php ``` - **Fixture / helper / factory / seed / context cost risks**: The replacement for `setTenantPanelContext()` must not make provider setup, browser fixtures, or broad workspace setup implicit. - **Expensive defaults or shared helper growth introduced?**: none planned. - **Heavy-family additions, promotions, or visibility changes**: none planned. - **Surface-class relief / special coverage rule**: Standard-native Filament coverage unless route/navigation changes are visible in browser flows. - **Closing validation and reviewer handoff**: run focused guards, affected domain directories, Spec 288 pack, Spec 293 pack, and Pint dirty. - **Budget / baseline / trend follow-up**: document any material guard runtime increase in the implementation close-out. - **Review-stop questions**: Does `/admin/tenants...` still return a product page? Does a helper still emit legacy URLs? Does intended URL handling preserve legacy paths? Did a test helper alias keep the old name? Did RBAC weaken? - **Escalation path**: document-in-feature for allowed technical references; follow-up-spec for structural rename/localization issues. - **Active feature PR close-out entry**: Guardrail / Route Cutover. - **Why no dedicated follow-up spec is needed**: The route cutover is bounded. DB/model rename and broader copy/localization are explicit non-goals and can become follow-ups only if product needs them. ## Project Structure ### Documentation (this feature) ```text specs/297-managed-environment-canonical-route-cutover/ ├── spec.md ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── legacy-surface-audit.md ├── tasks.md ├── contracts/ │ └── managed-environment-canonical-route-contract.md └── checklists/ └── requirements.md ``` ### Source Code (repository root) Expected touched surfaces during implementation: ```text apps/platform/app/ ├── Providers/Filament/ ├── Filament/ │ ├── Pages/ │ └── Resources/ ├── Support/ │ ├── Workspaces/ │ ├── OperationRunLinks.php │ └── ManagedEnvironmentLinks.php (only if needed) └── Http/ apps/platform/bootstrap/providers.php apps/platform/routes/web.php apps/platform/tests/ ├── Pest.php ├── Feature/ ├── Unit/ └── Browser/ ``` **Structure Decision**: Use existing Laravel/Filament app structure and existing route/helper/test conventions. Do not create a new base application folder or dependency. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |---|---|---| | Cross-cutting route/link/test-helper cleanup | Legacy route truth exists in multiple owners and cannot be retired safely in one local page | Local cleanup would leave intended URLs, tests, or link builders able to resurrect old paths | | Bounded canonical link helper if needed | Runtime link generation must have one owner to make guard tests meaningful | Scattered route-name literals would recreate the drift this spec removes | | New guard tests | Regression risk is route/link resurrection after a cutover | Manual review and ad hoc source scans are not durable enough | ## Phase 0: Safety Gate 1. Run: ```bash git status --short --branch git diff --stat git log -1 --oneline ``` 2. Confirm the implementation branch is `297-managed-environment-canonical-route-cutover` or a session branch created from it. 3. Stop if unrelated uncommitted changes exist. 4. Read: ```text .specify/memory/constitution.md specs/287-cutover-prerequisite-completion/ specs/288-quality-gates-no-legacy-enforcement/ specs/293-post-cutover-suite-stabilization/ specs/296-full-suite-green-signal-restoration/ ``` ## Phase 1: Baseline Audit Refresh `legacy-surface-audit.md` before code edits: ```bash git status --short --branch git diff --stat cd apps/platform ./vendor/bin/sail artisan route:list | rg "admin/t|admin/tenants|provider-connections|required-permissions|workspaces/.*/environments|operations" rg "TenantPanelProvider|panel:\s*'tenant'|panel:\s*\"tenant\"|/admin/t/|/admin/tenants|TenantResource::getUrl|TenantDashboard::getUrl|TenantRequiredPermissions::getUrl|setTenantPanelContext|admin\.operations" . --glob '!vendor' --glob '!node_modules' ``` Classify each finding as runtime, test, copy, historical, provider-specific, or allowed technical reference. ## Phase 2: Remove Dormant TenantPanelProvider - Delete `apps/platform/app/Providers/Filament/TenantPanelProvider.php` if no true runtime dependency exists. - Ensure `apps/platform/bootstrap/providers.php` does not reference it. - Replace tests that inspect the file with provider-registration and route-list guards. - Add/extend `NoLegacyTenantPanelRuntimeTest`. ## Phase 3: Establish Canonical Managed Environment Link Contract - Locate repo-real managed-environment route helpers first. - Create or extend `ManagedEnvironmentLinks` only if needed. - Cover index/detail/required-permissions/diagnostics/access-scopes/operations. - Replace direct legacy link generation in runtime surfaces. - Add contract tests that assert no generated URL contains `/admin/tenants` or `/admin/t/`. ## Phase 4: Retire `/admin/tenants...` - Remove active TenantResource route registration or move it out of active discovery. - If a temporary redirect is unavoidable, require unique workspace/environment resolution and document the exception. Default is 404. - Update global search for any retired resource. - Add/extend `NoActiveTenantResourceRoutesTest`. ## Phase 5: Intended URL Legacy Rejection - Update `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, or repo-real intended URL owners. - Reject `/admin/t...` and `/admin/tenants...` as final destinations. - Normalize legacy `/admin/operations` to workspace operations when workspace is known. - Fall back to workspace home or environment index when unsafe. - Keep external URLs blocked. ## Phase 6: Required Permissions And Provider Connections - Replace old required-permissions and provider-connection tenant URLs. - Ensure tenantless provider-connection resource remains canonical. - Ensure required-permissions uses the workspace/environment route. - Add/extend legacy route tests proving old URLs do not return 200. ## Phase 7: Test Helper Rename - Rename `setTenantPanelContext()` to the chosen canonical helper, for example `setAdminEnvironmentContext()`. - Update every test usage. - Do not keep an alias under the old name. - Add guard coverage that fails on old helper resurrection. ## Phase 8: Copy Cleanup In Touched Active Surfaces - Replace tenant-first product copy only in files touched by this cutover. - Keep Microsoft/provider-specific tenant ID copy where correct. - List remaining old references in `legacy-surface-audit.md`. ## Phase 9: Regression Proof Pack Run focused proof: ```bash cd apps/platform ./vendor/bin/sail artisan test --compact \ tests/Feature/Guards/NoLegacyTenantPanelRuntimeTest.php \ tests/Feature/Guards/NoActiveTenantResourceRoutesTest.php \ tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php \ tests/Feature/Workspaces/WorkspaceIntendedUrlLegacyRejectionTest.php \ tests/Feature/ProviderConnections/LegacyRedirectTest.php \ tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php \ tests/Feature/Spec080WorkspaceManagedTenantAdminMigrationTest.php \ tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php \ tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php ``` Run the Spec 288 guard pack and Spec 293 cutover lane listed in the spec. Run browser smoke only if visible navigation flows were touched. ## Phase 10: Broad Validation Run at least: ```bash cd apps/platform ./vendor/bin/sail artisan test --compact tests/Feature/Guards ./vendor/bin/sail artisan test --compact tests/Feature/Workspaces ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections ./vendor/bin/sail artisan test --compact tests/Feature/RequiredPermissions ./vendor/bin/sail artisan test --compact tests/Feature/Filament ./vendor/bin/sail bin pint --dirty --format agent git diff --check ``` Raw full suite is optional unless requested; if run, record the exact result.