TenantAtlas/specs/089-provider-connections-tenantless-ui/plan.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

117 lines
7.1 KiB
Markdown

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