TenantAtlas/specs/135-canonical-tenant-context-resolution/research.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

109 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research: Spec 135 Canonical Tenant Context Resolution
## Decision 1: Preserve explicit split semantics between panels
**Decision**: Keep tenant-panel and workspace-admin tenant resolution as two explicit concepts. Tenant-panel flows continue to use panel-native tenant context. Workspace-admin flows continue to use `OperateHubShell::activeEntitledTenant(Request $request): ?Tenant`.
**Rationale**:
- The spec explicitly forbids an unspecified universal resolver.
- Existing code already encodes the correct admin behavior in `OperateHubShell`.
- Tenant-panel screens legitimately use `Filament::getTenant()` and `Tenant::current()` because the tenant is part of panel-native routing semantics.
**Alternatives considered**:
- Build a single app-wide tenant resolver service: rejected because it would blur the admin-versus-tenant distinction the spec wants to preserve.
- Convert all existing tenant reads to admin semantics: rejected because tenant-panel pages are intentionally panel-native.
## Decision 2: Treat `OperateHubShell` as the canonical admin priority rule
**Decision**: Use `OperateHubShell::activeEntitledTenant(Request $request): ?Tenant` as the single admin source of truth and document its priority order as:
1. entitled Filament tenant,
2. entitled remembered workspace tenant,
3. null when neither is valid.
**Rationale**:
- `OperateHubShell` already resolves the conflict the spec describes.
- It already checks entitlement before returning a tenant.
- It already drives visible admin UI labels such as “Filtered by tenant” and tenant return affordances.
**Alternatives considered**:
- Resolve directly from session in each page/resource: rejected because the bug class is inconsistent per-surface resolution.
- Prefer remembered tenant over Filament tenant: rejected because existing admin code and the specs conflict scenario require Filament tenant to win.
## Decision 3: Use alert delivery as the admin reference pattern
**Decision**: Use `AlertDeliveryResource` as the “already correct” admin reference pattern for query scoping, tenant filter defaults, and tenant filter option narrowing.
**Rationale**:
- The resource already scopes by workspace and tenant entitlement.
- It already uses `OperateHubShell::activeEntitledTenant()` for both query narrowing and filter default behavior.
- Its record URL and read-only list structure already fit the features target class of admin monitoring flows.
**Alternatives considered**:
- Use Operations as the reference pattern: rejected because Operations is one of the surfaces that still has an inconsistency in its KPI widget.
- Use a tenant-panel resource as reference: rejected because the feature is specifically about admin-panel canonical behavior.
## Decision 4: Treat Operations page and KPI widget as one remediation unit
**Decision**: Plan the Operations page shell and `OperationsKpiHeader` widget as one remediation slice.
**Rationale**:
- `Monitoring\Operations` already uses `OperateHubShell` for header actions and table query narrowing.
- `OperationsKpiHeader` still reads `Filament::getTenant()` directly, which can disagree with the page header and table when only remembered tenant context exists.
- The spec requires visible tenant context, counts, widgets, filters, and links to resolve from the same canonical tenant.
**Alternatives considered**:
- Fix only the page query: rejected because the current inconsistency class is specifically page shell versus widget mismatch.
- Leave the widget workspace-wide while the table is tenant-scoped: rejected because that violates visible-scope parity.
## Decision 5: Harden OperationRun filters against stale persisted state
**Decision**: Revalidate tenant-sensitive persisted filters against the current canonical admin tenant on every relevant request.
**Rationale**:
- `OperationRunResource` persists filters in session.
- The tenant filter default already uses `OperateHubShell`, but some other filter option builders still reference raw `Filament::getTenant()`.
- The spec explicitly calls out persisted tenant-sensitive filter state as a regression risk when switching tenants.
**Alternatives considered**:
- Disable filter persistence entirely: rejected because persistence is a broader UX choice outside this feature.
- Trust the persisted filter if it still parses: rejected because syntactic validity is not the same as tenant-scope validity.
## Decision 6: Entra groups need record-resolution hardening, not just table scoping
**Decision**: Treat Entra groups as a list-plus-record-resolution problem. Scope must be aligned in `table()`, `getEloquentQuery()`, direct record/view access, and global search.
**Rationale**:
- `EntraGroupResource::table()` narrows using `Tenant::current()`.
- `EntraGroupResource::getEloquentQuery()` currently returns the broader base query.
- The resource has a view page, so direct record URLs can bypass table-only constraints.
- The spec explicitly names Entra group record-resolution and search as in-scope remediation.
**Alternatives considered**:
- Scope only the table query: rejected because the feature exists to fix list/detail/search parity.
- Remove the view page: rejected because the spec focuses on safe access, not reducing product capability.
## Decision 7: Use a panel-aware search rule or disable admin global search for Entra groups
**Decision**: Prefer a panel-aware scoped global-search query for Entra groups. If implementation cannot guarantee parity cheaply, disable global search for that resource in admin contexts.
**Rationale**:
- The constitution and spec both require tenant-safe, non-member-safe search behavior.
- The repo already has a tenant-scoped search concern based on `Filament::getTenant()`, but that is not sufficient for admin remembered-tenant semantics.
- The spec allows disabling global search where safe parity cannot be guaranteed.
**Alternatives considered**:
- Leave current search behavior implicit: rejected because the spec explicitly calls out search entry points.
- Build a broad search-only exception: rejected because it would reintroduce scope drift.
## Decision 8: The guardrail should be a focused architecture test, not a new lint tool
**Decision**: Implement the lightweight guardrail as a Pest architecture/regression test that scans selected admin Filament paths for raw `Filament::getTenant()` and `Tenant::current()` patterns, with a documented allowlist for tenant-panel-native files.
**Rationale**:
- The repo already relies on Pest for regression guards.
- This keeps the feature self-contained and avoids adding external tooling.
- The test can fail with actionable file paths and still permit explicit documented exceptions.
**Alternatives considered**:
- Add a custom PHPStan rule or external linter: rejected because it is heavier than the spec requires.
- Rely on code review discipline only: rejected because the feature explicitly asks for a maintainable guardrail.