Aller au contenu

ADR-0037 — Capacités de déploiement déclarées par template, cible choisie sous contrainte

  • Statut : Proposée — contient une contradiction assumée avec ADR-0009, à arbitrer
  • Date : 2026-09-09
  • Contexte amont : ADR-0009 (taxonomie des templates, cible socle|pod dérivée), ADR-0012 (Helm comme packaging K8s), ADR-0016 (cercles), ADR-0017 (couloir), R-236 (Forge propriétaire du vocabulaire structurel)
  • Ne décide rien pour l'exécution immédiate. Aucun code n'est modifié par cet ADR.

0. Ce que l'audit a trouvé avant tout arbitrage

Cet ADR n'ouvre pas un sujet neuf. ADR-0009 l'a déjà tranché en 2026-06-10, et l'audit montre trois écarts entre ce qui est écrit, ce qui est construit, et ce qui manque.

0.1 La décision existe déjà — et elle dit l'inverse de « choix explicite »

ADR-0009 §« Cible de déploiement par cercle » :

C'est la 5ᵉ propriété du profil, dérivée des 4 autres et de la sensibilité de l'app — pas une décision indépendante.

CODEX_DEPLOY_TARGET ∈ {socle, pod} — résolu au manifeste, jamais saisi à la main.

La table de dérivation y est complète : paiement/fiscal → pod toujours ; scenario ∈ {on-premise, cloud-client} → pod ; cercle client ou prod SaaS → pod ; tenancy = dedicated → pod ; tout le reste → socle.

0.2 La dérivation n'a jamais été implémentée

Vérifié sur dynors-internal/applications/sirrat/backend/src/main :

grep '"pod"' | '"socle"'   →  0 occurrence en code Java de production

Les seules mentions de socle/pod sont dans la Javadoc de SirratCodexConstants et un faux positif (SirratHumanSessionService, « socle de lecture », sans rapport). L'en-tête d'ADR-0009 annonce « Sprint d'implémentation : amorcé (CODEX_DEPLOY_TARGET déjà constant) » : la constante existe, la règle non.

0.3 La clé porte aujourd'hui un tout autre concept

ValueResolutionService (ligne ~199) :

poser(valeurs, SirratCodexConstants.CODEX_DEPLOY_TARGET, stack.getDeploymentTarget());

Or stack.deploymentTarget vient de la migration 028, qui le définit ainsi :

deployment_target VARCHAR(120) — « Hôte ou groupe d'inventaire cible (ex. vps-prod-01). Null = cible par défaut du cercle. »

Donc CODEX_DEPLOY_TARGET est documenté comme socle|pod et alimenté avec un nom de machine. Deux concepts, une clé :

ADR-0009    CODEX_DEPLOY_TARGET = socle | pod        nature du placement   (dérivée)
migr. 028   deployment_target   = vps-prod-01        machine d'exécution   (déclarée)

Un template qui écrirait {{ CODEX_DEPLOY_TARGET }} en attendant pod recevrait vps-prod-01. Rien ne l'en avertit. À noter par ailleurs : setDeploymentTarget n'est appelé nulle part en code de production — seulement dans deux tests. La colonne est donc, en pratique, vide.

0.4 Le vrai manque : la dérivation ne vérifie aucune faisabilité

C'est le point que la proposition apporte réellement, et qu'aucun ADR ne couvre.

La table d'ADR-0009 dérive une cible depuis la politique — sensibilité, tenancy, scénario, cercle. Elle ne demande jamais si la forme technique de l'app peut être déployée ainsi. Conséquence mécanique :

app Flutter mobile publiée en store
  + tenancy = dedicated
  → la table dérive « pod »

« Pod » n'a aucun sens pour un binaire distribué en store. La même incohérence attend un Electron POS installé sur un poste, ou un front statique servi par un CDN. La règle produirait une cible que rien ne peut exécuter, et le refus n'arriverait qu'au moment du déploiement — au mieux.

Deux questions distinctes ont été confondues en une :

ce qui est POSSIBLE     faisabilité   propriété du TEMPLATE (forme technique)
ce qui est OBLIGATOIRE  politique     propriété du PROFIL   (ADR-0009)

ADR-0009 répond à la seconde et suppose la première résolue.


1. Le conflit à arbitrer

ADR-0009 (Acceptée) Proposition 2026-09-09
Choix de la cible dérivé, « jamais saisi à la main » explicite, porté par SIRRAT
Contrainte table de politique capacités déclarées par le template
Risque évité qu'on place FISCAL sur un socle mutualisé qu'on demande un pod à une app mobile

Les deux ne s'annulent pas — mais ADR-0009 dit « jamais saisi », et la proposition dit « explicitement choisi ». Cette phrase-là est en contradiction frontale, et je ne la tranche pas ici.

Trois lectures possibles, à trancher par le lead architecte :

A — la dérivation reste souveraine, les capacités ne sont qu'un garde-fou. Le template déclare ce qu'il supporte ; si la dérivation produit une cible non supportée, SIRRAT refuse au lieu de choisir autre chose. Aucune saisie n'apparaît. ADR-0009 est préservée mot pour mot ; on lui ajoute une garde de faisabilité. Coût : un profil légitime peut devenir indéployable sans recours, jusqu'à changement de profil.

