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

