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

7.1 KiB

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

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)

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)

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.