## Summary
- implement Spec 147 for workspace-first tenant selector and remembered tenant context enforcement
- harden canonical and tenant-bound route behavior so selected tenant mismatch stays informational
- fix drift finding subject fallback for workspace-safe RBAC identifiers and centralize finding subject resolution
## Testing
- vendor/bin/sail artisan test --compact tests/Feature/Filament/FindingViewRbacEvidenceTest.php tests/Feature/Findings/FindingsListDefaultsTest.php
- vendor/bin/sail bin pint --dirty --format agent
## Notes
- branch pushed at de0679cd8b
- includes the spec artifacts under specs/147-tenant-selector-remembered-context-enforcement/
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #176
16 KiB
Tasks: Tenant Selector and Remembered Context Enforcement
Input: Design documents from /specs/147-tenant-selector-remembered-context-enforcement/
Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
Tests: For runtime behavior changes in this repo, tests are REQUIRED (Pest).
Operations: This feature does not introduce long-running, remote, queued, or scheduled work. No OperationRun creation or lifecycle changes are required.
RBAC: This feature hardens admin-plane context behavior without changing the capability model. Tasks MUST preserve 404 for non-members or non-entitled actors and 403 for in-scope capability denial, and MUST include positive and negative authorization regression coverage for tenant-bound and canonical routes.
UI Naming: Copy must preserve the distinction between workspace, selected tenant, no tenant selected, viewed tenant, and run tenant, and must not imply that selected tenant state determines route legitimacy.
Filament UI Action Surfaces: This feature does not add new actions. Existing header, row, bulk, and empty-state action inventories must remain consistent while selector and shell semantics are hardened.
Filament UI UX-001 (Layout & IA): Existing layouts remain intact. Tasks below only adjust selector semantics, workspace-safe fallback behavior, and informational mismatch messaging within current screens.
Badges: Existing centralized lifecycle presentation from Spec 146 must continue to be used anywhere lifecycle or selector availability is shown.
Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
Phase 1: Setup (Shared Infrastructure)
Purpose: Prepare the focused regression targets and implementation surfaces for context-enforcement work.
- T001 [P] Create the remembered-context unit test target in
tests/Unit/Support/Workspaces/WorkspaceContextRememberedTenantTest.php - T002 [P] Create the choose-tenant and workspace-fallback feature test target in
tests/Feature/Workspaces/ChooseTenantPageTest.php - T003 [P] Review and extend the existing shell and canonical route regression targets in
tests/Feature/OpsUx/OperateHubShellTest.phpandtests/Feature/Operations/TenantlessOperationRunViewerTest.php - T004 [P] Create the workspace-context global-search safety regression target in
tests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Centralize selector eligibility, remembered-context validation, and workspace-safe no-tenant fallback before any story-specific surface work begins.
⚠️ CRITICAL: No user story work can begin until this phase is complete
- T005 Implement validated remembered-tenant lookup, invalidation, and explicit clear helpers in
app/Support/Workspaces/WorkspaceContext.php - T006 [P] Refactor shell active-tenant resolution states for workspace-level, tenant-bound, and canonical routes in
app/Support/OperateHub/OperateHubShell.php - T007 [P] Align active-lane selector eligibility helpers across
app/Services/Tenants/TenantOperabilityService.phpandapp/Models/User.php - T008 Remove hidden tenant-required gating from workspace-safe middleware and routes in
app/Support/Middleware/EnsureFilamentTenantSelected.phpandroutes/web.php - T009 Audit and harden workspace-context global-search scoping so remembered tenant state cannot leak tenant-owned results in
app/Filament/Resources/TenantResource.php,app/Filament/Resources/OperationRunResource.php, andtests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php - T010 Add foundational coverage for shared remembered-context and shell-resolution behavior in
tests/Unit/Support/Workspaces/WorkspaceContextRememberedTenantTest.phpandtests/Feature/OpsUx/OperateHubShellTest.php
Checkpoint: Foundation ready. Selector and shell rules are centralized, and user stories can proceed against one shared context model.
Phase 3: User Story 1 - Trust The Active Tenant Selector (Priority: P1) 🎯 MVP
Goal: Make the standard header selector and choose-tenant page represent only the normal active operating lane.
Independent Test: In one workspace with active, draft, onboarding, and archived tenants, verify that only active tenants appear as selectable choices in both the header selector and choose-tenant page while non-active tenants remain discoverable elsewhere.
Tests for User Story 1 ⚠️
- T011 [P] [US1] Add choose-tenant eligibility and empty-state assertions in
tests/Feature/Workspaces/ChooseTenantPageTest.php - T012 [P] [US1] Add header selector scope and non-active exclusion assertions in
tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php - T013 [P] [US1] Add managed-tenant discoverability assertions for onboarding and archived records in
tests/Feature/Filament/ManagedTenantsLandingLifecycleTest.php
Implementation for User Story 1
- T014 [US1] Refactor active-lane tenant retrieval and selection handling in
app/Filament/Pages/ChooseTenant.php - T015 [US1] Align POST tenant selection enforcement with the shared selector rule in
app/Http/Controllers/SelectTenantController.php - T016 [US1] Update choose-tenant copy and card behavior to reflect the active-lane-only selector meaning in
resources/views/filament/pages/choose-tenant.blade.php - T017 [US1] Update the header context-bar tenant list and selector affordances to use the same active-lane semantics in
resources/views/filament/partials/context-bar.blade.php - T018 [US1] Preserve intentional non-active tenant discoverability in workspace admin surfaces in
app/Filament/Pages/Workspaces/ManagedTenantsLanding.phpandapp/Filament/Resources/TenantResource.php
Checkpoint: User Story 1 is complete when both selector surfaces expose the same active-only operating lane and no contradictory inactive choices remain.
Phase 4: User Story 2 - Recover Safely From Stale Remembered Context (Priority: P2)
Goal: Revalidate remembered tenant context on use and degrade cleanly to a legitimate no-tenant workspace state when it becomes stale or invalid.
Independent Test: Persist remembered context for an active tenant, then invalidate it by lifecycle change, entitlement loss, or workspace switch and verify the shell falls back to no selected tenant while /admin, /admin/operations, and /admin/tenants remain usable.
Tests for User Story 2 ⚠️
- T019 [P] [US2] Add remembered-context invalidation scenarios for workspace mismatch, missing tenant, and selector-ineligible lifecycle in
tests/Unit/Support/Workspaces/WorkspaceContextRememberedTenantTest.php - T020 [P] [US2] Add workspace switch, explicit clear, and no-selected-tenant fallback assertions in
tests/Feature/Workspaces/ChooseTenantPageTest.php,tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php, andtests/Feature/Rbac/TenantResourceAuthorizationTest.php
Implementation for User Story 2
- T021 [US2] Update workspace-switch and explicit-clear flows to preserve workspace-safe no-tenant fallback in
app/Http/Controllers/SwitchWorkspaceController.phpandapp/Http/Controllers/ClearTenantContextController.php - T022 [US2] Remove workspace-page assumptions that an active tenant is required in
app/Filament/Pages/Monitoring/Operations.php,app/Filament/Resources/TenantResource.php, andapp/Support/Middleware/EnsureFilamentTenantSelected.php - T023 [US2] Update shell and chooser copy to present “no tenant selected” as a legitimate workspace state in
resources/views/filament/partials/context-bar.blade.phpandresources/views/filament/pages/choose-tenant.blade.php
Checkpoint: User Story 2 is complete when stale remembered context clears deterministically and workspace-level pages continue working without selected tenant state.
Phase 5: User Story 3 - Keep Route Legitimacy Separate From Header Context (Priority: P3)
Goal: Preserve route-authoritative behavior for tenant-bound pages and canonical run viewers even when selected tenant context differs, is empty, or has just been cleared.
Independent Test: Open authorized tenant-bound and canonical run routes with mismatched, stale, or empty selected tenant state and verify the pages still resolve while non-member or non-entitled access keeps 404 semantics and capability denial keeps 403 semantics.
Tests for User Story 3 ⚠️
- T024 [P] [US3] Add positive and negative tenant-bound route-authority assertions in
tests/Feature/TenantRBAC/ArchivedTenantRouteAccessTest.phpandtests/Feature/TenantRBAC/TenantRouteDenyAsNotFoundTest.php - T025 [P] [US3] Add canonical run mismatch, no-selected-tenant, entitlement, and capability assertions in
tests/Feature/Operations/TenantlessOperationRunViewerTest.php
Implementation for User Story 3
- T026 [US3] Refine route-authoritative tenant resolution for admin-panel pages in
app/Support/OperateHub/OperateHubShell.phpandapp/Filament/Concerns/ResolvesPanelTenantContext.php - T027 [US3] Preserve mismatch-as-information behavior for canonical run viewing in
app/Filament/Pages/Operations/TenantlessOperationRunViewer.phpandresources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.php - T028 [US3] Audit workspace and canonical route integration so selected tenant never acts as a legitimacy shortcut in
routes/web.phpandapp/Policies/OperationRunPolicy.php
Checkpoint: User Story 3 is complete when route identity and policy fully outrank selected tenant state on tenant-bound and canonical pages.
Phase 6: Polish & Cross-Cutting Concerns
Purpose: Final regression validation, formatting, and manual verification across all stories.
- T029 [P] Run focused Pest suites and render/query-safety validation covering
tests/Unit/Support/Workspaces/WorkspaceContextRememberedTenantTest.php,tests/Feature/Workspaces/ChooseTenantPageTest.php,tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php,tests/Feature/Filament/ManagedTenantsLandingLifecycleTest.php,tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php,tests/Feature/Rbac/TenantResourceAuthorizationTest.php,tests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php,tests/Feature/TenantRBAC/ArchivedTenantRouteAccessTest.php,tests/Feature/TenantRBAC/TenantRouteDenyAsNotFoundTest.php,tests/Feature/OpsUx/OperateHubShellTest.php, andtests/Feature/Operations/TenantlessOperationRunViewerTest.php - T030 Run formatting for touched files with
vendor/bin/sail bin pint --dirty --format agent - T031 [P] Validate the manual verification checklist in
specs/147-tenant-selector-remembered-context-enforcement/quickstart.mdand review/admin/tenantsplus/admin/operationsagainstdocs/product/standards/list-surface-review-checklist.md
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies, can start immediately
- Foundational (Phase 2): Depends on Setup completion and blocks all story work
- User Story 1 (Phase 3): Depends on Foundational completion
- User Story 2 (Phase 4): Depends on Foundational completion and benefits from US1 surface alignment
- User Story 3 (Phase 5): Depends on Foundational completion and is safest after US1 and US2 because it hardens route behavior on the same shell/context surfaces
- Polish (Phase 6): Depends on all desired user stories being complete
User Story Dependencies
- User Story 1 (P1): No dependency on other stories after Foundational; this is the MVP slice
- User Story 2 (P2): Depends on the shared context-validation foundation and uses the selector semantics stabilized in US1
- User Story 3 (P3): Depends on the shared context-validation foundation and benefits from the no-tenant and selector semantics stabilized in US1 and US2
Within Each User Story
- Tests for the story should be written first and fail before implementation
- Shared support-layer changes should land before UI surface refinements
- Route and middleware behavior should be stabilized before final mismatch-copy adjustments
- Story-specific validation should pass before moving to the next priority story
Parallel Opportunities
- T001, T002, T003, and T004 can run in parallel because they prepare separate test targets
- T006 and T007 can run in parallel after T005 defines the shared remembered-context contract
- T011, T012, and T013 can run in parallel because they cover different selector and discoverability surfaces
- T019 and T020 can run in parallel because they cover different fallback paths
- T024 and T025 can run in parallel because they cover different route categories
- T029 and T031 can run in parallel after implementation is complete
Parallel Example: User Story 1
# Launch the P1 selector regression tasks together:
Task: "Add choose-tenant eligibility and empty-state assertions in tests/Feature/Workspaces/ChooseTenantPageTest.php"
Task: "Add header selector scope and non-active exclusion assertions in tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php"
Task: "Add managed-tenant discoverability assertions in tests/Feature/Filament/ManagedTenantsLandingLifecycleTest.php"
Parallel Example: User Story 2
# After the foundation is in place, stale-context validation and workspace-switch coverage can proceed together:
Task: "Add remembered-context invalidation scenarios in tests/Unit/Support/Workspaces/WorkspaceContextRememberedTenantTest.php"
Task: "Add workspace switch and no-selected-tenant fallback assertions in tests/Feature/Workspaces/ChooseTenantPageTest.php, tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php, and tests/Feature/Rbac/TenantResourceAuthorizationTest.php"
Parallel Example: User Story 3
# Route-authority coverage can split by tenant-bound and canonical routes:
Task: "Add tenant-bound route-authority assertions in tests/Feature/TenantRBAC/ArchivedTenantRouteAccessTest.php and tests/Feature/TenantRBAC/TenantRouteDenyAsNotFoundTest.php"
Task: "Add canonical run mismatch and authorization assertions in tests/Feature/Operations/TenantlessOperationRunViewerTest.php"
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup
- Complete Phase 2: Foundational
- Complete Phase 3: User Story 1
- Stop and validate the selector semantics independently on the header selector and choose-tenant page
- Demo or merge the MVP slice if acceptable
Incremental Delivery
- Complete Setup + Foundational to establish one shared context model
- Deliver User Story 1 to make the active selector trustworthy
- Deliver User Story 2 to make stale remembered context recover safely
- Deliver User Story 3 to harden tenant-bound and canonical route authority
- Finish with Polish for regression validation and formatting
Team Strategy
- One engineer owns the support-layer foundation in
WorkspaceContext,OperateHubShell, and middleware/routes - A second engineer prepares selector-surface and fallback regression coverage in parallel once the shared context contract is clear
- Route-authority hardening for canonical and tenant-bound pages can proceed as a separate stream after the foundation is merged
Notes
- [P] tasks touch separate files and can be worked in parallel without blocking each other
- Every user story remains independently testable after the foundational phase
- This feature does not add schema changes, Graph calls, or new operations workflows
- Keep route legitimacy tied to route subject and policy, never to raw remembered tenant session state