# Research — Unified Managed Tenant Onboarding Wizard (073) This document resolves planning unknowns and records key implementation decisions. ## Decisions ### 1) Managed Tenant model = existing `Tenant` - **Decision:** Treat the existing `App\Models\Tenant` as the “Managed Tenant” concept. - **Rationale:** The admin panel tenancy, membership model, and most operational flows already key off `Tenant`. - **Alternatives considered:** - Introduce a new `ManagedTenant` model/table. - Keep `Tenant` as-is and build onboarding as “just another page”. - **Why rejected:** A second tenant-like model would duplicate authorization, routing, and operational conventions. ### 2) Workspace-scoped uniqueness + stable route key - **Decision:** Enforce uniqueness by `(workspace_id, tenant_id)` (where `tenant_id` is the Entra tenant ID), and ensure Filament’s route tenant key stays globally unique. - **Rationale:** The feature spec explicitly defines the uniqueness key, and cross-workspace safety requires first-class scoping. - **Implementation note:** Today `tenants.external_id` is unique and is force-set to `tenant_id` in `Tenant::saving()`. If we allow the same `tenant_id` across workspaces, `external_id` must NOT be set to `tenant_id` anymore. Prefer a generated opaque stable `external_id` (UUID) and keep `tenant_id` strictly as the business identifier. - **Alternatives considered:** - Keep global uniqueness on `tenant_id` and keep using `external_id = tenant_id`. - **Why rejected:** Conflicts with the clarified uniqueness key and complicates “deny-as-not-found” behavior via DB constraint errors. ### 3) Wizard route location = workspace-scoped (`/admin/w/{workspace}/...`) - **Decision:** Mount onboarding at a workspace-scoped route: `/admin/w/{workspace}/managed-tenants/onboarding`. - **Rationale:** This path is explicitly exempted from forced tenant selection in `EnsureFilamentTenantSelected`, allowing onboarding before a tenant exists. - **Alternatives considered:** - Tenant-scoped Filament routes (`/admin/t/{tenant}/...`). - Reusing Filament’s built-in tenant registration page (`tenantRegistration`). - **Why rejected:** Tenant-scoped routes require a tenant to exist/selected; built-in registration is a legacy entry point we must remove. ### 4) Verification implementation = existing provider operation (`provider.connection.check`) - **Decision:** Use `provider.connection.check` (module `health_check`) executed via `ProviderConnectionHealthCheckJob` as the onboarding verification run. - **Rationale:** It already uses `OperationRun`, writes sanitized outcomes, and performs Graph calls off-request. - **Alternatives considered:** - New onboarding-specific operation type. - **Why rejected:** Adds duplication without a clear benefit for v1. ### 5) Authorization surface = workspace capability (Owner+Manager) - **Decision:** Add a dedicated workspace capability for onboarding (e.g., `workspace_managed_tenant.onboard`) and grant it to workspace Owner and Manager in `WorkspaceRoleCapabilityMap`. - **Rationale:** The spec requires Owner+Manager; existing workspace capabilities don’t exactly match this (e.g., `WORKSPACE_MANAGE` is Owner-only). - **Alternatives considered:** - Check workspace role strings (`owner/manager`) directly. - Reuse an unrelated capability like `WORKSPACE_MEMBERSHIP_MANAGE`. - **Why rejected:** Constitution forbids role-string checks in feature code; reusing unrelated capability broadens authorization implicitly. ### 6) Legacy entry points = removed/404 (no redirects) - **Decision:** Remove/disable these entry points and ensure 404 behavior: - `/admin/register-tenant` (Filament registration page) - `/admin/managed-tenants*` legacy redirects - `/admin/new` redirect - `/admin/w/{workspace}/managed-tenants/onboarding` redirect stub - **Rationale:** FR-001 requires wizard-only entry and “not found” behavior. ## Open Questions - None. All technical unknowns required for planning are resolved.