# Data Model: Filament Workspace Tenancy & Environment Routing Cutover **Date**: 2026-05-07 **Branch**: `280-workspace-tenancy-environment-routing` ## Overview This slice introduces no new persistence. It replaces a temporary routing and panel-tenancy shell with a workspace-first runtime contract built on existing `Workspace` and `ManagedEnvironment` truth. The data model for this package is therefore a set of derived route, view-model, and navigation contracts that implementation must keep aligned across pages, middleware, and shared link builders. ## Persisted Truth Unchanged - `Workspace` remains the workspace-owned root context. - `ManagedEnvironment` remains the environment-scoped managed target inside one workspace. - No table ownership, foreign-key ownership, artifact ownership, or role/capability family changes are introduced in this slice. - Provider registration remains in `apps/platform/bootstrap/providers.php`; no new provider or asset persistence is introduced. ## Derived Runtime Contracts ### 1. Workspace Admin Context **Persistence**: derived from route parameters plus `WorkspaceContext` session state **Owner**: workspace-first admin shell | Field | Type | Required | Notes | |---|---|---|---| | `workspace_id` | int | yes | Canonical workspace scope for the current request | | `workspace_slug` | string | yes | Route-safe workspace identifier | | `workspace_name` | string | yes | Operator-visible workspace label | | `entry_mode` | string | yes | `chooser` or `dashboard` | | `remembered_environment_id` | int | no | Derived only when the remembered environment belongs to the same workspace | **Rules**: - `/admin` resolves through this contract to either workspace selection or the canonical workspace dashboard. - Non-member workspace access remains `404`. - Switching workspaces invalidates any remembered environment that belongs to another workspace before any environment page renders. ### 2. Workspace Dashboard View **Persistence**: none, derived from `WorkspaceOverviewBuilder` **Owner**: `WorkspaceOverview` | Field | Type | Required | Notes | |---|---|---|---| | `workspace` | object | yes | Workspace summary for the active workspace | | `overview_payload` | array | yes | Existing builder output for signal cards, summaries, and quick actions | | `environment_chooser_url` | string | yes | Canonical workspace-scoped environment chooser route | | `operations_url` | string | yes | Canonical workspace operations route | **Rules**: - The workspace dashboard is the primary decision surface after workspace selection. - It must not silently behave like a second environment dashboard. - It reuses current builder output rather than introducing a new summary system. ### 3. Environment Chooser View **Persistence**: none, derived from `ManagedTenantsLanding` data plus existing `ChooseTenant` selection logic **Owner**: workspace-scoped environment chooser surface | Field | Type | Required | Notes | |---|---|---|---| | `workspace` | object | yes | Active workspace summary | | `environments` | list | yes | Selectable managed environments within the workspace | | `open_environment_url` | string | yes | Workspace-first environment dashboard URL per row/card | | `switch_workspace_url` | string | no | Secondary escape hatch back to workspace choice | **Environment item fields**: | Field | Type | Required | Notes | |---|---|---|---| | `id` | int | yes | ManagedEnvironment key | | `slug` | string | yes | Route-safe environment identifier | | `name` | string | yes | Operator-visible environment label | | `lifecycle_status` | string | yes | Existing operability/lifecycle status | | `posture_hint` | string | no | Existing discoverability/operability summary only | **Rules**: - Only environments belonging to the active workspace and accessible to the actor appear. - Archived or otherwise non-selectable environments do not appear and do not resolve. - `ChooseTenant` may survive as an implementation seam, but not as a second public chooser contract. ### 4. Managed Environment Page Context **Persistence**: derived from workspace-first route parameters plus `WorkspaceContext` **Owner**: all environment-scoped pages touched by this slice | Field | Type | Required | Notes | |---|---|---|---| | `workspace_id` | int | yes | Outer scope boundary | | `workspace_slug` | string | yes | Outer scope route key | | `managed_environment_id` | int | yes | Nested environment scope | | `managed_environment_slug` | string | yes | Nested environment route key | | `managed_environment_name` | string | yes | Operator-visible environment label | | `breadcrumb_segments` | list | yes | `Workspace -> Managed Environment -> page` | | `page_category` | string | yes | Derived route classification after legacy families are removed | **Rules**: - The `{environment}` parameter must belong to the `{workspace}` parameter or the request is `404`. - Breadcrumb and context-bar ordering must always be `Workspace -> Managed Environment -> page`. - No touched page may keep `/admin/t` or `/admin/tenants/{environment}` as a valid public route. ### 5. Managed Environment Dashboard View **Persistence**: none, derived from `TenantDashboardSummaryBuilder` and current dashboard widgets **Owner**: `TenantDashboard` | Field | Type | Required | Notes | |---|---|---|---| | `workspace` | object | yes | Outer workspace summary for breadcrumb/context | | `managed_environment` | object | yes | Active environment summary | | `dashboard_summary` | array | yes | Existing summary-builder payload | | `primary_follow_up_url` | string | no | Existing recommended-action destination | | `operations_url` | string | yes | Canonical workspace operations route with explicit environment filter | **Rules**: - The surface remains the canonical environment dashboard. - Existing widget and header-action ownership stays intact. - Operations navigation from this surface always enters the workspace operations family with explicit environment context. ### 6. Workspace Operations Scope **Persistence**: derived route/query/session state already modeled by `Monitoring\Operations` **Owner**: canonical workspace operations hub and detail viewer | Field | Type | Required | Notes | |---|---|---|---| | `workspace_id` | int | yes | Required workspace scope | | `managed_environment_id` | int | no | Optional environment prefilter | | `tenant_scope` | string | no | Existing workspace-wide versus narrowed state flag | | `active_tab` | string | no | Existing operations tab state | | `problem_class` | string | no | Existing scoped deep-link filter | | `nav_context` | object | no | Back-link label and URL for environment return flow | **Rules**: - The collection route is `/admin/workspaces/{workspace}/operations`. - The detail route is `/admin/workspaces/{workspace}/operations/{run}`. - Explicit environment filters outside the current workspace or actor entitlement are `404`. - Stale remembered environment filters may be discarded, but explicit hostile filters may not widen scope silently. ### 7. Searchable Destination Contract **Persistence**: none, derived from Filament resource configuration **Owner**: touched globally searchable resources | Field | Type | Required | Notes | |---|---|---|---| | `resource_key` | string | yes | `workspace` or `managed_environment` | | `record_title_attribute` | string | yes | Existing Filament record title attribute | | `destination_kind` | string | yes | `view` or `edit` | | `destination_route` | string | yes | Valid route after the workspace-first cutover | | `global_search_enabled` | bool | yes | Search remains enabled only if destination stays valid | **Rules**: - `WorkspaceResource` remains searchable only if its view/edit destination stays valid. - `TenantResource` remains searchable only if its view/edit destination stays valid after the environment route move. - Touched surfaces that cannot satisfy Filament’s view/edit rule must be disabled from global search in the same slice. ## Route Invariants - Public operator route families after the cutover are rooted at `/admin/workspaces/{workspace}`. - `/admin/t/{environment}` is removed, not redirected. - `/admin/tenants/{environment}/required-permissions` is removed, not redirected. - `/admin/w/{workspace}/managed-tenants` is removed, not redirected. - `/admin/operations` and `/admin/operations/{run}` are removed, not redirected. - Shared builders and helpers must stop emitting `panel: 'tenant'` for touched operator destinations. - `Workspace` is the only Filament tenant for operator routing; `ManagedEnvironment` is nested route context only. ## State Transitions 1. `NoWorkspaceSelected` -> `ChooseWorkspace` 2. `WorkspaceSelected` -> `WorkspaceDashboard` 3. `WorkspaceDashboard` -> `EnvironmentChooser` 4. `EnvironmentChooser` -> `ManagedEnvironmentDashboard` 5. `ManagedEnvironmentDashboard` -> `EnvironmentScopedPage` or `WorkspaceOperationsScope` 6. `WorkspaceSwitch` -> clear cross-workspace environment context before rendering the new workspace dashboard or chooser 7. `LegacyEnvironmentRouteRequested` -> `NotFound` 8. `LegacyWorkspaceChooserRequested` -> `NotFound` 9. `LegacyOperationsRouteRequested` -> `NotFound` ## Deferred Boundaries - No new provider connection/profile entity or abstraction is introduced here. - No artifact ownership retargeting is introduced here. - No RBAC role or capability family change is introduced here. - No UI copy neutralization or localization rewrite is introduced here. - No quality-gate pack or no-legacy automation beyond the local grep/guard proof is introduced here.