Description
La réconciliation Keycloak n'est pas totalement idempotente : la création du groupe racine d'un chemin est soumise à une course (TOCTOU) lors d'une synchronisation concurrente.
createGroup (keycloak-client.service.ts:128-136) appelle client.groups.create({ name }) sans vérification d'existence ni tolérance au 409. Le chemin racine de getOrCreateGroupByPath (keycloak-client.service.ts:187) fait getRootGroupByName(rootName) ?? await this.createGroup(rootName) : deux réconciliations concurrentes (cron + événement project.upsert, ou deux upserts) peuvent toutes deux passer la lecture puis se disputer la création — l'une reçoit un 409 et lève.
Le chemin des sous-groupes est déjà corrigé : getOrCreateSubGroupByName (keycloak-client.service.ts:197-221) traite le 409 comme « existe déjà » et relit le groupe. Seul le groupe racine échappe à cette protection.
Etapes de reproduction
- Déclencher une synchronisation concurrente du même projet (cron de réconciliation + événement
project.upsert simultanés, ou deux upserts).
- Observer l'échec d'un des deux chemins par un 409 Keycloak lors de la création du groupe racine.
- Voir la sync marquée en échec via
capturePluginResult.
Version de la console impactée
server-nestjs (réconciliation Keycloak)
Définition du fini
Issues liees
Relie a #2087 : migration des plugins vers une approche declarative (moteur Alchemy.run), dont cette idempotence est un prerequis.
Description
La réconciliation Keycloak n'est pas totalement idempotente : la création du groupe racine d'un chemin est soumise à une course (TOCTOU) lors d'une synchronisation concurrente.
createGroup(keycloak-client.service.ts:128-136) appelleclient.groups.create({ name })sans vérification d'existence ni tolérance au 409. Le chemin racine degetOrCreateGroupByPath(keycloak-client.service.ts:187) faitgetRootGroupByName(rootName) ?? await this.createGroup(rootName): deux réconciliations concurrentes (cron + événementproject.upsert, ou deux upserts) peuvent toutes deux passer la lecture puis se disputer la création — l'une reçoit un 409 et lève.Le chemin des sous-groupes est déjà corrigé :
getOrCreateSubGroupByName(keycloak-client.service.ts:197-221) traite le 409 comme « existe déjà » et relit le groupe. Seul le groupe racine échappe à cette protection.Etapes de reproduction
project.upsertsimultanés, ou deux upserts).capturePluginResult.Version de la console impactée
server-nestjs (réconciliation Keycloak)
Définition du fini
getOrCreateSubGroupByNameIssues liees
Relie a #2087 : migration des plugins vers une approche declarative (moteur Alchemy.run), dont cette idempotence est un prerequis.