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: