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
121 lines
4.2 KiB
Markdown
121 lines
4.2 KiB
Markdown
# 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) — spec’s `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 run’s 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.
|