Ziel: Spec/Plan/Tasks für “RBAC UI Enforcement Helper v2” (suite-wide, mixed visibility, record-scoped tenant) bereitstellen, damit die anschließende Implementierung sauber reviewbar ist.
Enthält
Feature-Spec inkl. RBAC-UX Contract (Non-member 404/hidden, member-no-cap disabled + Tooltip, member-with-cap enabled).
Implementation Plan + Research/Decisions.
Contracts:
UiEnforcement v2 (mixed visibility composition, tenant resolvers, bulk preflight).
Guardrails (CI-failing allowlist guard gegen ad-hoc Filament auth patterns).
Data-model/Quickstart/Tasks inkl. “Definition of Done”.
Review-Fokus
Scope: Tenant plane only (/admin/t/{tenant}), Platform plane out of scope.
Bulk semantics: authorization-only all-or-nothing; eligibility separat mit Feedback.
preserveVisibility() nur tenant-scoped, verboten für record-scoped/cross-tenant.
Standard tooltip copy: “Insufficient permission — ask a tenant Owner.”
Keine Code-Änderungen
PR ist spec-only (keine Runtime-Änderungen).
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #82
84 lines
2.7 KiB
Markdown
84 lines
2.7 KiB
Markdown
# Contract: UiEnforcement v2 (RBAC UI Enforcement Helper)
|
|
|
|
**Branch**: `066-rbac-ui-enforcement-helper-v2`
|
|
**Date**: 2026-01-30
|
|
**Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas-066-rbac-ui-enforcement-helper-v2/specs/066-rbac-ui-enforcement-helper/spec.md`
|
|
|
|
## Scope
|
|
|
|
- Tenant plane only (`/admin/t/{tenant}`).
|
|
- Platform plane (`/system`) is out of scope for v2.
|
|
|
|
## RBAC-UX Contract (summary)
|
|
|
|
1) **Non-member**
|
|
- Actions are hidden and cannot execute.
|
|
- Direct access/execution attempts must deny-as-not-found (404) where reachable.
|
|
|
|
2) **Member without capability**
|
|
- Action is visible but disabled.
|
|
- Disabled tooltip is standardized via `UiTooltips` (default copy is defined in the feature spec).
|
|
- Disabled action cannot execute (Filament no-op is acceptable).
|
|
|
|
3) **Member with capability**
|
|
- Action is enabled and executes.
|
|
- Destructive/high-impact actions require confirmation (`->requiresConfirmation()`).
|
|
|
|
## Mixed visibility composition (business + RBAC)
|
|
|
|
### `preserveVisibility()`
|
|
|
|
**Contract**
|
|
- The helper MUST NOT write `->visible(...)` or `->hidden(...)`.
|
|
- The helper MAY still set disabled state + tooltip + confirmation + server-side guard.
|
|
|
|
**Restriction**
|
|
- Allowed only on tenant-scoped surfaces where routing already denies non-members (404).
|
|
- Forbidden for record-scoped / cross-tenant lists (must not leak discoverability).
|
|
|
|
### `andVisibleWhen(callable $businessVisible)`
|
|
|
|
**Contract**
|
|
- The helper combines business visibility AND RBAC visibility.
|
|
- If business visibility is false, the action is not visible (even for authorized members).
|
|
|
|
### `andHiddenWhen(callable $businessHidden)`
|
|
|
|
**Contract**
|
|
- The helper combines business-hidden OR RBAC-hidden semantics.
|
|
|
|
## Tenant context providers
|
|
|
|
### `tenantFromFilament()` (default)
|
|
|
|
- Uses Filament tenant context (`Filament::getTenant()` or equivalent).
|
|
- Intended for tenant-scoped pages/actions.
|
|
|
|
### `tenantFromRecord()`
|
|
|
|
- Treats `$record` as the tenant.
|
|
- Intended for record-scoped surfaces like `TenantResource` row actions.
|
|
|
|
### `tenantFrom(callable $resolver)`
|
|
|
|
- Maps a record (or selection) to a tenant instance.
|
|
- Intended for “record → tenant” mapping surfaces.
|
|
|
|
## Bulk preflight authorization
|
|
|
|
### Default behavior (v2)
|
|
|
|
- All-or-nothing **authorization**: if any selected record is unauthorized, the bulk action is disabled and cannot execute.
|
|
- Business eligibility is handled separately by the action implementation (e.g., skip inactive/archived records with clear feedback).
|
|
|
|
### Preflight hooks
|
|
|
|
- Custom: `preflightSelection(callable $preflight)`
|
|
- Built-ins:
|
|
- `preflightByTenantMembership()`
|
|
- `preflightByCapability()`
|
|
|
|
**Performance**
|
|
- Preflight should use set-based DB queries where feasible; avoid N+1 membership checks.
|
|
|