# Implementation Plan: Unified Managed Tenant Onboarding Wizard (073) **Branch**: `073-unified-managed-tenant-onboarding-wizard` | **Date**: 2026-02-03 | **Spec**: specs/073-unified-managed-tenant-onboarding-wizard/spec.md **Input**: Feature specification from `specs/073-unified-managed-tenant-onboarding-wizard/spec.md` **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Deliver a single, resumable onboarding wizard for Managed Tenants that: (1) identifies/upserts a managed tenant within the current workspace, (2) attaches or configures a Provider Connection, (3) runs verification asynchronously as an `OperationRun` with sanitized outcomes, and (4) optionally kicks off bootstrap operations. Implementation approach: reuse existing primitives (`App\Models\Tenant`, Provider Connections, `provider.connection.check` operation type, workspace + tenant isolation middleware, canonical capability registries) and replace legacy tenant registration/redirect entry points with a single workspace-scoped wizard route. ## Technical Context **Language/Version**: PHP 8.4.x (Composer constraint: `^8.2`) **Primary Dependencies**: Laravel 12, Filament 5, Livewire 4+, Pest 4, Sail 1.x **Storage**: PostgreSQL (Sail) + SQLite in tests where applicable **Testing**: Pest (via `vendor/bin/sail artisan test`) **Target Platform**: Web app (Sail for local dev; container-based deploy on Linux) **Project Type**: Web application (Laravel monolith) **Performance Goals**: Onboarding UI renders DB-only; all Graph calls occur in queued work tracked by `OperationRun`; avoid N+1 via eager loading for any list/detail. **Constraints**: Tenant isolation (404 vs 403 semantics); no secret material ever returned to the UI/logs; idempotent run-start and onboarding session resume; destructive-like actions require confirmation. **Scale/Scope**: Workspace-scoped onboarding; expected low volume but high correctness/safety requirements. ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* GATE RESULT: PASS (no planned constitution violations). - Inventory-first: onboarding writes only tenant metadata + configuration pointers; no inventory/snapshot side effects. - Read/write separation: onboarding creates/updates records and starts operations; all mutating actions are authorized, audited, and tested. - Graph contract path: verification uses existing `GraphClientInterface` methods (e.g., `getOrganization()`), and runs only in queued jobs. - Deterministic capabilities: use `App\Support\Auth\Capabilities` + `WorkspaceRoleCapabilityMap`; add a dedicated onboarding capability granted to Owner+Manager. - RBAC-UX semantics: workspace membership enforced via `ensure-workspace-member`; tenant membership enforced via `EnsureFilamentTenantSelected` / `DenyNonMemberTenantAccess` with deny-as-not-found (404). Missing capability returns 403. - Destructive confirmation: any archive/delete/credential-rotation actions involved in onboarding must be `->action(...)->requiresConfirmation()`. - Run observability: verification + optional bootstrap actions start via `OperationRun` and enqueue only; monitoring pages remain DB-only. - Data minimization: onboarding session stores only non-secret fields; run failures store reason codes + sanitized messages. - BADGE-001: introduce/extend Managed Tenant status badges via `BadgeCatalog` domain mapping (no per-page mapping). ## Project Structure ### Documentation (this feature) ```text specs/073-unified-managed-tenant-onboarding-wizard/ ├── plan.md # This file (/speckit.plan command output) ├── research.md # Phase 0 output (/speckit.plan command) ├── data-model.md # Phase 1 output (/speckit.plan command) ├── quickstart.md # Phase 1 output (/speckit.plan command) ├── contracts/ # Phase 1 output (/speckit.plan command) └── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ ├── Pages/ │ │ └── Workspaces/ │ │ ├── ManagedTenantsLanding.php │ │ └── (new) ManagedTenantOnboardingWizard.php │ └── Pages/Tenancy/ │ └── RegisterTenant.php # legacy entry point to remove/disable ├── Http/Controllers/ │ └── TenantOnboardingController.php # legacy admin-consent helper; evaluate usage ├── Jobs/ │ └── ProviderConnectionHealthCheckJob.php # verification via OperationRun ├── Models/ │ ├── Tenant.php │ ├── ProviderConnection.php │ └── (new) TenantOnboardingSession.php └── Services/ ├── Auth/ │ ├── WorkspaceCapabilityResolver.php │ └── WorkspaceRoleCapabilityMap.php ├── Providers/ │ ├── ProviderOperationRegistry.php │ └── ProviderGateway.php └── Graph/ └── GraphClientInterface.php database/migrations/ ├── (new) *_add_workspace_scoped_unique_tenant_id.php └── (new) *_create_tenant_onboarding_sessions_table.php routes/web.php tests/Feature/ └── (new) ManagedTenantOnboardingWizardTest.php ``` **Structure Decision**: Laravel web application (monolith). Onboarding wizard is a Filament page mounted on a workspace-scoped route under `/admin/w/{workspace}/...` (no tenant context required to start). ## Complexity Tracking > **Fill ONLY if Constitution Check has violations that must be justified** | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | [e.g., 4th project] | [current need] | [why 3 projects insufficient] | | [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | No constitution violations are anticipated for this feature. ## Phase 0 — Outline & Research (complete) Outputs: - `research.md`: decisions + rationale + alternatives (no unresolved clarifications). Key research conclusions: - Reuse `App\Models\Tenant` as “Managed Tenant” (no new base concept), but introduce `pending` status for the onboarding lifecycle. - Replace legacy onboarding/registration routes (`/admin/register-tenant`, redirects under `/admin/managed-tenants/*`) with a single workspace-scoped onboarding wizard. - Use existing provider verification operation type (`provider.connection.check`) executed via `ProviderConnectionHealthCheckJob` with `OperationRun` tracking. ## Phase 1 — Design & Contracts (complete) Outputs: - `data-model.md`: entities, fields, relationships, validation, state transitions. - `contracts/*`: documented HTTP routes + action contracts (OpenAPI-style where applicable). - `quickstart.md`: dev notes, env vars, how to run tests. Design highlights: - Data model - Tenants: change status lifecycle to include `pending`, ensure `workspace_id` is NOT NULL + FK, and enforce global uniqueness of `tenant_id` (Entra tenant ID) bound to exactly one workspace. - Onboarding sessions: new table/model for resumable state (strictly non-secret) keyed by `(workspace_id, tenant_id)`. - Authorization - Introduce a workspace capability for onboarding (e.g., `workspace_managed_tenant.onboard`) and map it to Owner+Manager via `WorkspaceRoleCapabilityMap`. - Enforce server-side authorization for every mutation and operation-start; 404 for non-members and cross-workspace access; 403 for members missing capability. - Runs - Verification is a queued `OperationRun` using `provider.connection.check`. - Optional bootstrap actions become separate `OperationRun` types (only if they exist in the ProviderOperationRegistry). ## Phase 2 — Implementation Plan (to be executed by /speckit.tasks) This plan intentionally stops before creating `tasks.md`. Proposed sequencing for tasks: 1) Introduce `TenantOnboardingSession` model + migration, and add workspace-scoped uniqueness for tenants. 2) Implement `ManagedTenantOnboardingWizard` page mounted at `/admin/w/{workspace}/managed-tenants/onboarding`. 3) Wire verification start to existing `ProviderConnectionHealthCheckJob` / `provider.connection.check` operation. 4) Remove/disable legacy entry points (`RegisterTenant`, redirect routes) and ensure “not found” behavior. 5) Add Pest feature tests for: 404 vs 403 semantics, idempotency, resumability, and sanitized run outcomes.