# 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.