docs(004): align spec and tasks with policy version capture

This commit is contained in:
Ahmed Darrazi 2025-12-23 00:38:28 +01:00
parent 0f8ecfe470
commit 8026a38233
2 changed files with 237 additions and 229 deletions

View File

@ -28,59 +28,57 @@ ## Scope
- **Policy Types**: `settingsCatalogPolicy` only (initially) - **Policy Types**: `settingsCatalogPolicy` only (initially)
- **Graph Endpoints**: - **Graph Endpoints**:
- GET `/deviceManagement/configurationPolicies/{id}/assignments` - GET `/deviceManagement/configurationPolicies/{id}/assignments`
- POST/PATCH `/deviceManagement/configurationPolicies/{id}/assign` - POST/PATCH `/deviceManagement/configurationPolicies/{id}/assignments`
- GET `/deviceManagement/roleScopeTags` (for reference data) - GET `/deviceManagement/roleScopeTags` (for reference data)
- **Backup Behavior**: Optional (checkbox "Include Assignments & Scope Tags") - GET `/deviceManagement/assignmentFilters` (for filter names)
- **Backup Behavior**: Optional at capture time with separate checkboxes ("Include assignments", "Include scope tags") on Add Policies and Capture Snapshot actions (defaults: true)
- **Restore Behavior**: With group mapping UI for unresolved group IDs - **Restore Behavior**: With group mapping UI for unresolved group IDs
--- ---
## User Stories ## User Stories
### User Story 1 - Backup with Assignments & Scope Tags (Priority: P1) ### User Story 1 - Capture Assignments & Scope Tags (Priority: P1)
**As an admin**, I want to optionally include assignments and scope tags when backing up Settings Catalog policies, so that I have complete policy state for migration or disaster recovery. **As an admin**, I want to optionally include assignments and scope tags when capturing policy snapshots or adding policies to a Backup Set, so that I have complete policy state for migration or disaster recovery.
**Acceptance Criteria:** **Acceptance Criteria:**
1. **Given** I create a new Backup Set for Settings Catalog policies, 1. **Given** I add policies to a Backup Set (or capture a snapshot from the Policy view),
**When** I enable the checkbox "Include Assignments & Scope Tags", **When** I enable "Include assignments" and/or "Include scope tags",
**Then** the backup captures: **Then** the capture stores:
- Assignment list (groups, users, devices with include/exclude mode) - Assignments on the PolicyVersion (include/exclude targets + filters)
- Scope Tag IDs referenced by the policy - Scope tags on the PolicyVersion as `{ids, names}`
- Metadata about assignment count and scope tag names - A BackupItem linked via `policy_version_id` that copies assignments for restore
2. **Given** I view a Backup Set with assignments included, 2. **Given** I create a Backup Set,
**When** I expand a Backup Item detail, **When** I complete the form,
**Then** I see: **Then** no assignments/scope tags checkbox appears on that screen (selection happens when adding policies).
- "Assignments: 3 groups, 2 users" summary
- "Scope Tags: Default, HR-Admins" list
- JSON tab with full assignment payload
3. **Given** I create a Backup Set without enabling the checkbox, 3. **Given** I disable either checkbox,
**When** the backup completes, **When** the capture completes,
**Then** assignments and scope tags are NOT captured (payload-only backup) **Then** the corresponding PolicyVersion fields are `null` and the BackupItem is created without those data.
--- ---
### User Story 2 - Policy View with Assignments Tab (Priority: P1) ### User Story 2 - Policy Version View with Assignments (Priority: P1)
**As an admin**, I want to see a policy's current assignments and scope tags in the Policy View, so I understand its targeting and visibility. **As an admin**, I want to see a policy version's captured assignments and scope tags, so I understand targeting and visibility at that snapshot.
**Acceptance Criteria:** **Acceptance Criteria:**
1. **Given** I view a Settings Catalog policy, 1. **Given** I view a Settings Catalog Policy Version,
**When** I navigate to the "Assignments" tab, **When** assignments were captured,
**Then** I see: **Then** I see:
- Table with columns: Type (Group/User/Device), Name, Mode (Include/Exclude), ID - Include/Exclude group targets with group display name (or "Unknown Group (ID: ...)")
- "Scope Tags" section showing: Default, HR-Admins (editable IDs) - Filter name (if present) with filter mode (include/exclude)
- "Not assigned" message if no assignments exist - Scope tags list from the version
2. **Given** a policy has 10 assignments, 2. **Given** assignments were not captured for this version,
**When** I filter by "Include only" or "Exclude only", **When** I open the assignments panel,
**Then** the table filters accordingly **Then** I see "Assignments were not captured for this version."
3. **Given** assignments include deleted groups (orphaned IDs), 3. **Given** scope tags were not captured,
**When** I view the assignments tab, **When** I view the version,
**Then** orphaned entries show as "Unknown Group (ID: abc-123)" with warning badge **Then** I see a "Scope tags not captured" empty state.
--- ---
@ -135,39 +133,47 @@ ## Functional Requirements
### Backup & Storage ### Backup & Storage
**FR-004.1**: System MUST provide a checkbox "Include Assignments & Scope Tags" on the Backup Set creation form (default: unchecked). **FR-004.1**: System MUST provide separate checkboxes "Include assignments" and "Include scope tags" on:
- Add Policies to Backup Set action
- Capture snapshot action in Policy view
Defaults: checked. Backup Set creation form MUST NOT show these checkboxes.
**FR-004.2**: When assignments are included, system MUST fetch assignments using fallback strategy: **FR-004.2**: When assignments are included, system MUST fetch assignments using fallback strategy:
1. Try: `/deviceManagement/configurationPolicies/{id}/assignments` 1. Try: `/deviceManagement/configurationPolicies/{id}/assignments`
2. If empty/fails: Try `$expand=assignments` on policy fetch 2. If empty/fails: Try `$expand=assignments` on policy fetch
3. Store: 3. Continue capture with assignments `null` on failure (fail-soft) and set `assignments_fetch_failed: true`.
- Assignment array (each with: `target` object, `id`, `intent`, filters)
- Extracted metadata: group names (resolved via `/directoryObjects/getByIds`), user UPNs, device IDs
- Warning flags for orphaned IDs
- Fallback flag: `assignments_fetch_method` (direct | expand | failed)
**FR-004.3**: System MUST store Scope Tag IDs in backup metadata (from policy payload `roleScopeTagIds` field). **FR-004.3**: System MUST enrich assignments with:
- Group display name + orphaned flag via `/directoryObjects/getByIds`
- Assignment filter name via `/deviceManagement/assignmentFilters`
- Preserve target type (include/exclude) and filter mode (`deviceAndAppManagementAssignmentFilterType`)
**FR-004.4**: Backup Item `metadata` JSONB field MUST include: **FR-004.4**: System MUST store assignments and scope tags on PolicyVersion:
- `policy_versions.assignments` (array, nullable)
- `policy_versions.scope_tags` as `{ids: [], names: []}` (nullable)
- hashes for deduplication (`assignments_hash`, `scope_tags_hash`)
BackupItem MUST link to PolicyVersion via `policy_version_id` and copy assignments for restore.
**FR-004.5**: Capture metadata stored on PolicyVersion and BackupItem MUST include:
```json ```json
{ {
"assignment_count": 5, "has_assignments": true,
"scope_tag_ids": ["0", "abc-123"], "has_scope_tags": true,
"scope_tag_names": ["Default", "HR-Admins"], "has_orphaned_assignments": false,
"has_orphaned_assignments": false "assignments_fetch_failed": false
} }
``` ```
Assignment counts are derived from `assignments` at display time.
**FR-004.5**: System MUST gracefully handle Graph API failures when fetching assignments (log warning, continue backup with flag `assignments_fetch_failed: true`).
### UI Display ### UI Display
**FR-004.6**: Policy View MUST show an "Assignments" tab for Settings Catalog policies displaying: **FR-004.6**: Policy Version view MUST show an assignments panel for Settings Catalog versions displaying:
- Assignments table (type, name, mode, ID) - Include/Exclude targets with group display name or "Unknown Group (ID: ...)"
- Assignment filter name + filter mode (include/exclude) when present
- Scope Tags section - Scope Tags section
- Empty state if no assignments - Empty state if assignments or scope tags were not captured
**FR-004.7**: Backup Item detail view MUST show assignment count and scope tag names in metadata summary. **FR-004.7**: Backup Set items table MUST show assignment count (derived from `backup_items.assignments`) and scope tag names from the linked PolicyVersion.
**FR-004.8**: System MUST render orphaned group IDs as "Unknown Group (ID: {id})" with warning icon. **FR-004.8**: System MUST render orphaned group IDs as "Unknown Group (ID: {id})" with warning icon.
@ -235,12 +241,33 @@ ## Non-Functional Requirements
## Data Model Changes ## Data Model Changes
### Migration: `backup_items` table extension ### Migration: `policy_versions` assignments + scope tags
```php
Schema::table('policy_versions', function (Blueprint $table) {
$table->json('assignments')->nullable()->after('metadata');
$table->json('scope_tags')->nullable()->after('assignments');
$table->string('assignments_hash', 64)->nullable()->after('scope_tags');
$table->string('scope_tags_hash', 64)->nullable()->after('assignments_hash');
$table->index('assignments_hash');
$table->index('scope_tags_hash');
});
```
### Migration: `backup_items` policy_version_id
```php
Schema::table('backup_items', function (Blueprint $table) {
$table->foreignId('policy_version_id')->nullable()->constrained('policy_versions');
});
```
### Migration: `backup_items` assignments copy
```php ```php
Schema::table('backup_items', function (Blueprint $table) { Schema::table('backup_items', function (Blueprint $table) {
$table->json('assignments')->nullable()->after('metadata'); $table->json('assignments')->nullable()->after('metadata');
// stores: [{target:{...}, id, intent, filters}, ...] // copy of PolicyVersion assignments for restore safety
}); });
``` ```
@ -253,13 +280,21 @@ ### Migration: `restore_runs` table extension
}); });
``` ```
### `policy_versions.scope_tags` JSONB schema
```json
{
"ids": ["0", "123"],
"names": ["Default", "HR"]
}
```
### `backup_items.metadata` JSONB schema ### `backup_items.metadata` JSONB schema
```json ```json
{ {
"assignment_count": 5, "has_assignments": true,
"scope_tag_ids": ["0", "123"], "has_scope_tags": true,
"scope_tag_names": ["Default", "HR"],
"has_orphaned_assignments": false, "has_orphaned_assignments": false,
"assignments_fetch_failed": false "assignments_fetch_failed": false
} }
@ -321,6 +356,11 @@ ### Endpoints to Add (Production-Tested Strategies)
- For Scope Tag resolution (cache 1 hour) - For Scope Tag resolution (cache 1 hour)
- Scope Tag IDs also available in policy payload's `roleScopeTagIds` array - Scope Tag IDs also available in policy payload's `roleScopeTagIds` array
5. **GET** `/deviceManagement/assignmentFilters?$select=id,displayName`
- For assignment filter name resolution (cache 1 hour)
- Filter IDs in assignments: `deviceAndAppManagementAssignmentFilterId`
- Filter mode: `deviceAndAppManagementAssignmentFilterType` (include/exclude)
### Graph Contract Updates ### Graph Contract Updates
Add to `config/graph_contracts.php`: Add to `config/graph_contracts.php`:
@ -348,18 +388,17 @@ ### Graph Contract Updates
## UI Mockups (Wireframe Descriptions) ## UI Mockups (Wireframe Descriptions)
### Policy View - Assignments Tab ### Policy Version View - Assignments Panel
``` ```
[General] [Settings] [Assignments] [JSON] [General] [Settings] [JSON]
Assignments (5) Assignments (5)
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Type │ Name │ Mode │ ID │ │ Type │ Name │ Filter │ ID │
├─────────┼───────────────────┼─────────┼─────────┤ ├─────────┼───────────────────┼─────────┼─────────┤
│ Group │ All Users │ Include │ abc-123 │ │ Include group │ All Users │ Test (include) │ abc-123 │
│ Group │ Contractors │ Exclude │ def-456 │ │ Exclude group │ Contractors │ - │ def-456 │
│ User │ john@contoso.com │ Include │ ghi-789 │
└─────────────────────────────────────────────────┘ └─────────────────────────────────────────────────┘
Scope Tags (2) Scope Tags (2)
@ -367,18 +406,20 @@ ### Policy View - Assignments Tab
• HR-Admins (ID: 123) • HR-Admins (ID: 123)
``` ```
### Backup Creation - Checkbox ### Add Policies to Backup Set - Checkboxes
``` ```
Create Backup Set Add Policies to Backup Set
───────────────── ─────────────────────────
Select Policies: [Settings Catalog: 15 selected] Select Policies: [Settings Catalog: 15 selected]
☑ Include Assignments & Scope Tags ☑ Include assignments
Captures group/user targeting and RBAC scope. Captures include/exclude targeting and filters.
Adds ~2-5 KB per policy with assignments.
[Cancel] [Create Backup] ☑ Include scope tags
Captures policy scope tag IDs.
[Cancel] [Add Policies]
``` ```
### Restore Wizard - Group Mapping Step ### Restore Wizard - Group Mapping Step
@ -408,15 +449,18 @@ ### Unit Tests
- `AssignmentFetcherTest`: Mock Graph responses, test parsing - `AssignmentFetcherTest`: Mock Graph responses, test parsing
- `GroupMapperTest`: Test ID resolution, mapping logic - `GroupMapperTest`: Test ID resolution, mapping logic
- `ScopeTagResolverTest`: Test caching, name resolution - `ScopeTagResolverTest`: Test caching, name resolution
- `AssignmentFilterResolverTest`: Test caching and ID filtering
### Feature Tests ### Feature Tests
- `BackupWithAssignmentsTest`: E2E backup creation with checkbox - `BackupWithAssignmentsConsistencyTest`: PolicyVersion as source of truth
- `PolicyViewAssignmentsTabTest`: UI rendering, orphaned IDs - `VersionCaptureWithAssignmentsTest`: Snapshot capture with assignments/scope tags
- `PolicyVersionViewAssignmentsTest`: UI rendering, orphaned IDs, filters
- `RestoreGroupMappingTest`: Wizard flow, mapping persistence - `RestoreGroupMappingTest`: Wizard flow, mapping persistence
- `RestoreAssignmentApplicationTest`: Graph API calls, outcomes - `RestoreAssignmentApplicationTest`: Graph API calls, outcomes
### Manual QA ### Manual QA
- Create backup with/without assignments checkbox - Add policies to backup set with/without assignments and scope tags
- Capture snapshot with/without assignments and scope tags
- Restore to same tenant (auto-match groups) - Restore to same tenant (auto-match groups)
- Restore to different tenant (group mapping wizard) - Restore to different tenant (group mapping wizard)
- Handle orphaned group IDs gracefully - Handle orphaned group IDs gracefully
@ -426,10 +470,10 @@ ### Manual QA
## Rollout Plan ## Rollout Plan
### Phase 1: Backup with Assignments (MVP) ### Phase 1: Backup with Assignments (MVP)
- Add checkbox to Backup form - Add checkboxes on Add Policies + Capture Snapshot actions
- Fetch assignments from Graph - Fetch assignments from Graph
- Store in `backup_items.assignments` - Store on PolicyVersion (copy assignments to BackupItem)
- Display in Policy View (read-only) - Display in Policy Version view (read-only)
- **Duration**: ~8-12 hours - **Duration**: ~8-12 hours
### Phase 2: Restore with Group Mapping ### Phase 2: Restore with Group Mapping
@ -440,7 +484,7 @@ ### Phase 2: Restore with Group Mapping
### Phase 3: Scope Tags ### Phase 3: Scope Tags
- Resolve Scope Tag names - Resolve Scope Tag names
- Display in UI - Display in Policy Version view
- Handle restore warnings - Handle restore warnings
- **Duration**: ~4-6 hours - **Duration**: ~4-6 hours
@ -461,7 +505,7 @@ ## Risks & Mitigations
| Risk | Mitigation | | Risk | Mitigation |
|------|------------| |------|------------|
| Graph API assignments endpoint slow/fails | Async fetch, fail-soft with warning | | Graph API assignments endpoint slow/fails | Fail-soft with warning |
| Target tenant has 1000+ groups | Searchable dropdown with pagination | | Target tenant has 1000+ groups | Searchable dropdown with pagination |
| Group IDs change across tenants | Group name-based matching fallback | | Group IDs change across tenants | Group name-based matching fallback |
| Scope Tag IDs don't exist in target | Log warning, allow policy creation | | Scope Tag IDs don't exist in target | Log warning, allow policy creation |
@ -470,8 +514,8 @@ ## Risks & Mitigations
## Success Criteria ## Success Criteria
1. ✅ Backup checkbox functional, assignments captured 1. ✅ Capture checkboxes functional, assignments captured
2. ✅ Policy View shows assignments tab with accurate data 2. ✅ Policy Version view shows assignments widget with accurate data
3. ✅ Group Mapping wizard handles 100+ groups smoothly 3. ✅ Group Mapping wizard handles 100+ groups smoothly
4. ✅ Restore applies assignments with 90%+ success rate 4. ✅ Restore applies assignments with 90%+ success rate
5. ✅ Audit logs record all mapping decisions 5. ✅ Audit logs record all mapping decisions

