ADR-014 — consolidated_data.metadataSources = source unique des corrections humaines ; dépréciation du sous-système admin_validations¶
| Statut | Accepted |
| Date | 22/05/2026 |
| Décideurs | Romain BAZIL, Nicolas Léonard |
| Origine | Décision design Romain × Jérôme × Nicolas (22/05/2026) — consolidated-spreadsheet comme support de travail unique pour fiabiliser la donnée d'un projet. PRs project-analysis : #223, #224, #225, #226. |
Contexte¶
Avant le 22/05, deux mécanismes parallèles enregistraient les corrections humaines sur la donnée projet :
- Sous-système
admin_validations(table dédiée, panel UI, routes back, client PythonupsertFromPipeline). Workflow : le pipeline IA pré-remplit une row par champ critique avecstatus='pending', l'AM valide / corrige / rejette dans un panel dédié, le service propage la valeur acceptée versconsolidated_dataet marquestatus='validated'. consolidated_data.metadataSources[field](colonne JSONB sur la tableconsolidated_data). Workflow : l'AM édite directement un champ via la consolidated-spreadsheet ou un endpointPOST /consolidated-data/field, qui écrit la valeur + un marqueurmetadataSources[field] = { source: 'manual' | 'workflow' | 'extracted' | ..., validatedBy, validatedAt, adminComment }.
Un incident observé en mai a révélé le coût de cette dualité : les saisies via la spreadsheet n'apparaissaient pas dans admin_validations, donc la détection de conflit côté pipeline (AdminValidationService.detectConflicts) ne pouvait pas les protéger. À chaque rerun, le pipeline écrasait silencieusement les saisies AM via l'auto-activation des section_versions. Cf. analyse complète dans la session ayant produit PR #223.
La décision design du 22/05 (consolidated-spreadsheet support unique) tranche : un seul flow AM, un seul système de traçage. Cette ADR formalise le choix architectural sous-jacent.
Décision¶
consolidated_data.metadataSources[field].source est l'unique source de vérité pour les corrections humaines.
source: 'manual'→ saisie directe AM via la spreadsheet (édition cellule ou/consolidated-data/field).source: 'workflow'→ l'AM a cliqué "Utiliser cette valeur" sur une colonne workflow, ou "Accepter tout un workflow" en batch.source: 'extracted'/'calculated'→ valeur produite par le pipeline IA, non encore touchée par un humain.source: 'pappers'→ valeur produite par un refresh Pappers (tablecompanies/project_owners— horsconsolidated_data, mais même logique de marqueur sur les rows entités).
La protection contre l'écrasement par auto-activation du pipeline (cf. PR #223) lit exclusivement metadataSources[field].source === 'manual'. Aucune autre logique ne dépend du sous-système admin_validations pour préserver les saisies humaines.
Le sous-système admin_validations devient déprécié et sera démantelé en plusieurs PRs :
| Layer | État | PR |
|---|---|---|
UI panel (admin-validation-panel.tsx, consolidated-data.tsx) |
Retiré | #224 + #225 (démontage rendu) puis #226 (suppression fichiers) |
Front API hooks orphelins (useProcessValidation, etc.) |
Morts, à supprimer | À ouvrir |
Back routes mutations (/admin-validations/{process, resolve-conflict, reopen, refresh, analyze}) + service |
Morts, à supprimer | À ouvrir |
Endpoint POST /admin-validations/upsert-from-pipeline + client Python agents/app/tools/admin_validations_client.py + appel fiche_consolidation |
À retirer en coordination avec le pipeline IA | À ouvrir |
Banner "Données manquantes pour un scoring fiable" basé sur validationSummary.pendingCount |
À réinterpréter ou retirer (sémantique de "pending" devenue ambiguë) | À arbitrer |
Table DB admin_validations |
Drop après les retraits ci-dessus | Migration Drizzle séparée |
Aucun nouveau développement n'écrit dans admin_validations. Aucun nouveau read consumer n'est ajouté côté front (les rares lectures restantes — useGetProjectValidations pour les badges de conflit dans la spreadsheet, useGetValidationSummary pour le banner scoring — sont marquées comme legacy et partiront avec leurs producteurs).
Rationale¶
Une seule source > deux sources désynchronisées¶
Cet incident n'était pas un bug d'implémentation isolé : c'était une conséquence prévisible de deux paths d'écriture qui ne se voient pas. Tant que les deux coexistent, n'importe quelle évolution unilatérale d'un des deux génère une fenêtre de désynchronisation. La synchronisation bilatérale (faire en sorte que POST /consolidated-data/field crée aussi une row admin_validations) aurait été techniquement possible mais aurait augmenté la complexité sans valeur ajoutée — on aurait dupliqué le même fait en deux endroits.
Cohérent avec la décision design 22/05¶
La consolidated-spreadsheet est désormais le support unique de fiabilisation. La logique de traçage doit suivre la même règle : un seul mécanisme. metadataSources est embarqué dans la même table que la valeur elle-même (consolidated_data), ce qui élimine toute possibilité de divergence entre la valeur et son origine.
metadataSources est plus riche que ce qu'admin_validations exprimait¶
| Information | admin_validations |
consolidated_data.metadataSources |
|---|---|---|
| Source actuelle de la valeur | Implicite (status='validated' + validatedBy) |
Explicite (source: 'manual' | 'workflow' | ...) |
| Historique de validation par champ | Une row | Une entrée par champ |
| Conflit avec pipeline | conflictDetected flag |
Pas géré côté metadata, mais détectable au moment du write |
| Traçabilité workflow d'origine | Champ générique sourceLabel |
metadataSources.workflowId / workflowName / versionId quand source='workflow' |
| Auditabilité | Row dédiée + log | Snapshot dans la même row que la valeur |
La granularité par-champ + l'attachement à la valeur elle-même rendent metadataSources plus expressif et plus simple à raisonner.
Le sous-système admin_validations était une vue de travail, pas un journal d'audit¶
L'argument "garder admin_validations pour l'historique" ne tient pas. La table était rétractable (status change, correctedValue overwrite, reopen réinitialise) — ce n'est pas un journal append-only. L'historique d'audit véritable, s'il devient utile, devra être construit séparément (event sourcing sur les writes consolidated_data).
Conformité avec ADR-005 (chosen_source array)¶
ADR-005 distingue chosen_sources (array, état persisté) de ui_clicked_source (debug UX). Le marqueur metadataSources[field].source reste un singleton, mais le rationale est aligné : on capture le fait (cette valeur vient de telle source) sans prétendre que c'était l'unique choix possible. La cascade convergence (ADR-001) reste libre de fournir une valeur consensuelle, le marqueur enregistre simplement quelle source a été retenue.
Pourquoi un démantèlement progressif, pas un big-bang¶
Le client Python upsertFromPipeline continue de poster vers admin_validations à chaque run du pipeline IA. Retirer l'endpoint avant de retirer le client casse le pipeline en production. Le bon ordre est :
- Désactiver le client Python (ne plus appeler
upsertFromPipeline) - Vérifier sur preprod qu'aucune row n'est plus créée
- Retirer l'endpoint back
- Retirer le service + les routes mutation
- Drop la table DB
Chaque étape est une PR vérifiable indépendamment.
Conséquences¶
Ce qui change¶
- Spec produit : la consolidated-spreadsheet est l'unique surface de travail AM/analyste pour la donnée projet. Plus de panel séparé, plus de double saisie.
- Spec technique : toute nouvelle UI ou intégration qui veut tracer une correction humaine doit écrire dans
consolidated_data.metadataSources[field]. L'usage d'admin_validationsest interdit pour de nouveaux flows. - Documentation : la doc in-app (cf. ADR-006) doit expliquer que le badge "👤 saisie manuelle" / "✓ accepté d'un workflow" sur les cellules reflète
metadataSources[field].source, pasadmin_validations.status.
Ce qu'il faut faire¶
- [ ] PR cleanup back : routes
/admin-validations/*mutations + service (owner candidat : Alban) - [ ] PR cleanup Python :
admin_validations_client.py+ appel dansfiche_consolidation(owner candidat : Alban en coordination avec l'équipe agents) - [ ] Arbitrage banner "Données manquantes pour un scoring fiable" : retrait ou réinterprétation
- [ ] Migration Drizzle : drop
admin_validations(en dernier) - [ ] Mise à jour de la doc in-app sur la sémantique des badges
metadataSources.source
Ce qui ne change pas¶
- ADR-001 (cascade fiabilité), ADR-002 (consolidateur par agent), ADR-003 (hiérarchie de précédence), ADR-005 (
chosen_sourcesarray), ADR-013 (ordre cible activé) restent applicables. Cette ADR opère au niveau du traçage des corrections humaines, pas de la génération de la valeur consolidée. - Le pipeline IA continue de produire
consolidated_dataviasection_versions+ auto-activation (avec la protection #223). - Les rows d'entités (
project_owners,companies,projects.deed_signature_date) éditées via la spreadsheet via leurs hooks dédiés (useUpdateProjectOwner,useUpdateCompany,useUpdateDeedSignatureDate) suivent la même logique conceptuelle, mais leur stockage propre n'utilise pasmetadataSourcesaujourd'hui. Si on veut un badge source sur ces lignes, il faudra étendre — ticket séparé.
Sources¶
- Source migrée :
bricks-os/wiki/architecture/ADR-014-metadata-sources-source-unique-corrections-humaines.md - Catalogue des sources legacy