action="create" dont la valeur
diffère d’un fait déjà actif pour le même prédicat, il ne choisit pas
à la place de l’utilisateur : il ouvre un conflit.
Le principe : cacher les deux, jamais deviner
Dans le Context Assembler, tout fait dont l’id figure dans unConflictSet avec status="open" est exclu du scoring et bloqué avec
reason_code = conflict_open — y compris les faits encore candidate
(qui n’auraient de toute façon jamais été servis, mais sont maintenant
tracés explicitement comme bloqués). Le packet renvoyé porte alors un
warning :
Modèle ConflictSet
Observabilité
GET /v1/conflicts renvoie, en plus de la liste, deux compteurs pensés
pour du monitoring sans avoir à parser la liste : open_count et
oldest_open_seconds (âge du plus ancien conflit encore ouvert, null
s’il n’y en a aucun). Rien ne résout un conflit tout seul — c’est
volontaire (« cacher les deux, ne jamais deviner ») ; ce compteur est le
garde-fou contre une accumulation silencieuse.
Résoudre un conflit
POST /v1/conflicts/{id}/resolve avec {project_id, keep_fact_id} :
- le fait gardé passe à
active(transitioncandidate/disputed → activesi nécessaire) ; - tous les autres faits du set passent à
superseded, avecsupersedes_idpointant le fait gardé ; - le set passe à
resolved(+resolved_at).
/v1/context sert le fait gardé normalement — il n’est plus
bloqué par conflict_open.
Erreurs typées : conflict_not_found (404 — y compris un conflit d’un
autre projet, même erreur qu’un id inconnu, aucune fuite),
conflict_already_resolved (409), fact_not_in_conflict (422, si
keep_fact_id n’appartient pas au set).
Référence API — Conflits
GET /v1/conflicts, POST /v1/conflicts/{id}/resolve : schémas exacts.
