TenantAtlas/specs/136-admin-canonical-tenant/data-model.md
ahmido 45a804970e feat: complete admin canonical tenant rollout (#165)
## Summary
- complete Spec 136 canonical admin tenant rollout across admin-visible and shared Filament surfaces
- add the shared panel-aware tenant resolver helper, persisted filter-state synchronization, and admin navigation segregation for tenant-sensitive resources
- expand regression, guard, and parity coverage for admin-path tenant resolution, stale filters, workspace-wide tenant-default surfaces, and panel split behavior

## Validation
- `vendor/bin/sail artisan test --compact tests/Feature/Guards/AdminTenantResolverGuardTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TableStatePersistenceTest.php`
- `vendor/bin/sail artisan test --compact --filter='CanonicalAdminTenantFilterState|PolicyResource|BackupSchedule|BackupSet|FindingResource|BaselineCompareLanding|RestoreRunResource|InventoryItemResource|PolicyVersionResource|ProviderConnectionResource|TenantDiagnostics|InventoryCoverage|InventoryKpiHeader|AuditLog|EntraGroup'`
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4.0+ compliance is preserved with Filament v5.
- Provider registration remains unchanged in `bootstrap/providers.php`.
- `PolicyResource` and `PolicyVersionResource` have admin global search disabled explicitly; `EntraGroupResource` keeps admin-aware scoped search with a View page.
- Destructive and governance-sensitive actions retain existing confirmation and authorization behavior while using canonical tenant parity.
- No new assets were introduced, so deployment asset strategy is unchanged and does not add new `filament:assets` work.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #165
2026-03-13 08:09:20 +00:00

186 lines
7.1 KiB
Markdown

# Data Model: Spec 136 Admin Panel Canonical Tenant Resolution Full Rollout
## Overview
This feature introduces no new database tables or persisted business objects. Its data model is request-time and behavioral: it formalizes how surface classification, panel mode, canonical admin tenant context, session-backed filter state, and sensitive actions combine into one deterministic tenant outcome.
## Entity: Surface Inventory Entry
**Purpose**: Represents one admin-visible or admin-reachable surface in the rollout manifest.
**Fields**:
- `surface_name`
- `class_name`
- `surface_kind` enum: `resource`, `page`, `widget`, `shared_helper`, `guarded_file`
- `classification` enum: `type_a`, `type_b`, `type_c`
- `visibility_mode` enum: `admin_visible`, `shared_code_path`, `tenant_panel_only_but_reviewed`
- `has_persisted_tenant_filters` boolean
- `has_global_search` boolean
- `has_sensitive_actions` boolean
- `guard_required` boolean
**Relationships**:
- may reference one resource, page, or widget class
- may depend on one or more support-layer resolver or filter-state helpers
**Validation rules**:
- Every rollout surface must have exactly one classification.
- Type A and Type B surfaces must be guard-covered unless explicitly documented as an exception.
- Type C surfaces must not acquire hidden tenant enforcement in read or write paths.
## Entity: Canonical Admin Tenant Context
**Purpose**: Represents the single tenant context used by workspace-admin tenant-sensitive flows.
**Source fields**:
- `workspace_id`
- `panel_id`
- `route_tenant_id` nullable
- `filament_tenant_id` nullable
- `remembered_tenant_id` nullable
- `resolved_tenant_id` nullable
- `resolution_source` enum: `route`, `filament`, `remembered`, `none`
- `is_entitled` boolean
**Relationships**:
- belongs to one current workspace
- may resolve to one entitled tenant
- drives header context, queries, filter defaults, widgets, links, and sensitive actions for Type A and Type B admin surfaces
**Validation rules**:
- `resolved_tenant_id` must be null when no entitled tenant exists.
- The same resolved tenant must drive every tenant-sensitive element of the same admin surface.
- When the panel is admin, raw panel-native tenant reads are not valid substitutes for this entity.
## Entity: Panel Resolver Contract
**Purpose**: Encodes which resolver is allowed in a given panel mode.
**Fields**:
- `panel_id`
- `allowed_source` enum: `operate_hub_shell`, `filament_tenant`, `none`
- `fallback_behavior` enum: `remembered_allowed`, `no_fallback`, `safe_none`
- `applies_to` enum: `admin`, `tenant`, `shared_surface`
**Validation rules**:
- Admin panel uses `operate_hub_shell`.
- Tenant panel uses `filament_tenant`.
- Shared surfaces must branch by current panel rather than blending both rules.
## Entity: Persisted Tenant Filter Session State
**Purpose**: Represents session-backed filter state whose validity depends on the current canonical admin tenant.
**Fields**:
- `filters_session_key`
- `tenant_sensitive_filters` array
- `previous_resolved_tenant_id` nullable
- `current_resolved_tenant_id` nullable
- `resolution_action` enum: `apply`, `reseed`, `clear`
- `has_invalid_values` boolean
**Relationships**:
- belongs to one table or page surface
- depends on one `Canonical Admin Tenant Context`
**Validation rules**:
- Tenant-sensitive filter values must be cleared or reseeded when resolved tenant changes.
- If `current_resolved_tenant_id` is null, tenant-specific filter state must not remain active.
- Workspace-wide Type B surfaces may preserve non-tenant filters while clearing or reseeding tenant-specific ones.
## Entity: Sensitive Action Scope Contract
**Purpose**: Represents the tenant parity requirement between visible surface context and a sensitive action target.
**Fields**:
- `surface_name`
- `action_name`
- `visible_tenant_id` nullable
- `query_tenant_id` nullable
- `action_tenant_id` nullable
- `authorization_outcome` enum: `ok`, `not_found`, `forbidden`
- `confirmation_required` boolean
**Relationships**:
- belongs to one rollout surface
- depends on one resolved tenant context and one authorization decision
**Validation rules**:
- `action_tenant_id` must equal `visible_tenant_id` for Type A surfaces.
- Out-of-scope or missing tenant access must remain `not_found` when membership is not established.
- Existing destructive-like actions must still require confirmation and server-side authorization.
## Entity: Search and Record Resolution Contract
**Purpose**: Represents list, detail, deep-link, and global-search parity for tenant-sensitive resources.
**Fields**:
- `surface_name`
- `access_path` enum: `list`, `detail`, `direct_url`, `deep_link`, `global_search`
- `panel_mode` enum: `admin`, `tenant`
- `resolved_tenant_id` nullable
- `record_tenant_id` nullable
- `parity_outcome` enum: `aligned`, `disabled`, `not_found`, `forbidden`
**Validation rules**:
- Detail and direct access must never be broader than list scope.
- Global search must either match list/detail parity or be disabled.
- Admin no-context behavior must be explicit and deterministic per surface class.
## Entity: Guard Coverage Entry
**Purpose**: Documents whether a file is enforced by the admin tenant resolver guard or allowed as an exception.
**Fields**:
- `relative_path`
- `surface_name`
- `guard_status` enum: `guarded`, `exception`
- `exception_reason` nullable
- `review_owner`
**Validation rules**:
- Exceptions must be explicit and stable.
- Admin-only files are not valid exceptions for raw `Filament::getTenant()` or `Tenant::current()` reads.
- Guard output must clearly distinguish true violations from approved tenant-panel-native usage.
## State Transitions
### Canonical admin tenant state
1. `none`
- No entitled route, Filament, or remembered tenant is available.
- Outcome: safe no-tenant-selected behavior by surface type.
2. `remembered`
- Only remembered tenant is valid and entitled.
- Outcome: remembered tenant becomes the canonical admin tenant for the full request.
3. `filament`
- A Filament tenant is valid and entitled.
- Outcome: Filament tenant becomes the canonical admin tenant for the full request.
4. `route`
- A route tenant parameter is present and entitled for a tenant-panel or shared route.
- Outcome: route tenant governs the request where panel-native semantics apply.
### Persisted filter synchronization state
1. `unchanged`
- Previous and current resolved tenant IDs match.
- Outcome: persisted tenant filter values may remain.
2. `tenant_switched`
- Previous and current resolved tenant IDs differ.
- Outcome: tenant-sensitive filter values are cleared or reseeded.
3. `tenant_removed`
- Current resolved tenant ID becomes null.
- Outcome: tenant-sensitive filter values are cleared.
## Invariants
- One workspace-admin surface uses one tenant source for every tenant-sensitive element.
- Shared resources must branch by panel mode instead of mixing admin and tenant rules inside one execution path.
- Persisted tenant filter state is never trusted without synchronization.
- Search, list, detail, and deep-link behavior cannot exceed the same tenant boundary.
- Existing destructive or governance-sensitive actions keep their confirmations and authorization while gaining tenant-target parity.