# Data Model — Unified Managed Tenant Onboarding Wizard (073) ## Entities ### Workspace Existing entity. Onboarding is always initiated within a selected workspace. ### Tenant (Managed Tenant) Existing model: `App\Models\Tenant` **Key fields (existing or to be confirmed/extended):** - `id` (PK) - `workspace_id` (FK to workspaces) - `tenant_id` (string; Entra tenant ID) — spec’s `entra_tenant_id` - `external_id` (string; globally unique route key used by Filament tenancy) - `name` (string) - `domain` (string|null) - `status` (string) — v1 lifecycle: - `pending` (created / onboarding) - `active` (ready) - `archived` (no longer managed) **Indexes / constraints (design intent):** - Unique: `(workspace_id, tenant_id)` - Keep `external_id` globally unique (for `/admin/t/{tenant}` routing) and do **not** force it to equal `tenant_id`. **State transitions:** - `pending` → `active` after successful verification - `active` → `archived` on soft-delete (existing behavior) - `archived` → `active` on restore (existing behavior) ### ProviderConnection Existing model: `App\Models\ProviderConnection` - Belongs to `Tenant` - Contains `entra_tenant_id` (string) and default/active flags. ### TenantOnboardingSession (new) New model/table to persist resumable onboarding state. Must never persist or return secrets. **Proposed fields:** - `id` (PK) - `workspace_id` (FK) - `tenant_id` (FK to tenants.id) — nullable until tenant is created, depending on wizard flow - `entra_tenant_id` (string) — denormalized for upsert/idempotency before tenant exists - `current_step` (string; e.g., `identify`, `connection`, `verify`, `bootstrap`, `complete`) - `state` (jsonb/json) — safe fields only (no secrets) - `tenant_name` - `tenant_domain` - `selected_provider_connection_id` - `verification_run_id` (OperationRun id) - `bootstrap_run_ids` (array) - `started_by_user_id` (FK users) - `updated_by_user_id` (FK users) - `completed_at` (timestamp|null) - timestamps **Constraints:** - Unique: `(workspace_id, entra_tenant_id)` **State transitions:** - `in_progress` (implied by `completed_at = null`) → `completed` (`completed_at != null`) ## Validation rules (high level) - `entra_tenant_id` (`tenant_id`) must be a non-empty string; validate as GUID format if enforced elsewhere. - Tenant name required to create tenant. - ProviderConnection selection must belong to the same tenant/workspace. ## Authorization boundaries - Workspace scope: non-members denied as 404. - Workspace member but missing onboarding capability: 403. - Tenant scope: once tenant exists/selected, tenant membership rules apply as currently implemented.