TenantAtlas/specs/297-managed-environment-canonical-route-cutover/plan.md
ahmido 3ec582a182 feat: retire legacy tenant route surfaces (#352)
## Summary
- retire legacy `/admin/t` and active `/admin/tenants` product surfaces in favor of canonical workspace-scoped managed-environment routes
- centralize runtime URL generation through `ManagedEnvironmentLinks` and update intended URL handling to reject legacy tenant paths
- remove dormant tenant panel runtime, rename test helpers to the admin environment context, and add guard coverage for route/helper regressions

## Validation
- targeted Feature guard, workspace, provider connection, required permissions, and Filament test lanes run under Sail
- browser smoke coverage run for provider connection and workspace RBAC environment access flows
- formatting and diff checks completed with Pint and `git diff --check`

## Notes
- Filament remains on v5 with Livewire v4
- provider registration stays in `apps/platform/bootstrap/providers.php`
- retired tenant resource global search is disabled and destructive action confirmation rules remain unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #352
2026-05-12 23:35:03 +00:00

20 KiB

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
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:
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)

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:

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:
git status --short --branch
git diff --stat
git log -1 --oneline
  1. Confirm the implementation branch is 297-managed-environment-canonical-route-cutover or a session branch created from it.
  2. Stop if unrelated uncommitted changes exist.
  3. Read:
.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:

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.
  • 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:

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:

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.