# Data Model: Onboarding Lifecycle, Operation Checkpoints & Concurrency MVP ## Entity: TenantOnboardingSession Existing persisted workflow record for managed tenant onboarding drafts. ### Fields | Field | Type | Required | Notes | |---|---|---|---| | `id` | bigint | yes | Primary key | | `workspace_id` | foreign key | yes | Workspace ownership and authorization boundary | | `tenant_id` | foreign key nullable | conditional | May remain null before tenant identification, per constitution exception | | `entra_tenant_id` | string | yes | Current draft identity key for resumable uniqueness | | `current_step` | string nullable | no | Existing UI-oriented step breadcrumb; no longer canonical workflow truth | | `state` | json nullable | no | Detailed persisted step data and run references | | `started_by_user_id` | foreign key nullable | no | Draft creator | | `updated_by_user_id` | foreign key nullable | no | Last mutating actor | | `completed_at` | timestamp nullable | no | Terminal historical marker | | `cancelled_at` | timestamp nullable | no | Terminal historical marker | | `version` | bigint or integer | yes | Starts at `1`, increments on every relevant successful mutation | | `lifecycle_state` | controlled string or enum | yes | Canonical workflow state | | `current_checkpoint` | controlled string or enum nullable | no | Governing checkpoint for forward progress | | `last_completed_checkpoint` | controlled string or enum nullable | no | Last satisfied checkpoint | | `reason_code` | string nullable | no | Machine-readable lifecycle precision | | `blocking_reason_code` | string nullable | no | Machine-readable explicit blocker | | `created_at` | timestamp | yes | Existing audit chronology | | `updated_at` | timestamp | yes | Existing audit chronology | ### Relationships - Belongs to `Workspace` - Belongs to `Tenant` when identified - Belongs to `startedByUser` - Belongs to `updatedByUser` - References relevant verification and bootstrap `OperationRun` records through IDs stored in `state` ### Validation Rules - `version >= 1` - `lifecycle_state` must be one of the controlled lifecycle values - `current_checkpoint` and `last_completed_checkpoint` must be null or one of the controlled checkpoint values - `blocking_reason_code` must be null unless the workflow is actually blocked from forward progress - `completed_at` and `cancelled_at` remain mutually exclusive terminal markers - Terminal records (`completed_at` or `cancelled_at` set) are non-editable ## Entity: OnboardingLifecycleState Controlled value set used for canonical workflow truth. ### Values | Value | Meaning | |---|---| | `draft` | No active governing checkpoint run and not yet ready for activation | | `verifying` | Relevant verification run is queued or running | | `action_required` | Operator intervention is required before safe progression | | `bootstrapping` | One or more selected bootstrap runs are queued or running | | `ready_for_activation` | All gating conditions are satisfied and the workflow awaits final activation | | `completed` | Activation succeeded and the draft is historical | | `cancelled` | Draft was intentionally cancelled and is historical | ## Entity: OnboardingCheckpoint Controlled checkpoint precision used for progression and UI state. ### Values | Value | Meaning | |---|---| | `identify` | Tenant identity or workspace-scoped start state | | `connect_provider` | Provider selection or connection management checkpoint | | `verify_access` | Verify Access checkpoint | | `bootstrap` | Optional bootstrap checkpoint | | `complete_activate` | Final activation checkpoint | ## Entity: OnboardingReasonCode Machine-readable precision for non-terminal workflow state. ### Expected starter values - `verification_blocked_permissions` - `verification_failed` - `provider_connection_changed` - `verification_result_stale` - `bootstrap_failed` - `bootstrap_partial_failure` - `owner_activation_required` This list is intentionally controlled and should be extended centrally rather than spread through page code. ## Entity: RelevantCheckpointRun Derived relationship between a draft and the `OperationRun` records that currently govern progression. ### Verification invariants - The verification run must match the currently selected provider connection. - A queued or running verification run implies `lifecycle_state = verifying`. - A terminal verification run only allows progression when it is current for the selected connection and satisfies readiness conditions. ### Bootstrap invariants - Bootstrap run references remain stored in `state` as selected run metadata. - One or more queued or running relevant bootstrap runs imply `lifecycle_state = bootstrapping`. - Any blocking bootstrap failure moves the workflow to `action_required`. ## State Transition Model | From | Trigger | To | Notes | |---|---|---|---| | `draft` | verification started | `verifying` | Relevant verification run created or reused | | `verifying` | verification succeeds and no bootstrap selected | `ready_for_activation` | Verification must be current for selected connection | | `verifying` | verification succeeds and bootstrap started | `bootstrapping` | Selected bootstrap runs become active | | `verifying` | verification blocked, failed, stale, or mismatched | `action_required` | `reason_code` and optional `blocking_reason_code` set | | `action_required` | blocker resolved by rerun or reset | `verifying`, `bootstrapping`, `ready_for_activation`, or `draft` | Chosen by centralized lifecycle recalculation | | `bootstrapping` | all selected bootstrap runs complete successfully | `ready_for_activation` | No remaining blocker allowed | | `bootstrapping` | selected bootstrap run fails in blocking way | `action_required` | Failure reason preserved | | any editable state | cancel action | `cancelled` | Terminal and immutable | | `ready_for_activation` | activation succeeds | `completed` | Terminal and immutable | ## Concurrency Contract - Every relevant mutation must carry an expected `version`. - The write path must compare expected version to persisted version inside the same transaction or atomic update. - On mismatch, no lifecycle field, checkpoint field, JSON state, or related run reference may be partially written. - Conflict rejection keeps the user on the wizard and returns a visible refresh-required error.