# 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 spec’s 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 feature’s 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.