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