0069 — topology.layers : déclaration explicite des couches (DAG, grain phase)
Accepted (2026-06-15)
Contexte
Section intitulée « Contexte »L’ADR 0039 modélise le déploiement par un
profil scalaire : un point sur une chaîne totalement ordonnée
base ⊂ metrics ⊂ store ⊂ obs ⊂ dataops (chaque profil inclut tous les
précédents). L’ADR 0056 en fait le
pilote déclaratif : catalog.profile → default_target → séquence de phases.
Ce modèle a une limite d’expressivité révélée à l’usage (et confirmée par une cartographie multi-agents, ADR 0067) : un ordre total impose des dépendances fausses. Exemples concrets :
- déclarer
storeforcemetrics(tout ce qui précède dans la chaîne), alors que le stockage ne dépend PAS de metrics-server ; - on ne peut PAS exprimer «
gitops+metricsSANSmonitoring» : la chaîne oblige à prendreobs(donc monitoring) avantdataops/gitops; profile: dataops(cheminatlas) monte gitops et dataops d’un bloc — pas moyen d’avoir l’un sans l’autre.
Or les dépendances réelles entre couches forment un DAG, pas une chaîne
: metrics-server → [bootstrap] (aucun stockage),
storage-simple → [bootstrap] (branche sœur, indépendante de metrics),
monitoring → [storage], dataops → [storage, monitoring] (PVC + SeaweedFS),
gitops-seed → [gitops, dataops]. La chaîne totale est une linéarisation
arbitraire de ce DAG.
Point décisif : ce DAG existe déjà.
L’ADR 0066 a institué un graphe
de dépendances atomique unique (component_deps dans rollback-lib.sh) au
grain composant, avec un tri topologique stable (topo_sort) calibré pour
reproduire l’ordre codé des arms. Réintroduire une seconde table de
dépendances phase→phase en Python recréerait exactement le double-graphe que
l’ADR 0066 a supprimé (les deux dériveraient).
Décision
Section intitulée « Décision »Introduire un champ layers au top-level de la topologie : un ENSEMBLE de
couches (grain phase) que l’outil ordonne par tri topologique du DAG de
dépendances, en remplacement progressif du profil scalaire.
-
Grain = la phase (les briques montables de
run-phases.sh:metrics-server,storage-simple,monitoring,gitops,dataops,gitops-seed,ceph,sc,datalake…) — chacune a déjà un arm bash prouvé et idempotent.layersREORDONNE des briques existantes, n’en invente aucune. -
L’ordre est DÉRIVÉ, pas déclaré :
layersest un set.resolve_layerscalcule la fermeture transitive des prérequis puis un tri topologique stable. Déclarer[dataops]tire automatiquementstorage+monitoring; déclarer[metrics-server]ne tire RIEN d’autre (preuve de la fausse dépendance levée). -
Une seule source de vérité d’ordre : le DAG atomique de
rollback-lib.sh(ADR 0066).resolve_layersappelletopo_sort/phase_closuredu bash (via le même pont queroundtrip.py), il ne réimplémente PAS un graphe Python. Le tri-up est l’inverse cohérent du tri-down (rollback). -
layersau TOP-LEVEL (frère denodes/storage) : c’est une INTENTION de déploiement, pas une métadonnée descriptive —catalogredevient purement descriptif (arch/terrain/topology/status, ADR 0039). -
Conditionnel backend (seul) : la couche
storagese résout enstorage-simple(local-path) ouceph+sc(ceph) ;datalake/smoke-s3/wordpresssont interdits hors backend ceph (PlanError). -
HA reste structurelle : dérivée des nœuds (
>1 control→ha-3cp, ADR 0047/0055), horslayers.layersne pilote que la queue applicative. -
Rétrocompatibilité :
catalog.profilereste un alias déprécié-doux.layersabsent → dérivé du profil (layers_from_profile, projection du préfixe cumulatif).layersprésent → il prime. Aucun.exampleversionné n’est réécrit (honnêteté ADR 0052) ; un nouvel.examplepédagogique illustre unlayersnon-préfixe (ex.[gitops, metrics]). -
Les chemins nommés survivent comme PRESETS :
socle/atlas/storage-real/cluster-dataops/atlas-ceph/ha-3cprestent les cibles queuppasse àrun-phases.sh <nom>(parité bash figée partest_parity).default_targetmappelayers→ un preset quand il en existe un ; sinonupcible un nouvel arm génériquelayers <séquence>(l’ordre est fourni par Python, le bash exécute — ADR 0063 : bash exécute, Python décide).
Conséquences
Section intitulée « Conséquences »- Expressivité fine :
layers: [metrics-server](zéro stockage),[gitops, metrics-server](sans monitoring),[dataops](avec ses seuls vrais prérequis) — des paliers que le profil scalaire ne pouvait pas exprimer. - Zéro duplication de graphe :
resolve_layersréutilise le DAG ADR 0066. Le drift « deux graphes parallèles » est structurellement évité (RISQUE principal borné). resolve_layersferme les dépendances manquantes (déclarer[obs]sans store en local-path ajoutestorage+ un message) : pas de couche orpheline.- Migration incrémentale, sans big-bang (Lots A/B/C calqués sur ADR 0066),
profileactif tout du long. - ADR amendés : 0039 (le profil devient
un préfixe particulier de
layers), 0056 (layers= forme explicite du graphe de dépendances), 0068 (son « À revoir si » anticipait ce mécanisme générique —layersl’est). 0066 reste le socle technique réutilisé, inchangé. - Preuve (ADR 0034/0052) : un run from-scratch d’un
layersnon-préfixe (ex.[gitops, metrics], impossible viaprofile) + rejeuchanged=0— la justification empirique de la feature.
À revoir si
Section intitulée « À revoir si »- Un besoin réel de grain composant émerge (ex. Grafana sans Loki) :
layersdescendrait au grain composant (le DAG ADR 0066 le porte déjà), au prix d’une décomposition des playbooks composites. - Le mapping
layers → preset(court terme) devient un frein : on passerait à unPHASES_OVERRIDEbash généralisé (toutlayersnon-preset via l’arm générique), rendant les presets de simples raccourcis.
Alternatives écartées
Section intitulée « Alternatives écartées »- Coder une table
PHASE_DEPSen Python : recrée le double-graphe qu’ADR 0066 a supprimé ; rejeté au profit de la projection du graphe atomique unique. - Grain composant d’emblée : ~40+ composants, exige de décomposer chaque playbook ; sur-dimensionné pour le besoin (« couches fines »), reporté.
- Supprimer
profileimmédiatement : casse les.example, les presets et la parité bash d’un coup ; rejeté au profit de l’alias déprécié-doux. layerssouscatalog:catalogest descriptif (ADR 0039) ; une intention de déploiement vit au top-level, commenodes/storage.