# Implementation Plan: RBAC Troubleshooting & Tenant UI Bugfix Pack v1 **Branch**: `067-rbac-troubleshooting` | **Date**: 2026-01-31 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from [specs/067-rbac-troubleshooting/spec.md](spec.md) **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Tighten tenant-plane (`/admin`) RBAC UX and eliminate sharp edges by: - Applying the existing `UiEnforcement` pattern consistently on Tenant admin surfaces (especially Tenant view header actions). - Ensuring archived (soft-deleted) tenants can still resolve in tenant-scoped routes for entitled members (404 only for non-members). - Adding a tenant-scoped diagnostics surface to detect and repair membership invariants (missing owner) and prevent GUID-vs-bigint mistakes. - Adding targeted Pest tests to lock in 404 vs 403 semantics and action disable/tooltip UX, and re-running existing last-owner invariant tests as regression coverage. ## Technical Context **Language/Version**: PHP 8.4 (per repo guidelines) **Primary Dependencies**: Laravel 12, Filament v5, Livewire v4 **Storage**: PostgreSQL (via Laravel Sail) **Testing**: Pest v4 (PHPUnit v12 runner) **Target Platform**: Laravel Sail (Docker) on macOS/Linux **Project Type**: Web application (Laravel monolith) **Performance Goals**: N/A (admin UX + correctness) **Constraints**: Tenant-plane only (`/admin`); no `/system` expansion; no new outbound integration calls for diagnostics; must preserve RBAC-UX semantics (404 vs 403) **Scale/Scope**: Touches Tenant admin UI, tenancy binding for archived tenants, membership invariants + tests ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first: clarify what is “last observed” vs snapshots/backups - Read/write separation: any writes require preview + confirmation + audit + tests - Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php` - Deterministic capabilities: capability derivation is testable (snapshot/golden tests) - RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; non-member tenant access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks) - RBAC-UX: destructive-like actions require `->requiresConfirmation()` and clear warning text - RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics) - Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked - Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun` - Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter - Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens - Badge semantics (BADGE-001): status-like badges use `BadgeCatalog` / `BadgeRenderer`; no ad-hoc mappings; new values include tests **Gate evaluation**: PASS (no constitution violations intended). - No new Microsoft Graph call paths are introduced. - Mutations involved (archive/restore, membership repairs) keep explicit confirmation and server-side enforcement. - Auditing: membership repairs use existing audit logger patterns; tenant lifecycle actions already log. **Post-design re-check**: PASS (design artifacts: [research.md](research.md), [data-model.md](data-model.md), [contracts/](contracts/)). ## Project Structure ### Documentation (this feature) ```text specs/[###-feature]/ ├── plan.md # This file (/speckit.plan command output) ├── research.md # Phase 0 output (/speckit.plan command) ├── data-model.md # Phase 1 output (/speckit.plan command) ├── quickstart.md # Phase 1 output (/speckit.plan command) ├── contracts/ # Phase 1 output (/speckit.plan command) └── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ ├── Resources/ │ ├── Pages/ │ └── Widgets/ ├── Models/ ├── Policies/ ├── Services/ └── Support/ database/ ├── migrations/ └── factories/ resources/ ├── views/ └── css/ routes/ └── web.php tests/ ├── Feature/ └── Unit/ ``` **Structure Decision**: Laravel monolith. Changes will land in `app/Filament/**` (tenant UI), `app/Models/**` (tenant binding/invariants), `app/Services/**` (membership repairs), and `tests/**` (Pest feature tests). ## Complexity Tracking > **Fill ONLY if Constitution Check has violations that must be justified** N/A. ## Phase 0 — Research (repo-backed) ### Key findings (evidence) - Tenancy config: [app/Providers/Filament/AdminPanelProvider.php](../../app/Providers/Filament/AdminPanelProvider.php#L32-L45) uses `->tenant(Tenant::class, slugAttribute: 'external_id')` and `->tenantRoutePrefix('t')`. - Tenant archiving is soft delete + status update: [app/Models/Tenant.php](../../app/Models/Tenant.php#L63-L92) sets `status='archived'` during soft delete and restores back to `active`. - Tenant view header actions currently bypass UI enforcement: - `EditAction::make()` is unconditional (can lead to 403-on-click UX): [app/Filament/Resources/TenantResource/Pages/ViewTenant.php](../../app/Filament/Resources/TenantResource/Pages/ViewTenant.php#L19-L24) - `Deactivate` currently executes without capability checks (security issue): [app/Filament/Resources/TenantResource/Pages/ViewTenant.php](../../app/Filament/Resources/TenantResource/Pages/ViewTenant.php#L51-L74) - Tenant list Restore action lacks icon: [app/Filament/Resources/TenantResource.php](../../app/Filament/Resources/TenantResource.php#L406-L454) - Membership invariant “last owner” is already enforced server-side: [app/Services/Auth/TenantMembershipManager.php](../../app/Services/Auth/TenantMembershipManager.php#L253-L287) - Membership uniqueness is already enforced at DB level: [database/migrations/2026_01_25_022729_create_tenant_memberships_table.php](../../database/migrations/2026_01_25_022729_create_tenant_memberships_table.php#L12-L26) ### Decisions - Archived tenant 404s for members: implement tenant route binding that includes soft-deleted tenants, then rely on membership middleware to enforce deny-as-not-found for non-members. - Replace ad-hoc tooltips on tenant actions with centralized `UiTooltips` constants (add a tenant-archived tooltip string). - Standardize tenant page header actions using `UiEnforcement` so member-without-capability sees disabled actions with tooltip (no normal-click 403). **Output**: [research.md](research.md) ## Phase 1 — Design (data model + contracts) ### Design outline - Tenant status UX - Archived tenants remain viewable for entitled members. - Actions become status-aware (e.g., Deactivate disabled when already archived). - Diagnostics - Tenant-scoped, DB-only page that reports: - Missing owner (0 owners) - Identifier misuse risk (guardrails against using external GUID where an internal FK is expected) - Repair actions are capability-gated and audit-logged. ### Artifacts - [data-model.md](data-model.md) - [contracts/](contracts/) - [quickstart.md](quickstart.md) ## Phase 2 — Planning (for tasks.md) Dependency-ordered implementation outline (will be expanded in `tasks.md`): 1. Fix tenant view header actions (Edit/Deactivate) to use UI enforcement + tooltips. 2. Add Restore icon consistency on tenant list row menu. 3. Ensure archived tenant is resolvable in tenant-scoped routes for members (avoid false 404). 4. Add tenant status banner on view page. 5. Add diagnostics page + safe repair actions for “missing owner”. 6. Add regression tests for: - Readonly UX (disabled + tooltip) - Readonly cannot deactivate - Archived tenant view access (member OK, non-member 404) - GUID vs bigint guard test 7. Keep the no-ad-hoc authorization guard green.