# Phase 1 Data Model: Canonical Provider Connection State Cleanup ## Overview This feature removes the persisted legacy provider-state columns `status` and `health_status` and replaces their last remaining behavioral responsibility with one explicit lifecycle truth: `is_enabled`. Consent and verification remain on their existing canonical enums. No new table, no new persisted summary artifact, and no new cross-domain provider-state framework are introduced. ## Persistent Source Truths ### ProviderConnection **Purpose**: Canonical tenant-owned provider integration record for provider lifecycle, consent, verification, and supporting diagnostics. **Key fields**: - `id` - `tenant_id` - `workspace_id` - `provider` - `entra_tenant_id` - `display_name` - `is_default` - `connection_type` - `is_enabled` - `consent_status` - `consent_granted_at` - `consent_last_checked_at` - `consent_error_code` - `consent_error_message` - `verification_status` - `migration_review_required` - `migration_reviewed_at` - `scopes_granted` - `last_health_check_at` - `last_error_reason_code` - `last_error_message` - `metadata` **Removed fields**: - `status` - `health_status` **Relationships**: - `ProviderConnection` belongs to `Tenant` - `ProviderConnection` belongs to `Workspace` - `ProviderConnection` has one `ProviderCredential` **Validation rules**: - `is_enabled` is the only persisted lifecycle truth and defaults to `true` for newly created connections. - `consent_status` remains the only consent truth. - `verification_status` remains the only verification truth. - `status` and `health_status` are not recreated through accessors, casts, appended attributes, helper arrays, or audit context shims. - Existing unique constraints on tenant, provider, and Entra tenant identity remain unchanged. - Existing indexes for `consent_status` and `verification_status` remain. Legacy indexes tied to removed columns are dropped. No new lifecycle index is introduced unless implementation profiling proves it necessary. ### ProviderCredential **Purpose**: Encrypted credential material associated with a provider connection. **Key fields**: - `provider_connection_id` - encrypted credential payload fields **Relationships**: - `ProviderCredential` belongs to `ProviderConnection` **Validation rules**: - This feature does not change credential storage or encryption. - Credential presence remains a diagnostic or blocker input for lifecycle-safe verification semantics, not a fourth provider-state dimension. ### HealthResult (internal contract) **Purpose**: Canonical result object emitted by provider health checks before persistence. **Key fields**: - `verification_status` - `reason_code` - `message` - `meta` **Validation rules**: - The contract must not carry legacy `status` or `health_status` fields. - The contract may carry only the minimum data needed to derive canonical consent and verification outcomes plus diagnostics. ## Canonical State Families ### Lifecycle (derived from `is_enabled`) **Persisted form**: - `is_enabled = true` - `is_enabled = false` **Operator-facing form**: - `Enabled` - `Disabled` **Rule**: - Lifecycle answers only whether the connection is administratively allowed to operate. - Lifecycle does not imply consent and does not imply the latest verification outcome. ### ProviderConsentStatus **Values**: - `unknown` - `required` - `granted` - `failed` - `revoked` **Rule**: - Consent answers only whether required provider consent currently exists and whether consent-specific failure or revocation was detected. - Consent does not imply lifecycle and does not imply technical verification success. ### ProviderVerificationStatus **Values**: - `unknown` - `pending` - `healthy` - `degraded` - `blocked` - `error` **Rule**: - Verification answers only what the latest verification attempt or local canonical blocker currently proves. - Verification does not imply lifecycle and does not rewrite consent. ## State Transitions ### Lifecycle transitions | From | To | Trigger | Notes | |------|----|---------|-------| | `true` | `false` | Explicit disable action | Consent and verification remain intact as separate truths. | | `false` | `true` | Explicit enable action | Lifecycle resumes without fabricating consent or a healthy verification state. | ### Consent transitions | From | To | Trigger | Notes | |------|----|---------|-------| | `unknown` or `required` | `granted` | Successful admin consent callback or successful verification that proves consent still exists | Does not change lifecycle. | | `granted` | `revoked` | Verification detects consent was revoked | Verification may become `blocked`, but lifecycle remains separate. | | any | `failed` | Consent-specific failure during callback or verification | Does not disable the connection. | ### Verification transitions | From | To | Trigger | Notes | |------|----|---------|-------| | `unknown` | `pending` | Verification start | Existing start surfaces remain authoritative. | | `pending` | `healthy`, `degraded`, `blocked`, or `error` | Verification completion | Derived from the health-check result and current consent truth. | | any | `unknown` | New connection creation, successful enable with present credentials, or credential mutation that invalidates prior verification but does not prove a blocker | Keeps stale success from surviving a material configuration change. | | any | `blocked` | Local or remote evidence proves a blocker, such as missing credentials, invalid connection type, consent missing, or review-required state | Blocked remains a verification consequence, not a lifecycle substitute. | ## Mutation Rules ### Connection creation and onboarding bootstrap **Expected persisted shape**: - `is_enabled = true` - `consent_status = required` for pre-consent onboarding starts - `verification_status = unknown` - diagnostics cleared or initialized from the current event ### Consent callback **Expected persisted shape**: - lifecycle unchanged or initialized to `is_enabled = true` - `consent_status` updated from callback outcome - `verification_status = unknown` - consent and error timestamps refreshed ### Verification start **Expected persisted shape**: - lifecycle unchanged - consent unchanged - `verification_status = pending` - legacy status or health projections are not written ### Verification completion / health check **Expected persisted shape**: - lifecycle unchanged - `consent_status` updated only when the result proves a consent consequence - `verification_status` updated from the canonical health result - error and recency diagnostics refreshed ### Enable and disable actions **Expected persisted shape**: - Disable writes only `is_enabled = false` plus any audit metadata that records the lifecycle change. - Enable writes `is_enabled = true` and resets stale verification truth without fabricating a healthy state. - When enabling reveals a local blocker such as missing credentials, `verification_status` becomes `blocked` and the blocker reason is recorded in diagnostics. ### Credential-source and mutation-service changes **Expected persisted shape**: - lifecycle unchanged unless the operator explicitly disables the connection - consent unchanged unless the mutation directly changes consent evidence - verification reset to `unknown` or `blocked` depending on whether the mutation invalidates prior verification or proves a blocker ## Derived Surface Contracts ### Canonical provider state bundle **Purpose**: Shared derived state used by provider detail, edit, tenant summaries, and system read-only views. | Field | Type | Required | Description | |------|------|----------|-------------| | `lifecycle` | string | yes | `enabled` or `disabled`, derived from `is_enabled` | | `isEnabled` | boolean | yes | Raw persisted lifecycle truth | | `consentStatus` | string nullable | yes | Canonical consent status | | `verificationStatus` | string nullable | yes | Canonical verification status | | `connectionType` | string | yes | Platform or dedicated | | `isDefault` | boolean | yes | Default designation | | `lastCheckedAt` | string nullable | no | Stored verification recency | | `lastErrorReasonCode` | string nullable | no | Latest diagnostic reason | | `lastErrorMessage` | string nullable | no | Latest diagnostic message | | `migrationReviewRequired` | boolean | yes | Existing migration-review diagnostic | **Validation rules**: - No helper array or view model may expose `status` or `health_status` keys. - Diagnostics remain subordinate to lifecycle, consent, and verification. ### Tenant provider summary **Purpose**: Compact provider summary rendered from `TenantResource::providerConnectionState()` and the shared provider-state Blade entry. | Field | Type | Required | Description | |------|------|----------|-------------| | `state` | string | yes | `missing`, `configured`, or `default_configured` | | `cta_url` | string | yes | Canonical provider-connections destination | | `needs_default_connection` | boolean | yes | Signals action needed without inventing provider health | | `display_name` | string nullable | no | Chosen connection display name | | `provider` | string nullable | no | Provider label | | `is_enabled` | boolean nullable | no | Raw lifecycle truth when a connection exists | | `consent_status` | string nullable | no | Canonical consent status | | `verification_status` | string nullable | no | Canonical verification status | | `last_health_check_at` | string nullable | no | Stored verification recency | | `last_error_reason_code` | string nullable | no | Latest diagnostic reason | **Validation rules**: - Missing or non-default states must never read as healthy or ready. - The summary must not return removed legacy keys for compatibility. ### System tenant health rollup **Purpose**: Read-only system-directory aggregate used to derive `SystemHealth` badges. | Field | Type | Required | Description | |------|------|----------|-------------| | `tenantStatus` | string | yes | Existing tenant lifecycle/status input | | `criticalProviderCount` | integer | yes | Count of each tenant's default Microsoft provider connection when its `verification_status` is `blocked` or `error` | | `warningProviderCount` | integer | yes | Count of each tenant's default Microsoft provider connection when its `verification_status = degraded` | | `missingPermissionCount` | integer | yes | Existing tenant-permission signal | **Validation rules**: - System health rollups must not query removed `health_status` values. - Tenant-directory list rollups count only the default Microsoft provider connection for each tenant. Non-default connections may still appear on the system tenant detail page, but they do not affect list rollup counts. - The same provider connection must produce semantically aligned admin and system readings. ## Removal Rules 1. `status` and `health_status` are removed from the database schema, Eloquent model interactions, helper arrays, Blade views, badge domains, query filters, and audit metadata. 2. `ProviderConnection::classificationProjection()` and any equivalent projection helpers no longer emit removed fields. 3. Runtime gates read lifecycle from `is_enabled`, not from any verification or consent surrogate. 4. Diagnostics remain stored and displayed only as supporting facts, not as another state language. ## No New Persistence Beyond The Narrow Lifecycle Field - No new table is introduced. - No new provider-readiness summary is persisted. - No new enum class is introduced for lifecycle; the persisted truth is a boolean and the operator-facing labels are derived. - The cleanup removes more persisted truth than it adds: two legacy columns out, one lifecycle column in.