Découverte du mapping : la confirmation de RDV (confirmWindow + POST /roster/book/confirm) écrivait assigned_tech
SANS la garde « déjà assigné dans F » que /roster/assign-job applique → une confirmation client (ou staff) pouvait
écraser une assignation faite manuellement dans F. Même classe que le bug double-assignation.
- fConflict(legacyId, techId) : garde PARTAGÉE extraite (ticketAssignState), réutilisée par les 3 chemins d'écriture
(assign-job refactorisé pour l'utiliser → plus de dérive).
- confirmWindow (chemin CLIENT, pas de force) : si conflit F, N'ÉCRASE PAS — enregistre le créneau choisi
(booking_status Proposé) et laisse le répartiteur confirmer le bon tech. Message client « nous confirmerons sous peu ».
- POST /roster/book/confirm (chemin STAFF) : 409 conflict sauf force=true → l'OPS réaffiche « déjà assigné dans F à X ».
- hold_minutes défaut 10 → 5 (décision Louis) : réservation exclusive 5 min → retour au pool → indicatif (revalidé
à la confirmation via la garde ci-dessus).
Déployé (hub redémarré, sain, SSE clients se reconnectent via 187115c/89f7efc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>