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
4.2 KiB
4.2 KiB
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’sentra_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:draftonboardingactivearchived
Indexes / constraints (design intent):
- Unique:
tenant_id(global uniqueness; binds the tenant to exactly one workspace) external_idmust remain globally unique for Filament tenancy routing
State transitions:
draft→onboardingafter identification is recordedonboarding→activeon owner activationactive→archivedvia 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_nameenvironmentprimary_domainnotesselected_provider_connection_idverification_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_idto 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
statemust 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.