Implements workspace-scoped managed tenant onboarding wizard (Filament v5 / Livewire v4) with strict RBAC (404/403 semantics), resumable sessions, provider connection selection/creation, verification OperationRun, and optional bootstrap. Removes legacy onboarding entrypoints and adds Pest coverage + spec artifacts (073).
8.8 KiB
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
GraphClientInterfacemethods (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 viaEnsureFilamentTenantSelected/DenyNonMemberTenantAccesswith 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
OperationRunand 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
BadgeCatalogdomain mapping (no per-page mapping).
Project Structure
Documentation (this feature)
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)
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\Tenantas “Managed Tenant” (no new base concept), but introducependingstatus 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 viaProviderConnectionHealthCheckJobwithOperationRuntracking.
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, ensureworkspace_idis NOT NULL + FK, and enforce global uniqueness oftenant_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).
- Tenants: change status lifecycle to include
- Authorization
- Introduce a workspace capability for onboarding (e.g.,
workspace_managed_tenant.onboard) and map it to Owner+Manager viaWorkspaceRoleCapabilityMap. - Enforce server-side authorization for every mutation and operation-start; 404 for non-members and cross-workspace access; 403 for members missing capability.
- Introduce a workspace capability for onboarding (e.g.,
- Runs
- Verification is a queued
OperationRunusingprovider.connection.check. - Optional bootstrap actions become separate
OperationRuntypes (only if they exist in the ProviderOperationRegistry).
- Verification is a queued
Phase 2 — Implementation Plan (to be executed by /speckit.tasks)
This plan intentionally stops before creating tasks.md.
Proposed sequencing for tasks:
- Introduce
TenantOnboardingSessionmodel + migration, and add workspace-scoped uniqueness for tenants. - Implement
ManagedTenantOnboardingWizardpage mounted at/admin/w/{workspace}/managed-tenants/onboarding. - Wire verification start to existing
ProviderConnectionHealthCheckJob/provider.connection.checkoperation. - Remove/disable legacy entry points (
RegisterTenant, redirect routes) and ensure “not found” behavior. - Add Pest feature tests for: 404 vs 403 semantics, idempotency, resumability, and sanitized run outcomes.