## 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
196 lines
6.6 KiB
Markdown
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. |