From f248bb084f642824bb4599580664ae8f2cc34758 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 24 Aug 2026 14:10:10 -0400 Subject: [PATCH] filiation : nommer les trois ages d'un ecosysteme Un ecosysteme nait fils, peut rester mutualise par choix, et s'emancipe quand il le decide. Le moteur avait rencontre ce motif trois fois sans le nommer : client_artefacts_actif, serveur_ops_forge_externe, client_backup_cible. serveur_ops_forge_externe etait citee par le registre des dependances ET par le README, definie nulle part : l'exemption ne pouvait jamais s'appliquer. Elle existe maintenant, avec un amont obligatoire. Consequence pour les modeles : quatre n'ont pas de forge, ce ne sont pas des lacunes mais des ecosystemes au premier age. Ils le declarent. Reste a faire : l'instrument qui PROUVE qu'une emancipation a coupe le lien. Sans lui, on croirait s'etre emancipe en restant dependant. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 57 ++++++++++++++++ docs/audit/preuve-2026-08-24.md | 14 ++-- docs/filiation-emancipation.md | 101 ++++++++++++++++++++++++++++ roles/serveur_ops/defaults/main.yml | 23 ++++++- roles/serveur_ops/tasks/main.yml | 5 +- 5 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 docs/filiation-emancipation.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ce34210..b28cca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # CHANGELOG — Set-OPS +## 2026-08-24 — Filiation, mutualisation, émancipation : nommer un motif que le moteur avait déjà + +Un écosystème ne naît pas autoportant. Il lui faut une forge pour lire son génome, une +source d'artefacts pour ses paquets, un dépôt pour ses sauvegardes — et rien de tout cela +n'existe la première seconde. Il **emprunte** donc à son hôte. + +Le moteur avait rencontré ce besoin **trois fois sans le reconnaître** : + +``` +client_artefacts_actif dérivé : « une source existe-t-elle chez moi ? » +serveur_ops_forge_externe « je lis mon génome ailleurs » +client_backup_cible patient 0 sauvegarde chez eregion — mutualisé, en production +``` + +Trois astuces, une seule notion — désormais déclarée dans `docs/filiation-emancipation.md`. + +### Ce que ça change pour les modèles + +Quatre modèles sur six n'ont pas de forge : `collaboration`, `identite`, `observabilite`, +`presence-web`. On pouvait lire ça comme une lacune — leur `ops-01` n'aurait rien à cloner. +C'en est une autre : ce sont des écosystèmes **au premier âge**, qui lisent leur génome +chez leur hôte. L'ajout d'une forge n'est pas une correction, c'est une **émancipation**. + +Les quatre le déclarent maintenant explicitement. `forge` et `integral` restent émancipés +par défaut. + +**Et personne ne l'aurait vu** : le harnais ne regarde pas les modèles privés — P17 n'en +découvre qu'un seul, celui du dépôt public. Encore un vert sur un périmètre vide. + +### Une promesse sans substance + +`serveur_ops_forge_externe` était citée par `docs/dependances-groupes.yml` **et** par le +README du rôle, et **définie nulle part**. L'exemption qu'elle portait ne pouvait donc +jamais s'appliquer : le registre refusait un écosystème sans forge tout en documentant +comment l'autoriser. La variable existe maintenant, avec son amont obligatoire — lever le +drapeau sans nommer chez qui l'on emprunte, c'est ne rien déclarer du tout. + +### Ce que l'émancipation devra prouver + +Trois temps, dont le dernier est celui qu'on oublie : + +1. **déclarer** — lever le drapeau, déployer le service chez soi ; +2. **migrer l'état** — génome, sauvegardes, certificats ; +3. **prouver que le lien est coupé.** + +Sans le troisième, on croirait s'être émancipé en restant dépendant sans le savoir. Une +émancipation non prouvée est une émancipation non faite. **L'instrument de cette preuve +n'existe pas encore** — c'est la prochaine pièce. + +### Ce que ça ouvre, côté commerce + +L'hébergeur ne vend plus seulement des machines : il vend **l'abri pendant la jeunesse**. +Chaque service mutualisé est une ligne de facture ; chaque émancipation est une décision du +client, jamais une rupture technique. Pour une clientèle d'OBNL et de coopératives : on +entre à bas coût, on grandit vers l'autonomie, et **on n'est jamais captif** — le génome +est chez soi dès le premier jour. + ## 2026-08-24 — Le poste d'exploitation entre dans les modèles, et un silence de plus est fermé `serveur_ops` n'existait que chez patient 0. Aucun modèle ne le portait — donc **aucun diff --git a/docs/audit/preuve-2026-08-24.md b/docs/audit/preuve-2026-08-24.md index b523768..ee82aa5 100644 --- a/docs/audit/preuve-2026-08-24.md +++ b/docs/audit/preuve-2026-08-24.md @@ -28,7 +28,7 @@ | P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. | | P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. | | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | -| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). | +| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 32 groupes (inventaire dechiffre et parse). | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | | P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 24 secret(s) exige(s), tous presents. Voute reelle : 25 cle(s), aucun manque. | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | @@ -36,17 +36,17 @@ | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. | | P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). | | P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 7 reseau(x), aucune collision avec la plage tenant. | -| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 52 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. | +| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 54 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. | | P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 48 groupe(s), 77 regle(s). | -| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). | +| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | -| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 33 VM placee(s), aucun nom ni VMID en collision. | +| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. | | P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 25 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 14, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 47 scripts expliques et atteignables, 98 cibles make documentees, 57 roles avec README. | -| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 34 exigence(s) de role, toutes satisfaites (118 cle(s) declaree(s) par l'instance). | -| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). | -| P34 | Chaque document declare son lecteur | — | ✅ OK | 41 document(s) declarent leur lecteur (22 genere(s) exempte(s)). | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 34 exigence(s) de role, toutes satisfaites (121 cle(s) declaree(s) par l'instance). | +| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (34 groupes). | +| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (22 genere(s) exempte(s)). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | | P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. | diff --git a/docs/filiation-emancipation.md b/docs/filiation-emancipation.md new file mode 100644 index 0000000..4cd2c66 --- /dev/null +++ b/docs/filiation-emancipation.md @@ -0,0 +1,101 @@ +# Filiation, mutualisation, émancipation + +> **Pour qui :** l'**architecte** de la lignée et l'**hébergeur** qui abrite des +> écosystèmes tiers. Décrit les trois âges d'un écosystème et ce qu'ils impliquent au plan. + +## Le constat + +Un écosystème ne naît pas autoportant. Il lui faut une forge pour lire son génome, une +source d'artefacts pour ses paquets, un dépôt pour ses sauvegardes — et rien de tout cela +n'existe la première seconde. Il emprunte donc à son hôte. + +Jusqu'ici, le moteur a rencontré ce besoin **trois fois sans le nommer** : + +``` +client_artefacts_actif dérivé : « une source existe-t-elle chez moi ? » +serveur_ops_forge_externe « je lis mon génome ailleurs » +client_backup_cible patient 0 sauvegarde chez eregion — mutualisé, en production +``` + +Trois astuces, une seule notion. Ce document la déclare. + +## Les trois âges + +``` +FILIATION l'enfant emprunte à son parent ce qu'il ne porte pas encore +MUTUALISATION un état DURABLE et CHOISI — on emprunte parce qu'on le veut +ÉMANCIPATION l'écosystème porte tout lui-même +``` + +**L'émancipation n'est pas une obligation.** Un petit organisme peut rester mutualisé +toute sa vie ; ce qui compte est qu'il *puisse* s'émanciper, et que la sortie ne soit ni +un piège ni une reconstruction. C'est la différence entre un locataire et un captif. + +## Ce que ça veut dire pour les modèles + +Un modèle sans forge n'est pas un modèle amputé : c'est un écosystème **au premier âge**, +dont le runner lit le génome chez son hôte. L'ajout d'une forge n'est pas une correction, +c'est une **émancipation**. + +``` +premier âge serveur_ops_forge_externe: true + l'adresse de la forge de l'hôte +émancipé serveur_ops_forge_externe: false + sa propre serveur_forgejo au plan +``` + +## Ce que ça veut dire pour l'hébergeur + +L'hébergeur ne vend plus seulement des machines : il vend **l'abri pendant la jeunesse**. +Chaque service mutualisé est une ligne de facture, et chaque émancipation est une décision +du client — jamais une rupture technique. + +Pour une clientèle d'OBNL et de coopératives, c'est le bon geste : on entre à bas coût, on +grandit vers l'autonomie, et **on n'est jamais captif**, puisque le génome est déjà chez +soi dès le premier jour. + +## Ce qui est mutualisable, et ce qui ne l'est pas + +| service | mutualisable | remarque | +|---|---|---| +| forge (génome) | oui | `serveur_ops_forge_externe` | +| source d'artefacts | oui | `client_artefacts` se désactive seul si aucune n'existe | +| sauvegarde | oui | `client_backup_cible` pointe déjà hors de l'écosystème | +| observabilité | oui | l'hébergeur surveille ses locataires | +| relais courriel | oui | | +| DNS | partiellement | un sous-domaine délégué, pas la zone entière | +| **PKI** | à trancher | une AC intermédiaire signée par l'hôte est possible, mais l'AC **définit l'identité** de l'écosystème | +| **identité** (LDAP/SSO) | le plus intime | mutualiser l'annuaire, c'est confier ses gens | +| **la voûte** | **jamais** | les secrets ne se mutualisent pas, à aucun âge | + +## Ce que l'émancipation doit prouver + +Basculer une déclaration ne suffit pas. Une émancipation est un **acte outillé** en trois +temps : + +1. **déclarer** — lever le drapeau au plan, déployer le service chez soi ; +2. **migrer l'état** — le génome, les sauvegardes, les certificats existants ; +3. **prouver que le lien est coupé** — et c'est le temps qu'on oublie. + +Sans cette troisième preuve, on croirait s'être émancipé en restant dépendant sans le +savoir. C'est exactement le défaut que ce dépôt traque partout ailleurs : un vert sur un +périmètre vide. Une émancipation non prouvée est une émancipation non faite. + +## Forme attendue d'une déclaration + +Tout service mutualisable doit se déclarer de la même façon, pour qu'un exploitant lise +l'état de son écosystème d'un coup d'œil plutôt qu'en fouillant chaque rôle : + +```yaml +_externe: true # je l'emprunte +_amont: "https://…" # à qui +``` + +`false` — ou l'absence du couple — signifie *chez moi*. Le rôle **refuse** de démarrer si +le drapeau est levé sans que l'amont soit nommé : emprunter sans dire à qui, c'est ne rien +déclarer du tout. + +## État au 2026-08-24 + +Seule la forge porte cette déclaration complète (`serveur_ops`). Les autres services +mutualisés le sont par des mécanismes antérieurs, cohérents mais non uniformes. Les +aligner sur la forme ci-dessus reste à faire — et l'instrument de preuve de l'étape 3 +n'existe pas encore. diff --git a/roles/serveur_ops/defaults/main.yml b/roles/serveur_ops/defaults/main.yml index dfdc409..70b004b 100644 --- a/roles/serveur_ops/defaults/main.yml +++ b/roles/serveur_ops/defaults/main.yml @@ -44,9 +44,30 @@ serveur_ops_ansible: "ansible-core>=2.18,<2.19" serveur_ops_forge_hote: >- {{ (groups['serveur_forgejo'] | default([]) | first | default('')) }} serveur_ops_forge_url: >- - https://forge.{{ domaine_interne }} + {{ serveur_ops_forge_amont if (serveur_ops_forge_externe | bool) + else 'https://forge.' ~ domaine_interne }} serveur_ops_forge_organisation: "genome" +# --- FILIATION, MUTUALISATION, ÉMANCIPATION ---------------------------------- +# +# Un écosystème naît FILS : il n'héberge pas encore tout ce dont il a besoin, et emprunte +# à son hôte. Il peut rester ainsi — la MUTUALISATION est un état durable et choisi, pas +# une infirmité — ou devenir AUTOPORTANT le jour où il le décide. +# +# La forge est le premier service concerné. `false` : le génome est lu sur la forge de +# CET écosystème (état émancipé). `true` : il est lu chez l'hôte (état natal, ou +# mutualisation assumée) — et l'écosystème n'a alors pas besoin d'héberger de forge. +# +# CE N'EST PAS UN RABAIS. Un modèle sans forge n'est pas un modèle amputé : c'est un +# écosystème au premier âge, dont le runner lit le génome chez son parent. L'émancipation +# consiste à lever ce drapeau, déployer sa propre forge, et PROUVER que le lien est coupé. +# +# Cette variable était citée par `docs/dependances-groupes.yml` et par le README sans +# exister nulle part (constaté le 2026-08-24) : une exemption qui ne pouvait jamais +# s'appliquer, donc un écosystème sans forge que le registre refusait quand même. +serveur_ops_forge_externe: false +serveur_ops_forge_amont: "" + # Les dépôts du génome, et où ils atterrissent. Les noms sont ceux que la forge porte # (minuscules) ; les destinations reprennent la disposition du poste du mainteneur, où # le moteur et les instances sont des dossiers frères — c'est cette fraternité que diff --git a/roles/serveur_ops/tasks/main.yml b/roles/serveur_ops/tasks/main.yml index b0d786e..7633b3e 100644 --- a/roles/serveur_ops/tasks/main.yml +++ b/roles/serveur_ops/tasks/main.yml @@ -11,10 +11,13 @@ - (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | list | length) == 1 - (serveur_ops_depots | selectattr('role', 'eq', 'instance') | list | length) >= 1 - serveur_ops_instance | default('') | length > 0 + - not (serveur_ops_forge_externe | bool) or (serveur_ops_forge_amont | length > 0) fail_msg: >- serveur_ops exige `domaine_interne`, exactement un dépôt `role: moteur`, au moins un dépôt `role: instance`, et `serveur_ops_instance` (le dossier que le lien - `instance` doit désigner). Déclarer le tout dans group_vars/serveur_ops.yml. + `instance` doit désigner). Et si `serveur_ops_forge_externe` est levé — écosystème + au premier âge, qui lit son génome chez son hôte — `serveur_ops_forge_amont` doit + nommer cette forge. Déclarer le tout dans group_vars/serveur_ops.yml. - name: Installer l'outillage du poste ansible.builtin.apt: