TenantAtlas/specs/068-workspaces-v2/plan.md
2026-02-01 12:19:57 +01:00

9.7 KiB

Implementation Plan: Workspace Model, Memberships & Managed Tenants (v2)

Branch: 068-workspaces-v2 | Date: 2026-01-31 | Spec: spec.md Input: Feature specification from spec.md

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

Summary

Introduce a Workspace hierarchy as the primary scope for the tenant-plane admin panel:

  • Workspace-scoped routing (/admin/w/{workspace}/...) with deny-as-not-found (404 semantics) for non-members.
  • Workspace memberships (owner/manager/operator/readonly) as the source of truth for access and capability derivation.
  • Workspace switcher + choose-workspace/no-access entry flows with session + users.last_workspace_id persistence.
  • Managed Tenants become children of Workspaces; onboarding entry becomes canonical under Workspace scope.
  • Global search becomes workspace-safe (results only within current workspace; no leakage).

If the repository also contains tenant-scoped routes (e.g. /admin/t/{tenant}/...), they must be made workspace-safe by ensuring the tenant belongs to the currently selected workspace (or by denying-as-not-found).

Design decisions and rationale are captured in research.md.

Technical Context

Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4, Tailwind CSS v4
Storage: PostgreSQL (via Sail)
Testing: Pest v4 (+ PHPUnit v12 underneath)
Target Platform: Web application; local dev in Laravel Sail; deploy via containers (Dokploy)
Project Type: Web application (Laravel monolith)
Performance Goals: Admin UX: workspace-scoped pages should remain responsive (target < 200ms p95 for DB-only requests on typical datasets).
Constraints:

  • Sail-first execution for all PHP/Artisan/Composer/Node commands.
  • No full provider refactor; keep changes scoped to Workspace model, routing/scope, memberships, and managed tenant scoping.
  • Preserve deny-as-not-found semantics for non-members; do not leak via global search. Scale/Scope: Multi-workspace, multi-tenant-plane: 1 user may belong to many workspaces; each workspace may contain multiple managed tenants.

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

Gate evaluation (pre-Phase 0): PASS (with notes)

  • Inventory-first: Not directly affected (workspace + membership + routing).
  • Read/write separation: Workspace/membership mutations are DB-only; require confirmation for destructive-like actions (remove member / demote last owner attempt), emit audit entries, and add tests.
  • Graph contract path: No new Graph calls introduced.
  • Deterministic capabilities: Extend the canonical capability registry with workspace-plane capabilities; implement deterministic role → capability mapping and test it.
  • RBAC-UX: Apply the same semantics with Workspace scope:
    • Non-member workspace scope → deny-as-not-found.
    • Member missing capability → forbidden.
    • UI disabled vs hidden rules remain.
  • Global search safety: Introduce workspace-scoped global search query behavior (no results outside current workspace).
  • Run observability: Not applicable (no long-running operations planned). Membership changes still require audit logs.
  • Badge semantics: Not expected to change for this feature.

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/
│   ├── Resources/
│   │   ├── WorkspaceResource.php
│   │   └── ...
│   └── Pages/
│       ├── ChooseWorkspace.php
│       └── NoAccess.php
├── Http/
│   └── Middleware/
│       ├── EnsureWorkspaceSelected.php
│       └── EnsureWorkspaceMember.php
├── Models/
│   ├── Workspace.php
│   ├── WorkspaceMembership.php
│   └── Tenant.php  (existing; updated to belong to Workspace)
├── Policies/
│   ├── WorkspacePolicy.php
│   └── WorkspaceMembershipPolicy.php
├── Support/
│   ├── Auth/
│   │   ├── Capabilities.php (extend with workspace-plane capabilities)
│   │   └── WorkspaceRole.php (new enum)
│   └── Rbac/
│       ├── UiEnforcement.php (extend or add workspace variant)
│       └── ...
└── Services/
  └── Auth/
    ├── RoleCapabilityMap.php (tenant-plane)
    └── WorkspaceRoleCapabilityMap.php (new; workspace-plane)

database/migrations/
tests/Feature/
routes/web.php
bootstrap/app.php (middleware registration; Laravel 12)
bootstrap/providers.php (panel providers; Laravel 11+ rule)

Structure Decision: Laravel monolith. Implement Workspace scope via middleware + Filament pages/resources under the existing admin panel.

Phase 0 — Outline & Research (output: research.md)

Completed in research.md. Key outcomes:

  • URL identity: slug preferred, id fallback.
  • Current workspace persistence: session + users.last_workspace_id.
  • Managed Tenant uniqueness: global unique entra_tenant_id.
  • Zero memberships: neutral /admin/no-access page.
  • Workspace-scoped routing + middleware enforcement.
  • Global search scoped to active workspace.
  • Workspace-level audit stream for membership mutations, implemented as AuditLog-compatible entries (stable action IDs; redacted).

Phase 1 — Design & Contracts

Data model

See data-model.md for entities, constraints, and invariants.

Contracts

High-level entry-point contract is captured in contracts/admin-workspaces.openapi.yaml.

Quickstart

Manual verification guidance is in quickstart.md.

Post-design Constitution Re-check

Expected PASS: this feature is DB-only and authorization-heavy; ensure:

  • deny-as-not-found for non-members is enforced at middleware + query levels,
  • member-without-capability is 403,
  • deterministic capability mapping tests exist,
  • membership mutations are audited.

Phase 2 — Implementation Task Preview (for /speckit.tasks)

Planned task groupings:

  1. Database migrations + models (workspaces, memberships, user last_workspace_id, tenant workspace_id)
  2. Workspace context + middleware + routing (choose-workspace, no-access, /admin/w/{workspace} prefix)
  3. RBAC/capabilities (capability registry additions + role maps + policies)
  4. Filament resources/pages (Workspace CRUD, Members relation manager, managed tenant onboarding/list scoping)
  5. Global search scoping (workspace-safe global search queries)
  6. Auditing for membership mutations (AuditLog-compatible)
  7. Workspace lifecycle (archive/unarchive + selection invalidation)
  8. Deterministic capability mapping tests (golden/snapshot)
  9. Tests (Pest): last-owner guard, 404/403 semantics, global search scoping, workspace selection flows, migration verification

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]