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

176 lines
12 KiB
Markdown

# Tasks: Workspace Model, Memberships & Managed Tenants (v2)
**Input**: Design documents from `specs/068-workspaces-v2/`
**Prerequisites**: `plan.md` (required), `spec.md` (user stories), `research.md`, `data-model.md`, `contracts/`, `quickstart.md`
**Tests**: Required (Pest) because this feature changes runtime behavior and authorization semantics.
## Phase 1: Setup (Shared Infrastructure)
- [X] T001 Ensure Sail services are running for development (docker-compose.yml)
- [X] T002 Verify admin panel provider registration (bootstrap/providers.php)
- [X] T003 [P] Create new feature test folder structure (tests/Feature/Workspaces/)
---
## Phase 2: Foundational (Blocking Prerequisites)
- [X] T004 Create Workspace migrations (database/migrations/*_create_workspaces_table.php)
- [X] T005 Create WorkspaceMembership migrations (includes unique `(workspace_id, user_id)` constraint) (database/migrations/*_create_workspace_memberships_table.php)
- [X] T006 Add `users.last_workspace_id` migration (database/migrations/*_add_last_workspace_id_to_users_table.php)
- [X] T007 Add `tenants.workspace_id` migration + indexes + global unique entra tenant id constraint (workspace_id migration + existing `tenant_id` unique constraint)
- [X] T008 [P] Create `Workspace` model + relationships (app/Models/Workspace.php)
- [X] T009 [P] Create `WorkspaceMembership` model + relationships (app/Models/WorkspaceMembership.php)
- [X] T010 [P] Add `workspace()` relationship on Tenant model (app/Models/Tenant.php)
- [X] T011 Create Workspace role enum (app/Support/Auth/WorkspaceRole.php)
- [X] T012 Extend capability registry with workspace-plane capabilities (app/Support/Auth/Capabilities.php)
- [X] T013 Create workspace role→capability mapping (app/Services/Auth/WorkspaceRoleCapabilityMap.php)
- [X] T014 Implement workspace capability resolver (app/Services/Auth/WorkspaceCapabilityResolver.php)
- [X] T015 [P] Add Workspace + Membership policies for server-side enforcement (app/Policies/WorkspacePolicy.php)
- [X] T016 [P] Add WorkspaceMembership policy + last-owner guard hooks (app/Policies/WorkspaceMembershipPolicy.php)
- [X] T017 Implement workspace-scoped context helper (session + user last_workspace_id) (app/Support/Workspaces/WorkspaceContext.php)
- [X] T018 Implement workspace selection middleware (app/Http/Middleware/EnsureWorkspaceSelected.php)
- [X] T019 Implement workspace membership middleware (deny-as-not-found) (app/Http/Middleware/EnsureWorkspaceMember.php)
- [X] T020 Register middleware in Laravel 12 middleware pipeline (bootstrap/app.php)
- [X] T021 Add workspace resolver helper (slug preferred, id fallback) (app/Support/Workspaces/WorkspaceResolver.php)
**Checkpoint**: Foundation ready (DB + capability system + middleware hooks exist)
---
## Phase 3: User Story 1 — Choose workspace and get correct scope (Priority: P1) 🎯 MVP
**Goal**: Users must always operate within an active Workspace context; all workspace-scoped routes are deny-as-not-found for non-members; global search never leaks records.
**Independent Test**: A user with memberships can select/switch workspaces and only see/search within the current workspace; a non-member gets 404 semantics.
### Tests (US1)
- [X] T022 [P] [US1] Add workspace selection routing tests (covers session vs `last_workspace_id` precedence + invalidation) (tests/Feature/Workspaces/WorkspaceSelectionTest.php)
- [X] T023 [P] [US1] Add non-member isolation tests for workspace-scoped routes (tests/Feature/Workspaces/WorkspaceIsolationTest.php)
- [X] T024 [P] [US1] Add global search scoping tests (tests/Feature/Workspaces/WorkspaceGlobalSearchTest.php)
### Implementation (US1)
- [X] T025 [US1] Create ChooseWorkspace page (admin panel) (app/Filament/Pages/ChooseWorkspace.php)
- [X] T026 [US1] Create NoAccess page (admin panel) (app/Filament/Pages/NoAccess.php)
- [X] T027 [US1] Add routes/entry behavior for `/admin/choose-workspace` + `/admin/no-access` (AdminPanelProvider authenticatedRoutes + EnsureWorkspaceSelected allowlist)
- [X] T028 [US1] Implement workspace switcher (user menu) wiring to WorkspaceContext (app/Providers/Filament/AdminPanelProvider.php)
- [X] T029 [US1] Introduce workspace route group `/admin/w/{workspace}` with required middleware (routes/web.php)
- [X] T030 [US1] Introduce workspace-scoped global search query trait (app/Filament/Concerns/ScopesGlobalSearchToWorkspace.php)
- [X] T031 [US1] Apply workspace global search scoping to workspace-owned resources (app/Filament/Resources/**)
**Checkpoint**: User Story 1 works and is testable standalone.
---
## Phase 4: User Story 2 — Manage workspace members and roles (Priority: P2)
**Goal**: Workspace Owner/Manager can add members, change roles, and remove members; last-owner cannot be removed/demoted; all membership mutations are audited.
**Independent Test**: From Workspace view, manage memberships; verify last-owner guard; verify audit entries.
### Tests (US2)
- [X] T032 [P] [US2] Add last-owner guard tests (tests/Feature/Workspaces/LastOwnerGuardTest.php)
- [X] T033 [P] [US2] Add membership mutation authorization tests (403 vs 404 semantics) (tests/Feature/Workspaces/WorkspaceMembershipAuthorizationTest.php)
- [X] T034 [P] [US2] Add audit logging tests for membership mutations (tests/Feature/Workspaces/WorkspaceMembershipAuditTest.php)
### Implementation (US2)
- [X] T035 [US2] Add `workspace_id` support to AuditLog storage (nullable `tenant_id` where required) + indexes (database/migrations/*_add_workspace_id_to_audit_logs_table.php)
- [X] T036 [US2] Extend AuditLog model with optional `workspace()` relationship (app/Models/AuditLog.php)
- [X] T037 [US2] Implement workspace audit logger service writing AuditLog entries + stable action ids (app/Services/Audit/WorkspaceAuditLogger.php)
- [X] T038 [US2] Create WorkspaceResource CRUD with View page (required for global search) (app/Filament/Resources/Workspaces/WorkspaceResource.php)
- [X] T039 [US2] Add WorkspaceResource pages (List/Create/View/Edit) (app/Filament/Resources/Workspaces/Pages/)
- [X] T040 [US2] Implement Members relation manager on WorkspaceResource (app/Filament/Resources/Workspaces/RelationManagers/MembershipsRelationManager.php)
- [X] T041 [US2] Apply UI enforcement (disabled vs hidden + confirmation) for membership actions (app/Support/Rbac/UiEnforcement.php)
- [X] T042 [US2] Enforce last-owner guard server-side in membership mutations (app/Policies/WorkspaceMembershipPolicy.php)
**Checkpoint**: User Story 2 works and is testable standalone.
---
## Phase 5: User Story 3 — Onboard a managed tenant inside a workspace (Priority: P3)
**Goal**: Managed Tenant CRUD/listing/onboarding is always workspace-scoped and uses the canonical onboarding entry under workspace.
**Independent Test**: Add a managed tenant in Workspace A; it never appears in Workspace B; old entry points redirect.
### Tests (US3)
- [X] T043 [P] [US3] Add managed tenant scoping tests (tests/Feature/Workspaces/ManagedTenantScopingTest.php)
- [X] T044 [P] [US3] Add legacy entry-point redirect tests (tests/Feature/Workspaces/LegacyOnboardingRedirectTest.php)
### Implementation (US3)
- [X] T045 [US3] Update tenant-plane naming/terminology in UI to “Managed Tenant” within workspace context (app/Filament/Resources/TenantResource.php)
- [X] T046 [US3] Scope managed tenant list query to current workspace (app/Filament/Resources/TenantResource/Pages/ListTenants.php)
- [X] T047 [US3] Ensure managed tenant create uses current workspace_id and blocks cross-workspace edits (app/Filament/Resources/TenantResource/Pages/CreateTenant.php)
- [X] T048 [US3] Implement canonical onboarding route under workspace scope (app/Filament/Resources/TenantResource/Pages/OnboardingManagedTenant.php)
- [X] T049 [US3] Redirect legacy `/admin/new` entry to choose-workspace or last_workspace onboarding (routes/web.php)
**Checkpoint**: User Story 3 works and is testable standalone.
---
## Final Phase: Migration, Polish & Cross-Cutting Concerns
- [X] T050 Create migration to backfill Default Workspace + assign existing tenants + bootstrap owner membership (database/migrations/*_backfill_default_workspace_and_memberships.php)
- [X] T051 Add workspace selection invalidation when workspace archived or membership removed (app/Support/Workspaces/WorkspaceContext.php)
- [X] T052 Ensure workspace-scoped navigation labels/IA are consistent (resources/views/** and app/Filament/**)
- [X] T053 Run formatter on touched files using Sail (`bin pint --dirty`) (vendor/bin/sail)
- [X] T054 Run targeted test suite for this feature using Sail (`artisan test --compact --filter=Workspaces`) (vendor/bin/sail)
- [X] T055 Validate manual quickstart checklist remains accurate (specs/068-workspaces-v2/quickstart.md)
---
## Additions (Consistency + Constitution Requirements)
- [X] T056 [P] Add deterministic workspace capability mapping golden tests (tests/Feature/Workspaces/WorkspaceCapabilitiesTest.php)
- [X] T057 [P] Add unique workspace membership constraint test (tests/Feature/Workspaces/WorkspaceMembershipUniquenessTest.php)
- [X] T058 [P] Add unique Entra tenant id constraint test (tests/Feature/Workspaces/ManagedTenantUniquenessTest.php)
- [X] T059 [P] Add migration safety test for backfill (SC-005) (tests/Feature/Workspaces/WorkspaceBackfillMigrationTest.php)
- [X] T060 [P] Add workspace lifecycle tests (archive/unarchive + selection invalidation) (tests/Feature/Workspaces/WorkspaceLifecycleTest.php)
- [X] T061 Add `archived_at` (or equivalent) to workspaces table + index (database/migrations/*_add_archived_at_to_workspaces_table.php)
- [X] T062 Add archive/unarchive actions with confirmation + authorization in WorkspaceResource (app/Filament/Resources/WorkspaceResource.php)
- [X] T063 [US1] Add “Create Workspace” action on ChooseWorkspace/NoAccess pages (supports first workspace) (app/Filament/Pages/ChooseWorkspace.php and app/Filament/Pages/NoAccess.php)
- [X] T064 [P] Add test for creating the first workspace from no-access/choose-workspace flows (tests/Feature/Workspaces/WorkspaceCreationEntryFlowTest.php)
---
## Dependencies & Execution Order
### Phase Dependencies
- Phase 1 (Setup) → Phase 2 (Foundational) → Phase 3 (US1) → Phase 4 (US2) → Phase 5 (US3) → Final Phase
### User Story Dependencies
- **US1** depends on Foundational (workspace context, middleware, capability registry).
- **US2** depends on Foundational (workspace models + policies) and benefits from US1 routing/context.
- **US3** depends on Foundational (workspace_id on managed tenants) and US1 routing/context.
## Parallel Execution Examples
### US1
- [P] Write tests in tests/Feature/Workspaces/WorkspaceSelectionTest.php, tests/Feature/Workspaces/WorkspaceIsolationTest.php, tests/Feature/Workspaces/WorkspaceGlobalSearchTest.php
- Implement pages in app/Filament/Pages/ChooseWorkspace.php and app/Filament/Pages/NoAccess.php
### US2
- [P] Write tests in tests/Feature/Workspaces/LastOwnerGuardTest.php, tests/Feature/Workspaces/WorkspaceMembershipAuthorizationTest.php, tests/Feature/Workspaces/WorkspaceMembershipAuditTest.php
- Implement audit storage/model updates in app/Models/AuditLog.php + app/Services/Audit/WorkspaceAuditLogger.php
### US3
- [P] Write tests in tests/Feature/Workspaces/ManagedTenantScopingTest.php and tests/Feature/Workspaces/LegacyOnboardingRedirectTest.php
- Update tenant resource pages in app/Filament/Resources/TenantResource/Pages/
## Implementation Strategy
- MVP scope is **US1 only** (workspace selection + scoping + search safety + deny-as-not-found isolation).
- Then implement US2 (memberships + auditing + last-owner guard).
- Then implement US3 (managed tenant scoping + canonical onboarding + redirects).