Structure des opt-ins dans un projet Forge¶
Renommage CLI (ADR-016)
La famille de commandes est désormais forge opt-in:install/remove/enable/disable/list
(avec tiret). Les mentions optin: ci-dessous conservent le nom d'époque
des tickets livrés ; la commande actuelle est forge opt-in:enable. Voir le
glossaire opt-in et
ADR-016.
Ticket :
OPTINS-PROJECT-STRUCTURE-001. Ce document pose le contrat d'une convention de branchement local des opt-ins dans une application Forge générée. Il est architecture + documentation uniquement : aucun code n'est généré, aucune commande n'est ajoutée, aucun paquet n'est déplacé. L'implémentation viendra dans des tickets ultérieurs.
Objectif¶
Donner à un projet Forge un lieu unique, explicite et lisible pour voir et brancher les modules opt-in qu'il active :
- quels opt-ins sont activés ;
- quelles routes ils ajoutent ;
- quelles migrations ils utilisent ;
- quels starters leur sont liés ;
- quelle documentation locale existe ;
- comment ils sont branchés dans
mvc/routes.py.
C'est l'équivalent Forge — volontairement simple et sans magie — de ce que les bundles apportent dans Symfony, mais aligné sur la charte Forge (pas d'écriture invisible, refus de la magie cachée, une seule façon officielle de faire).
Différence entre package opt-in et branchement projet¶
Deux choses distinctes, à ne jamais confondre :
| Package opt-in | Branchement projet | |
|---|---|---|
| Où | packages/forge-mvc-* (mono-dépôt Forge) + PyPI |
dossier optins/ dans le projet utilisateur |
| Contient | le code complet du module (logique, API publique) | uniquement le câblage local : routes, migrations utilisées, README, docs locales |
| Installé par | pip install forge-mvc-<module> |
présent dans le projet généré |
| Maintenu par | l'équipe Forge | l'utilisateur (son application) |
Règle verrouillée : les packages distribués restent dans
packages/forge-mvc-*. Le dossier optins/ côté projet ne contient
pas le code complet du package — il ne fait que le brancher.
Pourquoi pas de découverte automatique¶
Forge ne fait pas de discovery magique des opt-ins. Aucune
inspection automatique de site-packages, aucun chargement implicite au
démarrage, aucun « plugin scan ».
Raisons (charte v2) :
- Refuser la magie cachée (§3) : un opt-in actif doit être visible dans le code du projet, pas deviné à l'exécution.
- Pas d'écriture invisible (§9) : Forge ne réécrit pas
silencieusement
mvc/routes.py. - Une seule façon officielle (§11) : le branchement passe toujours
par
optins/registry.py, appelé explicitement.
Conséquence directe : Forge Core ne dépend pas des opt-ins et ne les charge pas automatiquement. Un opt-in absent ne casse jamais le core.
Dossier optins/¶
Structure cible côté projet utilisateur :
optins/
├── __init__.py
├── registry.py
├── iot/
│ ├── __init__.py
│ ├── routes.py
│ ├── README.md
│ ├── migrations/
│ └── docs/
├── rbac/
│ ├── __init__.py
│ ├── routes.py
│ └── README.md
└── media/
├── __init__.py
├── routes.py
└── README.md
Chaque sous-dossier optins/<module>/ est le point de branchement
local d'un package opt-in installé. Il reste mince : il référence
le package, il ne le duplique pas.
optins/registry.py¶
Le registre central énumère, de façon lisible et explicite, les opt-ins branchés du projet :
def register_optins(router):
from optins.iot.routes import register as register_iot
register_iot(router)
Et le projet l'appelle explicitement dans mvc/routes.py :
Pas de décorateur magique, pas d'auto-import : on lit dans
registry.py la liste exacte des opt-ins actifs.
optins/<module>/routes.py¶
Chaque opt-in expose une fonction register(router) qui délègue à
l'API publique du package :
# optins/iot/routes.py
from forge_mvc_iot import register_iot_routes
def register(router):
register_iot_routes(router)
Le code métier vit dans le package (forge_mvc_iot) ; optins/iot/
fait seulement le pont. C'est l'API publique du package qui reste le
contrat de complétude (charte v2 §10).
Migrations opt-in¶
Un opt-in peut apporter des migrations SQL (ex. iot_events pour Forge
IoT). La convention :
- la migration source est packagée dans le module
(
forge_mvc_iot/migrations/, package data) ; optins/<module>/migrations/côté projet reçoit la copie locale effectivement appliquée (via l'outil dédié du module, ex.forge iot:init, puisforge migration:apply) ;- le SQL reste visible (charte v2 §5) et appliqué explicitement.
Ce ticket ne déplace ni ne copie aucune migration : il fixe seulement où elle se branchera côté projet.
Starters opt-in¶
Les starters restent officiellement gérés par Forge CLI
(forge starter:build, profils forge new). Un starter opt-in (ex.
welcome-optin-iot) peut, à terme, générer une structure optins/<module>/
prête à brancher — mais la responsabilité du starter reste côté Forge
CLI. Le dossier optins/ est la cible de cette génération, pas un
système concurrent.
Documentation locale¶
La documentation complète d'un opt-in reste dans la doc officielle
Forge (par exemple les pages docs/iot/*). Côté projet, chaque
optins/<module>/README.md ne reçoit qu'un README utile et minimal :
ce que l'opt-in branche dans ce projet, les commandes à lancer, et un
lien vers la doc officielle. On ne duplique pas la documentation de
référence dans chaque projet.
Exemple avec Forge IoT¶
Activer Forge IoT dans un projet, avec la convention cible :
optins/
├── registry.py # appelle register_iot(router)
└── iot/
├── routes.py # register(router) -> register_iot_routes(router)
├── migrations/ # copie locale de *_create_iot_events.sql
├── README.md # « IoT branché ici ; voir docs/iot/ »
└── docs/ # notes locales minimales
# optins/iot/routes.py
from forge_mvc_iot import register_iot_routes
def register(router):
register_iot_routes(router) # /api/iot/events, etc.
Le package forge-mvc-iot (dans packages/forge-mvc-iot/) fournit tout
le reste : contrat MQTT, subscriber, repository, API HTTP, CLI
(forge iot:doctor, iot:init, iot:listen, iot:simulate). Voir
l'architecture Forge IoT et l'audit de
clôture IoT.
Exemple vivant : le starter welcome-iot
Cette convention n'est pas que théorique : le starter
welcome-iot génère réellement
cette structure optins/iot/ dans le projet créé
(OPTINS-IOT-PROJECT-BRIDGE-001). C'est le premier opt-in officiel
branché via optins/registry.py. Les autres modules (rbac,
media…) suivront le même modèle dans des tickets ultérieurs.
Comparaison avec les bundles Symfony¶
| Symfony | Forge (cible optins/) |
|---|---|
| Bundle = package réutilisable | Package opt-in forge-mvc-* |
config/bundles.php liste les bundles actifs |
optins/registry.py liste les opt-ins branchés |
| Auto-configuration / compiler passes | Aucune auto-config — branchement explicite |
| Recipes Flex modifient le projet | Forge n'écrit jamais en invisible (§9) |
| Bundle découvert par le framework | Pas de discovery magique ; appel explicite |
L'esprit est le même (« un endroit pour voir les modules activés »), mais Forge reste plus explicite : tout le branchement est du Python lisible que l'utilisateur contrôle, sans couche d'auto-magie.
Décisions verrouillées¶
- Les packages distribués restent dans
packages/forge-mvc-*. - Le dossier
optins/côté projet ne contient pas le code complet du package. optins/sert au branchement local : routes, migrations, README, docs locales.- Le branchement est explicite via
optins/registry.py. - Forge Core ne charge pas automatiquement tous les opt-ins.
- Pas de discovery magique.
- Les starters opt-in restent gérés par Forge CLI, mais peuvent
générer une structure
optins/. - La documentation complète reste dans la doc officielle ; le projet local reçoit seulement un README utile.
Hors périmètre¶
Ce ticket pose le contrat. Ne sont pas faits ici :
- pas de commande
forge optin:enable/forge optin:disable; - pas de génération automatique de
optins/; - pas de modification de
forge new; - pas de déplacement des packages ;
- aucune modification fonctionnelle IoT / RBAC / media / workflow / stats / MFA ;
- pas de refonte des starters existants ;
- pas de migration automatique.
Tickets suivants¶
OPTINS-IOT-PROJECT-BRIDGE-001(livré) — applique concrètement cette structure à Forge IoT : le starterwelcome-optin-iotgénèreoptins/iot/et branche l'API viaoptins/registry.py.OPTINS-CLI-ENABLE-AUDIT-001(livré) — cadre la future commandeforge optin:enable: voir l'auditforge optin:enable(commande cible, idempotence, dry-run, gestion des conflits, sans discovery ni écrasement silencieux).OPTINS-CLI-ENABLE-IOT-001(livré) — implémenteforge optin:enable iot(dry-run par défaut,--apply, idempotent). Voir la référence CLI.OPTINS-CLI-ENABLE-ROUTES-APPLY-001(livré) —--applybranchemvc/routes.pysi la structure est reconnue (router = Router()), sinon[WARN]+ instruction manuelle (aucune modification).OPTINS-CLI-LIST-001(livré) —forge optin:list, commande lecture seule qui affiche l'état local des opt-ins (absent/partiel/activépouriot), sans rien créer ni modifier.