B — capacités d'abord, politique ensuite, choix explicite quand il reste du jeu. La cible retenue est l'intersection capacités(template) ∩ politique(profil). Si l'intersection contient une seule valeur, elle s'impose (pas de saisie). Si elle en contient plusieurs, SIRRAT exige un choix explicite — aucun défaut. C'est la proposition, et elle amende ADR-0009 sur « jamais saisi à la main ». Coût : amender une ADR Acceptée, et accepter qu'une même app puisse être placée différemment selon le cercle sans que la politique l'impose.

C — statu quo documenté. On corrige seulement la collision de clé (§0.3) et on laisse la dérivation non implémentée jusqu'à ce qu'un besoin réel se présente. Coût : l'incohérence Flutter/pod reste latente ; elle se découvrira en déploiement.

Recommandation de rédaction, non de décision : A est la plus économe et ne rouvre rien ; B est la plus juste si l'on prévoit d'ajouter des backends (ECS, Nomad, OpenShift), parce qu'à ce moment-là plusieurs cibles deviennent simultanément possibles ET conformes, et la table de politique d'ADR-0009 ne saura pas départager.


2. Modèle proposé (sous réserve de l'arbitrage §1)

2.1 Quatre acteurs, quatre responsabilités

Forge          catalogue des CAPACITÉS       ce que cette forme technique sait faire
SIRRAT         choix + gouvernance + manifeste   ce qui est retenu, et pourquoi
dynors-ops     exécution                     comment on le fait réellement
backend        runtime                       Compose · Kubernetes · VM · store · CDN

C'est la même répartition que R-236 pour le vocabulaire : Forge possède la structure, SIRRAT remplit les emplacements. Une capacité de déploiement est une structure.

2.2 Capacités, pas « K8s-ready »

« K8s-ready » est un attribut binaire attaché à une technologie, et c'est précisément l'inférence à éviter :

Spring Boot  ≠  Kubernetes
Spring Boot  →  compatible Kubernetes
             →  si le template l'autorise
             →  et si une cible pod est déclarée dans le cercle
             →  alors K8s est possible

Vocabulaire proposé — un template déclare un ensemble, jamais une valeur :

CONTAINER · KUBERNETES · COMPOSE · STATIC_WEB · MOBILE_DISTRIBUTION · DESKTOP_PACKAGE · BATCH

Esquisse par forme technique (à valider avec le catalogue réel, non audité ici) :

dynors-spring-api:            [COMPOSE, KUBERNETES]
dynors-worker:                [COMPOSE, KUBERNETES, BATCH]
dynors-angular-runtime-web:   [COMPOSE, STATIC_WEB, KUBERNETES]
dynors-flutter-public-mobile: [MOBILE_DISTRIBUTION]
dynors-electron-offline-pos:  [DESKTOP_PACKAGE]

L'intérêt du modèle est là : ajouter ECS, Nomad, OpenShift ou un serverless demain n'oblige à toucher ni l'application, ni son profil — seulement le catalogue de capacités et le backend d'exécution.

2.3 Articulation avec l'existant

  • ADR-0012 matérialise pod → Helm/K8s. Dans ce modèle, pod cesse d'être une valeur et devient la conjonction « capacité KUBERNETES disponible » + « cible pod déclarée dans le cercle ». ADR-0012 n'est pas contredite : elle décrit le backend, pas le choix.
  • cibles.yml (dynors-ops) tient déjà le registre de ce qui est réellement déployable par (cercle, couloir). La vérification « la cible existe-t-elle physiquement ? » y a déjà son lieu — inutile d'en créer un second. La chaîne complète devient :
capacités (Forge)      →  ce que la forme technique sait faire
politique (ADR-0009)   →  ce que le profil impose
cible déclarée (Ops)   →  ce qui existe réellement          ← déjà construit, P0.4
                          ─────────────────────────────
                          refus si l'intersection est vide

3. Prérequis, quel que soit l'arbitrage

Ces deux points sont des défauts constatés, pas des options :

  1. Séparer les deux sens de CODEX_DEPLOY_TARGET (§0.3). Une clé ne peut pas signifier socle|pod dans un ADR et vps-prod-01 en base. Le nommage naturel : CODEX_DEPLOY_TARGET = nature du placement, CODEX_DEPLOY_HOST = machine — mais renommer une clé Codex touche les templates Forge, donc cela relève de R-236 et d'un slice dédié.
  2. Décider du sort de stack.deploymentTarget. Jamais écrit en production, jamais lu ailleurs que dans ce poser(...). Soit il est alimenté et devient la machine cible, soit il est retiré. Le laisser vide et branché produit aujourd'hui un CODEX_DEPLOY_TARGET silencieusement absent.

4. Ce que cet ADR ne fait pas

  • Aucune modification de code, de migration, de catalogue Forge.
  • Aucun amendement d'ADR-0009 tant que §1 n'est pas arbitré.
  • Aucune implémentation de la dérivation socle|pod — elle reste non construite, et c'est désormais écrit plutôt que supposé acquis.