# Data Model: Provider Connection Full Cutover

**Feature**: [specs/081-provider-connection-cutover/spec.md](spec.md)  
**Date**: 2026-02-07

This document describes the entities involved in Spec 081 using the repo’s current schema.

## Entities

### Tenant

**Represents**: A managed tenant target (Entra/Intune tenant) within a workspace.

**Relevant attributes (existing)**

- `id` (PK)
- `workspace_id` (FK)
- `name`
- `tenant_id` (GUID-ish, used as Entra tenant ID)
- `external_id` (alternate tenant identifier)
- Legacy (deprecated by this spec):
  - `app_client_id`
  - `app_client_secret` (encrypted)
  - `app_certificate_thumbprint`
  - `app_notes`

**Derived helpers (existing)**

- `graphTenantId(): ?string` returns `tenant_id` or `external_id`.
- `graphOptions(): array{tenant:?string,client_id:?string,client_secret:?string}` (to be deprecated/unused at runtime).

**Relationships (existing)**

- `providerConnections(): HasMany` → ProviderConnection
- `providerCredentials(): HasManyThrough` → ProviderCredential (via ProviderConnection)
- `operationRuns(): HasMany` (implicit via OperationRun.tenant_id)

### ProviderConnection

**Represents**: A workspace-owned integration connection for a tenant + provider (e.g., Microsoft).

**Table**: `provider_connections`

**Fields (existing)**

- `id` (PK)
- `workspace_id` (FK, NOT NULL after migration)
- `tenant_id` (FK)
- `provider` (string, e.g. `microsoft`)
- `entra_tenant_id` (string; expected to match the tenant’s Entra GUID)
- `display_name` (string)
- `is_default` (bool)
- `status` (string; default `needs_consent`)
- `health_status` (string; default `unknown`)
- `scopes_granted` (jsonb array)
- `last_health_check_at` (timestamp)
- `last_error_reason_code` (string, nullable)
- `last_error_message` (string, nullable; must be sanitized)
- `metadata` (jsonb)
- `created_at`, `updated_at`

**Relationships (existing)**

- `tenant(): BelongsTo`
- `workspace(): BelongsTo`
- `credential(): HasOne` → ProviderCredential

**Invariants (existing + required)**

- Uniqueness: `unique (tenant_id, provider, entra_tenant_id)`
- Exactly one default per (tenant_id, provider): enforced by partial unique index `provider_connections_default_unique`.

**Behaviors (existing)**

- `makeDefault()` clears other defaults and sets this record default in a DB transaction.

### ProviderCredential

**Represents**: Encrypted credential material for a provider connection.

**Table**: `provider_credentials`

**Fields (existing)**

- `id` (PK)
- `provider_connection_id` (FK, unique)
- `type` (string; default `client_secret`)
- `payload` (encrypted array; hidden from serialization)
- `created_at`, `updated_at`

**Payload contract (current)**

- For `type=client_secret`:
  - `client_id` (string)
  - `client_secret` (string)
  - optional `tenant_id` (string) validated against `ProviderConnection.entra_tenant_id`

### OperationRun

**Represents**: A canonical record for a long-running or operationally relevant action.

**Table**: `operation_runs`

**Key fields (existing)**

- `id` (PK)
- `workspace_id` (FK)
- `tenant_id` (FK nullable in some cases)
- `type` (string)
- `status` (`queued`|`running`|`completed`)
- `outcome` (`pending`|`succeeded`|`partially_succeeded`|`failed` + reserved `cancelled`)
- `context` (json)
- `failure_summary` (json)
- `summary_counts` (json)
- `started_at`, `completed_at`

**Context contract (provider-backed runs)**

- `provider` (string)
- `provider_connection_id` (int)
- `target_scope.entra_tenant_id` (string)
- `module` (string; from ProviderOperationRegistry definition)

**Spec 081 extension (planned)**

- Introduce `outcome=blocked` and store `reason_code` + link-only `next_steps` in safe context/failure summary.

## State Transitions

### ProviderConnection default selection

- `is_default: false -> true` via `makeDefault()`.
- Invariant: only one default per (tenant_id, provider).

### Provider-backed operation starts

- Start surface enqueues work and creates/dedupes `OperationRun`.
- If blocked (missing default connection/credential), an `OperationRun` is still created and finalized as blocked.

