## 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
147 lines
5.1 KiB
Markdown
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. |