NeuralWall Rules Kit

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.md invariant #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.md est 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.yaml est 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 du content DB (le même dict qu'avant — render.py ne 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 branche export/).
  • 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} valident content contre 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, promotion reviewed/verified par un reviewer/admin. La modération (Phase 6) s'applique à la ligne fiche au lieu d'à la proposal.
  • Chaque écriture est journalisée (audit_log), avec l'ancien et le nouveau content_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 --check dé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)

  1. Gouvernance (cette session) : cet ADR + amendement CLAUDE.md #1.
  2. Fondations DB : modèle Fiche (content JSONB, content_hash, trust_tier, status, versions) + migration Alembic.
  3. Écriture : endpoints POST/PUT /fiches (validate + lint + persist + audit), lecture GET /fiches[/{id}].
  4. Export : scripts/export_fiches.py (DB → YAML canonique + rendus) + garde-fou CI --check.
  5. Migration de données : import des profiles/*/fiche.yaml existants en base (seed one-shot), puis la base fait autorité.
  6. 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.