# Feature Specification: Provider Connections (Tenantless UI + Tenant Transparency) **Feature Branch**: `089-provider-connections-tenantless-ui` **Created**: 2026-02-12 **Status**: Draft **Input**: User description: "Provider Connections als workspace-weites Integrations-Asset (tenantless UI) + Tenant-Transparenz" ## Clarifications ### Session 2026-02-12 - Q: Welche Provider sind im MVP-Scope dieser Spec? → A: Nur Microsoft (Graph) im MVP; UI/IA bleibt generisch „Provider Connections“. - Q: Wie wird „aktiver Tenant-Kontext“ für den Default-Filter bestimmt? → A: TenantContext kommt aus Session/Context-Switcher; `tenant_id` Querystring kann den Default überschreiben. - Q: Soll „Provider Connections“ in Filament Global Search erscheinen? → A: Nein, Global Search ist für diese Resource deaktiviert. - Q: Wie soll die Auditability konkret umgesetzt werden? → A: AuditLog + OperationRun. ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Workspace-weite Übersicht (Priority: P1) Als Operator/Admin im Workspace möchte ich Provider Connections zentral unter Settings → Integrations finden und über eine canonical Route ohne Tenant-Parameter aufrufen, damit Integrationen enterprise-typisch auffindbar sind und die UI nicht von tenant-scoped Routes abhängt. **Why this priority**: Stellt Informationsarchitektur wieder her, reduziert Kontextbrüche und eliminiert fragiles tenant-scoped Routing als Voraussetzung. **Independent Test**: Aufruf der canonical Liste und Verifikation von Scoping + Default Filter + Tenant-Spalte. **Acceptance Scenarios**: 1. **Given** ein User ist Mitglied im Workspace und Mitglied in Tenant A, aber nicht in Tenant B, **When** er die Provider-Connections-Liste öffnet, **Then** sieht er ausschließlich Connections aus Tenant A. 2. **Given** ein aktiver Tenant-Kontext ist gesetzt (Tenant A), **When** die Liste geöffnet wird, **Then** ist initial ein Tenant-A Filter aktiv und kann vom User entfernt werden. 3. **Given** ein aktiver Tenant-Kontext ist gesetzt (Tenant A) und der User ruft die Liste mit `?tenant_id=` auf, **When** die Liste lädt, **Then** ist Tenant B als Filter aktiv (Querystring überschreibt den Context-Default), ohne dass nicht-berechtigte Tenants Metadaten leaken. 4. **Given** ein User ist kein Workspace-Mitglied, **When** er die canonical Provider-Connections-Route aufruft, **Then** erhält er 404 (deny-as-not-found). --- ### User Story 2 - Sicherer Detail-/Edit-Zugriff ohne Secrets (Priority: P2) Als Tenant-Mitglied möchte ich eine Provider Connection ansehen und (mit entsprechender Berechtigung) verwalten, damit ich Integrationsprobleme diagnostizieren und beheben kann, ohne dass Secrets im Klartext sichtbar werden. **Why this priority**: Detail-/Edit-Surfaces sind die riskantesten Stellen für Metadaten-Leaks und Secret-Exfiltration. **Independent Test**: Direkte View/Edit Zugriffe mit unterschiedlichen Membership-/Capability-Kombinationen; UI zeigt keine Klartext-Secrets. **Acceptance Scenarios**: 1. **Given** ein User ist Workspace-Mitglied aber nicht Mitglied im owning Tenant einer Connection, **When** er die Detailseite direkt aufruft, **Then** erhält er 404. 2. **Given** ein User ist Tenant-Mitglied, aber ihm fehlt die View-Capability, **When** er Liste oder Detailseite aufruft, **Then** erhält er 403. 3. **Given** ein User hat View-, aber nicht Manage-Capability, **When** er die Detailseite öffnet, **Then** sind Manage-Aktionen sichtbar aber deaktiviert (mit Tooltip), und ein server-seitiger Mutationsversuch wird mit 403 abgewiesen. 4. **Given** irgendein berechtigter User sieht Liste/Detail, **When** Credentials/Secrets dargestellt werden, **Then** werden niemals Klartext-Secrets angezeigt und es gibt keine Copy-Aktionen für Secrets. --- ### User Story 3 - Tenant-Detailseite zeigt effektiven Provider-State + Deep Link (Priority: P3) Als Operator im Tenant-Kontext möchte ich auf der Tenant-Detailseite den effektiven Default-Provider-Connection-Status sehen und über eine CTA direkt zur vorgefilterten Provider-Connections-Liste springen, damit ich Kontext behalte und trotzdem zentral arbeiten kann. **Why this priority**: Reduziert Kontextbruch und macht den „effective state“ dort sichtbar, wo Operators ihn erwarten. **Independent Test**: Tenant-Detailseite zeigt Card + CTA; CTA führt zur gefilterten canonical Liste. **Acceptance Scenarios**: 1. **Given** Tenant A hat eine aufgelöste Default-Connection, **When** der User die Tenant-Detailseite öffnet, **Then** sieht er Display Name + Status/Health + Last Check. 2. **Given** Tenant A hat keine gültige Default-Connection, **When** der User die Tenant-Detailseite öffnet, **Then** sieht er einen klaren „Needs action“-State und eine CTA zur gefilterten Liste. ### Edge Cases - User ist Workspace-Mitglied, aber in keinem Tenant Mitglied → Liste zeigt 0 Rows (ohne Tenant-Hinweise). - `tenant_id` Filter verweist auf einen nicht-mitgliedschaftlich berechtigten Tenant → Liste zeigt 0 Rows; direkte Record-Zugriffe bleiben 404. - Mitgliedschaft wird entzogen → direkte Zugriffe auf vormals sichtbare Records liefern 404. - Sehr viele Tenants/Connections (MSP) → Scoping bleibt performant (server-seitige, membership-basierte Filterung ohne große In-Memory-ID-Listen). - Health/Last Error enthält sensitive Inhalte → UI zeigt nur Reason Codes und gekürzte, nicht-sensitive Messages; keine Secrets. ## Requirements *(mandatory)* ### Functional Requirements - **FR-001 (Canonical route)**: The system MUST expose Provider Connections at a canonical admin route without requiring a tenant route parameter (e.g., `/admin/provider-connections`). - **FR-002 (Tenant filter)**: The canonical list MUST support optional filtering by owning tenant (e.g., `?tenant_id=`). - **FR-003 (Tenant transparency)**: List and detail MUST clearly show the owning tenant (name + environment indicator where applicable) and provide a deep link to tenant detail. - **FR-004 (Context-respecting default filter)**: If a tenant context is active, the list MUST default to filtering by the current tenant. The user MUST be able to remove the filter. - **FR-004a (Default resolution precedence)**: The default tenant filter MUST be resolved from the active TenantContext (session/context switcher). If a `tenant_id` query parameter is present, it MUST take precedence over the TenantContext-derived default. - **FR-005 (Zero-leak scoping)**: Index/List MUST only return connections for tenants where the user is a member. The UI MUST not reveal metadata for non-member tenants. - **FR-006 (Direct access semantics)**: Direct record access (view/edit) MUST be deny-as-not-found (404) when the user is not a member of the owning tenant. - **FR-007 (Workspace gating)**: If the user is not a workspace member, all Provider Connections routes MUST return 404. - **FR-008 (Capability-first RBAC)**: Capabilities MUST gate: view (list/detail), manage (create/edit/enable/disable/set-default/credential updates), run (health checks / operation triggers). Missing capability MUST return 403. - **FR-009 (UI enforcement behavior)**: When missing capability but otherwise eligible (member), the UI SHOULD render actions disabled with a tooltip describing the missing capability (not silently removed), while server-side enforcement stays authoritative. - **FR-010 (No secrets in UI)**: The UI MUST never display plaintext secrets and MUST NOT provide copy actions for secrets. Non-secret identifiers (e.g., Entra tenant ID) MAY be copyable. - **FR-011 (List columns minimum)**: The list MUST include at least: Tenant, Provider, Display name, Entra tenant ID, Default indicator, Status badge, Health badge, Last check timestamp, Last error (reason code + truncated message). - **FR-012 (List filters minimum)**: The list MUST provide filters for: Tenant, Provider, Status, Health, Default-only. - **FR-013 (Tenant detail card)**: Tenant detail MUST show an “effective provider connection state” for the tenant and provide a CTA to open the canonical Provider Connections list pre-filtered for that tenant. - **FR-014 (Legacy redirect)**: Legacy tenant-scoped URLs for provider connections MUST behave according to the “Legacy URL Redirect Matrix” (302 redirects only for entitled members; no-leak 404 otherwise) and remain for at least two releases. - **FR-015 (Scalability requirement)**: Tenant-visibility scoping MUST be performed at query time based on membership relationships and MUST NOT depend on loading large tenant-id lists into memory. - **FR-016 (MVP provider scope)**: The MVP MUST support Microsoft (Graph) as the only provider. The navigation label and IA remain generic (“Provider Connections”). - **FR-017 (Global Search)**: Provider Connections MUST NOT appear in global search. - **FR-018 (Create tenant resolution)**: Create MUST resolve the target tenant to an entitled tenant via `tenant_id` query parameter or the active TenantContext default. If no entitled tenant can be resolved, the create surface MUST behave as not found (404). ### Information Architecture - Provider Connections MUST be placed under Settings → Integrations → Provider Connections. - Provider Connections MUST feel “workspace-level” even when a tenant context is active (context affects default filtering, not canonical addressability). ### Scope Boundaries - In scope: tenantless canonical navigation and routing; tenant transparency in list/detail; context-respecting default filter; deny-as-not-found membership rules; capability-first action gating; legacy redirect behavior. - Out of scope: shared connections across multiple tenants; redesign of default/override semantics; changing the meaning of “default”; large wizard refactors. ### Assumptions & Dependencies - A "workspace" membership model exists and can be evaluated for every request. - A "tenant membership" model exists and is the source of truth for which tenants a user may access. - Provider Connections already belong to exactly one owning tenant and one workspace. - A tenant detail surface exists where the “Provider connection” card can be shown. - The system has, or can represent, a provider identifier for connections (even if only Microsoft exists in the MVP). - The system has an active TenantContext concept (e.g., chosen via a context switcher) that can be read when rendering admin pages. ### Query Parameter Contract - **QP-001**: The canonical tenant filter query parameter name is `tenant_id`. - **QP-002**: The `tenant_id` value is the managed tenant’s external identifier (the same identifier used in `/admin/tenants/{tenant}` routes), not a database primary key. ### Tenant Transparency Conventions - **TT-001**: “Environment indicator” means a human-readable label that distinguishes tenant environments when such labeling exists in the workspace (e.g., Production vs Staging). If no such label exists for a tenant, the UI MUST omit the environment indicator (not substitute guesses). ### Default Filter Removal - **DF-001**: The default tenant filter is a usability default only. Users MAY remove it at any time. - **DF-002**: Removing the default filter MUST NOT change authorization boundaries: list and detail remain scoped to tenants the user is a member of. ### Authorization Semantics (404 vs 403) - **AS-001**: Not a workspace member → 404 for list/detail/edit and any action endpoints. - **AS-002**: Workspace member but not a member of the owning tenant → list returns no rows for that tenant; direct record access → 404. - **AS-003**: Tenant member but missing required capability → 403 for the protected surface/action. ### Audit & Observability - **AO-001**: User-initiated actions that change state (set default, enable/disable, credentials update/rotate) MUST be auditable. - **AO-002**: User-initiated run/health actions MUST be auditable, either via an audit event or a run record that is reachable via a canonical “view run” link. - **AO-003 (Decision)**: Manage/state-change actions MUST write an AuditLog entry. Run/health actions MUST create an OperationRun (or equivalent run record) that is reachable via a canonical “view run” link. ### Backward Compatibility - **BC-001**: Redirect MUST preserve intent (tenant filter) and MUST NOT leak tenant names/metadata for non-members. - **BC-002**: Deprecation window is at least two releases. ### Legacy URL Redirect Matrix - **LR-001 (Redirect, tenant-managed scope)**: Requests to legacy workspace-managed tenant routes MUST redirect (302) to the canonical route and preserve intent via the tenant filter: - `/admin/tenants/{tenant_external_id}/provider-connections` → `/admin/provider-connections?tenant_id={tenant_external_id}` - `/admin/tenants/{tenant_external_id}/provider-connections/create` → `/admin/provider-connections/create?tenant_id={tenant_external_id}` - `/admin/tenants/{tenant_external_id}/provider-connections/{record}/edit` → `/admin/provider-connections/{record}/edit?tenant_id={tenant_external_id}` - **LR-002 (No-leak)**: For non-workspace members or users who are not members of the target tenant, legacy URLs MUST behave as not found (404) and MUST NOT redirect. - **LR-003 (Explicit exclusion)**: The previously removed tenant-panel management route shape `/admin/t/{tenant_external_id}/provider-connections` remains not found (404) and is not part of the redirect surface. ## UI Action Matrix *(mandatory when Filament is changed)* | Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | |---|---|---|---|---|---|---|---|---|---|---| | Provider Connections (List) | Admin → Settings → Integrations → Provider Connections | Create (manage) | Tenant deep link + View action | View (view), Edit (manage) | None (not required) | Create (manage) | n/a | n/a | Yes | Default tenant filter applies when tenant context active | | Provider Connection (View) | Provider Connections → View | Enable/Disable (manage), Set default (manage), Health check (run), Credential update (manage) | n/a | Edit (manage) | None | n/a | Same as header actions | n/a | Yes | Non-membership is 404; missing capability is 403; destructive-like actions require confirmation | | Provider Connection (Create/Edit) | Provider Connections → Create/Edit | None | n/a | None | None | n/a | n/a | Save (manage), Cancel | Yes | Secrets never displayed; credential updates require confirmation | | Tenant detail “Provider connection” card | Tenant detail page | Open Provider Connections (view) | n/a | None | None | Optional: Create connection (manage) | n/a | n/a | No | CTA links to canonical list filtered by tenant | ### Key Entities *(include if feature involves data)* - **Workspace**: A membership-gated scope that owns managed tenants and integrations. - **Managed Tenant**: A tenant within a workspace; users are members of specific tenants. - **Provider Connection**: An integration record owned by exactly one managed tenant and one workspace; includes status/health metadata and non-secret identifiers. - **Capabilities**: Named permissions that gate view/manage/run behavior; UI enforcement reflects missing capabilities. - **Audit Event / Run Record**: Captures sensitive user-initiated actions for later review. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001 (IA discoverability)**: A workspace admin can reach Provider Connections from the admin sidebar in ≤ 2 clicks (Settings → Integrations → Provider Connections). - **SC-002 (No metadata leaks)**: For non-members of a tenant, 100% of direct access attempts to that tenant’s Provider Connection detail/edit routes return 404 and do not reveal tenant/provider/status/health metadata. - **SC-003 (RBAC correctness)**: For tenant members lacking a required capability, 100% of protected endpoints return 403; the UI consistently shows disabled actions with an explanatory tooltip. - **SC-004 (Context-respecting UX)**: From a tenant detail page, the “Open Provider Connections” CTA lands on a pre-filtered list for that tenant with a first-attempt success rate ≥ 95% in acceptance testing.