140 lines
6.5 KiB
Markdown
140 lines
6.5 KiB
Markdown
# Data Model — Self-Service Tenant Onboarding & Connection Readiness
|
||
|
||
**Spec**: [spec.md](spec.md)
|
||
|
||
No new persistent tables are required for this slice. Readiness is computed at render time from existing onboarding, provider connection, verification, and permission-posture truth.
|
||
|
||
## Entities
|
||
|
||
### TenantOnboardingSession (`managed_tenant_onboarding_sessions`)
|
||
|
||
**Purpose**: Workspace-scoped onboarding workflow record that owns resumability, checkpoint progression, and links to the managed tenant once identified.
|
||
|
||
**Key fields (existing)**:
|
||
- `workspace_id` (required FK)
|
||
- `tenant_id` (nullable FK to `tenants.id` until the tenant is linked)
|
||
- `entra_tenant_id`
|
||
- `current_step`
|
||
- `version`
|
||
- `lifecycle_state`
|
||
- `current_checkpoint`
|
||
- `last_completed_checkpoint`
|
||
- `reason_code`
|
||
- `blocking_reason_code`
|
||
- `completed_at`, `cancelled_at`
|
||
- `state` JSON, constrained by `TenantOnboardingSession::STATE_ALLOWED_KEYS`
|
||
|
||
**Relevant `state` keys (existing)**:
|
||
- `tenant_name`
|
||
- `primary_domain`
|
||
- `provider_connection_id`
|
||
- `selected_provider_connection_id`
|
||
- `verification_operation_run_id`
|
||
- `bootstrap_operation_types`
|
||
- `bootstrap_operation_runs`
|
||
- `connection_recently_updated`
|
||
|
||
**Relationships (existing)**:
|
||
- Belongs to `Workspace`
|
||
- May belong to `Tenant`
|
||
- Belongs to `startedByUser`
|
||
- Belongs to `updatedByUser`
|
||
|
||
### ProviderConnection (`provider_connections`)
|
||
|
||
**Purpose**: Tenant-owned provider access record whose consent, verification, and target-scope state inform onboarding readiness.
|
||
|
||
**Key fields (existing, relevant)**:
|
||
- `workspace_id`
|
||
- `tenant_id`
|
||
- `provider`
|
||
- `display_name`
|
||
- `connection_type`
|
||
- `is_default`
|
||
- `is_enabled`
|
||
- `consent_status`
|
||
- `verification_status`
|
||
- target-scope identity fields consumed by `ProviderConnectionTargetScopeNormalizer`
|
||
|
||
**Relationships / invariants (existing)**:
|
||
- The selected provider connection must belong to the same workspace and tenant as the onboarding draft.
|
||
- `ProviderConnectionSurfaceSummary::forConnection()` is the shared source for provider summary wording and contextual identity detail.
|
||
|
||
### VerificationRunEvidence (`operation_runs`, existing subset)
|
||
|
||
**Purpose**: Existing supporting evidence for verification and bootstrap readiness, including canonical operation detail links.
|
||
|
||
**Key fields (existing, relevant)**:
|
||
- `workspace_id`
|
||
- `tenant_id`
|
||
- `type`
|
||
- `status`
|
||
- `outcome`
|
||
- `context.provider_connection_id`
|
||
- `context.verification_report`
|
||
- `summary_counts`
|
||
|
||
**Constraints / invariants (existing)**:
|
||
- The verification run must belong to the same workspace and tenant as the onboarding draft.
|
||
- Verification evidence is only trustworthy for readiness when `context.provider_connection_id` matches the draft’s selected provider connection.
|
||
- Canonical evidence links must continue to flow through `OperationRunLinks` / tenantless operation helpers.
|
||
|
||
### PermissionPostureOverview (derived from existing permission posture data)
|
||
|
||
**Purpose**: Existing stored permission comparison summary used by onboarding verification assist and readiness freshness cues.
|
||
|
||
**Source (existing)**:
|
||
- `TenantPermissionService::compare(...)`
|
||
- `TenantRequiredPermissionsViewModelBuilder::build(...)`
|
||
|
||
**Relevant derived fields (existing)**:
|
||
- `overview.overall`
|
||
- `overview.counts.missing_application`
|
||
- `overview.counts.missing_delegated`
|
||
- `overview.counts.error`
|
||
- `overview.freshness.last_refreshed_at`
|
||
- `overview.freshness.is_stale`
|
||
|
||
**Invariant (existing)**:
|
||
- Permission freshness is stale when no refresh exists or `last_refreshed_at` is older than 30 days (`TenantRequiredPermissionsViewModelBuilder::deriveFreshness`).
|
||
|
||
### OnboardingReadinessSummary (computed, not persisted)
|
||
|
||
**Purpose**: Operator-facing derived summary rendered on the onboarding landing picker and route-bound draft view.
|
||
|
||
**Proposed runtime shape (presentation-only)**:
|
||
- `draft`: `id`, `tenant_name`, `stage_label`, `draft_status_label`, `started_by`, `updated_by`, `last_updated_human`
|
||
- `checkpoint`: `current_checkpoint`, `last_completed_checkpoint`, `lifecycle_state`
|
||
- `provider_summary`: `readiness_summary`, `consent_state`, `verification_state`, `target_scope_summary`, `contextual_identity_line`
|
||
- `verification`: `status`, `overall`, `run_id`, `run_url`, `is_active`, `matches_selected_connection`
|
||
- `freshness`: `connection_recently_updated`, `verification_mismatch`, `permission_last_refreshed_at`, `permission_data_is_stale`
|
||
- `blocker`: `reason_code`, `blocking_reason_code`, `operator_summary`
|
||
- `next_action`: `label`, `kind`, `url_or_action`, `required_capability`
|
||
- `supporting_links`: `operation_url`, `tenant_url`, `consent_url` when already available from existing routes/helpers
|
||
|
||
**Important rule**: This is a presentation shape only. It must map directly from existing onboarding lifecycle, provider connection, verification, and permission-posture truth. It is not a new domain model or persisted state family.
|
||
|
||
## Derived Rules / Invariants
|
||
|
||
- A draft without tenant identity cannot be ready; the primary action remains the identify-tenant step.
|
||
- A draft without a selected provider connection cannot be ready; the primary action remains connect/select provider.
|
||
- A verification run that does not match the selected provider connection is stale for readiness and must force a non-ready outcome.
|
||
- `connection_recently_updated=true` invalidates previous verification trust until verification reruns.
|
||
- Stale permission posture (`overview.freshness.is_stale=true`) must surface as a readiness attention cue or diagnostic freshness cue, not as ready.
|
||
- Top-level readiness wording stays platform-neutral. Provider-specific permission names and consent instructions remain inside secondary diagnostics.
|
||
- Supporting evidence uses canonical operation links only; no page-local run URLs are introduced.
|
||
|
||
## Rendering Precedence (derived, not persisted)
|
||
|
||
No new persisted transitions are introduced. The readiness summary should follow this rendering precedence when choosing one primary next action:
|
||
|
||
1. No identified tenant: `Identify tenant`
|
||
2. No selected provider connection: `Connect provider`
|
||
3. Consent missing or revoked: `Grant consent`
|
||
4. Permission diagnostics blocked or incomplete: `Review permissions` / `Grant consent` as dictated by existing provider-owned diagnostics
|
||
5. Verification missing, stale, or mismatched: `Start verification` or `Rerun verification`
|
||
6. Verification active: `Open operation` or `Refresh`
|
||
7. Bootstrap selected and still active/failed: `Review bootstrap`
|
||
8. Lifecycle ready for activation: `Complete onboarding`
|
||
|
||
The multi-draft landing surface uses the same precedence, but only in compact form so the operator can choose the correct draft to open. |