# Phase 1 Design — Data Model (Provider Connections)

**Feature**: Provider Connections (tenantless UI + tenant transparency)

This feature primarily re-frames existing data under a canonical tenantless UI while enforcing workspace/tenant isolation.

## Entities

### Workspace

Represents the top-level isolation boundary. Users must be workspace members to access any Provider Connections surface.

**Key relationships**
- Workspace has many Tenants.
- Workspace has many ProviderConnections.

### Tenant

A managed tenant inside a Workspace.

**Key relationships**
- Tenant belongs to Workspace (`tenants.workspace_id`).
- Tenant has many ProviderConnections (`provider_connections.tenant_id`).

### ProviderConnection

An integration record owned by exactly one Tenant (and by extension, one Workspace).

**Storage**: `provider_connections` (PostgreSQL)

**Key columns (existing schema)**
- `workspace_id` (FK-ish, scoped to Workspace)
- `tenant_id` (FK to Tenants)
- `provider` (string; MVP: `microsoft`)
- `entra_tenant_id` (string)
- `display_name` (string)
- `is_default` (bool)
- `status` (string)
- `health_status` (string)
- `scopes_granted` (jsonb)
- `last_health_check_at` (timestamp)
- `last_error_reason_code` (string|null)
- `last_error_message` (string|null; sanitized/truncated)
- `metadata` (jsonb)

**Constraints / indexes (existing)**
- Unique: `(tenant_id, provider, entra_tenant_id)`
- Partial unique: one default per `(tenant_id, provider)` where `is_default = true`

**Relationships**
- ProviderConnection belongs to Tenant.
- ProviderConnection belongs to Workspace.

**State / badges**
- `status`: expected to map through BadgeCatalog (domain: ProviderConnectionStatus)
- `health_status`: expected to map through BadgeCatalog (domain: ProviderConnectionHealth)

### TenantMembership (isolation boundary)

A user-to-tenant entitlement table (exact schema is implementation-defined in the app; referenced by existing auth services).

**Purpose**
- Drives query-time scoping (JOIN-based) for list views.
- Drives deny-as-not-found semantics for direct record access.

### WorkspaceMembership (isolation boundary)

A user-to-workspace entitlement table.

**Purpose**
- Gates the entire feature with 404 semantics for non-members.

### AuditLog (governance)

Used for state-changing actions (manage actions).

**Requirements**
- Stable action IDs.
- Redacted/sanitized payloads (no secrets).

### OperationRun (observability)

Used for run/health actions.

**Requirements**
- Canonical “view run” link.
- Reason codes + sanitized messages.

## Query Scoping (JOIN-based)

**Goal**: No metadata leaks and MSP-scale performance.

List and global data pulls MUST:
- Start from `provider_connections`.
- JOIN to tenants and membership tables to enforce entitlement.
- Optionally apply a tenant filter based on:
  1) `tenant_id` querystring (takes precedence)
  2) otherwise, active TenantContext-derived default

Direct record access MUST:
- Resolve the owning tenant from the record.
- If user is not entitled to that tenant (or not a workspace member), respond 404.
- If entitled but lacking capability, respond 403.

## Validation Rules (UI-level)

- `provider` must be one of allowed providers (MVP: `microsoft`).
- `entra_tenant_id` must be present and formatted as expected (string).
- `display_name` required.
- Actions that toggle `is_default` must preserve the partial unique invariant.
- Any stored error messages must be sanitized and truncated.

## Data Minimization / Secrets

- No plaintext secrets are ever rendered.
- No “copy secret” affordances.
- Non-secret identifiers (e.g., Entra tenant ID) may be copyable.
