# Feature 004: Assignments & Scope Tags - Task Breakdown

## Overview
This document breaks down the implementation plan into granular, actionable tasks organized by phase and user story.

**Total Estimated Tasks**: ~60 tasks  
**MVP Scope**: Tasks marked with ⭐ (updated)  
**Full Implementation**: All tasks (~30-40 hours)

---

## Plan Updates (Implementation Reality)
- Assignments + scope tags are stored on PolicyVersion; BackupItem links to PolicyVersion and copies assignments.
- Include assignments/scope tags checkboxes live on Add Policies to Backup Set and Capture Snapshot actions (not on Backup Set creation).
- Policy assignments UI moved to Policy Version view via Livewire widget.
- Assignment filters resolved via `/deviceManagement/assignmentFilters` and displayed with include/exclude mode.

---

## Phase 1: Setup & Database (Foundation)

**Duration**: 2-3 hours  
**Dependencies**: None  
**Parallelizable**: No (sequential setup)

### Tasks

**1.1** [X] ⭐ Create migration: `add_assignments_to_backup_items`
- File: `database/migrations/xxxx_add_assignments_to_backup_items.php`
- Add `assignments` JSONB column after `metadata`
- Make nullable
- Write reversible `down()` method
- Test: `php artisan migrate` and `migrate:rollback`

**1.2** [X] ⭐ Create migration: `add_group_mapping_to_restore_runs`
- File: `database/migrations/xxxx_add_group_mapping_to_restore_runs.php`
- Add `group_mapping` JSONB column after `results`
- Make nullable
- Write reversible `down()` method
- Test: `php artisan migrate` and `migrate:rollback`

**1.3** [X] ⭐ Update `BackupItem` model with assignments cast
- Add `'assignments' => 'array'` to `$casts`
- Add `assignments` to `$fillable`
- Test: Create BackupItem with assignments, verify cast works

**1.4** [X] ⭐ Add `BackupItem` assignment accessor methods
- `getAssignmentCountAttribute(): int`
- `hasAssignments(): bool`
- `getGroupIdsAttribute(): array`
- `getScopeTagIdsAttribute(): array`
- `getScopeTagNamesAttribute(): array`
- `hasOrphanedAssignments(): bool`
- `assignmentsFetchFailed(): bool`

**1.5** [X] ⭐ Add `BackupItem` scope: `scopeWithAssignments()`
- Filter policies with non-null assignments
- Use `whereNotNull('assignments')` and `whereRaw('json_array_length(assignments) > 0')`

**1.6** [X] ⭐ Update `RestoreRun` model with group_mapping cast
- Add `'group_mapping' => 'array'` to `$casts`
- Add `group_mapping` to `$fillable`
- Test: Create RestoreRun with group_mapping, verify cast works

**1.7** [X] ⭐ Add `RestoreRun` group mapping helper methods
- `hasGroupMapping(): bool`
- `getMappedGroupId(string $sourceGroupId): ?string`
- `isGroupSkipped(string $sourceGroupId): bool`
- `getUnmappedGroupIds(array $sourceGroupIds): array`
- `addGroupMapping(string $sourceGroupId, string $targetGroupId): void`

**1.8** [X] ⭐ Add `RestoreRun` assignment outcome methods
- `getAssignmentRestoreOutcomes(): array`
- `getSuccessfulAssignmentsCount(): int`
- `getFailedAssignmentsCount(): int`
- `getSkippedAssignmentsCount(): int`

**1.9** [X] ⭐ Update `config/graph_contracts.php` with assignments endpoints
- Add `assignments_list_path` (GET)
- Add `assignments_create_path` (POST `/assign` for settingsCatalogPolicy)
- Add `assignments_delete_path` (DELETE)
- Add `supports_scope_tags: true`
- Add `scope_tag_field: 'roleScopeTagIds'`

