ADR-071 : convention unique de provisioning des opt-ins adossés à la base¶
Statut¶
Acceptée (2026-07-10).
Contexte¶
Plusieurs opt-ins Forge livrent leur propre schéma de table (le paquet embarque
le DDL, une commande <opt-in>:init le dépose dans le projet, puis une commande
d'application crée la table). Deux conventions concurrentes coexistent aujourd'hui,
alors qu'elles répondent au même besoin.
Convention A, migrations versionnées (mvc/migrations/ + forge migration:apply) :
la commande init copie une migration embarquée (forge_mvc_<paquet>/migrations/)
vers mvc/migrations/, sans exécution SQL ni connexion base ; l'application se fait
ensuite par forge migration:apply, qui trace la migration dans forge_migrations.
Suivie par 7 opt-ins : audit, images, iot, jobs, notifications,
settings, video.
Convention B, schéma de modèle déclaratif (mvc/models/sql/ + forge db:apply) :
la commande init copie un fichier .sql vers mvc/models/sql/, appliqué par
forge db:apply (réapplication idempotente, sans table de suivi). Suivie par le
socle auth du cœur (forge auth:init : users, auth_tokens,
auth_audit_log, etc.) et par le nouvel opt-in forge-mvc-sessions-db
(forge sessions:init, retour terrain 016 F34).
Ce double standard a deux coûts :
- incohérence de découverte : deux verbes d'application (
db:applycontre
migration:apply), deux dossiers cibles, pour une même action « provisionner la
table d'un opt-in » ; le porteur doit se souvenir, opt-in par opt-in, quelle
convention s'applique ; - ambiguïté de sens :
mvc/models/sql/décrit le modèle de données de
l'application, tandis qu'une table d'infrastructure livrée figée par un paquet
(forge_sessions,forge_jobs, ...) n'est pas un modèle que l'application
possède ou étend. Les ranger au même endroit brouille la frontière.
Le retour terrain 016 (F34) a rendu l'écart visible : sessions:init a été calqué
sur images:init mais a divergé sur la cible (models/sql + db:apply au lieu de
migrations/ + migration:apply), au motif que « sessions est un schéma de modèle
comme auth ». Cette assimilation est contestable : users est bien du modèle
applicatif (l'auteur l'étend de colonnes de profil), alors que forge_sessions est
de la pure plomberie de framework que l'application ne modélise jamais.
Décision¶
Le provisioning d'un opt-in adossé à la base passe par la convention A :
migration versionnée dans mvc/migrations/, appliquée par forge migration:apply.
La convention B (mvc/models/sql/ + forge db:apply) reste réservée au modèle
de données applicatif : les tables que l'application possède et étend, dont le
socle auth (forge auth:init). Ce n'est pas un provisioning d'opt-in au sens
de cet ADR, mais l'amorçage du modèle du projet.
Critère de tri, pour un schéma livré par un paquet :
- table d'infrastructure, figée par le paquet, jamais modélisée ni étendue par
l'application (sessions, jobs, notifications, audit, événements IoT, réglages,
médias, transcodages) → migration (mvc/migrations/+migration:apply) ; - table du modèle applicatif, que l'auteur possède et fait évoluer (socle
userset ses tables auth) → schéma de modèle (mvc/models/sql/+
db:apply).
Réalignement de forge-mvc-sessions-db¶
forge-mvc-sessions-db était le seul opt-in du côté « migration » de la frontière
à suivre encore la convention B. Il est réaligné sur la convention A :
- le DDL
forge_sessionsest livré comme migration embarquée
(forge_mvc_sessions_db/migrations/) au lieu desql/*.sql; forge sessions:initcopie la migration versmvc/migrations/et suggère
forge migration:apply(au lieu demvc/models/sql/+db:apply) ;- le docstring du store, la notice de l'opt-in et la doc cœur de session citent la
nouvelle cible ; la fixture applicative de test suit la même convention.
La rupture est interne et assumée en phase bêta (ADR-009), sans alias de
compatibilité : le contrat fonctionnel du store (SessionStore, colonne version
de concurrence optimiste, horodatage UTC) est inchangé ; seule la mécanique de
dépôt du schéma bouge.
Conséquences¶
- Une seule façon officielle de provisionner un opt-in adossé à la base
(principe 11) : « la commande<opt-in>:initdépose une migration,forge migration:applyl'applique ». - La frontière
mvc/models/sql/(modèle applicatif) contremvc/migrations/
(deltas de schéma, dont ceux des opt-ins) devient nette et explicable. - Les migrations d'opt-ins sont tracées dans
forge_migrations, ce qui permet à un
paquet de livrer un delta ultérieur à une version future sans réappliquer ni
écraser le schéma existant. - Tout nouvel opt-in adossé à la base suit d'emblée la convention A ; le patron
de référence pour écrire sa commandeinitestimages:init/audit:init,
plussessions:init. forge auth:initn'est pas touché : le socle auth reste du modèle applicatif
(models/sql+db:apply), documenté comme tel.- Coût du réalignement : un ticket sur
forge-mvc-sessions-db(migration embarquée,
sessions:initrepointée, tests et notice mis à jour). Aucun autre opt-in n'est
concerné, les 7 autres suivant déjà la convention retenue.
Charte appliquée¶
- Principe 11 (une seule façon officielle de faire chaque chose) : suppression du
double standard de provisioning. - Principe 8 (noyau minimal, briques opt-in) : la convention vaut pour les opt-ins ;
le cœur ne gagne aucune mécanique. - Principe 3 (refuser la magie cachée) et §7 (Forge affiche) :
initprépare des
fichiers sans exécuter de SQL ni se connecter ; l'application reste une commande
explicite (migration:apply). - Règle A (retirer la cause, pas le symptôme) : on unifie la convention plutôt que
de documenter au cas par cas quel opt-in suit laquelle. - Relations : prolonge le retour terrain 016 (F34), s'appuie sur la séparation des
comptes DDL/DML d'ADR-033 et surdb:initd'ADR-067, s'inscrit dans le cœur
agnostique BDD d'ADR-054.