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