feat/004-assignments-scope-tags #4
@ -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"] }`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user