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

3.6 KiB

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.