ADR-0022 — Révision convenue : trancher un fil sur un changement de valeur, sans quitter la modale¶
| Statut | 🟢 Accepté — 2026-06-19 ; implémenté 2026-06-21 (backend revue.py / workflow.py / labo.py + frontend Revue.tsx ; 5 tests dans test_revue.py, suite verte) |
| Portée | Fin de vie d'un fil de revue qui CONCLUT un changement de valeur. Modèle FilRevue, résolution (/api/demandes/{id}/fils/{fil_id}/repondre), nouvelle application (…/appliquer-revision), garde de valider, modale de revue (frontend) |
| Décision | La résolution d'un fil porte un verdict : maintenu (rien ne change) ou révision convenue (le reviewer saisit la valeur cible, sa justification voyage avec, la demande revient en révision). Le junior applique en un clic, dans la modale même de l'hypothèse — fork du jeu + justif au niveau étude + re-soumission. La validation refuse tant qu'une révision convenue n'est pas appliquée |
| Réf. | ADR-0008 — Revue contradictoire ancrée ; ADR-0009 — Justifications (héritage 2 niveaux) ; ADR-0021 — Critique sourcée |
🎯 À retenir
Un fil pouvait se résoudre sur « validons donc à 1,5 % » sans que rien ne change :
resolu ne touchait ni la valeur (restée 2 %) ni le statut de la demande, et le junior
n'avait ni moment ni lieu pour appliquer le 1,5. Pire, le senior pouvait valider
depuis revue_senior sans garde → figer une valeur que tout le monde avait rejetée.
On donne à la résolution un verdict : maintenu ou révision convenue. La révision
convenue porte la valeur cible (saisie par le reviewer) ET sa justification (la
justif suit la valeur — sinon on figerait 1,5 % sous un texte qui défendait 2 %). Le
junior applique dans la modale de l'hypothèse, en un clic. Mécaniquement : fork du jeu
(immuable, ADR-0008) + Justification(demande, champ) au niveau étude (override, ADR-0009)
+ re-soumission. Un garde empêche valider tant que la révision n'est pas appliquée.
Contexte¶
Observé en QA (parcours junior). Un fil ancré sur « Revalorisation des salaires » converge :
le senior critique (« 1,5 est plus adaptée »), le junior répond (« d'accord »), le senior
résout (« validons donc à 1,5 »). Le fil passe resolu. Mais la valeur figée reste 2 %.
La vérité du code :
repondre_fil(résolution) fait une seule chose :fil.statut = "resolu". Aucun effet sur la valeur ni sur le statut de la demande.soumettre-revue(le seul endroit qui (re)fige une valeur) exige le statuten_etudeoua_revoir. La demande est restée enrevue_senior→ le junior ne peut rien (409). La modale de revue est en lecture seule ; son seul bouton après résolution est « Fermer ».validerpasserevue_senior → valideesans aucun contrôle. Le senior peut donc, juste après « validons à 1,5 », cliquer Valider et figer 2 %.
Deux défauts en découlent :
resoluest surchargé. Il signifie soit « défense acceptée, la valeur reste » (le junior a défendu, cf.test_cycle_fil_critique_defense_resolution), soit « changement convenu » (ce cas-ci). Le système ne les distingue pas, et seul le premier est sûr à valider.- Danger d'intégrité d'audit. Une valeur explicitement rejetée peut être figée puis auditée puis livrée — frontalement contraire au rail ADR-0008 (« ce qui est approuvé est exactement ce qui est livré »).
Le chemin de changement existait (le senior renvoie → la demande passe a_revoir → le
junior fork au Plan de travail → re-soumets), mais : (a) rien ne mène un fil résolu vers
ce chemin ; (b) « renvoyer » dit rejet, alors qu'ici les deux ont convergé — le
vocabulaire combat le geste, ce qui explique que le senior ait naturellement résolu.
Décision¶
- D-REVCONV-1 — Résolution à deux verdicts. La résolution d'un fil (par un reviewer,
senior ou externe) porte un verdict explicite :
maintenu(la valeur reste — la défense du junior tient, ou aucune révision n'est nécessaire ; comportement actuel) ourévision convenue(les deux conviennent d'un changement de valeur). - D-REVCONV-2 — La justification voyage avec la valeur. Une révision convenue PORTE deux
choses : la valeur cible (saisie par le reviewer) et la justification du reviewer
(le texte de sa résolution + son
referentiel_cle+ sa pièce de contre-source, ADR-0021). À l'application, cette justification supplante celle du junior pour ce champ — sinon on figerait « 1,5 % » sous un texte qui argumentait « 2 % ». La justif suit la balance : c'est l'argument du reviewer qui a fait pencher le changement, c'est lui qui est figé. - D-REVCONV-3 — Renvoi implicite. Résoudre en
révision convenueramène la demande ena_revoir(rouvre le droit de révision du junior). Plus de geste « renvoyer » séparé à oublier ; on ne peut plus atteindre l'état contradictoire « fil résolu sur un changement, demande validable avec l'ancienne valeur ». - D-REVCONV-4 — Le junior applique, dans la modale, en un clic. Le reviewer propose la
valeur (à la résolution) ; le junior applique (séparation des pouvoirs : l'auteur du jeu
reste celui qui exécute la révision et en porte la responsabilité d'audit). L'application se
fait sans quitter la modale de l'hypothèse. Mécaniquement, en une transaction :
(1) fork du jeu courant avec le champ révisé (jeu immuable → nouvelle version de la
lignée) ; (2) upsert de
Justification(demande, champ)= texte/référentiel/pièce du reviewer (niveau étude, qui override l'hérité au snapshot — ADR-0009 D-JUST-4) ; (3) re-soumission (recalcul + re-figeage →revue_senior). Le fil est marqué appliqué. - D-REVCONV-5 — Garde de validation.
validerrefuse (409) tant qu'une révision convenue n'est pas appliquée. On ne fige jamais une valeur explicitement rejetée. C'est le filet : même si l'UI déraille, l'intégrité d'audit tient au niveau serveur. - D-REVCONV-6 — Périmètre v1 : champs scalaires. La saisie in-modale de la valeur cible
couvre les champs scalaires (
taux_*,âge de retraite) — un nombre saisi, une application en un clic. Les champs complexes (grille IFC, plafonds, table de mortalité, causes) gardent une révision textuelle (verdict + justif) et s'appliquent au Plan de travail (fork du changement structuré). On ne sur-conçoit pas un éditeur in-modale pour la grille tant que le besoin n'est pas démontré. (Anti-sur-ingénierie, posé explicitement.)
In-modale tour-par-tour (maintenant) vs lot unique (futur) — pour référence¶
Le jeu est atomique : un fork re-soumet le jeu entier. Cela force un choix dès que plusieurs fils convergent vers une révision en même temps :
- In-modale tour-par-tour (retenu). On applique chaque révision dans sa modale → re-soumission → le senior re-valide → on applique la suivante. 100 % in-modale, épouse le geste de convergence, zéro navigation. Coût : une re-validation par révision (la revue itère — comportement courant et légitime des revues réelles).
- Lot unique (différé). Un bouton « Appliquer les N révisions convenues » qui fork une fois / re-soumet une fois. Mais il est transverse aux hypothèses → il ne peut pas vivre dans la modale d'une hypothèse ; il faut un point hors-modale (en-tête de la demande). On l'introduit seulement si la friction multi-révisions se manifeste en vrai.
Décision : tour-par-tour maintenant, lot unique en évolution pré-enregistrée. La friction à surveiller : un même renvoi qui exige ≥ 3 révisions simultanées régulièrement.
Conséquences¶
- Positif. Un fil convergent a enfin un moment (le verdict du reviewer) et un lieu (la modale même), sans détour par un « renvoyer » au vocabulaire hostile. La justif figée est cohérente avec la valeur figée. L'intégrité d'audit est garantie au serveur (D-REVCONV-5), pas seulement par convention.
- Migration. Nouveaux champs sur
FilRevue(verdict / valeur cible / applique) → entrée obligatoire dans_COLONNES_AJOUTEES(db.py) sous peine de 500 en prod (SQLite déployée). - Limite assumée (transitoire). Champs complexes non éditables in-modale (D-REVCONV-6) ; multi-révisions en tour-par-tour (lot unique différé). (Le portage de la pièce de contre-source : la justif reprenait jadis texte + référentiel mais pas le fichier — fermé par ADR-0024 : la pièce du message de résolution est désormais matérialisée dans la justification figée, tout en restant sur le fil.)
Déclencheur de réexamen (pré-enregistré)¶
- Friction multi-révisions (≥ 3 révisions simultanées récurrentes) → introduire le lot unique hors-modale.
- Besoin d'éditer un champ complexe depuis la revue (grille, plafonds) → étendre la saisie in-modale à un éditeur structuré, ou statuer que ces révisions restent au Plan de travail.
- Le reviewer veut imposer la valeur sans passer par le junior (ex. correction triviale) → re-statuer sur la séparation des pouvoirs D-REVCONV-4.