# Implementation Plan: Provider Connections (Tenantless UI + Tenant Transparency)

**Branch**: `089-provider-connections-tenantless-ui` | **Date**: 2026-02-12 | **Spec**: `specs/089-provider-connections-tenantless-ui/spec.md`
**Input**: Feature specification from `specs/089-provider-connections-tenantless-ui/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.

## Summary

Move Provider Connections to a canonical tenantless admin route (`/admin/provider-connections`) while enforcing workspace/tenant isolation (404 for non-members) and capability-first authorization (403 for members lacking capability). The list/details stay tenant-transparent (tenant column + deep links) and respect an active tenant context for default filtering, with a `tenant_id` querystring override.

## Technical Context

<!--
  ACTION REQUIRED: Replace the content in this section with the technical details
  for the project. The structure here is presented in advisory capacity to guide
  the iteration process.
-->

**Language/Version**: PHP 8.4.15 (Laravel 12)  
**Primary Dependencies**: Filament v5, Livewire v4, Laravel Sail, PostgreSQL  
**Storage**: PostgreSQL (Sail locally; production via Dokploy)  
**Testing**: Pest v4 (+ PHPUnit runner), Livewire component testing for Filament pages/actions  
**Target Platform**: Web app (Laravel), admin panel under `/admin`  
**Project Type**: Web application (single Laravel monolith)  
**Performance Goals**: JOIN-based membership scoping (no large `whereIn` lists), eager-load tenant on list/detail, paginate safely for MSP-scale datasets  
**Constraints**: Workspace non-members are 404; tenant non-members are 404; capability denial is 403 after membership is established; no plaintext secrets rendered; global search disabled for Provider Connections  
**Scale/Scope**: MSP-style workspaces with many tenants and many provider connections

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- Inventory-first: clarify what is “last observed” vs snapshots/backups
- Read/write separation: any writes require preview + confirmation + audit + tests
- Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php`
- Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
- RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; non-member tenant access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks)
- Workspace isolation: non-member workspace access is 404; tenant-plane routes require an established workspace context; workspace context switching is separate from Filament Tenancy
- RBAC-UX: destructive-like actions require `->requiresConfirmation()` and clear warning text
- RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun`
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
- Badge semantics (BADGE-001): status-like badges use `BadgeCatalog` / `BadgeRenderer`; no ad-hoc mappings; new values include tests
- Filament UI Action Surface Contract: for any new/modified Filament Resource/RelationManager/Page, define Header/Row/Bulk/Empty-State actions, ensure every List/Table has a record inspection affordance (prefer `recordUrl()` clickable rows; do not render a lone View row action), keep max 2 visible row actions with the rest in “More”, group bulk actions, require confirmations for destructive actions (typed confirmation for large/bulk where applicable), write audit logs for mutations, enforce RBAC via central helpers (non-member 404, member missing capability 403), and ensure CI blocks merges if the contract is violated or not explicitly exempted

**Gate evaluation (pre-Phase 0)**

- Inventory-first: Not impacted (UI/routing + entitlement/scoping only).
- Read/write separation: Manage actions are state-changing and will be confirmed + audited; run/health uses OperationRun.
- Graph contract path: Any health check job must use existing Graph abstractions; no external calls at render time.
- Deterministic capabilities: Uses existing capability registry (`PROVIDER_VIEW`, `PROVIDER_MANAGE`, `PROVIDER_RUN`).
- Workspace/Tenant isolation: Enforced via existing membership middleware + policy deny-as-not-found patterns.
- Global search: Explicitly disabled for Provider Connections.
- Action surface contract: List/detail surfaces must keep inspection affordance and proper action grouping.

## Project Structure

### Documentation (this feature)

```text
specs/089-provider-connections-tenantless-ui/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)
```text
app/
├── Filament/
│   └── Resources/
│       └── ProviderConnectionResource.php
│       └── ProviderConnectionResource/
│           └── Pages/
│               ├── ListProviderConnections.php
│               ├── CreateProviderConnection.php
│               └── EditProviderConnection.php
├── Policies/
│   └── ProviderConnectionPolicy.php
├── Support/
│   ├── Auth/Capabilities.php
│   ├── Rbac/UiEnforcement.php
│   └── Workspaces/WorkspaceContext.php
└── Http/
    └── Middleware/
        ├── EnsureWorkspaceMember.php
        └── EnsureWorkspaceSelected.php

routes/
└── web.php

tests/
├── Feature/
└── Unit/
    ├── Filament/
    └── Policies/
```

**Structure Decision**: Implement as a Laravel/Filament change (resource slug, navigation placement, scoping, and legacy redirects) plus targeted Pest tests. No new standalone services or packages are required.

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |

No constitution violations are required for this feature.
