ADR-0004 — Bascule vers un modèle CMS : la base de données devient la source
- Statut : Accepted
- Date : 2026-06-05
- Remplace partiellement : ADR-0001 (§ « une source → N rendus »), ADR-0002 (§2)
- Contexte associé : ADR-0003 (auth/RBAC/PR),
CLAUDE.mdinvariant #1
Contexte
Jusqu'ici, la source unique d'une fiche était un fichier YAML versionné dans profiles/
(invariant #1, ADR-0001). MACHINE / HUMAIN / IA étaient dérivées par scripts/render.py,
et toute contribution passait par une PR Git (DCO). La base de données ne stockait que des
métadonnées (proposals, consentements, notes, rôles, modération) — jamais de contenu de
fiche.
Ce modèle « git-first » garantit le zéro-drift et une reproductibilité d'ingestion parfaite, mais impose une friction d'auteur réelle : pour créer ou corriger une fiche il faut, in fine, produire un commit YAML et ouvrir une PR. Le porteur du projet a décidé de prioriser une expérience d'édition de type CMS (formulaire web → écriture directe), où la donnée saisie est immédiatement la source de vérité, sans détour par un fichier à committer à la main.
⚠️ Cette décision inverse l'invariant fondateur #1. Elle est consignée ici, par la gouvernance ADR, conformément à la règle « ne pas contourner un invariant sans signalement explicite ». L'invariant #1 du
CLAUDE.mdest amendé en conséquence (voir plus bas).
Décision
1. La base de données est la source de vérité
Une fiche est désormais une ligne en base (fiche), dont le contenu normatif est stocké
en JSON structuré (colonne content, type JSONB sur Postgres) validé contre
schema/fiche.schema.json au moment de l'écriture. Le YAML cesse d'être la source : il
devient un artefact d'export.
2. Le YAML et les rendus deviennent des exports reproductibles
profiles/<id>/fiche.yamlest généré depuis la base (export canonique, tri de clés stable) — pour la distribution CC-BY, l'archivage Git et la vérifiabilité externe.- MACHINE / HUMAIN / IA / RAG restent dérivés de
render.py, mais à partir ducontentDB (le même dict qu'avant —render.pyne change pas de contrat d'entrée). - Le pipeline de rendu garde sa garantie de pureté :
dict source unique → N rendus. Ce qui change, c'est où vit le dict source (DB au lieu d'un fichier).
3. La provenance Git est préservée par un export commité en CI
Pour ne pas perdre l'auditabilité et la licence CC-BY attachée à un historique :
- Un job d'export (CLI
scripts/export_fiches.py+ cron/CI) matérialise les YAML depuis la base et ouvre/maintient une PR « snapshot » (ou commit sur une brancheexport/). - L'historique Git devient un miroir d'audit de la base, pas l'inverse.
4. Écriture validée + auditée + soumise au trust_tier et au RBAC
POST /fiches/PUT /fiches/{id}validentcontentcontre le schéma et passent le linter sécurité avant persistance (refus 422 sinon). Aucun anti-pattern n'entre en base.- Le RBAC (ADR-0003) et le trust_tier (ADR-0002 §3) restent :
communityà la création, promotionreviewed/verifiedpar un reviewer/admin. La modération (Phase 6) s'applique à la ligneficheau lieu d'à laproposal. - Chaque écriture est journalisée (
audit_log), avec l'ancien et le nouveaucontent_hash.
Conséquences
- (+) Édition CMS frictionless : formulaire → écriture directe, brouillons, corrections immédiates, pas de manipulation Git par l'auteur.
- (+) Validation/lint à l'écriture : impossible de persister une fiche non conforme.
- (−) Perte du zéro-drift natif. Il existe désormais deux représentations (DB =
autorité, YAML = export). Le drift est possible si l'export n'est pas régénéré. → Mitigé
par §3 (export CI déterministe) + un garde-fou CI qui échoue si
export_fiches.py --checkdétecte une divergence entre la base et les YAML commités. - (−) Reproductibilité d'ingestion à reconstruire. NeuralWall ingérait des fichiers Git
immuables ; il doit maintenant ingérer un export horodaté/hashé. → Chaque export porte un
content_hash+exported_at; l'ingestion référence ce hash. - (−) Surface de sécurité accrue : on accepte du contenu de fiche en écriture HTTP (auparavant impossible). → Validation stricte, RBAC, rate-limit, audit, modération.
- (−) Les nombreuses docstrings « INVARIANT 1 : aucun contenu YAML en DB » des modèles
existants deviennent obsolètes pour la nouvelle table
fiche(les autres tables — proposal, note, etc. — restent métadonnées).
Plan de mise en œuvre (incrémental, multi-session)
- Gouvernance (cette session) : cet ADR + amendement
CLAUDE.md#1. - Fondations DB : modèle
Fiche(content JSONB, content_hash, trust_tier, status, versions) + migration Alembic. - Écriture : endpoints
POST/PUT /fiches(validate + lint + persist + audit), lectureGET /fiches[/{id}]. - Export :
scripts/export_fiches.py(DB → YAML canonique + rendus) + garde-fou CI--check. - Migration de données : import des
profiles/*/fiche.yamlexistants en base (seed one-shot), puis la base fait autorité. - Site : build depuis l'export (ou lecture API) ; l'éditeur web pointe sur les nouveaux endpoints.
Tant que les étapes 2-5 ne sont pas livrées, les YAML de profiles/ restent la source de
fait : la bascule est atomique à l'étape 5 (seed), pas avant.
Alternatives écartées
- Statu quo git-first : rejeté par le porteur pour cause de friction d'auteur.
- Hybride DB-brouillon / YAML-publié : conserve l'invariant #1 mais n'offre pas la sémantique « la donnée saisie EST la source » voulue. Écarté au profit du CMS plein.
- CMS sans export Git : perdrait la provenance CC-BY et l'auditabilité externe. Écarté : l'export Git (§3) est non négociable.