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
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)whereis_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:
tenant_idquerystring (takes precedence)- 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)
providermust be one of allowed providers (MVP:microsoft).entra_tenant_idmust be present and formatted as expected (string).display_namerequired.- Actions that toggle
is_defaultmust 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.