**1.10** [X] ⭐ Write unit tests: `BackupItemTest`
- Test assignment accessors
- Test scope `withAssignments()`
- Test metadata helpers (scope tags, orphaned flags)
- Expected: 100% coverage for new methods

**1.11** [X] ⭐ Write unit tests: `RestoreRunTest`
- Test group mapping helpers
- Test assignment outcome methods
- Test `addGroupMapping()` persistence
- Expected: 100% coverage for new methods

**1.12** [X] Run Pint: Format all new code
- `./vendor/bin/pint database/migrations/`
- `./vendor/bin/pint app/Models/BackupItem.php`
- `./vendor/bin/pint app/Models/RestoreRun.php`

**1.13** [X] Verify tests pass
- Run: `php artisan test --filter=BackupItem`
- Run: `php artisan test --filter=RestoreRun`
- Expected: All green

**1.14** [X] ⭐ Add migration: `add_assignments_to_policy_versions`
- Add `assignments`, `scope_tags`, `assignments_hash`, `scope_tags_hash`
- Add indexes on hashes
- Reversible `down()` method

**1.15** [X] ⭐ Add migration: `add_policy_version_id_to_backup_items`
- Add nullable `policy_version_id` FK to `policy_versions`
- Reversible `down()` method

**1.16** [X] ⭐ Update `PolicyVersion` model casts
- Add casts for `assignments` and `scope_tags`

**1.17** [X] ⭐ Add `BackupItem` relation to PolicyVersion
- `policyVersion(): BelongsTo`

---

## Phase 2: Graph API Integration (Core Services)

**Duration**: 4-6 hours  
**Dependencies**: Phase 1 complete  
**Parallelizable**: Yes (services independent)

### Tasks

**2.1** [X] ⭐ Create service: `AssignmentFetcher`
- File: `app/Services/Graph/AssignmentFetcher.php`
- Method: `fetch(string $tenantId, string $policyId): array`
- Implement primary endpoint: GET `/assignments`
- Implement fallback: GET with `$expand=assignments`
- Return empty array on failure (fail-soft)
- Log warnings with request IDs

