# 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.