# Implementation Plan: Workspace Model, Memberships & Managed Tenants (v2) **Branch**: `068-workspaces-v2` | **Date**: 2026-01-31 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from [spec.md](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](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) ```text 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) ```text 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](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](data-model.md) for entities, constraints, and invariants. ### Contracts High-level entry-point contract is captured in [contracts/admin-workspaces.openapi.yaml](contracts/admin-workspaces.openapi.yaml). ### Quickstart Manual verification guidance is in [quickstart.md](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] |