TenantAtlas/specs/089-provider-connections-tenantless-ui/data-model.md
ahmido fb4de17c63 feat(spec-089): provider connections tenantless UI (#107)
Implements Spec 089: moves Provider Connections to canonical tenantless route under `/admin/provider-connections`, enforces 404/403 semantics (workspace/tenant membership vs capability), adds tenant transparency (tenant column + filter + deep links), adds legacy redirects for old tenant-scoped URLs without leaking Location for 404 cases, and adds regression test coverage (RBAC semantics, filters, UI enforcement tooltips, Microsoft-only MVP scope, navigation placement).

Notes:
- Filament v5 / Livewire v4 compatible.
- Global search remains disabled for Provider Connections.
- Destructive/manage actions require confirmation and are policy-gated.

Tests:
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #107
2026-02-12 16:35:13 +00:00

118 lines
3.6 KiB
Markdown

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