TenantAtlas/specs/073-unified-managed-tenant-onboarding-wizard/research.md
Ahmed Darrazi ab0ffff1d1 feat(onboarding): enterprise wizard + tenantless run viewer
- Canonical /admin/onboarding entry point; legacy routes 404\n- Tenantless run viewer at /admin/operations/{run} with membership-based 404\n- RBAC UX (disabled controls + tooltips) and server-side 403\n- DB-only rendering/refresh; contract registry enforced\n- Adds migrations + tests + spec artifacts
2026-02-04 23:00:06 +01:00

68 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research — Managed Tenant Onboarding Wizard V1 (Enterprise) (073)
This document resolves planning unknowns and records key implementation decisions aligned with the clarified spec.
## Decisions
### 1) Managed Tenant model = existing `Tenant`
- **Decision:** Treat `App\Models\Tenant` as the “Managed Tenant” record.
- **Rationale:** Filament tenancy, membership model, and tenant-scoped flows already depend on `Tenant`; duplicating a second tenant-like table would multiply authorization and routing complexity.
- **Alternatives considered:** Introduce a new `ManagedTenant` model/table.
- **Why rejected:** Duplicates tenancy and membership boundaries; increases cross-plane leak risk.
### 2) Entra Tenant ID uniqueness = global, bound to one workspace
- **Decision:** Enforce global uniqueness for `tenants.tenant_id` (Entra Tenant ID) and bind it to exactly one workspace (the workspace_id on the tenant).
- **Rationale:** Matches FR-011 and the clarification decision (“global uniqueness bound to one workspace”).
- **Alternatives considered:** Allow the same Entra Tenant ID in multiple workspaces.
- **Why rejected:** Violates the clarified requirement and complicates deny-as-not-found behavior.
### 3) Canonical onboarding entry point = `/admin/onboarding` (only)
- **Decision:** Provide `/admin/onboarding` as the sole onboarding entry point.
- **Rationale:** Keeps a single user-facing URL for enterprise workflows; avoids fragmented legacy entry points.
- **Alternatives considered:** Workspace-scoped onboarding route (`/admin/w/{workspace}/...`).
- **Why rejected:** Conflicts with clarified spec (canonical `/admin/onboarding` only).
### 4) Tenantless operations viewer = existing `OperationRunResource` route `/admin/operations/{run}`
- **Decision:** Keep the route shape `/admin/operations/{run}` (already provided by `OperationRunResource` slug `operations`) and make it compliant by changing authorization + middleware behavior.
- **Rationale:** Minimizes routing surface area and leverages existing Monitoring → Operations UI.
- **Alternatives considered:** Create a separate “run viewer” page outside the resource.
- **Why rejected:** Duplicates infolist rendering and complicates observability conventions.
### 5) `/admin/operations/{run}` must not require selected workspace or auto-switch
- **Decision:** Exempt `/admin/operations/{run}` from forced workspace selection and from any “auto selection” side effects that would prevent tenantless viewing.
- **Rationale:** Spec requires (a) no workspace in the URL, (b) no pre-selected workspace required, (c) no auto-switching.
- **Alternatives considered:** Keep current `EnsureWorkspaceSelected` behavior (redirect to choose workspace).
- **Why rejected:** Violates FR-017a and can leak resource existence via redirects.
### 6) OperationRun authorization = workspace membership (non-member → 404)
- **Decision:** Authorize viewing a run by checking membership in the runs workspace; non-member gets deny-as-not-found (404).
- **Rationale:** FR-017a defines access semantics; runs must be viewable tenantlessly before activation.
- **Alternatives considered:** Authorize by `Tenant::current()` + matching `run.tenant_id`.
- **Why rejected:** Requires tenant routing/selection and breaks tenantless viewing.
### 7) OperationRun schema = add `workspace_id`, allow tenantless runs, preserve idempotency
- **Decision:** Add `operation_runs.workspace_id` (FK) and allow `tenant_id` to be nullable for pre-activation operations. Preserve DB-level dedupe using two partial unique indexes:
- Tenant-bound runs: `UNIQUE (tenant_id, run_identity_hash) WHERE tenant_id IS NOT NULL AND status IN ('queued', 'running')`
- Tenantless runs: `UNIQUE (workspace_id, run_identity_hash) WHERE tenant_id IS NULL AND status IN ('queued', 'running')`
- **Rationale:** Enables tenantless operations while preserving race-safe idempotency guarantees.
- **Alternatives considered:** Keep `tenant_id` required and always derive workspace via join.
- **Why rejected:** Blocks tenantless flows and makes authorization join-dependent.
### 8) Provider connection ownership = workspace-owned, default 1:1 binding
- **Decision:** Align Provider Connections to be workspace-owned and (by default) bound to exactly one managed tenant; reuse is disabled by default and policy-gated.
- **Rationale:** Matches FR-022/022a/022b and reduces blast radius of credential reuse.
- **Alternatives considered:** Keep provider connections tenant-owned.
- **Why rejected:** Conflicts with clarified spec ownership model.
## Open Questions
- None for planning; implementation will need to reconcile existing DB schema and policies with the decisions above.