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
2.3 KiB
2.3 KiB
Data Model — RBAC Troubleshooting & Tenant UI Bugfix Pack v1
Spec: spec.md
No new persistent tables are required for v1. Diagnostics findings are computed at runtime from existing tables.
Entities
Tenant (tenants)
Purpose: Tenant-plane scope boundary, lifecycle state, and tenant-scoped configuration root.
Key fields (existing):
id(bigint, PK)tenant_id(string GUID, external Microsoft Entra tenant identifier)external_id(string, used as Filament tenancy slug; often mirrorstenant_id)status(string, e.g.active,archived)deleted_at(nullable timestamp, soft delete)name,environment,is_current,metadata(assorted)
Lifecycle rules (existing):
- Soft delete sets
status='archived'. - Restore sets
status='active'.
TenantMembership (tenant_memberships)
Purpose: Tenant membership boundary + role assignment.
Key fields (existing):
id(uuid, PK)tenant_id(bigint, FK →tenants.id)user_id(bigint, FK →users.id)role(enum:owner,manager,operator,readonly)source/source_ref(provenance)created_by_user_id(nullable FK)
Constraints (existing):
- Unique
(tenant_id, user_id) - Index
(tenant_id, role)
DiagnosticsFinding (computed, not persisted)
Purpose: Represent a detected integrity/operational issue for the current tenant.
Proposed shape (runtime DTO / array):
id(string, stable key likemissing_owner)severity(string, e.g.warning/critical)title(string)description(string)repair_actions(array of available actions given actor capabilities)
Derived rules / invariants
- Missing owner:
tenant_membershipshas zero rows withrole='owner'for the tenant. - Duplicate membership: more than one membership row exists for a given
(tenant_id, user_id)(should be prevented by DB uniqueness; diagnostics treats this as “historical/edge-case”). - Identifier misuse risk: any query for membership/tenant scoping must use internal tenant key (
tenants.id) and not the GUID.
State transitions
- Tenant status:
active→archivedon soft deletearchived→activeon restore
- Membership role transitions:
- Allowed transitions are existing (
owner,manager,operator,readonly) but last-owner demotion/removal is prohibited.
- Allowed transitions are existing (