TenantAtlas/specs/067-rbac-troubleshooting/data-model.md
ahmido 3490fb9e2c feat: RBAC troubleshooting & tenant UI bugfix pack (spec 067) (#84)
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
2026-01-31 20:09:25 +00:00

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 mirrors tenant_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 like missing_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_memberships has zero rows with role='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:
    • activearchived on soft delete
    • archivedactive on restore
  • Membership role transitions:
    • Allowed transitions are existing (owner, manager, operator, readonly) but last-owner demotion/removal is prohibited.