TenantAtlas/specs/148-central-tenant-operability-policy/data-model.md
ahmido 417df4f9aa feat: central tenant operability policy (#177)
## Summary
- centralize tenant operability into a lane-aware, actor-aware policy boundary
- align selector eligibility, administrative discoverability, remembered context, tenant-bound routes, and canonical run viewers
- add focused Pest coverage plus Spec 148 artifacts and final polish task completion

## Validation
- `vendor/bin/sail artisan test --compact tests/Unit/Tenants/TenantOperabilityServiceTest.php tests/Unit/Tenants/TenantOperabilityOutcomeTest.php tests/Feature/Workspaces/ChooseTenantPageTest.php tests/Feature/Workspaces/SelectTenantControllerTest.php tests/Feature/TenantRBAC/ArchivedTenantRouteAccessTest.php tests/Feature/TenantRBAC/TenantRouteDenyAsNotFoundTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/OpsUx/OperateHubShellTest.php tests/Feature/Rbac/TenantLifecycleActionVisibilityTest.php tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php tests/Feature/Rbac/TenantResourceAuthorizationTest.php tests/Feature/Filament/ManagedTenantsLandingLifecycleTest.php tests/Feature/Filament/TenantGlobalSearchLifecycleScopeTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php tests/Feature/Onboarding/OnboardingDraftAuthorizationTest.php`
- `vendor/bin/sail bin pint --dirty --format agent`
- manual browser smoke checks on `/admin/choose-tenant`, `/admin/tenants`, `/admin/onboarding`, `/admin/onboarding/{draft}`, and `/admin/operations/{run}`

## Filament / platform notes
- Livewire v4 compliance preserved
- panel provider registration unchanged in `bootstrap/providers.php`
- Tenant resource global search remains backed by existing view/edit pages and is now separated from active-only selector eligibility
- destructive actions remain action closures with confirmation and authorization enforcement
- no asset pipeline changes and no new `filament:assets` deployment requirement

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #177
2026-03-17 11:48:55 +00:00

196 lines
6.6 KiB
Markdown

# Phase 1 Data Model: Central Tenant Operability Policy
## Overview
This feature does not require a new database table in its first implementation slice. The primary data-model work is the formalization of existing persistent records plus new derived domain objects that express tenant operability consistently across lanes.
## Persistent Domain Entities
### Tenant
**Purpose**: Durable workspace-owned record whose lifecycle influences operability.
**Key fields**:
- `id`
- `workspace_id`
- `external_id`
- `status` with canonical values `draft`, `onboarding`, `active`, `archived`
- `deleted_at` for archive persistence behavior
**Relationships**:
- Belongs to one workspace
- Has many onboarding sessions, audit logs, provider connections, and tenant-owned operational records
- May be referenced by canonical workspace records such as `OperationRun`
**Validation rules**:
- `workspace_id` must match the active workspace scope for all in-scope route or action decisions
- `status` must resolve through the canonical `TenantLifecycle` model
- Soft-delete state is an implementation input, never the only semantic rule
**State transitions relevant to this feature**:
- `draft``onboarding`
- `onboarding``active`
- `active``archived`
- `archived``active`
### TenantOnboardingSession
**Purpose**: Separate workspace-scoped workflow record used to evaluate onboarding-lane actions.
**Key fields**:
- `id`
- `workspace_id`
- `tenant_id` nullable until linked
- workflow state and lifecycle fields already used by onboarding services
**Relationships**:
- Belongs to one workspace
- Optionally belongs to one tenant
**Validation rules**:
- Must be in the same workspace as any linked tenant
- Onboarding-specific operability decisions must validate both tenant lifecycle and workflow resumability
### OperationRun
**Purpose**: Canonical workspace-owned record that may reference a tenant without becoming subordinate to selected tenant context.
**Key fields**:
- `id`
- `workspace_id`
- `tenant_id` nullable
- `type`
- `status`
- `outcome`
**Relationships**:
- Belongs to one workspace
- May belong to one tenant reference
**Validation rules**:
- Canonical route legitimacy is based on the run and workspace first
- Tenant-linked follow-up actions must still respect tenant entitlement and operability outcomes
### UserTenantPreference and WorkspaceContext Session State
**Purpose**: Stores remembered tenant context for the active operating lane.
**Key fields**:
- `user_id`
- `tenant_id`
- `last_used_at`
- session-scoped workspace and remembered-tenant identifiers
**Validation rules**:
- Remembered tenant is valid only when workspace match, tenant existence, entitlement, and selector-lane operability all still hold
## New Derived Domain Objects
### TenantInteractionLane
**Purpose**: Explicitly identifies the lane in which the tenant is being evaluated.
**Canonical values**:
- `standard_active_operating`
- `onboarding_workflow`
- `administrative_management`
- `canonical_workspace_record`
**Why it exists**:
- The same tenant can be selector-ineligible yet still administratively viewable or canonically referenceable
### TenantOperabilityContext
**Purpose**: Normalized evaluation input for the central policy layer.
**Fields**:
- `tenant`
- `actor`
- `workspaceId`
- `lane`
- `pageCategory`
- `linkedRecordType` nullable
- `linkedRecordId` nullable
- `onboardingDraft` nullable
- `requiredCapability` nullable
- `selectedTenant` nullable
**Context ownership**:
- Consumers normalize and pass route, record, workflow, and selected-tenant inputs into the context.
- The central service resolves workspace membership, tenant entitlement, capability truth, lifecycle, and archived persistence state through canonical workspace, tenant, and RBAC helpers at evaluation time.
- Consumers may request a capability-aware question, but they do not authoritatively decide membership or entitlement before evaluation.
**Validation rules**:
- `tenant.workspace_id` must equal `workspaceId`
- linked records must belong to the same workspace when tenant-linked
- `selectedTenant` may be informative but never authoritative
### TenantOperabilityQuestion
**Purpose**: Declares the exact semantic question the consumer is asking.
**Canonical values**:
- `selector_eligibility`
- `remembered_context_validity`
- `tenant_bound_viewability`
- `canonical_linked_record_viewability`
- `archive_eligibility`
- `restore_eligibility`
- `resume_onboarding_eligibility`
- `onboarding_completion_eligibility`
- `verification_readiness_eligibility`
- `administrative_discoverability`
### TenantOperabilityOutcome
**Purpose**: Structured result returned by the central operability policy.
**Fields**:
- `question`
- `allowed`
- `lifecycle`
- `lane`
- `reasonCode` nullable
- `requiredCapability` nullable
- `discoverable` boolean
- `informationalMessageKey` nullable
- `metadata` key-value bag for consumer-safe hints
**Behavior**:
- Must be stable enough for unit assertions and UI mapping
- May expose helper methods for common adapters, but raw outcome data remains canonical
### TenantOperabilityReasonCode
**Purpose**: Stable denial or ineligibility reason catalog.
**Initial values**:
- `workspace_mismatch`
- `tenant_not_entitled`
- `missing_capability`
- `wrong_lane`
- `selector_ineligible_lifecycle`
- `tenant_not_archived`
- `tenant_already_archived`
- `onboarding_not_resumable`
- `canonical_view_followup_only`
- `remembered_context_stale`
## Consumer Mapping
| Consumer | Primary question(s) |
|---|---|
| Choose-tenant page | `selector_eligibility` |
| Select-tenant controller | `selector_eligibility`, `remembered_context_validity` |
| WorkspaceContext | `remembered_context_validity` |
| OperateHubShell | `tenant_bound_viewability`, `canonical_linked_record_viewability`, `administrative_discoverability` |
| TenantActionPolicySurface | `archive_eligibility`, `restore_eligibility`, `resume_onboarding_eligibility`, `verification_readiness_eligibility` |
| ManagedTenantOnboardingWizard | `resume_onboarding_eligibility`, `onboarding_completion_eligibility`, `verification_readiness_eligibility` |
| TenantResource global search and admin listing | `administrative_discoverability` |
| TenantlessOperationRunViewer | `canonical_linked_record_viewability` and follow-up action questions |
## Migration Notes
- No persistence migration is required for the first slice.
- Existing boolean accessors on `TenantOperabilityService` may remain as compatibility adapters while consumers migrate to structured outcomes.
- Any new enums or value objects should live under `app/Support/Tenants` to stay aligned with existing lifecycle, page-category, and action-surface support code.