TenantAtlas/specs/143-tenant-lifecycle-operability-context-semantics/research.md
ahmido 641bb4afde feat: implement tenant lifecycle operability semantics (#172)
## Summary
- implement Spec 143 tenant lifecycle, operability, and tenant-context semantics across chooser, tenant management, onboarding, and canonical operation viewers
- add centralized tenant lifecycle and operability support types, audit action coverage, and lifecycle-aware badge and action handling
- add feature and unit coverage for tenant chooser eligibility, global search scoping, canonical operation access, onboarding authorization, and lifecycle presentation

## Testing
- vendor/bin/sail artisan test --compact
- vendor/bin/sail bin pint --dirty --format agent

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #172
2026-03-15 09:08:36 +00:00

65 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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