TenantAtlas/specs/073-unified-managed-tenant-onboarding-wizard/data-model.md
Ahmed Darrazi ab0ffff1d1 feat(onboarding): enterprise wizard + tenantless run viewer
- Canonical /admin/onboarding entry point; legacy routes 404\n- Tenantless run viewer at /admin/operations/{run} with membership-based 404\n- RBAC UX (disabled controls + tooltips) and server-side 403\n- DB-only rendering/refresh; contract registry enforced\n- Adds migrations + tests + spec artifacts
2026-02-04 23:00:06 +01:00

121 lines
4.2 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.

# Data Model — Managed Tenant Onboarding Wizard V1 (Enterprise) (073)
## Entities
### Workspace
Existing entity: `App\Models\Workspace`
- Onboarding is always initiated within a selected workspace context.
- Workspace membership is the primary isolation boundary for wizard + tenantless operations viewing.
### Tenant (Managed Tenant)
Existing model: `App\Models\Tenant`
**Key fields (existing or to extend):**
- `id` (PK)
- `workspace_id` (FK → workspaces)
- `tenant_id` (string; Entra Tenant ID) — specs `entra_tenant_id` (globally unique)
- `external_id` (string; Filament tenant route key; currently used in `/admin/t/{tenant}`)
- `name` (string)
- `primary_domain` (string|null)
- `notes` (text|null)
- `environment` (string)
- `status` (string) — v1 lifecycle:
- `draft`
- `onboarding`
- `active`
- `archived`
**Indexes / constraints (design intent):**
- Unique: `tenant_id` (global uniqueness; binds the tenant to exactly one workspace)
- `external_id` must remain globally unique for Filament tenancy routing
**State transitions:**
- `draft``onboarding` after identification is recorded
- `onboarding``active` on owner activation
- `active``archived` via archive/deactivate workflow
### Provider Connection
Existing model today: `App\Models\ProviderConnection` (currently tenant-owned)
**Spec-aligned ownership model (design intent):**
- Provider connections are workspace-owned.
- Default binding: provider connection bound to exactly one managed tenant.
- Reuse across managed tenants is disabled by default and policy-gated.
**Proposed key fields (target):**
- `id` (PK)
- `workspace_id` (FK → workspaces)
- `managed_tenant_id` (FK → tenants.id; required in v1 default binding)
- `provider` (string)
- `entra_tenant_id` (string)
- `is_default` (bool)
- `metadata` (json)
### Tenant Onboarding Session (new)
New model/table to persist resumable onboarding state for a workspace + Entra Tenant ID.
Must never persist secrets and must render DB-only.
**Proposed fields:**
- `id` (PK)
- `workspace_id` (FK)
- `managed_tenant_id` (FK → tenants.id; nullable until tenant is created)
- `entra_tenant_id` (string; denormalized identity key; globally unique across the system but still stored for idempotency)
- `current_step` (string; `identify`, `connection`, `verify`, `bootstrap`, `complete`)
- `state` (jsonb) — safe fields only (no secrets)
- `tenant_name`
- `environment`
- `primary_domain`
- `notes`
- `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: `entra_tenant_id` (global uniqueness) OR (if sessions are separate from tenants) unique `(workspace_id, entra_tenant_id)` with an additional global “tenant exists elsewhere” guard to enforce deny-as-not-found.
### Operation Run
Existing model: `App\Models\OperationRun`
**Spec-aligned visibility model (design intent):**
- Runs are viewable tenantlessly at `/admin/operations/{run}`.
- Access is granted only to members of the runs workspace; non-member → deny-as-not-found (404).
**Proposed schema changes:**
- Add `workspace_id` (FK → workspaces), required.
- Allow `tenant_id` to be nullable for pre-activation runs.
- Maintain DB-level active-run idempotency:
- `UNIQUE (tenant_id, run_identity_hash) WHERE tenant_id IS NOT NULL AND status IN ('queued', 'running')`
- `UNIQUE (workspace_id, run_identity_hash) WHERE tenant_id IS NULL AND status IN ('queued', 'running')`
## Validation rules (high level)
- `entra_tenant_id`: required, non-empty, validate GUID format.
- Tenant identification requires: `name`, `environment`, `entra_tenant_id`.
- Provider connection selected/created must be in the same workspace.
- Onboarding session `state` must be strictly whitelisted fields (no secrets).
## Authorization boundaries
- Workspace membership boundary: non-member → 404 (deny-as-not-found) for onboarding and tenantless operations run viewing.
- Capability boundary (within membership): action attempts without capability → 403.
- Owner-only boundary: activation and blocked override require workspace owner; override requires reason + audit.