# Implementation Plan: Tenant Selector and Remembered Context Enforcement **Branch**: `147-tenant-selector-remembered-context-enforcement` | **Date**: 2026-03-16 | **Spec**: [specs/147-tenant-selector-remembered-context-enforcement/spec.md](./spec.md) **Input**: Feature specification from `/specs/147-tenant-selector-remembered-context-enforcement/spec.md` **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Harden tenant context handling around one workspace-scoped truth model: the standard selector represents only the normal active operating lane, remembered tenant context is a revalidated convenience preference, and route legitimacy remains driven by record identity plus policy rather than header state. Implement the feature by consolidating active-lane eligibility around `TenantOperabilityService`, consolidating remembered-context validation and invalidation inside `WorkspaceContext`, and narrowing shell resolution in `OperateHubShell`, the header context bar, choose-tenant flow, and workspace-level pages so stale or non-active tenants cannot leak into active selection or break canonical and tenant-bound pages. ## Technical Context **Language/Version**: PHP 8.4.15 **Primary Dependencies**: Laravel 12, Filament 5, Livewire 4, Tailwind CSS 4 **Storage**: PostgreSQL plus existing session-backed workspace and remembered-tenant context; no schema change planned **Testing**: Pest 4 feature and unit tests, including Filament and Livewire coverage **Target Platform**: Laravel Sail web application on the Filament admin panel and workspace-canonical admin routes **Project Type**: Web application monolith **Performance Goals**: Keep tenant-context resolution render-safe and DB/session-only, add no external calls or background work, and verify that the shell, choose-tenant page, tenant detail, and operations viewer do not introduce material query-count regressions in the focused regression suite **Constraints**: Preserve Spec 143 workspace-first semantics, Spec 144 canonical viewer safety, and Spec 146 lifecycle presentation; keep Livewire v4 and Filament v5 patterns intact; keep provider registration unchanged in `bootstrap/providers.php`; no new panel or tenancy model; no raw session-truth assumptions; no authorization broadening **Scale/Scope**: Workspace shell context, choose-tenant flow, remembered tenant invalidation, tenant-bound route safety, and canonical operations viewer safety across existing `/admin` routes and the shared support layer ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first: PASS. This feature only changes selector and shell context semantics over existing workspace, tenant, and operation records. - Read/write separation: PASS. No new business write workflows, preview flows, or destructive mutations are introduced; existing context POST endpoints remain lightweight preference updates only. - Graph contract path: PASS. No Microsoft Graph calls are added or changed. - Deterministic capabilities: PASS. Capability resolution remains central; no new capability derivation paths are introduced. - RBAC-UX: PASS WITH ENFORCEMENT FOCUS. Admin-plane separation, deny-as-not-found semantics for non-members/non-entitled actors, and 403 for in-scope capability denials remain unchanged. The implementation must remove any hidden reliance on selected tenant equality as an authorization shortcut. - Workspace isolation: PASS. Workspace remains primary context, and workspace switching must continue to clear or re-evaluate tenant preference per workspace. - RBAC-UX destructive confirmation: PASS. No new destructive-like actions are introduced. - RBAC-UX global search: PASS WITH REQUIRED AUDIT. The implementation must audit workspace-context global search so remembered tenant state cannot leak tenant-owned results or hints when no active tenant is selected. - Tenant isolation: PASS. Tenant selection and remembered tenant resolution remain workspace-scoped and entitlement-checked. - Run observability: PASS. No `OperationRun` creation or mutation changes are planned. - Ops-UX 3-surface feedback: PASS. Not applicable because no operational workflow or run feedback changes are introduced. - Ops-UX lifecycle/service ownership: PASS. No `OperationRun.status` or `outcome` transitions are touched. - Ops-UX summary counts/guards/system runs: PASS. Not applicable. - Automation/data minimization: PASS. No new queued work or logging paths. - Badge semantics (BADGE-001): PASS. Lifecycle and mismatch surfaces continue consuming the existing centralized lifecycle presentation introduced by Spec 146. - UI naming (UI-NAMING-001): PASS. Copy must keep `workspace`, `selected tenant`, `no tenant selected`, `viewed tenant`, and `run tenant` semantically distinct. - Filament UI Action Surface Contract: PASS. Existing action surfaces remain, but the header context bar, choose-tenant page, and canonical run viewer messaging must keep their current action inventories consistent and must not reintroduce contradictory selector affordances. - Filament UI UX-001 (Layout & IA): PASS. Layout changes are not required. The plan only adjusts context semantics, fallback behavior, and informational messaging inside existing surfaces. - UI-STD-001 list surface checklist: PASS WITH REQUIRED REVIEW. Because `/admin/tenants` and `/admin/operations` are modified list surfaces, implementation and validation must reference `docs/product/standards/list-surface-review-checklist.md`. **Post-Design Re-check**: PASS. Phase 1 design keeps the feature read-only from an operational perspective, centralizes selector and remembered-context rules in existing support services, preserves Filament v5 + Livewire v4 architecture, and leaves panel registration in `bootstrap/providers.php` unchanged. ## Project Structure ### Documentation (this feature) ```text specs/147-tenant-selector-remembered-context-enforcement/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── tenant-context-enforcement.openapi.yaml └── tasks.md ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ ├── Concerns/ │ └── Pages/ │ ├── Monitoring/ │ ├── Operations/ │ └── Workspaces/ ├── Http/ │ ├── Controllers/ │ └── Middleware/ ├── Models/ │ ├── Tenant.php │ ├── User.php │ └── Workspace.php ├── Policies/ ├── Services/ │ ├── Auth/ │ └── Tenants/ └── Support/ ├── OperateHub/ ├── Tenants/ └── Workspaces/ resources/ └── views/ └── filament/ ├── pages/ └── partials/ routes/ └── web.php tests/ ├── Feature/ │ ├── Filament/ │ ├── Onboarding/ │ ├── Operations/ │ └── Rbac/ └── Unit/ ``` **Structure Decision**: Use the existing Laravel monolith and extend the current support-layer seams instead of creating a new selector subsystem. `app/Services/Tenants/TenantOperabilityService.php` remains the source for active-lane eligibility, `app/Support/Workspaces/WorkspaceContext.php` becomes the authoritative remembered-context validation and invalidation layer, `app/Support/OperateHub/OperateHubShell.php` becomes the route-safe shell resolver, and the affected UI surfaces stay in their current Filament pages and Blade partials. ## Phase 0 Research Summary - Confirmed the app already has the core building blocks for this feature: `TenantOperabilityService::canSelectAsContext()` defines active-lane eligibility, `WorkspaceContext` already stores remembered tenant IDs keyed by workspace, and `OperateHubShell::activeEntitledTenant()` already resolves route tenant, Filament tenant, and remembered tenant in one place. - Confirmed the current contradiction sources are architectural rather than missing primitives: the header context bar builds its own tenant list, `ChooseTenant` and `SelectTenantController` duplicate selection checks, and shell resolution still mixes route-authoritative and remembered-context behavior in ways that can leave stale or conflicting state alive longer than intended. - Confirmed route safety already exists in targeted places: `OperationRunPolicy` uses workspace membership, tenant entitlement, and capability checks without relying on selected tenant equality; `TenantlessOperationRunViewer` already has mismatch-aware banner logic. Spec 147 therefore needs to make the shell stop undermining these route-safe pages. - Confirmed `ChooseTenant` already filters to selectable tenants and its empty state already frames “No active tenants available”, which aligns with Spec 147 and suggests the plan should converge the header selector onto the same meaning rather than invent a new chooser role. - Confirmed Filament v5 tenancy docs support tenant menu customization, but this app already uses a custom workspace-first context bar and custom choose-tenant page. The correct direction is to keep the custom shell and centralize its rules, not to revert to the default Filament tenant switcher. ## Phase 1 Design ### Implementation Approach 1. Treat active selector eligibility as one shared contract. - Keep `TenantOperabilityService` as the single source of truth for whether a tenant belongs in the standard active selector. - Remove or collapse local header/query logic that can drift from `ChooseTenant` and `SelectTenantController` semantics. 2. Treat remembered tenant context as one shared workspace-scoped validation flow. - Strengthen `WorkspaceContext` so remembered tenant reads always validate workspace match, existence, entitlement-sensitive access, and active-lane eligibility before returning a tenant. - Expose explicit fallback semantics so stale remembered state deterministically clears to “no selected tenant”. 3. Split shell convenience from route legitimacy. - Narrow `OperateHubShell` to prefer route-authoritative tenants on tenant-bound pages, prefer canonical record legitimacy on workspace-level record viewers, and use remembered tenant only for active-lane convenience where appropriate. - Ensure mismatch stays informational on canonical and tenant-bound pages instead of influencing whether the page resolves. 4. Align all selector surfaces to the same lane meaning. - Refactor the header context bar and choose-tenant page to consume the same eligibility semantics and similar empty/no-context framing. - Ensure workspace switch and tenant clear flows both result in consistent context state and recovery behavior. 5. Preserve non-active tenant discoverability outside the selector. - Keep managed-tenant and onboarding/admin discovery surfaces intentionally usable for onboarding and archived tenants even while those tenants are excluded from the active selector. - Ensure workspace-level managed-tenant administration remains usable with no selected tenant. 6. Audit workspace-context search and add regression coverage around the architectural promises. - Verify global search does not use remembered tenant context to expose tenant-owned results when no active tenant is selected. - Cover active selector membership, stale remembered-context invalidation, no-selected-tenant workspace behavior, tenant-bound route mismatch, canonical run mismatch, and workspace switch isolation. 7. Preserve list-surface and render safety while hardening context semantics. - Review `/admin/tenants` and `/admin/operations` against `docs/product/standards/list-surface-review-checklist.md`. - Add focused validation that context resolution remains render-safe and does not introduce material query-count regressions in the main shell and viewer flows. ### Planned Workstreams - **Workstream A: Shared context resolution** Introduce or refine shared methods in `WorkspaceContext` and `OperateHubShell` for validated remembered tenant lookup, explicit context clearing, and workspace-safe fallback. - **Workstream B: Selector surface convergence** Update the header context bar, choose-tenant page, and `SelectTenantController` so all active-lane selections use the same eligibility rule and no non-active tenant can appear in one selector path but fail downstream. - **Workstream C: Route-safe shell behavior** Audit the tenant-bound and canonical workspace routes in scope, especially `/admin/tenants/{tenant}` and `/admin/operations/{run}`, so selected-tenant mismatch remains display-only and never becomes a legitimacy gate. - **Workstream D: Discoverability and search safety** Preserve managed-tenant and onboarding/admin discovery surfaces for non-active tenants and audit workspace-context global search so remembered tenant state cannot leak tenant-owned results. - **Workstream E: Regression hardening** Add focused Pest tests at the unit and feature level around remembered-context invalidation, selector membership, managed-tenant no-context behavior, workspace switching, global-search safety, and mismatched tenant behavior on tenant-bound and canonical pages. - **Workstream F: List-surface and render-safety validation** Review the affected list surfaces against `docs/product/standards/list-surface-review-checklist.md` and include focused render/query-safety checks for the shell, chooser, tenant detail, and canonical viewer flows. ## Testing Strategy - Add unit coverage for `WorkspaceContext` remembered-tenant revalidation and invalidation rules, including workspace mismatch, missing tenant, selector-ineligible lifecycle, and explicit clear behavior. - Add unit or feature coverage for `OperateHubShell` active tenant resolution rules across workspace-level pages, tenant-bound pages, and canonical record viewers. - Add focused feature tests for header and choose-tenant membership semantics so only active tenants are selectable in the normal active-lane flow. - Add focused feature tests for managed-tenant discovery surfaces so onboarding and archived tenants remain intentionally discoverable outside the selector and `/admin/tenants` remains usable with no selected tenant. - Add focused feature tests for workspace switch and clear-tenant flows so stale remembered tenant context does not bleed across workspaces and no-selected-tenant remains legitimate. - Update or add canonical viewer tests confirming `/admin/operations/{run}` remains valid under mismatched, cleared, or stale tenant context. - Update or add tenant-bound page tests confirming `/admin/tenants/{tenant}` remains route-authoritative when selected tenant differs or is absent. - Add a focused global-search audit or regression test confirming workspace-context search does not surface tenant-owned results because of remembered tenant state. - Validate the affected `/admin/tenants` and `/admin/operations` list surfaces against `docs/product/standards/list-surface-review-checklist.md`. - Add focused render/query-safety assertions or instrumentation in the targeted regression suite so the plan's performance goal is explicitly verified rather than implicit. - Run the minimum focused Pest suite through Sail, then ask whether to run the full suite. ## Complexity Tracking No constitution violations or exceptional complexity are planned at this stage.