**2.2** [X] ⭐ Add error handling to `AssignmentFetcher`
- Catch `GraphException`
- Log: tenant_id, policy_id, error message, request_id
- Return empty array (don't throw)
- Set flag for caller: `assignments_fetch_failed`

**2.3** [X] ⭐ Write unit test: `AssignmentFetcherTest::primary_endpoint_success`
- Mock Graph response with assignments
- Assert returned array matches response
- Assert no fallback called

**2.4** [X] ⭐ Write unit test: `AssignmentFetcherTest::fallback_on_empty_response`
- Mock primary returning empty array
- Mock fallback returning assignments
- Assert fallback called
- Assert assignments returned

**2.5** [X] ⭐ Write unit test: `AssignmentFetcherTest::fail_soft_on_error`
- Mock both endpoints throwing `GraphException`
- Assert empty array returned
- Assert warning logged

**2.6** [X] Create service: `GroupResolver`
- File: `app/Services/Graph/GroupResolver.php`
- Method: `resolveGroupIds(array $groupIds, string $tenantId): array`
- Implement: POST `/directoryObjects/getByIds`
- Return keyed array: `['group-id' => ['id', 'displayName', 'orphaned']]`
- Handle orphaned IDs (not in response)

**2.7** [X] Add caching to `GroupResolver`
- Cache key: `"groups:{$tenantId}:" . md5(implode(',', $groupIds))`
- TTL: 5 minutes
- Use `Cache::remember()`

**2.8** [X] Write unit test: `GroupResolverTest::resolves_all_groups`
- Mock Graph response with all group IDs
- Assert all resolved with names
- Assert `orphaned: false`

**2.9** [X] Write unit test: `GroupResolverTest::handles_orphaned_ids`
- Mock Graph response missing some IDs
- Assert orphaned IDs have `displayName: null`
- Assert `orphaned: true`

**2.10** [X] Write unit test: `GroupResolverTest::caches_results`
- Call resolver twice with same IDs
- Assert Graph API called only once
- Assert cache hit on second call

**2.11** [X] Create service: `ScopeTagResolver`
- File: `app/Services/Graph/ScopeTagResolver.php`
- Method: `resolve(array $scopeTagIds): array`
- Implement: GET `/deviceManagement/roleScopeTags?$select=id,displayName`
- Return array of scope tag objects
- Cache results (1 hour TTL)

**2.12** [X] Add cache to `ScopeTagResolver`
- Cache key: `"scope_tags:all"`
- TTL: 1 hour
- Fetch all scope tags once, filter in memory

**2.13** [X] Write unit test: `ScopeTagResolverTest::resolves_scope_tags`
- Mock Graph response
- Assert correct scope tags returned
- Assert filtered to requested IDs only

**2.14** [X] Write unit test: `ScopeTagResolverTest::caches_results`
- Call resolver twice
- Assert Graph API called only once
- Assert cache hit on second call

**2.15** [X] Run Pint: Format service classes
- `./vendor/bin/pint app/Services/Graph/`

**2.16** [X] Verify service tests pass
- Run: `php artisan test --filter=AssignmentFetcher`
- Run: `php artisan test --filter=GroupResolver`
- Run: `php artisan test --filter=ScopeTagResolver`
- Expected: All green, 90%+ coverage

**2.17** [X] ⭐ Create service: `AssignmentFilterResolver`
- File: `app/Services/Graph/AssignmentFilterResolver.php`
- GET `/deviceManagement/assignmentFilters?$select=id,displayName`
- Cache results (TTL 1 hour)

**2.18** [X] ⭐ Write unit test: `AssignmentFilterResolverTest`
- Assert filter name resolution by ID
- Assert cache behavior

---

## Phase 3: US1 - Capture Assignments & Scope Tags (MVP Core)

**Duration**: 4-5 hours  
**Dependencies**: Phase 2 complete  
**Parallelizable**: Partially (service and UI separate)

### Tasks
**3.1** [X] ⭐ Add include checkboxes to "Add policies" action
- File: `app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php`
- Components: `include_assignments`, `include_scope_tags` (default: true)
- Help text: assignments include/exclude + filters, scope tag IDs

**3.2** [X] ⭐ Add include checkboxes to "Capture snapshot" action
- File: `app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`
- Components: `include_assignments`, `include_scope_tags` (default: true)

**3.3** [X] ⭐ Create `PolicyCaptureOrchestrator`
- File: `app/Services/Intune/PolicyCaptureOrchestrator.php`
- Capture payload + assignments + scope tags
- Reuse existing PolicyVersion by snapshot hash
- Backfill assignments/scope tags when missing

**3.4** [X] ⭐ Store assignments/scope tags on PolicyVersion
- Update `VersionService::captureVersion` to accept assignments/scopeTags
- Store `assignments_hash` and `scope_tags_hash`

**3.5** [X] ⭐ Link BackupItem to PolicyVersion and copy assignments
- Add `policy_version_id` to backup_items
- BackupService uses orchestrator and copies assignments
- Scope tags live on PolicyVersion only

**3.6** [X] Update BackupSet items table columns
- Eager-load `policyVersion`
- Assignment count derived from `backup_items.assignments`
- Scope tags read from `policyVersion.scope_tags`

**3.7** [X] Add audit log entry: `backup.assignments.included`
- Log when `include_assignments` is enabled

**3.8** [X] ⭐ Write/Update feature tests
- `BackupWithAssignmentsConsistencyTest`
- `VersionCaptureWithAssignmentsTest`
- `PolicyVersionViewAssignmentsTest`

---

## Phase 4: US2 - Policy Version View with Assignments

**Duration**: 3-4 hours  
**Dependencies**: Phase 3 complete  
**Parallelizable**: Yes (independent of Phase 5)

### Tasks

**4.1** [X] ⭐ Create assignments widget for PolicyVersion view
- Livewire component: `PolicyVersionAssignmentsWidget`
- Render via `ViewPolicyVersion::getFooterWidgets`

**4.2** [X] Show include/exclude groups and filters
- Map target types to Include/Exclude labels
- Render group display name or "Unknown Group (ID: ...)"
- Show assignment filter name + filter mode

**4.3** [X] Show scope tags section
- Use `PolicyVersion.scope_tags['names']`
- Empty state when not captured

**4.4** [X] Remove assignments UI from Policy view
- Policy view no longer shows assignments (moved to PolicyVersion view)

**4.5** [X] ⭐ Update tests
- `PolicyVersionViewAssignmentsTest`

---

## Phase 5: US3 - Restore with Group Mapping (Core Restore)

**Duration**: 6-8 hours  
**Dependencies**: Phase 4 complete  
**Parallelizable**: No (complex, sequential)

### Tasks

**5.1** Detect unresolved groups in restore preview
- File: `app/Filament/Resources/RestoreResource/Pages/RestorePreview.php`
- Extract group IDs from source assignments
- Call `POST /directoryObjects/getByIds` for target tenant
- Compare: missing IDs = unresolved

**5.2** Create Filament wizard component: `GroupMappingStep`
- File: `app/Filament/Forms/Components/GroupMappingStep.php`
- Add as step 2 in restore wizard (between Preview and Confirm)
- Show only if unresolved groups exist

**5.3** Build group mapping table in wizard step
- Use Filament `Repeater` or custom table
- Columns: Source Group (name or ID), Target Group (dropdown), Skip (checkbox)
- Populate source groups from backup metadata

**5.4** Implement target group dropdown
- Use `Select::make('target_group_id')`
- `->searchable()`
- `->getSearchResultsUsing()` to query target tenant groups
- `->debounce(500)` for responsive search
- `->lazy()` to load options on demand

**5.5** Add "Skip" checkbox functionality
- When checked, set mapping value to `"SKIP"`
- Disable target group dropdown when skip checked

**5.6** Cache target tenant groups
- In wizard `mount()`, call `GroupResolver->getAllForTenant($targetTenantId)`
- Cache for 5 minutes
- Pre-warm dropdown options

**5.7** Persist group mapping to `RestoreRun`
- On wizard step 2 submit, save mapping to `$restoreRun->group_mapping`
- Use `RestoreRun->addGroupMapping()` helper
- Validate: all unresolved groups either mapped or skipped

**5.8** Create service: `AssignmentRestoreService`
- File: `app/Services/AssignmentRestoreService.php`
- Method: `restore(string $policyId, array $assignments, array $groupMapping): array`
- Prefer `/assign` action when supported; fallback to DELETE-then-CREATE pattern

**5.9** Implement DELETE existing assignments (fallback)
- Step 1: GET `/assignments` for target policy
- Step 2: Loop and DELETE each assignment
- Handle 204 No Content (success)
- Log warnings on failure, continue

**5.10** Implement CREATE new assignments with mapping (fallback)
- Step 3: Loop through source assignments
- Apply group mapping: replace source group IDs with target IDs
- Skip assignments marked `"SKIP"` in mapping
- POST each assignment to `/assignments`
- Handle 201 Created (success)
- Log per-assignment outcome

**5.11** Add rate limit protection (fallback only)
- Add 100ms delay between sequential POST calls: `usleep(100000)`
- Log request IDs for failed calls

**5.12** Handle per-assignment failures gracefully
- Don't throw on failure, collect outcomes
- Outcomes array: `['status' => 'success|failed|skipped', 'assignment' => ..., 'error' => ...]`
- Continue with remaining assignments (fail-soft)

**5.13** Store outcomes in `RestoreRun->results`
- Add `assignment_outcomes` key to results JSON
- Include successful, failed, skipped counts
- Store error details for failed assignments

**5.14** Add audit log entries
- `restore.group_mapping.applied`: When mapping saved
- `restore.assignment.created`: Per successful assignment
- `restore.assignment.failed`: Per failed assignment
- `restore.assignment.skipped`: Per skipped assignment

**5.15** Create job: `RestoreAssignmentsJob`
- File: `app/Jobs/RestoreAssignmentsJob.php`
- Dispatch async after restore initiated
- Call `AssignmentRestoreService`
- Handle job failures (log, mark RestoreRun as failed)

**5.16** Write feature test: `RestoreGroupMappingTest::detects_unresolved_groups`
- Create backup with group IDs
- Target tenant has different groups
- Start restore wizard
- Assert group mapping step appears
- Assert unresolved groups listed

**5.17** Write feature test: `RestoreGroupMappingTest::persists_group_mapping`
- Fill out group mapping form
- Submit wizard
- Assert `RestoreRun->group_mapping` populated
- Assert audit log entry created

**5.18** Write feature test: `RestoreGroupMappingTest::skips_mapped_groups`
- Map some groups, skip others
- Complete restore
- Assert skipped groups have `"SKIP"` value
- Assert skipped assignments not created

**5.19** Write feature test: `RestoreAssignmentApplicationTest::applies_assignments_successfully`
- Mock Graph API (DELETE 204, POST 201)
- Restore with mapped groups
- Assert assignments created in target tenant
- Assert outcomes logged

**5.20** Write feature test: `RestoreAssignmentApplicationTest::handles_failures_gracefully`
- Mock Graph API: some POSTs fail (400 or 500)
- Restore with mapped groups
- Assert successful assignments still created
- Assert failed assignments logged with errors
- Assert RestoreRun status reflects partial success

**5.21** Run Pint: Format restore code
- `./vendor/bin/pint app/Services/AssignmentRestoreService.php`
- `./vendor/bin/pint app/Jobs/RestoreAssignmentsJob.php`
- `./vendor/bin/pint app/Filament/Forms/Components/GroupMappingStep.php`

**5.22** Verify feature tests pass
- Run: `php artisan test --filter=RestoreGroupMapping`
- Run: `php artisan test --filter=RestoreAssignmentApplication`
- Expected: All green

---

## Phase 6: US4 - Restore Preview with Assignment Diff

**Duration**: 2-3 hours  
**Dependencies**: Phase 5 complete  
**Parallelizable**: Yes (extends Phase 5, doesn't block)

### Tasks

**6.1** Fetch target policy's current assignments
- In restore preview, call `AssignmentFetcher` for target policy
- Cache for duration of wizard session

**6.2** Implement diff algorithm
- Compare source assignments with target assignments
- Group by change type:
  - **Added**: In source, not in target
  - **Removed**: In target, not in source
  - **Unchanged**: In both (match by group ID + intent)

**6.3** Display diff in Restore Preview
- Use Filament `Infolist` with color coding:
  - Green (success): Added assignments
  - Red (danger): Removed assignments
  - Gray (secondary): Unchanged assignments
- Show counts: "3 added, 1 removed, 2 unchanged"

**6.4** Add Scope Tag diff
- Compare source scope tag IDs with target tenant scope tags
- Display: "Scope Tags: 2 matched, 1 not found in target"
- Show warning icon for missing scope tags

**6.5** Update feature test: `RestoreAssignmentApplicationTest::displays_assignment_diff`
- Visit restore preview
- Assert diff sections visible
- Assert color coding correct
- Assert counts accurate

**6.6** Run Pint: Format diff code
- `./vendor/bin/pint app/Filament/Resources/RestoreResource/Pages/RestorePreview.php`

**6.7** Verify test passes
- Run: `php artisan test --filter=displays_assignment_diff`
- Expected: Green

---

## Phase 7: Scope Tags (Full Support)

**Duration**: 2-3 hours  
**Dependencies**: Phase 6 complete  
**Parallelizable**: Yes (extends existing code)

### Tasks

**7.1** Extract scope tag IDs during backup
- In `AssignmentBackupService`, read `roleScopeTagIds` from policy payload
- Store in `metadata['scope_tag_ids']`

**7.2** Resolve scope tag names during backup
- Call `ScopeTagResolver->resolve($scopeTagIds)`
- Store in `metadata['scope_tag_names']`

**7.3** Validate scope tags during restore
- In `AssignmentRestoreService`, check if scope tag IDs exist in target tenant
- Call `POST /directoryObjects/getByIds` with type `['scopeTag']` (if supported) or GET all scope tags

**7.4** Log warnings for missing scope tags
- Log: "Scope Tag '{id}' not found in target tenant, restore will proceed"
- Don't block restore (Graph API handles missing scope tags gracefully)

**7.5** Update unit test: `ScopeTagResolverTest::handles_missing_scope_tags`
- Mock Graph response with some scope tags missing
- Assert warnings logged
- Assert restore proceeds

**7.6** Update feature test: `RestoreAssignmentApplicationTest::handles_missing_scope_tags`
- Restore with scope tag IDs not in target tenant
- Assert warnings logged
- Assert policy created successfully

**7.7** Run Pint: Format scope tag code

**7.8** Verify tests pass
- Run: `php artisan test --filter=ScopeTag`
- Expected: Green

---

## Phase 8: Polish & Performance

**Duration**: 3-4 hours  
**Dependencies**: Phase 7 complete  
**Parallelizable**: Partially (UI polish vs performance tuning)

### Tasks

**8.1** Add loading indicators to group mapping dropdown
- Use `wire:loading` on Select component
- Show spinner during search
- Disable dropdown while loading

**8.2** Add debouncing to group search
- Set `->debounce(500)` on Select component
- Test: Type quickly, verify search only fires after 500ms pause

**8.3** Optimize Graph API calls: batch group resolution
- In `GroupResolver`, batch max 100 IDs per POST
- If > 100 groups, split into chunks: `collect($groupIds)->chunk(100)`
- Merge results

**8.4** Add cache warming for target tenant groups
- In wizard `mount()`, pre-fetch all target tenant groups
- Cache for 5 minutes
- Display loading message while warming

**8.5** Add tooltips to UI elements
- Include assignments checkbox: "Captures include/exclude targeting and filters."
- Include scope tags checkbox: "Captures policy scope tag IDs."
- Group mapping: "Map source groups to target groups for cross-tenant migrations."
- Skip checkbox: "Don't restore assignments targeting this group."

**8.6** Update README: Add "Assignments & Scope Tags" section
- Overview of feature
- How to use include assignments/scope tags (Add Policies + Capture snapshot)
- How to use group mapping wizard
- Troubleshooting tips

**8.7** Create performance test: Large restore
- Restore 50 policies with 10 assignments each
- Measure duration
- Target: < 5 minutes
- Log: Graph API call count, cache hits

**8.8** Run performance test
- Execute test
- Record results in `quickstart.md`
- Optimize if needed (e.g., increase cache TTL)

**8.9** Run Pint: Format all code

**8.10** Final test run
- Run: `php artisan test`
- Expected: All tests green, 85%+ coverage

---

## Phase 9: Testing & QA

**Duration**: 2-3 hours  
**Dependencies**: Phase 8 complete  
**Parallelizable**: No (QA sequential)

### Tasks

**9.1** Manual QA: Add policies with assignments (same tenant)
- Open a Backup Set and add policies
- Enable "Include assignments" (and scope tags if desired)
- Verify add completes
- Verify assignments stored in DB
- Verify audit log entry

**9.2** Manual QA: Add policies without assignments
- Add policies to a Backup Set or capture a snapshot
- Disable "Include assignments"
- Verify add/capture completes
- Verify assignments column is null

**9.3** Manual QA: Policy Version view with assignments widget
- Navigate to a Policy Version with assignments
- Verify widget renders
- Verify orphaned IDs show warning
- Verify scope tags section

**9.4** Manual QA: Restore to same tenant (auto-match)
- Restore backup to original tenant
- Verify no group mapping step appears
- Verify assignments restored correctly

**9.5** Manual QA: Restore to different tenant (group mapping)
- Restore backup to different tenant
- Verify group mapping step appears
- Fill out mapping (map some, skip others)
- Verify assignments restored with mapped IDs
- Verify skipped assignments not created

**9.6** Manual QA: Handle orphaned group IDs
- Create backup with group ID that doesn't exist in target
- Restore to target tenant
- Verify warning displayed
- Verify orphaned group rendered as "Unknown Group"

**9.7** Manual QA: Handle Graph API failures
- Simulate API failure (disable network or use Http::fake with 500 response)
- Attempt add policies or capture snapshot with "Include assignments"
- Verify fail-soft behavior
- Verify warning logged

**9.8** Browser test: `GroupMappingWizardTest`
- Use Pest browser testing
- Navigate restore wizard
- Fill out group mapping
- Submit
- Verify mapping persisted

**9.9** Load testing: 100+ policies
- Create 100 policies with 20 assignments each
- Restore to target tenant
- Measure duration
- Verify < 5 minutes

**9.10** Staging deployment
- Deploy to staging via Dokploy
- Run manual QA scenarios on staging
- Verify no issues

**9.11** Document QA results
- Update `quickstart.md` with QA checklist results
- Note any issues or edge cases discovered

---

## Phase 10: Deployment & Documentation

**Duration**: 1-2 hours  
**Dependencies**: Phase 9 complete  
**Parallelizable**: No (sequential deployment)

### Tasks

**10.1** Review deployment checklist
- Migrations ready? ✅
- Rollback tested? ✅
- Env vars needed? (None for this feature)
- Queue workers running? ✅

**10.2** Run migrations on staging
- SSH to staging server
- Run: `php artisan migrate`
- Verify no errors
- Check backup_items and restore_runs tables

**10.3** Smoke test on staging
- Add policies to a Backup Set with "Include assignments"
- Restore to same tenant
- Verify success

**10.4** Update spec.md with implementation notes
- Add "Implementation Status" section
- Note any deviations from original spec
- Document known limitations (e.g., Settings Catalog only in Phase 1)

**10.5** Create migration guide
- Document: "Existing backups will NOT have assignments (not retroactive)"
- Document: "Re-add policies or capture snapshots with include assignments/scope tags to capture data"

**10.6** Add monitoring alerts
- Alert: Assignment fetch failure rate > 10%
- Alert: Group resolution failure rate > 5%
- Use Laravel logs or external monitoring (e.g., Sentry)

**10.7** Production deployment
- Deploy to production via Dokploy
- Run migrations
- Monitor logs for 24 hours
- Check for errors

**10.8** Verify no rate limit issues
- Monitor Graph API headers: `X-RateLimit-Remaining`
- If rate limiting detected, increase delays (currently 100ms)

**10.9** Final documentation update
- Update README with feature status
- Link to quickstart.md for developers
- Add to feature list

**10.10** Close tasks in tasks.md
- Mark all tasks complete ✅
- Archive planning docs (optional)

---

## Task Summary

### MVP Scope (⭐ Tasks)
**Total**: ~24 tasks  
**Estimated**: 16-22 hours

**Breakdown**:
- Phase 1 (Setup): 17 tasks (2-3h)
- Phase 2 (Graph Services): 18 tasks (4-6h)
- Phase 3 (Capture): 8 tasks (4-5h)
- Phase 4 (Policy Version view): 5 tasks (3-4h)
- Phase 5 (Restore, basic): Partial (subset for same-tenant restore)

**MVP Scope Definition**:
- ✅ Capture assignments/scope tags in Add Policies + Capture Snapshot (US1)
- ✅ Policy Version assignments widget (US2)
- ✅ Basic restore (same tenant, auto-match) (US3 partial)
- ❌ Group mapping wizard (defer to post-MVP)
- ❌ Restore preview diff (defer to post-MVP)
- ⚠️ Scope tags restore (capture done, restore deferred)

---

### Full Implementation
**Total**: ~64 tasks  
**Estimated**: 30-40 hours

**Breakdown**:
- Phase 1: 17 tasks (2-3h)
- Phase 2: 18 tasks (4-6h)
- Phase 3: 8 tasks (4-5h)
- Phase 4: 5 tasks (3-4h)
- Phase 5: 22 tasks (6-8h)
- Phase 6: 7 tasks (2-3h)
- Phase 7: 8 tasks (2-3h)
- Phase 8: 10 tasks (3-4h)
- Phase 9: 11 tasks (2-3h)
- Phase 10: 10 tasks (1-2h)

---

## Parallel Development Opportunities

### Track 1: Backend Services (Dev A)
- Phase 1: Database setup (17 tasks)
- Phase 2: Graph API services (18 tasks)
- Phase 3: Capture services (8 tasks)
- Phase 5: Restore service (22 tasks)

**Total**: ~16-22 hours

---

### Track 2: Frontend/UI (Dev B)
- Phase 4: Policy Version view (5 tasks)
- Phase 5: Group mapping wizard (subset of Phase 5, ~10 tasks)
- Phase 6: Restore preview diff (7 tasks)
- Phase 8: UI polish (subset, ~5 tasks)

**Total**: ~12-16 hours

---

### Track 3: Testing & QA (Dev C or shared)
- Phase 2: Unit tests for services (subset)
- Phase 3: Feature tests for backup (subset)
- Phase 4: Feature tests for policy version view (subset)
- Phase 9: Manual QA + browser tests (11 tasks)

**Total**: ~8-12 hours

**Note**: Testing can be done incrementally as phases complete.

---

## Dependencies Graph

```
Phase 1 (Setup)
    ↓
Phase 2 (Graph Services)
    ↓
Phase 3 (Capture) → Phase 4 (Policy Version view)
    ↓                    ↓
Phase 5 (Restore) ←------+
    ↓
Phase 6 (Preview Diff)
    ↓
Phase 7 (Scope Tags)
    ↓
Phase 8 (Polish)
    ↓
Phase 9 (Testing & QA)
    ↓
Phase 10 (Deployment)
```

**Parallel Phases**: Phase 3 and Phase 4 can run in parallel after Phase 2.

---

## Risk Mitigation Tasks

### Risk: Graph API Rate Limiting
- **Task 2.11**: Add retry logic with exponential backoff
- **Task 5.11**: Add 100ms delay between POSTs
- **Task 8.3**: Batch group resolution (max 100 per request)

### Risk: Large Group Counts (500+)
- **Task 5.4**: Implement searchable dropdown with debounce
- **Task 5.6**: Cache target tenant groups
- **Task 8.4**: Pre-warm cache on wizard mount

### Risk: Assignment Restore Failures
- **Task 5.12**: Fail-soft per-assignment error handling
- **Task 5.13**: Store outcomes in RestoreRun results
- **Task 5.14**: Audit log all outcomes

---

## Next Actions

1. **Review**: Team review of tasks.md, adjust estimates if needed
2. **Assign**: Assign tasks to developers (Track 1/2/3)
3. **Start**: Begin with Phase 1 (Setup & Database)
4. **Track Progress**: Update task status as work progresses
5. **Iterate**: Adjust plan based on discoveries during implementation

---

**Status**: Task Breakdown Complete  
**Ready for Implementation**: ✅  
**Estimated Duration**: 30-40 hours (MVP: 16-22 hours)
