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