TenantAtlas/specs/135-canonical-tenant-context-resolution/data-model.md
ahmido cc93329672 feat: canonical tenant context resolution (#164)
## Summary
- introduce a canonical admin tenant filter-state helper and route all in-scope workspace-admin tenant resolution through `OperateHubShell::activeEntitledTenant()`
- align operations monitoring, operation-run deep links, Entra group admin list/view/search behavior, and shared context-bar rendering with the documented scope contract
- add the Spec 135 design artifacts, architecture note, focused guardrail coverage, and non-regression tests for filter persistence, direct-record access, and global search safety

## Validation
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact tests/Feature/Monitoring/OperationsKpiHeaderTenantContextTest.php tests/Feature/Monitoring/OperationsTenantScopeTest.php tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php tests/Feature/Spec085/OperationsIndexHeaderTest.php tests/Feature/Spec085/RunDetailBackAffordanceTest.php tests/Feature/Filament/OperationRunListFiltersTest.php tests/Feature/Filament/EntraGroupAdminScopeTest.php tests/Feature/Filament/EntraGroupGlobalSearchScopeTest.php tests/Feature/DirectoryGroups/BrowseGroupsTest.php tests/Feature/Filament/EntraGroupEnterpriseDetailPageTest.php tests/Feature/Filament/PolicyVersionResolvedReferenceLinksTest.php tests/Feature/Filament/EntraGroupResolvedReferencePresentationTest.php tests/Feature/Guards/AdminTenantResolverGuardTest.php tests/Feature/OpsUx/OperateHubShellTest.php tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php tests/Feature/Alerts/AlertDeliveryDeepLinkFiltersTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TableStatePersistenceTest.php tests/Feature/Filament/TenantScopingTest.php tests/Feature/Filament/Alerts/AlertDeliveryViewerTest.php tests/Unit/Support/References/CapabilityAwareReferenceResolverTest.php`

## Notes
- Filament v5 remains on Livewire v4.0+ compliant surfaces only.
- No provider registration changes were needed; Laravel 12 provider registration remains in `bootstrap/providers.php`.
- Entra group global search remains enabled and is now scoped to the canonical admin tenant contract.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #164
2026-03-11 21:24:28 +00:00

147 lines
5.1 KiB
Markdown

# Data Model: Spec 135 Canonical Tenant Context Resolution
## Overview
This feature introduces no new database tables or persisted domain objects. Its data model is request-time and behavioral: it formalizes how existing workspace, tenant, filter, and record-access state combine into one canonical tenant-context outcome.
## Entity: Tenant Panel Context
**Purpose**: Represents the panel-native tenant context for tenant-panel flows.
**Source fields**:
- `panel_id`
- `tenant_route_parameter`
- `filament_tenant`
- `tenant_membership_status`
**Relationships**:
- belongs to one current panel
- resolves to one entitled tenant or null
**Validation rules**:
- Only valid on tenant-panel-native surfaces.
- Must not be substituted with admin remembered-tenant fallback semantics.
- Non-members must receive deny-as-not-found behavior.
## Entity: Admin Operational Tenant Context
**Purpose**: Represents the canonical active tenant for workspace-admin flows.
**Source fields**:
- `workspace_id`
- `filament_tenant_id` nullable
- `remembered_tenant_id` nullable
- `resolved_tenant_id` nullable
- `resolution_source` enum: `filament`, `remembered`, `none`
- `is_entitled` boolean
**Relationships**:
- belongs to one current workspace
- may resolve to one tenant in that workspace
- is used by headers, widgets, filters, queries, record links, and search
**Validation rules**:
- `resolved_tenant_id` must be null when no entitled tenant exists.
- If both `filament_tenant_id` and `remembered_tenant_id` exist and disagree, `filament_tenant_id` wins.
- Any resolved tenant must belong to the active workspace and pass tenant entitlement.
## Entity: Tenant Context Conflict
**Purpose**: Captures the request state where more than one tenant source exists for the same admin request.
**Fields**:
- `filament_tenant_id`
- `remembered_tenant_id`
- `workspace_id`
- `winning_source`
- `losing_source`
**Validation rules**:
- Conflict only exists when both source values are present and different.
- Conflict resolution must be deterministic for the entire request.
- Losing source must not leak into filters, widgets, counts, or navigation labels.
## Entity: Tenant-Sensitive Filter State
**Purpose**: Represents persisted or defaulted filter state whose valid values depend on the canonical tenant context.
**Fields**:
- `filter_name`
- `raw_value`
- `canonical_tenant_id` nullable
- `workspace_id`
- `is_valid_for_context` boolean
- `resolution_action` enum: `apply`, `reset`, `ignore`, `replace`
**Relationships**:
- belongs to one request flow
- may constrain one resource list or widget drill-down
**Validation rules**:
- Filter values must be revalidated whenever canonical tenant context changes.
- Filter option lists must never be broader than the underlying list/query scope.
- Invalid persisted tenant-sensitive values must not silently remain active after a tenant switch.
## Entity: Scoped Record Access Path
**Purpose**: Represents any route or UI affordance that can reveal a tenant-sensitive record.
**Fields**:
- `surface_type` enum: `list`, `detail`, `direct_url`, `deep_link`, `global_search`
- `resource_name`
- `workspace_id`
- `canonical_tenant_id` nullable
- `record_tenant_id` nullable
- `authorization_outcome` enum: `ok`, `not_found`, `forbidden`
**Relationships**:
- points to one resource class and optional record
- uses one context resolver appropriate to its panel
**Validation rules**:
- Detail/direct/search access must never be broader than the corresponding list scope.
- Out-of-scope or missing-context access must produce deterministic bounded behavior.
- Membership failures remain `not_found`; capability failures after membership is established remain `forbidden`.
## Entity: Admin Surface Guardrail Exception
**Purpose**: Documents files that are allowed to use panel-native tenant reads without violating the admin guardrail.
**Fields**:
- `file_path`
- `reason`
- `panel_semantics` enum: `tenant_native`, `approved_panel_native_surface`
- `review_owner`
**Validation rules**:
- Every exception must be explicit and stable.
- Admin-only files are not valid exceptions.
- Guardrail output must clearly distinguish violations from approved exceptions.
## State Transitions
### Admin request context state
1. `no_context`
- No valid Filament tenant and no valid remembered tenant.
- Outcome: safe empty/not-found bounded behavior.
2. `remembered_only`
- No valid Filament tenant, valid remembered tenant.
- Outcome: remembered tenant becomes canonical admin tenant.
3. `filament_only`
- Valid Filament tenant, no remembered tenant.
- Outcome: Filament tenant becomes canonical admin tenant.
4. `conflict`
- Valid Filament tenant and valid remembered tenant differ.
- Outcome: Filament tenant wins for the full request.
## Invariants
- Tenant-panel-native files use panel-native tenant semantics.
- Workspace-admin files use the canonical admin resolver when resolving an operational tenant.
- Visible tenant context and effective query scope must always match within the same request.
- Persisted tenant-sensitive filter state is never trusted without revalidation.
- Direct record URLs and search results cannot bypass the same tenant boundary as the list surface.