TenantAtlas/specs/147-tenant-selector-remembered-context-enforcement/research.md
2026-03-16 23:52:03 +01:00

5.6 KiB

Research: Tenant Selector and Remembered Context Enforcement

Decision 1: Keep active-lane selector eligibility anchored in TenantOperabilityService

Decision: Use App\Services\Tenants\TenantOperabilityService as the single authoritative rule for standard selector membership and require the header selector, choose-tenant page, and selection controllers to converge on that service.

Rationale: The service already encapsulates lifecycle-aware operability and exposes canSelectAsContext() and filterSelectable(). Spec 147 needs consistency, not a second selector rule. Reusing this service directly keeps Spec 143 lifecycle semantics intact and avoids per-surface drift.

Alternatives considered:

  • Build a new selector-specific eligibility service: rejected because it would duplicate canSelectAsContext() semantics and create a new place for lifecycle drift.
  • Let each surface query status = active independently: rejected because it would bypass existing operability semantics and repeat the exact fragmentation this spec is intended to remove.

Decision 2: Centralize remembered tenant revalidation in WorkspaceContext

Decision: Make App\Support\Workspaces\WorkspaceContext the authoritative place that validates, returns, clears, and invalidates remembered tenant context for active-lane use.

Rationale: WorkspaceContext already owns the session map keyed by workspace and already clears stale remembered IDs when workspace or lifecycle checks fail. It is the natural home for enforcing the workspace-scoped preference contract required by Spec 147.

Alternatives considered:

  • Revalidate remembered tenant context separately in controllers, Livewire pages, and Blade partials: rejected because it preserves scattered raw-session truth assumptions.
  • Move remembered-context validation into OperateHubShell: rejected because shell resolution should consume validated context, not become the storage owner.

Decision 3: Keep the custom workspace-first shell instead of reverting to Filament's default tenant switcher

Decision: Continue using the existing custom context bar and custom choose-tenant page, but align both to one active-lane rule set.

Rationale: The product architecture is explicitly workspace first. Filament's tenant switcher is tenant-menu-centric, while this app already uses a workspace-scoped shell, explicit workspace switching, and canonical workspace-level routes. Docs confirm Filament tenant menu customization exists, but the custom shell is already the correct product pattern here.

Alternatives considered:

  • Replace the custom selector with Filament's built-in tenant switcher: rejected because it would pull the product back toward a tenant-first mental model and would not solve the workspace-scoped remembered-context rules already present in the app.
  • Keep the custom shell but allow it to diverge from the choose-tenant page: rejected because Spec 147 explicitly requires shared semantics between both selector surfaces.

Decision 4: Treat route authority and shell context as separate layers

Decision: Preserve route-authority semantics for tenant-bound and canonical routes and use shell context only for convenience, not for legitimacy.

Rationale: OperationRunPolicy already authorizes canonical runs by workspace membership, tenant entitlement, and capability checks. TenantlessOperationRunViewer already renders non-blocking context banners. Spec 147 should extend this pattern consistently through shell resolution instead of reintroducing tenant-context-coupled validity at the page level.

Alternatives considered:

  • Use selected tenant equality as a fast validity shortcut for tenant-bound or canonical pages: rejected because it conflicts with Specs 143 and 144 and causes false invalidity.
  • Auto-switch the selected tenant to match the route record: rejected because it makes page rendering mutate user context implicitly and hides the distinction between route subject and current active lane.

Decision 5: “No tenant selected” is a first-class valid shell state

Decision: Treat no-selected-tenant as the correct fallback state for workspace-level pages after invalidation, workspace switch, or explicit clear.

Rationale: Workspace-level surfaces such as /admin and /admin/operations are legitimate without an active tenant. Spec 147 requires graceful degradation to a valid workspace-wide view instead of hidden failure when remembered context becomes stale.

Alternatives considered:

  • Force tenant reselection before allowing workspace-level pages: rejected because it would contradict the workspace-first architecture.
  • Keep stale remembered tenant until the user manually clears it: rejected because it would preserve hidden authority and inconsistent page behavior.

Decision 6: No schema change is required for this feature

Decision: Implement Spec 147 as behavior consolidation over existing session-backed workspace tenant preferences and existing user preference persistence paths.

Rationale: The app already stores current workspace in session and last tenant IDs in a workspace-keyed session map, with supplemental persistence through users.last_tenant_id or user_tenant_preferences. The problem is validation and consumption semantics, not missing storage.

Alternatives considered:

  • Introduce a new tenant-context table or richer persisted context model: rejected because the spec explicitly says remembered tenant context is a convenience preference, not a durable parallel ownership model.
  • Persist no tenant preference at all: rejected because remembered context remains a supported convenience feature within the workspace-scoped shell.