# 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.