- 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
4.6 KiB
4.6 KiB
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\Tenantas 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
ManagedTenantmodel/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/onboardingas 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/onboardingonly).
4) Tenantless operations viewer = existing OperationRunResource route /admin/operations/{run}
- Decision: Keep the route shape
/admin/operations/{run}(already provided byOperationRunResourceslugoperations) 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
EnsureWorkspaceSelectedbehavior (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 run’s 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()+ matchingrun.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 allowtenant_idto 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')
- Tenant-bound runs:
- Rationale: Enables tenantless operations while preserving race-safe idempotency guarantees.
- Alternatives considered: Keep
tenant_idrequired 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.