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