feat/004-assignments-scope-tags #4

Merged
ahmido merged 41 commits from feat/004-assignments-scope-tags into dev 2025-12-23 21:49:59 +00:00
Showing only changes of commit 0b8c0983a2 - Show all commits

View File

@ -141,12 +141,14 @@ ### Backup & Storage
**FR-004.2**: When assignments are included, system MUST fetch assignments using fallback strategy:
1. Try: `/deviceManagement/configurationPolicies/{id}/assignments`
2. If empty/fails: Try `$expand=assignments` on policy fetch
3. Continue capture with assignments `null` on failure (fail-soft) and set `assignments_fetch_failed: true`.
3. Continue capture with assignments `null` on failure (fail-soft) and set `assignments_fetch_failed: true` in PolicyVersion metadata.
- This flag covers any failure during assignment capture/enrichment (fetch, group resolution, filter resolution).
**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`)
- If filter name lookup fails or filter ID is unknown, keep filter ID + mode, omit the name, and continue capture (UI displays filter ID when name is missing).
**FR-004.4**: System MUST store assignments and scope tags on PolicyVersion:
- `policy_versions.assignments` (array, nullable)
@ -154,16 +156,7 @@ ### Backup & Storage
- 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
{
"has_assignments": true,
"has_scope_tags": true,
"has_orphaned_assignments": false,
"assignments_fetch_failed": false
}
```
Assignment counts are derived from `assignments` at display time.
**FR-004.5**: PolicyVersion metadata MUST include capture flags (see Data Model). BackupItem metadata MAY mirror these flags for display/audit, but PolicyVersion is the source of truth. Assignment counts are derived from `assignments` at display time.
### UI Display
@ -177,9 +170,14 @@ ### UI Display
**FR-004.8**: System MUST render orphaned group IDs as "Unknown Group (ID: {id})" with warning icon.
**Terminology**:
- **Orphaned group ID**: A group ID referenced in assignments that cannot be resolved in the source tenant during capture.
- **Unresolved group ID**: A group ID not found in the target tenant during restore mapping.
- UI SHOULD render both as "Unknown Group (ID: ...)" with warning styling.
### Restore with Group Mapping
**FR-004.9**: Restore preview MUST detect unresolved group IDs by calling POST `/directoryObjects/getByIds` with batch of group IDs extracted from source assignments (types: ["group"]). Missing IDs = unresolved.
**FR-004.9**: Restore preview MUST detect unresolved (target-missing) group IDs by calling POST `/directoryObjects/getByIds` with batch of group IDs extracted from source assignments (types: ["group"]). Missing IDs = unresolved.
**FR-004.10**: When unresolved groups exist, system MUST inject a "Group Mapping" step in the restore wizard showing:
- Source group (name from backup metadata or ID if name unavailable)
@ -201,6 +199,7 @@ ### Restore with Group Mapping
- 201 Created on POST = success
- Log request-id/client-request-id on any failure
6. Continue with remaining assignments if one fails (fail-soft)
7. Restore is best-effort: no transactional rollback between DELETE and POST. If DELETE succeeds but POST fails, record a failed outcome, mark the restore as partial, and allow retry.
**FR-004.13**: System MUST handle assignment restore failures gracefully:
- Log per-assignment outcome (success/skip/failure)
@ -219,9 +218,9 @@ ### Scope Tags
**FR-004.16**: System MUST resolve Scope Tag names via `/deviceManagement/roleScopeTags` (with caching, TTL 1 hour).
**FR-004.17**: During restore, system SHOULD preserve Scope Tag IDs if they exist in target tenant, or:
- Log warning if Scope Tag ID doesn't exist in target
- Allow policy creation to proceed (Graph API default behavior)
**FR-004.17**: During restore, system MUST preserve Scope Tag IDs that exist in the target tenant. If a Scope Tag ID is missing:
- Log a warning
- Proceed without that tag (best-effort, Graph API default behavior)
**FR-004.18**: Restore preview MUST show Scope Tag diff: "Scope Tags: 2 matched, 1 not found in target tenant".
@ -229,7 +228,7 @@ ### Scope Tags
## Non-Functional Requirements
**NFR-004.1**: Assignment fetching MUST NOT block backup creation (async or fail-soft).
**NFR-004.1**: Assignment fetching MUST NOT block capture actions (Add Policies / Capture Snapshot). Use async or fail-soft behavior.
**NFR-004.2**: Group mapping UI MUST support search/filter for tenants with 500+ groups.
@ -289,7 +288,7 @@ ### `policy_versions.scope_tags` JSONB schema
}
```
### `backup_items.metadata` JSONB schema
### `policy_versions.metadata` JSONB schema
```json
{
@ -300,6 +299,8 @@ ### `backup_items.metadata` JSONB schema
}
```
BackupItem metadata MAY include the same flags copied from the PolicyVersion for display/audit, but PolicyVersion is the source of truth.
---
## Graph API Integration
@ -338,7 +339,7 @@ ### Endpoints to Add (Production-Tested Strategies)
- **DELETE** `/deviceManagement/configurationPolicies/{id}/assignments/{assignmentId}`
- Returns: 204 No Content
- **Restore Strategy**: DELETE all existing assignments, then POST new ones (atomic via transaction pattern)
- **Restore Strategy**: DELETE all existing assignments, then POST new ones (best-effort; record per-assignment outcomes, no transactional rollback)
3. **POST** `/directoryObjects/getByIds` (Stable Group Resolution)
- Body: `{ "ids": ["id1", "id2"], "types": ["group"] }`