TenantAtlas/specs/188-provider-connection-state-cleanup/data-model.md
2026-04-10 12:31:26 +02:00

272 lines
12 KiB
Markdown

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