The Go worker for SCIM group synchronization is breaking on batch updates. Batching the PATCH requests to /api/v2/scim/v2/Groups/{id} seems right, but GC throws a 422 Unprocessable Entity when two groups in the same batch share a member that’s locked.
Worker traverses the source directory tree, finds changes, and builds the payload via delta calculation using hash comparison.
type BatchUpdate struct {
GroupID string
Ops []SCIMOp
}
batch := make([]BatchUpdate, 0)
for _, g := range deltas {
batch = append(batch, BatchUpdate{
GroupID: g.ID,
Ops: []SCIMOp{{
Op: "replace",
Path: "members",
Value: g.Members,
}},
})
}
Partial update failure is the blocker. If group A patches fine but group B hits a conflict, the whole batch response comes back failed. Reconciliation report generation needs to track successful group IDs separately. Can’t just swallow the error.
Source directory holds authority, so GC needs to match the source state. GC throws 422 because GC tries to add a member to group B that group A is currently claiming, or something like that. Lock state isn’t clearing fast enough between batch items.
Pre-resolving members blows up the API call count. DataLoader handles this in the GraphQL gateway layer, but this worker is raw HTTP.
- Need to isolate failures per group in the batch.
- Handle membership conflicts without aborting the run.
- Generate the report comparing source vs target structures after the sync.
Any way to force a membership conflict resolution by doing a DELETE on the member path before the replace? Or is there a flag on the PATCH to ignore conflicts?
Hash logic is solid. Just the sync execution is messy.