Implements Spec 093 (SCOPE-001) workspace isolation at the data layer. What changed - Adds `workspace_id` to 12 tenant-owned tables and enforces correct binding. - Model write-path enforcement derives workspace from tenant + rejects mismatches. - Prevents `tenant_id` changes (immutability) on tenant-owned records. - Adds queued backfill command + job (`tenantpilot:backfill-workspace-ids`) with OperationRun + AuditLog observability. - Enforces DB constraints (NOT NULL + FK `workspace_id` → `workspaces.id` + composite FK `(tenant_id, workspace_id)` → `tenants(id, workspace_id)`), plus audit_logs invariant. UI / operator visibility - Monitor backfill runs in **Monitoring → Operations** (OperationRun). Tests - `vendor/bin/sail artisan test --compact tests/Feature/WorkspaceIsolation` Notes - Backfill is queued: ensure a queue worker is running (`vendor/bin/sail artisan queue:work`). Spec package - `specs/093-scope-001-workspace-id-isolation/` (plan, tasks, contracts, quickstart, research) Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #112
211 lines
12 KiB
Markdown
211 lines
12 KiB
Markdown
# Tasks: 093 — SCOPE-001 Workspace ID Isolation
|
||
|
||
**Input**: Design documents from `/specs/093-scope-001-workspace-id-isolation/`
|
||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/, quickstart.md
|
||
**Tests**: For runtime behavior changes in this repo, tests are REQUIRED (Pest). Only docs-only changes may omit tests.
|
||
**Operations**: This feature introduces an operator command (long-running), so tasks include creating/reusing and updating a canonical `OperationRun` and creating `AuditLog` entries before/after.
|
||
|
||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||
|
||
## Format: `[ID] [P?] [Story] Description`
|
||
|
||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||
- Include file paths in descriptions
|
||
|
||
## Path Conventions
|
||
|
||
- Laravel app code: `app/`
|
||
- Migrations: `database/migrations/`
|
||
- Pest tests: `tests/Feature/`
|
||
|
||
## Phase 1: Setup (Shared Infrastructure)
|
||
|
||
**Purpose**: Confirm design inputs + existing code entrypoints
|
||
|
||
- [X] T001 Verify feature docs are present and consistent in specs/093-scope-001-workspace-id-isolation/{spec.md,plan.md,research.md,data-model.md,contracts/,quickstart.md,tasks.md}
|
||
- [X] T002 [P] Inventory target models exist for the 12 tables in app/Models/{Policy,PolicyVersion,BackupSet,BackupItem,RestoreRun,BackupSchedule,InventoryItem,InventoryLink,EntraGroup,Finding,EntraRoleDefinition,TenantPermission}.php
|
||
- [X] T003 [P] Identify audit logging entrypoints that must set workspace_id in app/Services/Intune/AuditLogger.php and app/Services/Audit/WorkspaceAuditLogger.php
|
||
|
||
---
|
||
|
||
## Phase 2: Foundational (Blocking Prerequisites)
|
||
|
||
**Purpose**: Shared building blocks used across all stories
|
||
|
||
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
||
|
||
- [X] T004 Create shared model concern for workspace binding + tenant immutability in app/Support/Concerns/DerivesWorkspaceIdFromTenant.php
|
||
- [X] T005 [P] Add supporting exception type for mismatch/immutability errors in app/Support/WorkspaceIsolation/WorkspaceIsolationViolation.php
|
||
- [X] T006 Add a small list of tenant-owned table names for reuse (command + tests) in app/Support/WorkspaceIsolation/TenantOwnedTables.php
|
||
|
||
**Checkpoint**: Foundation ready (shared enforcement building blocks exist)
|
||
|
||
---
|
||
|
||
## Phase 3: User Story 1 — Enforce workspace ownership at the data layer (Priority: P1) 🎯 MVP
|
||
|
||
**Goal**: Every tenant-owned record becomes explicitly workspace-bound, and the system prevents new tenant-owned writes without a correct workspace binding.
|
||
|
||
**Independent Test**:
|
||
- Creating a tenant-owned record without workspace_id results in workspace_id being derived from tenant.
|
||
- Creating/updating a tenant-owned record with a mismatched workspace_id is rejected.
|
||
|
||
### Tests for User Story 1
|
||
|
||
> NOTE: Write these tests first and ensure they fail before implementation.
|
||
|
||
- [X] T007 [P] [US1] Add unit-level enforcement tests for the shared concern in tests/Feature/WorkspaceIsolation/DerivesWorkspaceIdFromTenantTest.php
|
||
- [X] T007a [P] [US1] Add immutability test case (attempt to change tenant_id is rejected) in tests/Feature/WorkspaceIsolation/DerivesWorkspaceIdFromTenantTest.php
|
||
|
||
### Implementation for User Story 1
|
||
|
||
- [X] T008 [P] [US1] Add nullable workspace_id + indexes to policies in database/migrations/*_add_workspace_id_to_policies_table.php
|
||
- [X] T009 [P] [US1] Add nullable workspace_id + indexes to policy_versions in database/migrations/*_add_workspace_id_to_policy_versions_table.php
|
||
- [X] T010 [P] [US1] Add nullable workspace_id + indexes to backup_sets in database/migrations/*_add_workspace_id_to_backup_sets_table.php
|
||
- [X] T011 [P] [US1] Add nullable workspace_id + indexes to backup_items in database/migrations/*_add_workspace_id_to_backup_items_table.php
|
||
- [X] T012 [P] [US1] Add nullable workspace_id + indexes to restore_runs in database/migrations/*_add_workspace_id_to_restore_runs_table.php
|
||
- [X] T013 [P] [US1] Add nullable workspace_id + indexes to backup_schedules in database/migrations/*_add_workspace_id_to_backup_schedules_table.php
|
||
- [X] T014 [P] [US1] Add nullable workspace_id + indexes to inventory_items in database/migrations/*_add_workspace_id_to_inventory_items_table.php
|
||
- [X] T015 [P] [US1] Add nullable workspace_id + indexes to inventory_links in database/migrations/*_add_workspace_id_to_inventory_links_table.php
|
||
- [X] T016 [P] [US1] Add nullable workspace_id + indexes to entra_groups in database/migrations/*_add_workspace_id_to_entra_groups_table.php
|
||
- [X] T017 [P] [US1] Add nullable workspace_id + indexes to findings in database/migrations/*_add_workspace_id_to_findings_table.php
|
||
- [X] T018 [P] [US1] Add nullable workspace_id + indexes to entra_role_definitions in database/migrations/*_add_workspace_id_to_entra_role_definitions_table.php
|
||
- [X] T019 [P] [US1] Add nullable workspace_id + indexes to tenant_permissions in database/migrations/*_add_workspace_id_to_tenant_permissions_table.php
|
||
|
||
- [X] T019a [US1] Enforce tenant_id immutability in app/Support/Concerns/DerivesWorkspaceIdFromTenant.php (reject updates when tenant_id differs from original)
|
||
|
||
- [X] T020 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to Policy model in app/Models/Policy.php
|
||
- [X] T021 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to PolicyVersion model in app/Models/PolicyVersion.php
|
||
- [X] T022 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to BackupSet model in app/Models/BackupSet.php
|
||
- [X] T023 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to BackupItem model in app/Models/BackupItem.php
|
||
- [X] T024 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to RestoreRun model in app/Models/RestoreRun.php
|
||
- [X] T025 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to BackupSchedule model in app/Models/BackupSchedule.php
|
||
- [X] T026 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to InventoryItem model in app/Models/InventoryItem.php
|
||
- [X] T027 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to InventoryLink model in app/Models/InventoryLink.php
|
||
- [X] T028 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to EntraGroup model in app/Models/EntraGroup.php
|
||
- [X] T029 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to Finding model in app/Models/Finding.php
|
||
- [X] T030 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to EntraRoleDefinition model in app/Models/EntraRoleDefinition.php
|
||
- [X] T031 [P] [US1] Apply DerivesWorkspaceIdFromTenant concern to TenantPermission model in app/Models/TenantPermission.php
|
||
|
||
- [X] T032 [US1] Add tenants composite uniqueness to support composite FK in database/migrations/*_add_tenants_id_workspace_id_unique.php
|
||
|
||
**Post-backfill constraints (depends on US2 completion):**
|
||
- [X] T033 [US1] Enforce NOT NULL workspace_id for the 12 tenant-owned tables in database/migrations/*_enforce_workspace_id_not_null_on_tenant_owned_tables.php
|
||
- [X] T034 [US1] Add FK workspace_id → workspaces.id + composite FK (tenant_id, workspace_id) → tenants(id, workspace_id) for the 12 tables in database/migrations/*_add_workspace_isolation_constraints_to_tenant_owned_tables.php
|
||
|
||
**Checkpoint**: US1 complete once new writes are safe and DB constraints are enforceable after backfill.
|
||
|
||
---
|
||
|
||
## Phase 4: User Story 2 — Safely backfill existing production data (Priority: P2)
|
||
|
||
**Goal**: Operators can safely backfill missing workspace_id across all 12 tables without downtime.
|
||
|
||
**Independent Test**:
|
||
- With seeded rows missing workspace_id, running the command sets workspace_id correctly.
|
||
- Re-running the command is idempotent.
|
||
- If a tenant→workspace mapping cannot be resolved, the command aborts and reports.
|
||
|
||
### Tests for User Story 2
|
||
|
||
> NOTE: Write these tests first and ensure they fail before implementation.
|
||
|
||
- [X] T035 [P] [US2] Add backfill command tests in tests/Feature/WorkspaceIsolation/BackfillWorkspaceIdsCommandTest.php
|
||
|
||
### Implementation for User Story 2
|
||
|
||
- [X] T036 [US2] Implement operator command skeleton + options in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php
|
||
- [X] T036a [US2] Implement queued job runner for batch/table backfills in app/Jobs/BackfillWorkspaceIdsJob.php
|
||
- [X] T036b [US2] Dispatch jobs from app/Console/Commands/TenantpilotBackfillWorkspaceIds.php and ensure “start → dispatch → view run” flow
|
||
- [X] T037 [US2] Add concurrency lock (Cache::lock) to prevent concurrent backfills in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php
|
||
- [X] T038 [US2] Implement dry-run counts + per-table reporting in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php
|
||
- [X] T039 [US2] Implement per-workspace OperationRun creation in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php using app/Services/OperationRunService.php (ensureWorkspaceRunWithIdentity)
|
||
- [X] T040 [US2] Write start/end/outcome AuditLog summaries per workspace in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php using app/Services/Audit/WorkspaceAuditLogger.php
|
||
- [X] T041 [US2] Implement backfill updates for all 12 tables in app/Jobs/BackfillWorkspaceIdsJob.php (UPDATE ... FROM tenants WHERE workspace_id IS NULL)
|
||
- [X] T042 [US2] Implement abort-and-report behavior when tenant workspace_id is missing/unresolvable in app/Console/Commands/TenantpilotBackfillWorkspaceIds.php
|
||
- [X] T043 [US2] Implement progress tracking (counts + last processed id) persisted into OperationRun context from app/Jobs/BackfillWorkspaceIdsJob.php
|
||
|
||
**Checkpoint**: US2 complete when backfill is safe to run repeatedly and produces OperationRun + AuditLog observability.
|
||
|
||
---
|
||
|
||
## Phase 5: User Story 3 — Make audit logs unambiguous across scopes (Priority: P3)
|
||
|
||
**Goal**: If an audit log entry references a tenant, it must also reference a workspace.
|
||
|
||
**Independent Test**:
|
||
- Tenant-scoped audit logs always store workspace_id.
|
||
- DB prevents tenant_id set with workspace_id null.
|
||
- Workspace-only and platform-only logs remain allowed.
|
||
|
||
### Tests for User Story 3
|
||
|
||
> NOTE: Write these tests first and ensure they fail before implementation.
|
||
|
||
- [X] T044 [P] [US3] Add audit invariant tests for tenant/workspace/platform scopes in tests/Feature/WorkspaceIsolation/AuditLogScopeInvariantTest.php
|
||
|
||
### Implementation for User Story 3
|
||
|
||
- [X] T045 [US3] Ensure tenant-scoped audit writes include workspace_id in app/Services/Intune/AuditLogger.php
|
||
- [X] T046 [US3] Add migration to backfill audit_logs.workspace_id where tenant_id is present (join tenants) in database/migrations/*_backfill_workspace_id_on_audit_logs.php
|
||
- [X] T047 [US3] Add check constraint enforcing tenant_id IS NULL OR workspace_id IS NOT NULL in database/migrations/*_add_audit_logs_scope_check_constraint.php
|
||
|
||
**Checkpoint**: US3 complete when invariant is enforced in both app writes and DB.
|
||
|
||
---
|
||
|
||
## Phase 6: Polish & Cross-Cutting Concerns
|
||
|
||
- [X] T048 [P] Add validation SQL snippets for operators in specs/093-scope-001-workspace-id-isolation/quickstart.md
|
||
- [X] T049 Ensure tasks and rollout order remain accurate after implementation changes in specs/093-scope-001-workspace-id-isolation/plan.md
|
||
|
||
---
|
||
|
||
## Dependencies & Execution Order
|
||
|
||
### Story order
|
||
|
||
- Setup (Phase 1) → Foundational (Phase 2) → US1 (Phase 3) → US2 (Phase 4) → US1 constraints (T033–T034) → US3 (Phase 5) → Polish
|
||
|
||
### Dependency graph (story-level)
|
||
|
||
- US1 (nullable columns + app enforcement) → blocks US2 (backfill)
|
||
- US2 (backfill) → blocks US1 constraints (T033–T034)
|
||
- US3 can be started after Foundational, but DB check constraint should land after US2 backfill if historical audit_logs need repair first.
|
||
|
||
---
|
||
|
||
## Parallel execution examples
|
||
|
||
### Parallel Example: US1
|
||
|
||
- [P] T008–T019 can run in parallel (independent migrations per table)
|
||
- [P] T020–T031 can run in parallel (independent model updates)
|
||
|
||
### Parallel Example: US2
|
||
|
||
- [P] T035 (tests) can be written while T036–T038 (command skeleton + reporting) are implemented
|
||
|
||
### Parallel Example: US3
|
||
|
||
- [P] T044 (tests) can be written while T045–T047 are implemented
|
||
|
||
---
|
||
|
||
## Implementation Strategy
|
||
|
||
### MVP First (US1 only)
|
||
|
||
1. Complete Phase 1 (Setup)
|
||
2. Complete Phase 2 (Foundational)
|
||
3. Complete US1 through app enforcement + nullable columns (T007–T032)
|
||
4. STOP and validate US1 tests pass independently
|
||
|
||
### Incremental Delivery
|
||
|
||
1. Add US2 (backfill) and validate idempotency + observability (T035–T043)
|
||
2. Enforce US1 post-backfill DB constraints (T033–T034)
|
||
3. Add US3 audit invariant (T044–T047)
|
||
4. Final polish/runbook validation (T048–T049)
|