View File

@ -3,12 +3,20 @@ # Feature 004: Assignments & Scope Tags - Task Breakdown
## Overview ## Overview
This document breaks down the implementation plan into granular, actionable tasks organized by phase and user story. This document breaks down the implementation plan into granular, actionable tasks organized by phase and user story.
**Total Estimated Tasks**: 62 tasks **Total Estimated Tasks**: ~60 tasks
**MVP Scope**: Tasks marked with ⭐ (24 tasks, ~16-22 hours) **MVP Scope**: Tasks marked with ⭐ (updated)
**Full Implementation**: All tasks (~30-40 hours) **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) ## Phase 1: Setup & Database (Foundation)
**Duration**: 2-3 hours **Duration**: 2-3 hours
@ -96,6 +104,21 @@ ### Tasks
- Run: `php artisan test --filter=RestoreRun` - Run: `php artisan test --filter=RestoreRun`
- Expected: All green - 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) ## Phase 2: Graph API Integration (Core Services)
@ -194,87 +217,64 @@ ### Tasks
- Run: `php artisan test --filter=ScopeTagResolver` - Run: `php artisan test --filter=ScopeTagResolver`
- Expected: All green, 90%+ coverage - 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 - Backup with Assignments (MVP Core) ## Phase 3: US1 - Capture Assignments & Scope Tags (MVP Core)
**Duration**: 4-5 hours **Duration**: 4-5 hours
**Dependencies**: Phase 2 complete **Dependencies**: Phase 2 complete
**Parallelizable**: Partially (service and UI separate) **Parallelizable**: Partially (service and UI separate)
### Tasks ### 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.1** ⭐ Add checkbox to Backup creation form **3.2** [X] ⭐ Add include checkboxes to "Capture snapshot" action
- File: `app/Filament/Resources/BackupResource/Pages/CreateBackup.php` - File: `app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`
- Component: `Checkbox::make('include_assignments')` - Components: `include_assignments`, `include_scope_tags` (default: true)
- Label: "Include Assignments & Scope Tags"
- Help text: "Captures group/user targeting and RBAC scope. Adds ~2-5 KB per policy."
- Default: `false`
- Show only for Settings Catalog policies
**3.2** ⭐ Create service: `AssignmentBackupService` **3.3** [X] ⭐ Create `PolicyCaptureOrchestrator`
- File: `app/Services/AssignmentBackupService.php` - File: `app/Services/Intune/PolicyCaptureOrchestrator.php`
- Method: `backup(int $tenantId, string $policyId, bool $includeAssignments): BackupItem` - Capture payload + assignments + scope tags
- Call `AssignmentFetcher` if `includeAssignments` true - Reuse existing PolicyVersion by snapshot hash
- Call `ScopeTagResolver` for scope tags - Backfill assignments/scope tags when missing
- Update `backup_items.assignments` and `metadata`
**3.3** ⭐ Implement assignment fetch in `AssignmentBackupService` **3.4** [X] ⭐ Store assignments/scope tags on PolicyVersion
- Check `includeAssignments` flag - Update `VersionService::captureVersion` to accept assignments/scopeTags
- Call `$this->assignmentFetcher->fetch($tenantId, $policyId)` - Store `assignments_hash` and `scope_tags_hash`
- Store result in `$backupItem->assignments`
- If empty or failed, set `metadata['assignments_fetch_failed'] = true`
**3.4** ⭐ Implement scope tag resolution in `AssignmentBackupService` **3.5** [X] ⭐ Link BackupItem to PolicyVersion and copy assignments
- Extract `roleScopeTagIds` from policy payload - Add `policy_version_id` to backup_items
- Call `$this->scopeTagResolver->resolve($scopeTagIds)` - BackupService uses orchestrator and copies assignments
- Store in `metadata['scope_tag_ids']` and `metadata['scope_tag_names']` - Scope tags live on PolicyVersion only
**3.5** ⭐ Update `metadata` with assignment summary **3.6** [X] Update BackupSet items table columns
- Set `metadata['assignment_count']` - Eager-load `policyVersion`
- Set `metadata['has_orphaned_assignments']` (detect via GroupResolver) - Assignment count derived from `backup_items.assignments`
- Set `metadata['assignments_fetch_failed']` on error - Scope tags read from `policyVersion.scope_tags`
**3.6** Create job: `FetchAssignmentsJob` **3.7** [X] Add audit log entry: `backup.assignments.included`
- File: `app/Jobs/FetchAssignmentsJob.php` - Log when `include_assignments` is enabled
- Dispatch async after backup creation if checkbox enabled
- Call `AssignmentBackupService` in job
- Handle failures gracefully (log, don't retry)
**3.7** Add audit log entry: `backup.assignments.included` **3.8** [X] ⭐ Write/Update feature tests
- Create entry when checkbox enabled - `BackupWithAssignmentsConsistencyTest`
- Metadata: tenant_id, backup_set_id, policy_count, assignment_count - `VersionCaptureWithAssignmentsTest`
- `PolicyVersionViewAssignmentsTest`
**3.8** ⭐ Write feature test: `BackupWithAssignmentsTest::creates_backup_with_assignments`
- Mock Graph API responses (assignments, scope tags)
- Create backup with checkbox enabled
- Assert `backup_items.assignments` populated
- Assert `metadata['assignment_count']` correct
- Assert audit log entry created
**3.9** ⭐ Write feature test: `BackupWithAssignmentsTest::creates_backup_without_assignments`
- Create backup with checkbox disabled
- Assert `backup_items.assignments` is null
- Assert `metadata['assignment_count']` is 0 or not set
**3.10** ⭐ Write feature test: `BackupWithAssignmentsTest::handles_fetch_failure`
- Mock Graph API throwing exception
- Create backup with checkbox enabled
- Assert backup completes (fail-soft)
- Assert `metadata['assignments_fetch_failed'] = true`
- Assert warning logged
**3.11** Run Pint: Format new code
- `./vendor/bin/pint app/Services/AssignmentBackupService.php`
- `./vendor/bin/pint app/Jobs/FetchAssignmentsJob.php`
**3.12** Verify feature tests pass
- Run: `php artisan test --filter=BackupWithAssignments`
- Expected: All green
--- ---
## Phase 4: US2 - Policy View with Assignments Tab ## Phase 4: US2 - Policy Version View with Assignments
**Duration**: 3-4 hours **Duration**: 3-4 hours
**Dependencies**: Phase 3 complete **Dependencies**: Phase 3 complete
@ -282,60 +282,24 @@ ## Phase 4: US2 - Policy View with Assignments Tab
### Tasks ### Tasks
**4.1** ⭐ Add "Assignments" tab to Policy view **4.1** [X] ⭐ Create assignments widget for PolicyVersion view
- File: `app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php` - Livewire component: `PolicyVersionAssignmentsWidget`
- Use Filament `Tabs` component - Render via `ViewPolicyVersion::getFooterWidgets`
- Add tab: `Tab::make('Assignments')`
- Show only for Settings Catalog policies with assignments
**4.2** ⭐ Create assignments table in tab **4.2** [X] Show include/exclude groups and filters
- Use Filament `Table` component - Map target types to Include/Exclude labels
- Columns: Type (group/user/device), Name, Mode (Include/Exclude), ID - Render group display name or "Unknown Group (ID: ...)"
- Data source: `$this->record->assignments` (from BackupItem via Policy relationship) - Show assignment filter name + filter mode
**4.3** ⭐ Handle orphaned group IDs in table **4.3** [X] Show scope tags section
- Check if `displayName` is null - Use `PolicyVersion.scope_tags['names']`
- Render: "Unknown Group (ID: {id})" with warning icon - Empty state when not captured
- Use Filament `Badge` component with color `warning`
**4.4** Create Scope Tags section **4.4** [X] Remove assignments UI from Policy view
- Below assignments table - Policy view no longer shows assignments (moved to PolicyVersion view)
- Use Filament `Infolist` component
- Display scope tag names with IDs: "Default (ID: 0), HR-Admins (ID: abc-123)"
**4.5** Handle empty state **4.5** [X] ⭐ Update tests
- Show message: "No assignments found" - `PolicyVersionViewAssignmentsTest`
- Use Filament `EmptyState` component
- Icon: `heroicon-o-user-group`
**4.6** ⭐ Write feature test: `PolicyViewAssignmentsTabTest::displays_assignments_table`
- Create policy with assignments
- Visit policy view page
- Assert "Assignments" tab visible
- Assert table contains assignment data
**4.7** ⭐ Write feature test: `PolicyViewAssignmentsTabTest::displays_orphaned_ids_with_warning`
- Create policy with orphaned group ID (no name in metadata)
- Visit assignments tab
- Assert "Unknown Group" text visible
- Assert warning badge present
**4.8** ⭐ Write feature test: `PolicyViewAssignmentsTabTest::displays_scope_tags`
- Create policy with scope tags
- Visit assignments tab
- Assert scope tag names + IDs displayed
**4.9** ⭐ Write feature test: `PolicyViewAssignmentsTabTest::shows_empty_state`
- Create policy without assignments
- Visit assignments tab
- Assert "No assignments found" message visible
**4.10** Run Pint: Format view code
- `./vendor/bin/pint app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`
**4.11** Verify feature tests pass
- Run: `php artisan test --filter=PolicyViewAssignmentsTab`
- Expected: All green
--- ---
@ -589,13 +553,14 @@ ### Tasks
- Display loading message while warming - Display loading message while warming
**8.5** Add tooltips to UI elements **8.5** Add tooltips to UI elements
- Backup checkbox: "Captures group/user targeting and RBAC scope. Adds ~2-5 KB per policy." - 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." - Group mapping: "Map source groups to target groups for cross-tenant migrations."
- Skip checkbox: "Don't restore assignments targeting this group." - Skip checkbox: "Don't restore assignments targeting this group."
**8.6** Update README: Add "Assignments & Scope Tags" section **8.6** Update README: Add "Assignments & Scope Tags" section
- Overview of feature - Overview of feature
- How to use backup checkbox - How to use include assignments/scope tags (Add Policies + Capture snapshot)
- How to use group mapping wizard - How to use group mapping wizard
- Troubleshooting tips - Troubleshooting tips
@ -626,23 +591,22 @@ ## Phase 9: Testing & QA
### Tasks ### Tasks
**9.1** Manual QA: Backup with assignments (same tenant) **9.1** Manual QA: Add policies with assignments (same tenant)
- Navigate to backup creation - Open a Backup Set and add policies
- Enable checkbox - Enable "Include assignments" (and scope tags if desired)
- Verify backup completes - Verify add completes
- Verify assignments stored in DB - Verify assignments stored in DB
- Verify audit log entry - Verify audit log entry
**9.2** Manual QA: Backup without assignments **9.2** Manual QA: Add policies without assignments
- Navigate to backup creation - Add policies to a Backup Set or capture a snapshot
- Disable checkbox - Disable "Include assignments"
- Verify backup completes - Verify add/capture completes
- Verify assignments column is null - Verify assignments column is null
**9.3** Manual QA: Policy view with assignments tab **9.3** Manual QA: Policy Version view with assignments widget
- Navigate to policy with assignments - Navigate to a Policy Version with assignments
- Click "Assignments" tab - Verify widget renders
- Verify table renders
- Verify orphaned IDs show warning - Verify orphaned IDs show warning
- Verify scope tags section - Verify scope tags section
@ -666,7 +630,7 @@ ### Tasks
**9.7** Manual QA: Handle Graph API failures **9.7** Manual QA: Handle Graph API failures
- Simulate API failure (disable network or use Http::fake with 500 response) - Simulate API failure (disable network or use Http::fake with 500 response)
- Attempt backup with checkbox - Attempt add policies or capture snapshot with "Include assignments"
- Verify fail-soft behavior - Verify fail-soft behavior
- Verify warning logged - Verify warning logged
@ -715,7 +679,7 @@ ### Tasks
- Check backup_items and restore_runs tables - Check backup_items and restore_runs tables
**10.3** Smoke test on staging **10.3** Smoke test on staging
- Create backup with assignments - Add policies to a Backup Set with "Include assignments"
- Restore to same tenant - Restore to same tenant
- Verify success - Verify success
@ -726,7 +690,7 @@ ### Tasks
**10.5** Create migration guide **10.5** Create migration guide
- Document: "Existing backups will NOT have assignments (not retroactive)" - Document: "Existing backups will NOT have assignments (not retroactive)"
- Document: "Re-create backups with checkbox to capture assignments" - Document: "Re-add policies or capture snapshots with include assignments/scope tags to capture data"
**10.6** Add monitoring alerts **10.6** Add monitoring alerts
- Alert: Assignment fetch failure rate > 10% - Alert: Assignment fetch failure rate > 10%
@ -757,35 +721,35 @@ ### Tasks
## Task Summary ## Task Summary
### MVP Scope (⭐ Tasks) ### MVP Scope (⭐ Tasks)
**Total**: 24 tasks **Total**: ~24 tasks
**Estimated**: 16-22 hours **Estimated**: 16-22 hours
**Breakdown**: **Breakdown**:
- Phase 1 (Setup): 11 tasks (2-3h) - Phase 1 (Setup): 17 tasks (2-3h)
- Phase 2 (Graph Services): 5 tasks (subset, ~2-3h) - Phase 2 (Graph Services): 18 tasks (4-6h)
- Phase 3 (Backup): 6 tasks (3-4h) - Phase 3 (Capture): 8 tasks (4-5h)
- Phase 4 (Policy View): 6 tasks (3-4h) - Phase 4 (Policy Version view): 5 tasks (3-4h)
- Phase 5 (Restore, basic): Partial (subset for same-tenant restore) - Phase 5 (Restore, basic): Partial (subset for same-tenant restore)
**MVP Scope Definition**: **MVP Scope Definition**:
- ✅ Backup with assignments checkbox (US1) - ✅ Capture assignments/scope tags in Add Policies + Capture Snapshot (US1)
- ✅ Policy view with assignments tab (US2) - ✅ Policy Version assignments widget (US2)
- ✅ Basic restore (same tenant, auto-match) (US3 partial) - ✅ Basic restore (same tenant, auto-match) (US3 partial)
- ❌ Group mapping wizard (defer to post-MVP) - ❌ Group mapping wizard (defer to post-MVP)
- ❌ Restore preview diff (defer to post-MVP) - ❌ Restore preview diff (defer to post-MVP)
- ❌ Scope tags full support (defer to post-MVP) - ⚠️ Scope tags restore (capture done, restore deferred)
--- ---
### Full Implementation ### Full Implementation
**Total**: 62 tasks **Total**: ~64 tasks
**Estimated**: 30-40 hours **Estimated**: 30-40 hours
**Breakdown**: **Breakdown**:
- Phase 1: 13 tasks (2-3h) - Phase 1: 17 tasks (2-3h)
- Phase 2: 16 tasks (4-6h) - Phase 2: 18 tasks (4-6h)
- Phase 3: 12 tasks (4-5h) - Phase 3: 8 tasks (4-5h)
- Phase 4: 11 tasks (3-4h) - Phase 4: 5 tasks (3-4h)
- Phase 5: 22 tasks (6-8h) - Phase 5: 22 tasks (6-8h)
- Phase 6: 7 tasks (2-3h) - Phase 6: 7 tasks (2-3h)
- Phase 7: 8 tasks (2-3h) - Phase 7: 8 tasks (2-3h)
@ -798,9 +762,9 @@ ### Full Implementation
## Parallel Development Opportunities ## Parallel Development Opportunities
### Track 1: Backend Services (Dev A) ### Track 1: Backend Services (Dev A)
- Phase 1: Database setup (13 tasks) - Phase 1: Database setup (17 tasks)
- Phase 2: Graph API services (16 tasks) - Phase 2: Graph API services (18 tasks)
- Phase 3: Backup service (12 tasks) - Phase 3: Capture services (8 tasks)
- Phase 5: Restore service (22 tasks) - Phase 5: Restore service (22 tasks)
**Total**: ~16-22 hours **Total**: ~16-22 hours
@ -808,7 +772,7 @@ ### Track 1: Backend Services (Dev A)
--- ---
### Track 2: Frontend/UI (Dev B) ### Track 2: Frontend/UI (Dev B)
- Phase 4: Policy view tab (11 tasks) - Phase 4: Policy Version view (5 tasks)
- Phase 5: Group mapping wizard (subset of Phase 5, ~10 tasks) - Phase 5: Group mapping wizard (subset of Phase 5, ~10 tasks)
- Phase 6: Restore preview diff (7 tasks) - Phase 6: Restore preview diff (7 tasks)
- Phase 8: UI polish (subset, ~5 tasks) - Phase 8: UI polish (subset, ~5 tasks)
@ -820,7 +784,7 @@ ### Track 2: Frontend/UI (Dev B)
### Track 3: Testing & QA (Dev C or shared) ### Track 3: Testing & QA (Dev C or shared)
- Phase 2: Unit tests for services (subset) - Phase 2: Unit tests for services (subset)
- Phase 3: Feature tests for backup (subset) - Phase 3: Feature tests for backup (subset)
- Phase 4: Feature tests for policy view (subset) - Phase 4: Feature tests for policy version view (subset)
- Phase 9: Manual QA + browser tests (11 tasks) - Phase 9: Manual QA + browser tests (11 tasks)
**Total**: ~8-12 hours **Total**: ~8-12 hours
@ -836,7 +800,7 @@ ## Dependencies Graph
Phase 2 (Graph Services) Phase 2 (Graph Services)
Phase 3 (Backup) → Phase 4 (Policy View) Phase 3 (Capture) → Phase 4 (Policy Version view)
↓ ↓ ↓ ↓
Phase 5 (Restore) ←------+ Phase 5 (Restore) ←------+