docs(004): align spec and tasks with policy version capture
This commit is contained in:
parent
0f8ecfe470
commit
8026a38233
@ -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
|
||||||
|
|||||||
@ -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) ←------+
|
||||||
↓
|
↓
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user