# Data Model: Global Context Shell Contract ## Persistence Impact - No new database tables, columns, or persisted shell artifacts are introduced. - Existing session-backed fields remain the only durable support state used by the contract: - `current_workspace_id` - `workspace_intended_url` - `workspace_last_tenant_ids` - Existing user-level fields such as `users.last_workspace_id` and `users.last_tenant_id` or `user_tenant_preferences.last_used_at` remain support inputs only and do not become active shell truth. ## Context Source Inventory | Source | Context facet | Source role | Owning seam | Validation / notes | |---|---|---|---|---| | Explicit workspace switch request | Workspace | leading | `SwitchWorkspaceController` + `WorkspaceContext` | Must point to an accessible, selectable workspace before it can replace current workspace truth. | | Current session workspace | Workspace | leading | `WorkspaceContext` | Remains the default current-workspace truth when no stronger explicit request exists and membership is still valid. | | Remembered last workspace | Workspace | supporting | `WorkspaceContext` | Restore-only candidate used when no valid current session workspace exists and the entry flow allows restore. | | Route tenant parameter | Tenant | leading | Route + `OperateHubShell` | Strongest tenant source on tenant-bound routes. Must belong to the resolved workspace and remain entitled. | | Explicit tenant selection request | Tenant | leading | `SelectTenantController` + `OperateHubShell` | Can activate tenant scope only inside an already resolved workspace. | | Filament panel tenant state | Tenant | supporting | `ResolvesPanelTenantContext` | May support resolution only after workspace compatibility and entitlement checks succeed. | | Remembered tenant for resolved workspace | Tenant | supporting | `WorkspaceContext` | Restore-only candidate on tenantless-capable workspace pages. Never valid on its own for tenant-bound routes. | | Query hint | Workspace or Tenant | supporting only when contract explicitly allows it | `OperateHubShell` | Must never become effective truth unless the contract explicitly names the query-backed flow. | | View-local shell inference | Workspace or Tenant | never-leading | Shared shell partials and page-local views | Rendering surfaces may display resolved truth only. They cannot evaluate precedence or recovery. | ## Runtime Entities | Entity | Kind | Fields | Validation / Notes | |---|---|---|---| | RequestedWorkspaceContext | Derived runtime input | `workspaceIdentifier`, `source`, `intendedUrl`, `pageCategory` | Represents a workspace request from route, explicit switch flow, or initial restore path before validation. | | RequestedTenantContext | Derived runtime input | `tenantIdentifier`, `source`, `pageCategory`, `requiresExplicitTenant` | Represents route tenant, explicit tenant select, query hint, Filament tenant, or remembered tenant before validation. | | RememberedContextCandidate | Derived support state | `workspaceId`, `tenantId`, `source`, `eligible` | Represents stored last-used tenant for the active workspace or last-used workspace during initial resolution. Never leading by itself. | | ResolvedShellContext | Canonical request-scoped truth | `workspace`, `tenant`, `pageCategory`, `workspaceSource`, `tenantSource`, `state`, `recoveryDirective`, `displayMode` | The only context object shell UI and server-side consumers should trust for the current request. | | RecoveryDirective | Derived outcome | `action`, `destination`, `reason`, `preserveIntendedUrl` | Encodes whether the request renders tenantless state, redirects, or aborts. | | InvalidContext | Derived runtime outcome | `kind`, `source`, `reason`, `requestedWorkspaceIdentifier`, `requestedTenantIdentifier` | Captures why a requested or remembered context could not become active truth. | ## Supporting Enums / Value Domains ### ContextSource - `route` - `explicit_switch` - `explicit_select` - `session_workspace` - `filament_tenant` - `remembered` - `query_hint` - `none` ### ShellState - `tenant_scoped` - `tenantless_workspace` - `missing_workspace` - `invalid_workspace` - `missing_tenant` - `invalid_tenant` - `inaccessible_tenant` - `incompatible_tenant` ### RecoveryAction - `none` - `render_tenantless_workspace` - `redirect_choose_workspace` - `redirect_operations_index` - `redirect_evidence_overview` - `redirect_workspace_home` - `redirect_workspace_managed_tenants` - `redirect_workspace_record_fallback` - `abort_not_found` ### PageCategory - `workspace_scoped` - `workspace_chooser_exception` - `tenant_bound` - `tenant_scoped_evidence` - `canonical_workspace_record_viewer` ## Entity Details ### RequestedWorkspaceContext | Field | Type | Required | Notes | |---|---|---|---| | `workspaceIdentifier` | string or int | yes | May come from explicit workspace switch flow or a safe intended URL restore path. | | `source` | `ContextSource` | yes | `explicit_switch`, `session_workspace`, or `remembered` are the main inputs today. | | `intendedUrl` | string or null | no | Safe `/admin...` path captured via `WorkspaceIntendedUrl`. | | `pageCategory` | `PageCategory` | yes | Needed to determine if a tenantless fallback is valid after workspace resolution. | **Validation rules**: - Workspace must exist. - Workspace must not be archived or otherwise unselectable. - User must be a member of the workspace. - Cross-plane routes remain out of scope; only `web`-guarded admin and tenant routes participate. ### RequestedTenantContext | Field | Type | Required | Notes | |---|---|---|---| | `tenantIdentifier` | string or int | yes | May come from route param, explicit tenant selection, query hint, Filament panel state, or remembered session state. | | `source` | `ContextSource` | yes | Route and explicit selection are strongest; remembered is weakest. | | `pageCategory` | `PageCategory` | yes | Determines whether tenant fallback is valid, optional, or forbidden. | | `requiresExplicitTenant` | bool | yes | `true` for tenant-bound pages; `false` for workspace-scoped pages that can remain tenantless. | **Validation rules**: - Tenant must exist. - Tenant must belong to the resolved workspace. - User must be entitled to the tenant. - Tenant must satisfy the relevant operability question for the current lane. - Tenant must be compatible with the current route type. ### RememberedContextCandidate | Field | Type | Required | Notes | |---|---|---|---| | `workspaceId` | int | yes | Key for the remembered tenant map. | | `tenantId` | int or null | no | Candidate tenant for restore. | | `source` | `ContextSource` | yes | Today this is `remembered`, with supporting user-level last-used values. | | `eligible` | bool | yes | `false` once access, operability, or workspace match fails. | | `invalidReason` | string or null | no | Captures why the remembered candidate became ineligible during validation or cleanup. | **Validation rules**: - Candidate tenant must still exist. - Candidate tenant must still belong to the active workspace. - Candidate tenant must still be accessible to the user. - Candidate tenant must still pass `RememberedContextValidity` for the current lane. - Ineligible remembered context is cleared immediately and cannot survive as visible shell truth. ### ResolvedShellContext | Field | Type | Required | Notes | |---|---|---|---| | `workspace` | Workspace or null | yes | Null only when recovery requires chooser or not-found handling. | | `tenant` | Tenant or null | yes | Null is valid only in tenantless workspace state or before a hard recovery redirect. | | `pageCategory` | `PageCategory` | yes | Controls whether tenantless state is valid. | | `workspaceSource` | `ContextSource` | yes | Records which source actually won for workspace resolution. | | `tenantSource` | `ContextSource` | yes | Records which source actually won for tenant resolution, or `none`. | | `state` | `ShellState` | yes | The user-visible shell state. | | `recoveryDirective` | `RecoveryDirective` | yes | Defines what to render or where to redirect if the request cannot continue as requested. | | `displayMode` | string | yes | `tenant_scoped`, `tenantless`, or `recovery`. | **Invariants**: - A resolved tenant cannot exist without a resolved workspace. - Remembered context cannot become active if a stronger valid source exists. - The shell display must derive only from `ResolvedShellContext`. - Tenant-bound pages cannot render a remembered-tenant fallback as though it were an explicit route tenant. ### InvalidContext | Field | Type | Required | Notes | |---|---|---|---| | `kind` | string | yes | `workspace` or `tenant` | | `source` | `ContextSource` | yes | Identifies whether route, panel, remembered, or query input failed. | | `reason` | string | yes | `missing`, `inaccessible`, `incompatible`, `not_operable`, `not_member`, `archived`, or `mismatched_workspace` | | `requestedWorkspaceIdentifier` | string or int or null | no | Included for diagnostics and testing only. | | `requestedTenantIdentifier` | string or int or null | no | Included for diagnostics and testing only. | ## Relationships - `ResolvedShellContext` is composed from zero or one `RequestedWorkspaceContext`, zero or one `RequestedTenantContext`, zero or one `RememberedContextCandidate`, and zero or one `InvalidContext` plus `RecoveryDirective`. - `RecoveryDirective` is downstream of `ResolvedShellContext.state` and `PageCategory`. - `RememberedContextCandidate.workspaceId` is always keyed to the resolved workspace candidate; it is not global across workspaces. ## Resolution Rules ### Workspace Resolution 1. Try a valid explicit workspace request when the current entry flow provides one. 2. Otherwise use the current session workspace if it remains valid. 3. Otherwise allow a valid last-used workspace restore only during initial resolution. 4. Otherwise emit `missing_workspace` or `invalid_workspace` with a chooser-oriented recovery directive. ### Tenant Resolution 1. On tenant-bound pages, validate route tenant first and fail if it is missing, inaccessible, or incompatible. 2. On workspace-scoped pages, accept a valid route tenant or explicit tenant-selection request first. 3. Next accept a validated query-backed tenant hint only on routes where the contract explicitly allows query-backed shell resolution. 4. Next accept validated `Filament::getTenant()` only if it matches the resolved workspace and remains entitled. 5. Next accept remembered tenant only when the page category permits tenantless fallback and no stronger valid tenant source exists. 6. Otherwise resolve to tenantless workspace state or to an explicit recovery directive depending on page category. ## Recovery Matrix | Page category | Invalid workspace | Invalid explicit tenant | Missing tenant after clear | Invalid remembered tenant | |---|---|---|---|---| | `workspace_scoped` | redirect to chooser, or to `admin.operations.index` when cleanup is referrer-free or sentinel-driven | render tenantless workspace or clear request | render tenantless workspace on the current route, or use `admin.operations.index` when no safe prior route exists | clear remembered tenant and remain tenantless | | `workspace_chooser_exception` | remain on `/admin/choose-workspace` | not applicable | remain on `/admin/choose-workspace` until the user selects a workspace | not applicable | | `tenant_bound` | 404 or redirect to chooser if no workspace can be re-established | 404 when route tenant is invalid or inaccessible | redirect to `admin.workspace.managed-tenants.index` for the current workspace, else `admin.home` | ignored as active truth; route still governs | | `tenant_scoped_evidence` | redirect to chooser if workspace truth cannot be re-established | redirect to `admin.evidence.overview` when tenant detail context is no longer valid | redirect to `admin.evidence.overview` | clear remembered tenant and return to `admin.evidence.overview` | | `canonical_workspace_record_viewer` | 404 if the record itself is no longer entitled | 404 if tenant-scoped record access fails | remain on `admin.operations.view` when it is still workspace-safe, otherwise use the documented workspace fallback | clear remembered tenant and keep record-only rules | ## Documented Recovery Destinations | RecoveryAction | Route target | When it applies | |---|---|---| | `redirect_choose_workspace` | `/admin/choose-workspace` | Missing or unrecoverable workspace truth at shell entry or restore time | | `redirect_operations_index` | `admin.operations.index` | External or missing referrer, clear-flow sentinel path, and generic workspace-safe fallback for tenantless monitoring entry | | `redirect_evidence_overview` | `admin.evidence.overview` | Tenant-scoped evidence paths that must return to a workspace-safe evidence landing | | `redirect_workspace_managed_tenants` | `admin.workspace.managed-tenants.index` | Tenant-bound cleanup or workspace switch flows that must return to tenant selection inside the resolved workspace | | `redirect_workspace_home` | `admin.home` | Tenant-bound cleanup when no current workspace truth remains available | | `redirect_workspace_record_fallback` | `admin.operations.view` in the current-release scope | Canonical workspace record viewers that stay in workspace scope without reviving tenant truth | ## State Transitions | Trigger | From | To | Notes | |---|---|---|---| | Workspace switch | `tenant_scoped` | `tenant_scoped` or `tenantless_workspace` | Existing tenant survives only after compatibility re-check in target workspace. | | Tenant select | `tenantless_workspace` | `tenant_scoped` | Explicit user action; requires entitlement and operability validation. | | Tenant clear on workspace page | `tenant_scoped` | `tenantless_workspace` | Valid only on workspace-scoped pages. | | Tenant clear on tenant-bound page | `tenant_scoped` | `tenantless_workspace` plus redirect | Redirect destination depends on page category and route family. | | Remembered tenant invalidation | `tenant_scoped` or restore candidate | `tenantless_workspace` | Candidate is cleared and cannot stay visible. | | Workspace invalidation | any | `missing_workspace` or `invalid_workspace` | Recovery goes to chooser or not-found depending on entry path. | ## Display Semantics | Resolved state | Workspace label | Tenant label | Action affordances | |---|---|---|---| | `tenant_scoped` | Active workspace name | Active tenant name | Switch workspace, Select tenant, Clear tenant context | | `tenantless_workspace` | Active workspace name | `No tenant selected` | Switch workspace, Select tenant | | `missing_workspace` / `invalid_workspace` | `Choose workspace` or recovery label | hidden or disabled | Choose workspace, optional safe return | | `invalid_tenant` / `inaccessible_tenant` / `incompatible_tenant` | Active workspace name if valid | no stale tenant name shown | Recovery action only; no stale tenant truth |