6.8 KiB
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()andTenant::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:
- entitled Filament tenant,
- entitled remembered workspace tenant,
- null when neither is valid.
Rationale:
OperateHubShellalready 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\Operationsalready usesOperateHubShellfor header actions and table query narrowing.OperationsKpiHeaderstill readsFilament::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:
OperationRunResourcepersists filters in session.- The tenant filter default already uses
OperateHubShell, but some other filter option builders still reference rawFilament::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 usingTenant::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.