Aller au contenu

ADR-0023 — Taxonomie du journal : ce que chaque persona doit voir, et dans quel ordre

Statut 🟢 Accepté — 2026-06-21 ; implémenté (catégorie dérivée + filtrage (catégorie × persona) + synthèse externe dans workflow.py ; rendu groupé par phase dans Cabinet.tsx ; test dans test_revue.py, suite verte)
Portée Lecture du journal d'une demande (GET /api/demandes/{id}/evenements), filtrage par persona, rendu côté Cabinet. Pas d'écriture : le journal reste append-only et intégral (rien n'est supprimé en base).
Décision La catégorie d'un événement (jalon / controle / exploration) est dérivée de son type (fonction pure, pas de colonne). La visibilité devient une fonction de (catégorie × persona) au lieu du booléen visible_client seul. L'Externe ne reçoit pas le détail des explorations mais un résumé ; le rendu est groupé par phase, phase courante en tête.
Réf. ADR-0006 — Personas staff & cross-tenant (journal) ; ADR-0008 — Revue contradictoire ancrée ; ADR-0019 — Deux bancs

🎯 À retenir

Le journal devenait kilométrique : un dump plat, chronologique ascendant, dominé par les événements exploration (le staff qui tâtonne dans le labo). L'Externe — un auditeur indépendant — le voyait en entier, noyé sous le brouillon de cognition. Or un auditeur audite des décisions et leur justification, pas la cognition qui les a produites. On distingue trois catégories d'événements (jalon / contrôle / exploration), dérivées du type (zéro colonne, zéro migration, rétroactif). La visibilité devient (catégorie × persona) : le client voit les jalons publics ; l'externe voit jalons + contrôle, et l'exploration résumée (« N explorations par X » — preuve de diligence sans le bruit) ; le junior/senior voient tout (exploration repliable). Le rendu est groupé par phase, phase courante d'abord. Le journal reste intégral en base — on filtre la lecture, on n'efface jamais l'écriture.

Contexte

Observé en QA (parcours Externe, après validation senior → audit externe). Le journal (journal_demande, workflow.py) :

  • est ordonné order_by(EvenementDemande.id) = chronologique ascendant : l'état actionnable (soumission, validation, la tâche d'audit) est tout en bas, après un kilomètre de scroll ;
  • n'a que deux niveaux de visibilité : le consultant voit visible_client=True (jalons curés) ; tout le staff — junior, senior, ET externe — voit TOUT, dont chaque exploration (générée par _journaliser_exploration, labo.py, dédupliquée par salve mais empilée à travers acteurs et sessions).

Les dix types d'événements relèvent en réalité de trois natures :

  • Jalon — le squelette du workflow : assignation, demarrage, soumission_revue, validation_senior, transmission_externe, audit_approuve, audit_reserves, livraison.
  • Contrôle — la cuisine d'audit : critique_revue (+ réserves d'audit). C'est précisément ce que l'externe doit voir (preuve de contrôle, ADR-0008).
  • Exploration — la télémétrie opérationnelle : exploration des hypothèses sur la photo. Le tâtonnement du staff dans le labo (ADR-0019, banc d'expérimentation).

Le défaut de fond : un booléen visible_client ne peut pas exprimer un besoin à trois lentilles. Et la lentille de l'auditeur n'est pas « tout », c'est « les décisions et le contrôle ».

Décision

  • D-JOURNAL-1 — Catégorie DÉRIVÉE, pas stockée. La catégorie est une fonction pure du type (categorie_evenement(type) -> "jalon" | "controle" | "exploration", table de correspondance). Aucune colonne, donc aucune migration, aucun backfill, et l'historique existant est catégorisé rétroactivement. (Sobriété : ne pas ajouter une colonne quand une dérivation suffit ; évite le piège de nullabilité _COLONNES_AJOUTEES.)
  • D-JOURNAL-2 — Visibilité = (catégorie × persona). Remplace le filtre booléen unique par une matrice. Le visible_client existant reste la source du sous-ensemble client des jalons (certains jalons sont publics, d'autres non — inchangé).
Catégorie Client (consultant) Externe Junior / Senior
Jalon visible_client=True
Contrôle
Exploration résumé seulement ✅ (repliable côté UI)
  • D-JOURNAL-3 — Exploration RÉSUMÉE pour l'externe. L'externe ne reçoit pas les lignes d'exploration brutes (hors de son périmètre d'audit). Le serveur les remplace par une ligne de synthèse par acteur : « N explorations par X » — preuve que la diligence a eu lieu, sans le détail ligne à ligne. La preuve de non-arbitraire reste les justifications (texte + référentiel + pièces, ADR-0009), pas le log de tâtonnement. Pour junior/senior, l'exploration est repliée par défaut côté UI (rien de caché à l'équipe opérationnelle).
  • D-JOURNAL-4 — Rendu GROUPÉ PAR PHASE, phase courante en tête. Le frontend regroupe les événements par phase du workflow (Étude → Revue → Audit → Livraison), la phase courante d'abord (l'état actionnable est immédiatement visible), l'ordre chronologique préservé à l'intérieur de chaque phase (lecture chaîne-de-custody locale). Remplace le dump plat ascendant.
  • D-JOURNAL-5 — Intégral en base, filtré en lecture. On ne touche jamais à l'écriture : le journal reste append-only et complet (ADR-0006). Tout le filtrage/résumé est une transformation de lecture, par persona. Un audit ultérieur (ou un changement de politique) retrouve l'historique brut intact.

Conséquences

  • Positif. L'externe lit une vue d'audit (décisions + contrôle), pas un flux opérationnel ; le journal cesse d'être kilométrique pour tous ; l'ordre cesse d'enterrer l'actionnable. Zéro migration (catégorie dérivée), rétroactif sur l'historique.
  • Pas de perte d'information. Rien n'est effacé ; seule la lecture est cadrée par persona. L'externe garde la preuve de diligence (le résumé) sans le bruit.
  • Limite assumée. Le mapping type → catégorie est une table à tenir à jour quand un nouveau type d'événement apparaît (un type inconnu tombe par défaut en jalon — visible, jamais masqué par erreur : on préfère sur-montrer que cacher un événement d'audit).

Déclencheur de réexamen (pré-enregistré)

  • Un nouveau persona reviewer (ex. un second audit) → réévaluer la matrice (catégorie × persona).
  • L'externe réclame le détail d'exploration (cas d'audit approfondi) → ajouter un dépliement à la demande (le brut est en base, D-JOURNAL-5 le permet sans migration).
  • Le résumé par phase devient lui-même long (très grosse étude) → pagination/repli par phase.