ADR-032 : Périmètre de la configuration upload (seul upload_max_size est du core)¶
Statut¶
Acceptée et mise en œuvre (ticket UPLOAD-CONFIG-DECOUPLE-001).
Seul upload_max_size reste dans le cœur ; le reste de la config upload est lu par les opt-ins forge-mvc-files et forge-mvc-images depuis l'environnement.
Complète ADR-031 (même patron de découplage), prolonge ADR-019 / ADR-020 (extraction de l'upload) et renforce ADR-004 ainsi que le principe 8 (noyau minimal, briques opt-in).
Contexte¶
Le squelette nu (env/example) porte un bloc upload de quatre variables, et le registre core.forge déclare une cinquième clé :
UPLOAD_MAX_SIZE, UPLOAD_ROOT, UPLOAD_ALLOWED_EXTENSIONS, UPLOAD_ALLOWED_MIME_TYPES
(+ upload_max_image_pixels, défaut codé en dur dans core/forge.py, hors env)
Examen précis des consommateurs réels :
| Clé | Lue par | Le core la consomme ? |
|---|---|---|
upload_max_size |
core/http/request.py (borne le corps multipart) |
oui |
upload_root |
forge-mvc-files uniquement |
non |
upload_allowed_extensions |
forge-mvc-files uniquement |
non |
upload_allowed_mime_types |
forge-mvc-files uniquement |
non |
upload_max_image_pixels |
forge-mvc-images uniquement |
non |
Point décisif : le FileField du core (core/forms/fields.py) prend allowed_extensions, max_size, allowed_mime_types en paramètres (défaut None) et ne valide que si on les lui passe.
Il ne lit jamais le registre.
La validation par champ est donc explicite côté application, et les clés upload_allowed_* du registre ne servent qu'au flux de sauvegarde de forge-mvc-files.
Conséquence : dans un projet nu (sans opt-in), quatre des cinq variables ne sont lues par rien.
Leur présence dans env/example laisse croire que l'upload est géré et configuré, alors que le stockage/service relève de forge-mvc-files (ADR-019/020) et la validation par champ est explicite.
C'est de la connaissance d'opt-in qui fuite dans le noyau et le squelette nu.
upload_max_size est différent : le noyau lit cette valeur pour décider combien d'octets il accepte sur une requête multipart, avant tout opt-in.
C'est un contrôle anti-DoS intrinsèque à la couche HTTP.
On ne peut pas rendre le core ignorant d'une limite de corps multipart, puisque c'est lui qui lit les octets.
Décision¶
Seul upload_max_size reste un réglage du core.
Les quatre autres clés upload quittent le noyau et le squelette nu, vers les opt-ins qui les consomment.
| Clé | Avant | Après |
|---|---|---|
upload_max_size |
core registre + env/example |
inchangé (contrôle HTTP du noyau) |
upload_root |
core registre + env/example |
lu par forge-mvc-files via os.getenv (défaut storage/uploads) |
upload_allowed_extensions |
core registre + env/example |
idem forge-mvc-files (défaut sobre) |
upload_allowed_mime_types |
core registre + env/example |
idem forge-mvc-files |
upload_max_image_pixels |
core registre (hors env) | lu par forge-mvc-images via os.getenv("UPLOAD_MAX_IMAGE_PIXELS") (défaut 24000000) |
forge-mvc-files continue de lire upload_max_size depuis core.forge.get (valeur légitimement détenue par le noyau), mais lit ses propres clés (upload_root, extensions, MIME) depuis l'environnement.
Conséquences¶
Positives :
- Le noyau ne déclare plus de config de stockage/validation d'upload qu'il ne consomme pas ; il ne garde que la limite de corps multipart, qui lui revient.
- Le projet nu ne porte plus de variables trompeuses suggérant un upload « géré » alors qu'aucun opt-in n'est installé.
forge-mvc-filesetforge-mvc-imagespossèdent leur configuration.upload_max_image_pixelsdevient réellement configurable via l'environnement (aujourd'hui la docstring le prétend, à tort : aucune variable d'env n'existe).
À assumer :
- Rupture interne pré-1.0 de
core.forge.configure(retrait des kwargsupload_root,upload_allowed_extensions,upload_allowed_mime_types,upload_max_image_pixels).
Pas d'alias, à signaler auCHANGELOG.md. forge doctor: le checkcheck_prod_securitylitUPLOAD_ALLOWED_EXTENSIONSpour avertir d'uploads non restreints.
Dans un projet nu sansforge-mvc-files, ce contrôle perd son sens et doit être conditionné à la présence de l'opt-in.- Provisioning : le bloc d'upload de
forge-mvc-files(et la variable image deforge-mvc-images) est documenté par l'opt-in, ajouté àenv/devà l'installation, jamais écrit silencieusement (principe 9).
Mise en œuvre (ticket UPLOAD-CONFIG-DECOUPLE-001)¶
- Gardes d'abord :
upload_root/upload_allowed_*/upload_max_image_pixelsabsents decore/forge.py; projet nu sans ces variables ;upload_max_sizeconservé partout. forge-mvc-files: lectureos.getenvpourupload_root,upload_allowed_extensions,upload_allowed_mime_types(défauts propres) ; continue de lireupload_max_sizedepuis le core.forge-mvc-images:os.getenv("UPLOAD_MAX_IMAGE_PIXELS", "24000000"); idem le starterimage-safety.- Core : retirer les quatre slots de
core/forge.py(garderupload_max_size) etupload_rootde_PATH_KEYS; retirer le threading correspondant decore/app/app_factory.py(garderupload_max_size). - Squelette nu + dogfood racine : retirer
UPLOAD_ROOT,UPLOAD_ALLOWED_EXTENSIONS,UPLOAD_ALLOWED_MIME_TYPESdeenv/example,config.py,app.py(garderUPLOAD_MAX_SIZE). forge doctor: conditionner le check upload-restreint à la présence deforge-mvc-files.- Documentation :
docs/features/media.md/ doc files et images documentent le bloc env à ajouter ; mise à jour des parcours welcome concernés.
Validations :
python -m pytest -x -q
python -m compileall -q .
ruff check .
mkdocs build --strict
git diff --check
Alternatives rejetées¶
Découpler aussi upload_max_size.
Rejetée : le noyau lit cette valeur dans core/http/request.py pour borner le corps d'une requête multipart, avant tout opt-in.
C'est le parseur du core qui lit les octets, il doit donc connaître le plafond.
Un opt-in ne peut pas s'injecter dans le parsing.
Le seul découplage possible serait un renommage cosmétique (max_multipart_body_size), sans gain réel et au prix de la lisibilité de UPLOAD_MAX_SIZE.
Conserver les cinq clés dans le core comme un « contrat upload ».
Rejetée : c'est le même argument « le core déclare, l'opt-in applique » écarté pour le mail (ADR-031).
Quatre des cinq clés n'ont aucun consommateur dans le core.
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 (provisioning sans écriture invisible), principe 10 (API publique claire).