# Research: Tenant Lifecycle, Operability, and Context Semantics Foundation ## Decision 1: Reuse the existing `Tenant` lifecycle states as the canonical domain source - Decision: Keep `draft`, `onboarding`, `active`, and `archived` on `App\Models\Tenant` as the single canonical lifecycle vocabulary for tenant records, and build operability and context rules on top of that rather than introducing a second lifecycle store. - Rationale: `Tenant` already defines the lifecycle constants, soft-delete behavior, and current-selection safety (`makeCurrent()` rejects non-active tenants). The inconsistency is not missing states; it is inconsistent interpretation of those states across pages and helpers. - Alternatives considered: - Add a second lifecycle table or projection. Rejected because it would duplicate the same tenant-state truth and increase reconciliation complexity. - Derive tenant lifecycle entirely from onboarding session lifecycle. Rejected because onboarding drafts are workflow records and do not replace the durable tenant record. ## Decision 2: Model operability separately from lifecycle - Decision: Introduce a central operability layer that answers viewability, selector eligibility, action eligibility, onboarding resumability, and canonical monitoring referenceability independently of raw lifecycle. - Rationale: Current code uses raw `where('status', 'active')`, `isActive()`, or soft-delete checks in selectors, controllers, and actions. That pattern conflates lifecycle with every UX and authorization choice. - Alternatives considered: - Continue using direct `status === 'active'` checks with better comments. Rejected because it does not prevent future divergence. - Encode every decision directly into policies. Rejected because selector visibility, badge presentation, and page categorization are not purely authorization concerns. ## Decision 3: Treat remembered tenant context as preference state, not route legitimacy - Decision: Keep remembered tenant state in workspace context helpers, but treat it as filter and navigation preference only. Canonical workspace-owned record viewers must authorize by record and entitlement, not by active tenant equality. - Rationale: `TenantlessOperationRunViewer::mount()` currently aborts 404 when the active tenant differs from the run tenant even after `OperationRunPolicy` already authorizes the user. That is the concrete trust failure this foundation must eliminate. - Alternatives considered: - Keep the viewer mismatch 404 and document it. Rejected because it makes a valid record appear missing and violates the spec’s canonical-record rule. - Automatically switch the selected tenant when viewing a canonical record. Rejected because it mutates operator context as a side effect of inspection. ## Decision 4: Keep onboarding workflow state distinct from tenant lifecycle - Decision: Preserve `TenantOnboardingSession` as the owner of workflow progression, resumability, and checkpoint semantics, while `Tenant` owns lifecycle semantics for the durable tenant record. - Rationale: The codebase already uses `OnboardingLifecycleService`, `OnboardingDraftResolver`, and `TenantOnboardingSessionPolicy` to manage workflow-specific state. Folding that into tenant lifecycle would blur durable domain state with wizard progression. - Alternatives considered: - Collapse onboarding lifecycle into tenant status. Rejected because checkpoint-level progress, resumability, and versioning belong to the workflow record. - Hide onboarding tenants until activation. Rejected because operators need to resume, inspect, and audit onboarding work before activation. ## Decision 5: Extend centralized badge semantics instead of page-local mappings - Decision: Use `BadgeCatalog` and `BadgeDomain` as the only badge semantics extension point for tenant lifecycle presentation, and add exhaustive coverage tests there. - Rationale: Tenant status in `TenantResource` already uses `BadgeRenderer::label/color/icon(BadgeDomain::TenantStatus)`. The fix for `Unknown` rendering belongs in the badge domain mapping, not in per-page string conditionals. - Alternatives considered: - Patch each affected page with its own match statement. Rejected because it recreates drift. - Render raw status strings without badge mapping. Rejected because it breaks BADGE-001 and current UI consistency. ## Decision 6: Anchor authorization in existing policies and capability registries - Decision: Follow-up implementation work should keep server-side enforcement in `OperationRunPolicy`, tenant policies, onboarding policies, `CapabilityResolver`, `WorkspaceCapabilityResolver`, and `Capabilities`, with any new lifecycle-aware decisions delegated through central helpers rather than raw capability strings. - Rationale: The repo already has a strong split between 404 membership failures and 403 capability failures. The problem is contextual misuse, not absence of enforcement primitives. - Alternatives considered: - Introduce route-local authorization closures. Rejected because it would increase duplication and drift. - Rely on Filament visibility or disabling state alone. Rejected because the constitution explicitly forbids using UI as the security boundary. ## Decision 7: Plan follow-up implementation around existing test clusters - Decision: Target follow-up validation through existing Pest suites in `tests/Feature/Auth`, `tests/Feature/Monitoring`, `tests/Feature/Onboarding`, `tests/Feature/Filament`, `tests/Feature/Rbac`, `tests/Feature/TenantRBAC`, plus focused unit coverage for onboarding and badge semantics. - Rationale: The repo already has dedicated tests for tenant chooser selection, tenant switcher scope, archived tenant access, onboarding authorization, canonical monitoring behavior, and operation-run viewing. - Alternatives considered: - Create a new isolated test suite just for lifecycle semantics. Rejected because the behavior is cross-cutting and should strengthen the existing regression clusters. ## Filament and panel notes - Filament v5 runs on Livewire v4 in this repository. - Panel providers are registered in `bootstrap/providers.php`, which already contains `AdminPanelProvider`, `TenantPanelProvider`, and `SystemPanelProvider`. - Relevant searchable-resource posture in current scope: - `TenantResource` has a resource view surface and can remain globally searchable if desired. - `OperationRunResource` has global search disabled. - Workspace landing and onboarding wizard are pages, not resources.