TenantAtlas/specs/073-unified-managed-tenant-onboarding-wizard/data-model.md
ahmido 8e34b6084f 073-unified-managed-tenant-onboarding-wizard (#90)
Kontext / Ziel
Diese PR liefert den einzigen kanonischen Onboarding-Entry unter /admin/onboarding (workspace-first, tenantless bis zur Aktivierung) und ergänzt einen tenantless OperationRun-Viewer unter /admin/operations/{run} mit membership→404 Semantik.

Was ist enthalten?
Single entry point: /admin/onboarding ist der einzige Einstieg; Legacy Entry Points liefern echte 404 (keine Redirects).
Wizard v1 (Enterprise): idempotentes Identifizieren eines Managed Tenants (per Entra Tenant ID), resumable Session-Flow.
Provider Connection Step: Auswahl oder Erstellung, Secrets werden nie erneut gerendert / nicht in Session-State persistiert.
Verification als OperationRun: async/queued, DB-only Rendering im Wizard (keine Graph-Calls beim Rendern).
Tenantless Run Viewing: /admin/operations/{run} funktioniert ohne ausgewählten Workspace/Tenant, aber bleibt über Workspace-Mitgliedschaft autorisiert (non-member → 404).
RBAC-UX Semantik: non-member → 404, member ohne Capability → UI disabled + tooltip, server-side Action → 403.
Auditability: Aktivierung/Overrides sind auditierbar, stable action IDs, keine Secrets.
Tech / Version-Safety
Filament v5 / Livewire v4.0+ kompatibel.
Laravel 11+: Panel Provider Registrierung in providers.php (unverändert).
Tests / Format
vendor/bin/sail bin pint --dirty
Full suite: vendor/bin/sail artisan test --no-ansi → 984 passed, 5 skipped (exit 0)
Ops / Deployment Notes
Keine zusätzlichen Services vorausgesetzt.
Falls Assets registriert wurden: Deployment weiterhin mit php artisan filament:assets (wie üblich im Projekt).

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.fritz.box>
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #90
2026-02-04 23:30:55 +00:00

121 lines
4.2 KiB
Markdown
Raw Permalink 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.