Summary
Implements Spec 067 “RBAC Troubleshooting & Tenant UI Bugfix Pack v1” for the tenant admin plane (/admin) with strict RBAC UX semantics:
Non-member tenant scope ⇒ 404 (deny-as-not-found)
Member lacking capability ⇒ 403 server-side, while the UI stays visible-but-disabled with standardized tooltips
What changed
Tenant view header actions now use centralized UI enforcement (no “normal click → error page” for readonly members).
Archived tenants remain resolvable in tenant-scoped routes for entitled members; an “Archived” banner is shown.
Adds tenant-scoped diagnostics page (/admin/t/{tenant}/diagnostics) with safe repair actions (confirmation + authorization + audit log).
Adds/updates targeted Pest tests to lock the 404 vs 403 semantics and action UX.
Implementation notes
Livewire v4.0+ compliance: Uses Filament v5 + Livewire v4 conventions; widget Blade views render a single root element.
Provider registration: Laravel 11+ providers stay in providers.php (no changes required).
Global search: No global search behavior/resources changed in this PR.
Destructive actions:
Tenant archive/restore/force delete and diagnostics repairs execute via ->action(...) and include ->requiresConfirmation().
Server-side authorization is enforced (non-members 404, insufficient capability 403).
Assets: No new assets. No change to php artisan filament:assets expectations.
Tests
Ran:
vendor/bin/sail bin pint --dirty
vendor/bin/sail artisan test --compact (focused files for Spec 067)
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #84
8.3 KiB
Implementation Plan: RBAC Troubleshooting & Tenant UI Bugfix Pack v1
Branch: 067-rbac-troubleshooting | Date: 2026-01-31 | Spec: spec.md
Input: Feature specification from specs/067-rbac-troubleshooting/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
UiEnforcementpattern 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/*withoutOperationRun - 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, data-model.md, contracts/).
Project Structure
Documentation (this feature)
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)
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 uses
->tenant(Tenant::class, slugAttribute: 'external_id')and->tenantRoutePrefix('t'). - Tenant archiving is soft delete + status update: app/Models/Tenant.php sets
status='archived'during soft delete and restores back toactive. - 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.phpDeactivatecurrently executes without capability checks (security issue): app/Filament/Resources/TenantResource/Pages/ViewTenant.php
- Tenant list Restore action lacks icon: app/Filament/Resources/TenantResource.php
- Membership invariant “last owner” is already enforced server-side: app/Services/Auth/TenantMembershipManager.php
- Membership uniqueness is already enforced at DB level: database/migrations/2026_01_25_022729_create_tenant_memberships_table.php
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
UiTooltipsconstants (add a tenant-archived tooltip string). - Standardize tenant page header actions using
UiEnforcementso member-without-capability sees disabled actions with tooltip (no normal-click 403).
Output: 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.
- Tenant-scoped, DB-only page that reports:
Artifacts
Phase 2 — Planning (for tasks.md)
Dependency-ordered implementation outline (will be expanded in tasks.md):
- Fix tenant view header actions (Edit/Deactivate) to use UI enforcement + tooltips.
- Add Restore icon consistency on tenant list row menu.
- Ensure archived tenant is resolvable in tenant-scoped routes for members (avoid false 404).
- Add tenant status banner on view page.
- Add diagnostics page + safe repair actions for “missing owner”.
- 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
- Keep the no-ad-hoc authorization guard green.