fix(spec-004): Use documented CRUD on /assignments instead of undocumented /assign

Changes:
- Replace POST /assign with standard CRUD operations
- Restore strategy: DELETE existing + POST new assignments
- Graph Contract: assignments_create/update/delete_path + methods
- Handle 201 Created (POST) and 204 No Content (DELETE)
- Fail-soft: continue if individual assignment fails

Based on: Microsoft Learn Graph API docs + real-world usage patterns
This commit is contained in:
Ahmed Darrazi 2025-12-22 01:14:03 +01:00
parent 1fa15b4db2
commit 28f440718a

View File

@ -183,12 +183,18 @@ ### Restore with Group Mapping
**FR-004.11**: System MUST persist group mapping selections in RestoreRun metadata for audit and rerun purposes.
**FR-004.12**: When restoring assignments, system MUST:
1. Replace source group IDs with mapped target group IDs
2. Skip assignments marked "Skip"
1. Replace source group IDs with mapped target group IDs in assignment objects
2. Skip assignments marked "Skip" in group mapping
3. Preserve include/exclude intent and filters
4. Call POST `/deviceManagement/configurationPolicies/{id}/assign` (not PATCH) with complete mapped assignments array (replaces all assignments atomically)
5. Handle 204 No Content or 200 OK as success
6. Log Graph request-id and client-request-id on failure
4. Execute restore via DELETE-then-CREATE pattern:
- Step 1: GET existing assignments from target policy
- Step 2: DELETE each existing assignment (via DELETE `/assignments/{id}`)
- Step 3: POST each new/mapped assignment (via POST `/assignments`)
5. Handle failures gracefully:
- 204 No Content on DELETE = success
- 201 Created on POST = success
- Log request-id/client-request-id on any failure
6. Continue with remaining assignments if one fails (fail-soft)
**FR-004.13**: System MUST handle assignment restore failures gracefully:
- Log per-assignment outcome (success/skip/failure)
@ -274,26 +280,31 @@ ### Endpoints to Add (Production-Tested Strategies)
- Client-side filter to extract assignments
- **Reason**: Known Graph API quirks with assignment expansion on certain template families
2. **POST** `/deviceManagement/configurationPolicies/{id}/assign` (POST only, NOT PATCH)
- Body: `{ assignments: [assignment objects] }`
- Returns: 204 No Content or 200 OK
- **Note**: This is an action endpoint, replaces entire assignments array
- Example payload:
2. **Assignment CRUD Operations** (Standard Graph Pattern)
- **POST** `/deviceManagement/configurationPolicies/{id}/assignments`
- Body: Single assignment object
- Returns: 201 Created with assignment object
- Example:
```json
{
"assignments": [
{
"id": "00000000-0000-0000-0000-000000000000",
"target": {
"@odata.type": "#microsoft.graph.groupAssignmentTarget",
"groupId": "abc-123-def"
},
"intent": "apply"
}
]
}
```
- **PATCH** `/deviceManagement/configurationPolicies/{id}/assignments/{assignmentId}`
- Body: Assignment object (partial update)
- Returns: 200 OK with updated assignment
- **DELETE** `/deviceManagement/configurationPolicies/{id}/assignments/{assignmentId}`
- Returns: 204 No Content
- **Restore Strategy**: DELETE all existing assignments, then POST new ones (atomic via transaction pattern)
3. **POST** `/directoryObjects/getByIds` (Stable Group Resolution)
- Body: `{ "ids": ["id1", "id2"], "types": ["group"] }`
- Returns: `{ value: [{ id, displayName, ... }] }`
@ -316,11 +327,20 @@ ### Graph Contract Updates
```php
'settingsCatalogPolicy' => [
// ... existing
'assignments_path' => '/deviceManagement/configurationPolicies/{id}/assignments',
'assign_method' => 'POST',
'assign_path' => '/deviceManagement/configurationPolicies/{id}/assign',
// ... existing config
// Assignments CRUD (standard Graph pattern)
'assignments_list_path' => '/deviceManagement/configurationPolicies/{id}/assignments',
'assignments_create_path' => '/deviceManagement/configurationPolicies/{id}/assignments',
'assignments_create_method' => 'POST',
'assignments_update_path' => '/deviceManagement/configurationPolicies/{id}/assignments/{assignmentId}',
'assignments_update_method' => 'PATCH',
'assignments_delete_path' => '/deviceManagement/configurationPolicies/{id}/assignments/{assignmentId}',
'assignments_delete_method' => 'DELETE',
// Scope Tags
'supports_scope_tags' => true,
'scope_tag_field' => 'roleScopeTagIds', // array in policy payload
],
```