TenantAtlas/specs/080-workspace-managed-tenant-admin/plan.md
ahmido 3f09fd50f6 feat(spec-080): workspace-managed tenant administration migration (#97)
Implements Spec 080: split Filament into workspace-managed `/admin/*` (manage) vs tenant operations `/admin/t/{tenant}/*` (operate).

Highlights:
- Adds tenant operations panel (`tenant`) at `/admin/t` with tenancy by `Tenant.external_id`
- Keeps management resources in workspace panel (`admin`) under `/admin/tenants/*`
- Moves Provider Connections to workspace-managed routes: `/admin/tenants/{tenant}/provider-connections`
- Adds discoverability CTA on tenant view (Actions → Provider connections)
- Adds/updates Pest regression tests for routing boundaries, 404/403 RBAC-UX semantics, and global search isolation
- Includes full Spec Kit artifacts under `specs/080-workspace-managed-tenant-admin/`

Validation:
- `vendor/bin/sail bin pint --dirty`
- `vendor/bin/sail artisan test --compact tests/Feature/Spec080WorkspaceManagedTenantAdminMigrationTest.php`

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #97
2026-02-07 19:45:13 +00:00

9.3 KiB

Implementation Plan: Spec 080 Workspace-Managed Tenant Administration Migration

Branch: 080-workspace-managed-tenant-admin | Date: 2026-02-07 | Spec: /specs/080-workspace-managed-tenant-admin/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/080-workspace-managed-tenant-admin/spec.md

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

Summary

Migrate tenant administration surfaces out of tenant scope (/admin/t/{tenant}/*) into workspace scope (/admin/*) and keep tenant scope strictly for operational modules.

Implementation strategy:

  • Introduce a second Filament panel for tenant operations at /admin/t/{tenant}.
  • Convert the existing admin panel at /admin into a tenantless workspace management panel.
  • Register management resources/pages only in the workspace panel, ensuring tenant-scoped management routes are not registered (404).
  • Rewire internal CTAs/links (onboarding, required permissions, provider connection edit) to the new canonical workspace routes.

Technical Context

Language/Version: PHP 8.4.15 (Laravel 12)
Primary Dependencies: Filament v5, Livewire v4, Tailwind v4
Storage: PostgreSQL (via Sail)
Testing: Pest v4 (PHPUnit v12 runner)
Target Platform: Web application (server-rendered Filament/Livewire)
Project Type: Laravel monolith
Performance Goals: No new performance targets; management viewers must remain DB-only at render time
Constraints:

  • No external calls during render for management viewers (DB-only).
  • Dev-stage removed routes must 404 (no redirects).
  • 404 vs 403 semantics must follow RBAC-UX. Scale/Scope: Enterprise SaaS IA separation across admin surfaces (route + navigation correctness)

Constitution Check

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

Assessment (pre-Phase 0): PASS

  • No new Graph calls are introduced by this migration.
  • No new long-running operations are introduced.
  • Authorization behavior is being tightened via panel separation and route registration.
  • Monitoring/management viewers remain DB-only.

Notes:

  • This feature changes how routes are registered (which affects discovery, global search, and navigation). It must include regression tests ensuring removed tenant-scoped management routes do not exist.

  • 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)

  • 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

Project Structure

Documentation (this feature)

specs/[###-feature]/
├── 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/
│   ├── Pages/
│   │   ├── Workspaces/
│   │   ├── Monitoring/
│   │   └── …
│   ├── Resources/
│   └── Concerns/
├── Providers/
│   └── Filament/
│       ├── AdminPanelProvider.php
│       ├── TenantPanelProvider.php
│       └── SystemPanelProvider.php
├── Support/
│   └── Middleware/
routes/
└── web.php

tests/
├── Feature/
└── Unit/

bootstrap/
└── providers.php

Structure Decision: Laravel monolith. Panel providers define panel boundaries. Routes outside Filament are defined in routes/web.php.

Complexity Tracking

No constitution violations requiring justification.

Phase 0 — Outline & Research

Outputs (written during /speckit.plan):

  • /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/080-workspace-managed-tenant-admin/research.md

Research questions (all resolved):

  • How to configure a tenancy panel whose URLs are /admin/t/{tenant} without duplicating /t/.
  • How to enforce route removal: do not register resources/pages in the tenant panel.
  • How to keep global search isolated by panel registration.

Phase 1 — Design & Contracts

Outputs:

  • /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/080-workspace-managed-tenant-admin/data-model.md
  • /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/080-workspace-managed-tenant-admin/contracts/
  • /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/080-workspace-managed-tenant-admin/quickstart.md

Panel Design

Workspace panel (Manage)

  • ID: admin (keep existing ID to avoid breaking panel:admin middleware usage)
  • Path: /admin
  • Tenancy: disabled (no ->tenant(...))
  • Registered artifacts: tenant management resources/pages, monitoring pages.

Tenant panel (Operate)

  • ID: tenant (new)
  • Path: /admin/t
  • Tenancy: enabled via Tenant::class with slugAttribute: 'external_id'
  • Tenant route prefix: blank/null so tenant routes become /admin/t/{tenant}/...
  • Registered artifacts: operational resources/pages only (inventory, drift, backups, policies, directory, etc.).
  • Route-shape verification is mandatory: automated regression checks must assert canonical tenant URLs are /admin/t/{tenant} and /admin/t/{tenant}/... (never /admin/t/t/{tenant}).

Laravel 11+ provider registration requirement:

  • Register the new tenant panel provider in /Users/ahmeddarrazi/Documents/projects/TenantAtlas/bootstrap/providers.php.

Routing/Removal Mechanism

Tenant-scoped management routes return 404 by construction:

  • Management resources/pages are not registered in the tenant panel.
  • No redirects (dev-stage).

Authorization Design

  • Workspace management pages: require selected workspace + membership; non-member → 404.
  • Tenant operational routes: require workspace membership + tenant entitlement; non-entitled → 404.
  • Mutations: capability missing → 403 (server-side policy/gate); destructive-like actions require ->requiresConfirmation().

Global Search Design

  • Workspace panel: managed tenants are searchable (ensure resource has Edit/View page).
  • Tenant panel: tenant-management entities are not registered, so they cannot appear in global search.

Phase 2 — Implementation Plan (Code + Tests)

Stop condition for /speckit.plan: this section outlines implementation, but actual task breakdown happens in /speckit.tasks.

Planned steps:

  1. Add new panel provider for tenant operations (e.g., App\Providers\Filament\TenantPanelProvider).
  2. Register provider in bootstrap/providers.php (Laravel 11+ pattern).
  3. Refactor AdminPanelProvider into a tenantless workspace panel:
  • remove tenancy configuration (->tenant(...), tenant menu, tenant route prefix)
  • remove tenant-only middleware from the workspace panel pipeline
  1. Move/register management pages/resources into workspace panel:
  • TenantResource (managed tenants CRUD / manage view)
  • ProviderConnectionResource (workspace-managed connections by tenant)
  • required permissions viewer page
  • membership management surfaces
  • onboarding/activation surfaces
  1. Move/register operational pages/resources into the tenant panel.
  2. Rewire internal links/CTAs that currently build tenant-scoped management URLs to the new workspace-managed canonical URLs.
  3. Add regression tests (Pest) to cover:
  • workspace member can access /admin/tenants*
  • non-member gets 404
  • tenant entitlement required for /admin/t/{tenant}/...
  • canonical tenant panel route shape is /admin/t/{tenant}/... (no duplicated /t)
  • tenant-scoped management routes are missing (404)
  • link rewiring expectations (where feasible)
  1. Run targeted test pack and Pint:
  • vendor/bin/sail artisan test --compact tests/Feature/...
  • vendor/bin/sail bin pint --dirty