ADR-031 : Découplage complet du mail hors de core.forge¶
Statut¶
Acceptée et mise en œuvre (ticket MAIL-DECOUPLE-CORE-001).
Le cœur ne porte plus aucune configuration mail_* ; forge-mvc-mail lit la sienne depuis l'environnement.
Complète ADR-022 (extraction de l'email vers forge-mvc-mail) et renforce ADR-004 (périmètre du core minimal) ainsi que le principe 8 (noyau minimal, briques opt-in).
Contexte¶
ADR-022 a extrait core/mail/ vers l'opt-in forge-mvc-mail, mais a laissé un couplage résiduel que l'ADR notait lui-même : « sa seule dépendance core est core.forge pour la config ». Concrètement, le core et le squelette nu continuent de connaître le mail :
core/forge.pyréserve 13 clésmail_*dans son registre de configuration ;core.forge.configure()accepte des kwargsmail_*;core/app/app_factory.pylit un blocMAIL_*optionnel depuisconfig.py(_optional_mail_kwargs()) ;- le squelette nu pré-câble la config mail sur trois fichiers utilisateur :
env/example(blocMAIL_*),config.py(imports + attributs) etapp.py(13 argumentsmail_*passés àforge.configure).
forge-mvc-mail, quand il est installé, lit ces valeurs via l'API publique core.forge.get("mail_*").
Deux constats rendent ce couplage injustifié :
- Aucun code du core ne lit
core.forge.get("mail_*").
Seulforge-mvc-mailles consomme.
Le core auth manipule des adresses et des jetons de vérification (core/auth/email.py,reset.py) mais n'envoie aucun email. - Un projet nu (
forge new,requirements.txt = forge-mvcseul) transporte ainsi une cinquantaine de lignes de plomberie mail pour une brique qui n'est pas installée.
C'est contraire au principe 3 (refuser la magie cachée) et au principe 8.
Le test de légitimité d'ADR-004 (« si je retire cette brique, une application basique tourne-t-elle encore ? ») est satisfait : le mail doit relever de la seule responsabilité de son opt-in, registre de config compris.
Décision¶
Toute connaissance du mail quitte le core et le squelette nu.
forge-mvc-mail possède sa configuration de bout en bout, lue depuis l'environnement.
| Élément | Avant | Après |
|---|---|---|
Slots mail_* dans core/forge.py |
présents (défauts) | supprimés |
core.forge.configure(mail_*=…) |
accepte les kwargs | n'accepte plus |
core/app/app_factory.py _optional_mail_kwargs() |
présent | supprimé |
Source de config de forge-mvc-mail |
core.forge.get("mail_*") |
os.getenv (config interne au module) |
Squelette env/example / config.py / app.py |
pré-câblent MAIL_* |
aucune trace mail |
Défaut de MAIL_ENABLED quand la variable est absente |
True (côté core) |
False (zéro envoi accidentel) |
L'environnement est déjà chargé par config.py (load_dotenv), donc os.getenv("MAIL_*") fonctionne dès que l'utilisateur ajoute le bloc MAIL_* à son env/dev.
Le mail ne dépend plus du registre core, et le core ne dépend plus du mail.
Conséquences¶
Positives :
- Le core respecte strictement ADR-004 pour le mail :
grep -ri mail core/ne retourne plus queEmailField(validation de formulaire) et le moduleemailde la bibliothèque standard. - Un projet nu démarre sans aucune plomberie mail.
forge-mvc-maildevient autonome : il lit et porte sa propre config.- Défaut plus sûr (
MAIL_ENABLEDabsent => désactivé).
À assumer :
- Rupture interne de
core.forge.configure(retrait des kwargsmail_*).
Aucun alias ni guide de migration : phase bêta pré-1.0 (convention CLAUDE.md),forge-mvc-mailest le seul appelant et il est migré dans le même lot.
À signaler auCHANGELOG.md. - Le provisioning de la config mail (le bloc
MAIL_*à ajouter àenv/dev) est documenté et fourni par le module mail, jamais écrit silencieusement dans les fichiers utilisateur (principe 9).
Mode « Forge affiche ».
Mise en œuvre (ticket MAIL-DECOUPLE-CORE-001)¶
- Gardes d'abord (rouges au départ) :
mailabsent decore/forge.py; projet nu sansMAIL_*;forge-mvc-mailne référence pluscore.forgepour le mail. - Module mail :
MailConfig.from_env()(généralisecli._configure_forge_from_env) remplacefrom_forge(); basculemailer.from_config,transports,clidessus ; défautmail_enabled=False. - Core : retirer les 13 clés
mail_*decore/forge.pyet_optional_mail_kwargs()(+ son appel) decore/app/app_factory.py. - Squelette nu : retirer la plomberie mail de
env/example(hook §12, édition par script),config.pyetapp.py. - Documentation :
docs/features/mail.mddocumente le bloc env côté module ; mise à jour du parcours welcome-mail. - Recette : un projet nu démarre sans trace mail ; un projet
pip install forge-mvc-mail+ bloc env vérifiemail:test/mail:doctor(transportlog).
Validations :
python -m pytest -x -q
python -m compileall -q .
ruff check .
mkdocs build --strict
git diff --check
Alternatives rejetées¶
Conserver les slots mail_* inertes dans core.forge (demi-mesure : ne déplacer que la plomberie du squelette).
Rejetée : laisse de la connaissance mail dans le core, donc ne satisfait pas le principe 4.
Le mail doit être de la seule responsabilité de l'opt-in, registre compris.
Charte appliquée¶
Principe 3 (refuser la magie cachée), principe 4 / ADR-004 (périmètre du core), principe 8 (noyau minimal), principe 9 (pas d'écriture invisible dans le code utilisateur), principe 10 (l'API publique reste un contrat clair).