From 28f440718adea890346ba20aaaa849e32c9e8a2a Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Mon, 22 Dec 2025 01:14:03 +0100 Subject: [PATCH] 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 --- specs/004-assignments-scope-tags/spec.md | 76 +++++++++++++++--------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/specs/004-assignments-scope-tags/spec.md b/specs/004-assignments-scope-tags/spec.md index 58c405c..cca658b 100644 --- a/specs/004-assignments-scope-tags/spec.md +++ b/specs/004-assignments-scope-tags/spec.md @@ -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,25 +280,30 @@ ### 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: - ```json - { - "assignments": [ - { - "id": "00000000-0000-0000-0000-000000000000", - "target": { - "@odata.type": "#microsoft.graph.groupAssignmentTarget", - "groupId": "abc-123-def" - }, - "intent": "apply" - } - ] - } - ``` +2. **Assignment CRUD Operations** (Standard Graph Pattern) + + - **POST** `/deviceManagement/configurationPolicies/{id}/assignments` + - Body: Single assignment object + - Returns: 201 Created with assignment object + - Example: + ```json + { + "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"] }` @@ -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 ], ```