# Research: 106 — Required Permissions Sidebar Context Fix **Date**: 2026-02-22 | **Branch**: `106-required-permissions-sidebar-context` ## RQ-001: How does `EnsureFilamentTenantSelected` currently handle workspace-scoped pages with `{tenant}` params? **Finding**: The middleware at `app/Support/Middleware/EnsureFilamentTenantSelected.php` follows this flow: 1. **Livewire `/livewire/update`** — checks referer for known workspace-scoped patterns (currently only `/admin/operations/{run}`). If matched, calls `configureNavigationForRequest()` without setting tenant and returns early. 2. **`/admin/operations/{run}`** — early return with workspace nav (no tenant set). 3. **`/admin/operations`** — early return with workspace nav (no tenant set). 4. **Route has `{tenant}` param** (line ~74–131) — resolves tenant, validates workspace membership + tenant access, then **always** calls `Filament::setTenant($tenant, true)` and `configureNavigationForRequest()`. This is the root cause: `required-permissions` passes through here and gets tenant sidebar. 5. **Allowlisted workspace-scoped paths** (line ~133–141) — string prefix checks for `/admin/w/`, `/admin/workspaces`, `/admin/operations`, plus an exact-match array. These get workspace nav without tenant. **Root cause**: Step 4 has no exclusion mechanism. Any page with a `{tenant}` route parameter — including `TenantRequiredPermissions` — gets `Filament::setTenant()` called, triggering tenant sidebar. **Decision**: Add a path-based allowlist check **inside** step 4, before `Filament::setTenant()` is called. When the path matches a workspace-scoped page that uses `{tenant}` for data scoping (not sidebar scoping), skip `setTenant()` but still perform all authorization checks. **Alternatives considered**: - *Page-level override (e.g., `$isWorkspaceScoped` property)*: Would require Filament page resolution in middleware (too expensive / wrong layer). - *Separate middleware for Required Permissions route*: Duplicates authorization logic; fragile. - *Clear tenant after middleware runs*: Race condition with `configureNavigationForRequest()` which already ran. ## RQ-002: What pages currently use the allowlist pattern? **Finding**: The middleware already has two precedent patterns for workspace-scoped exemptions: | Pattern | Type | Location in middleware | |---|---|---| | `/admin/operations` | Exact match | Line ~67 | | `/admin/operations/{run}` | Regex | Line ~61 | | `/livewire/update` + referer `/admin/operations/{run}` | Referer check | Line ~49–56 | | `/admin/w/`, `/admin/workspaces`, etc. | Prefix/exact | Line ~133–141 | **Decision**: Follow the same pattern (regex match + referer check for Livewire) for `/admin/tenants/{tenant}/required-permissions`. ## RQ-003: How does `configureNavigationForRequest` determine sidebar? **Finding** (lines 152–247): - If `Filament::getTenant()` is filled → `$panel->navigation(true)` (default tenant nav). - If tenant is null → builds a custom `NavigationBuilder` with workspace-scoped items only (Manage workspaces, Operations, Alert targets/rules/deliveries, Alerts, Audit Log). **Decision**: No changes needed to `configureNavigationForRequest`. The fix is upstream: don't call `setTenant()` for workspace-scoped pages, so the method naturally shows workspace nav. ## RQ-004: What authorization must still run for exempted pages? **Finding**: The `{tenant}` param resolution block (lines 74–131) performs: 1. Null user check → early return 2. `HasTenants` interface check → abort 404 3. Tenant resolution by `external_id` (including soft-deleted) → abort 404 4. Workspace ID null check → abort 404 5. Tenant workspace ownership check → abort 404 6. Workspace existence check → abort 404 7. User workspace membership check → abort 404 8. `canAccessTenant()` → abort 404 **Decision**: All 8 checks must still run. Only `Filament::setTenant()` and `app(WorkspaceContext::class)->rememberLastTenantId()` are skipped for exempted workspace-scoped pages. The tenant is still resolved and authorized — it just isn't set as Filament's active tenant. ## RQ-005: Livewire update handling **Finding**: For Livewire `/livewire/update` requests, the middleware inspects the `Referer` header to determine context. Currently only `/admin/operations/{run}` is checked. **Decision**: Add `/admin/tenants/{tenant}/required-permissions` to the referer pattern check. The regex `#^/admin/tenants/[^/]+/required-permissions$#` will match referers from the Required Permissions page, ensuring Livewire re-renders (filter changes) also get workspace sidebar. ## RQ-006: Impact on `rememberLastTenantId()` **Finding**: When `Filament::setTenant()` is called in step 4, `rememberLastTenantId()` stores the tenant in session. This enables "return to last tenant" convenience. **Decision**: For workspace-scoped exempted pages, `rememberLastTenantId()` should NOT be called. Visiting Required Permissions should not change the user's "last active tenant" context. The tenant is used only for data display on that page. **Rationale**: If we called `rememberLastTenantId()`, navigating to Required Permissions would change the user's default tenant context globally — unexpected side effect for a workspace-level page.