doctrine : patient 0 n'est pas un tenant — corriger la raison, garder le geste

Le commit dc92f01 justifiait le retrait des roles de site de patient 0 par « patient 0
est un tenant comme les autres, c'est meme tout ce qu'il prouve ». C'est FAUX.

Un tenant ordinaire existe POUR SES GENS : Chezlepro heberge de l'identite, du courriel,
de la collaboration. Patient 0 n'heberge que LA LIGNEE. Il est l'ecosysteme d'origine et
le detenteur du genome, et il le reste.

Ce qui le quitte, ce sont deux responsabilites de SITE qu'il tenait faute d'un SITE
capable de les porter — a l'epoque un site n'etait qu'un fichier de carte, sans machines.
Le SITE existe desormais comme objet a part entiere : le geste etait donc juste, seule sa
raison ne l'etait pas.

Le texte est corrige plutot qu'efface, dans le CHANGELOG comme dans les fichiers : une
doctrine juste appuyee sur une raison fausse finit toujours par se retourner.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-25 09:34:02 -04:00
parent 0b6c036f8c
commit a6713ffdcd
4 changed files with 50 additions and 7 deletions

View file

@ -127,12 +127,22 @@ téléchargement.
quelle machine de la flotte. L'oublier aurait donné à la machine la plus puissante du
site la protection la plus faible.
### Patient 0 rendu à sa condition de tenant
### Patient 0 rend ses responsabilités de site
Son plan déclarait encore `serveur_ops_site` et `serveur_cache_site` — il tenait ces
rôles parce que le SITE n'existait pas comme objet à lui. Il existe. Un tenant qui
garderait ces rôles s'arrogerait un pouvoir sur ses voisins, et patient 0 est un tenant
comme les autres : c'est même tout ce qu'il prouve.
> **Corrigé le 2026-08-25.** Cette section affirmait que « patient 0 est un tenant comme
> les autres : c'est même tout ce qu'il prouve ». **C'est faux**, et le texte est corrigé
> plutôt qu'effacé. Un tenant ordinaire existe pour ses gens ; patient 0 n'héberge que la
> **lignée**. Il est l'écosystème d'origine et le détenteur du génome, et il le reste.
> Le geste était juste, la raison ne l'était pas — et une doctrine juste appuyée sur une
> raison fausse finit toujours par se retourner.
Son plan déclarait encore `serveur_ops_site` et `serveur_cache_site`. Il les tenait non
par nature mais **faute d'un SITE capable de les porter** : à l'époque, un site n'était
qu'un fichier de carte, sans machines à lui.
Le SITE existe désormais comme objet à part entière — machines déclarées dans son
underlay, sur leur propre réseau de fabric, avec leur voûte et leur runner. Matérialiser
et servir le cache aux voisins lui reviennent. Porter la lignée reste chez patient 0.
Bilan au devis : **26 règles à créer, 5 à retirer** (dont les trois périmées de patient 0).

View file

@ -737,7 +737,16 @@ site-appliquer: ## Applique un role aux machines du site — GROUPE=<serveur_ops
printf 'Groupes du site : %s\n' "$$connus"; \
exit 2; \
fi; \
ansible-playbook -i scripts/site_inventaire.py "playbooks/groupes/$(GROUPE).yml" $(ARGS)
@# LA VOUTE DU SITE, DERIVEE DU SYMLINK — jamais redeclaree.
@# Les roles partages exigent leurs secrets (Forgejo : secret_key, internal_token,
@# admin_password). Un tenant les tient de `group_vars/all/vault.yml` ; le site les
@# tient de `underlay.vault.yml`, qui vit a cote de sa carte et hors depot. Sans ce
@# `-e @`, le role echoue sur son assertion de secrets — et l'echec ne dit pas qu'il
@# manque un FICHIER, seulement que les valeurs sont vides.
voute="$$(dirname "$$(readlink -f underlay.yml)")/underlay.vault.yml"; \
supp=(); [[ -f "$$voute" ]] && supp+=( -e "@$$voute" ); \
ansible-playbook -i scripts/site_inventaire.py "playbooks/groupes/$(GROUPE).yml" \
"$${supp[@]}" $(ARGS)
.PHONY: site-creer
site-creer: ## Cree les machines du site depuis l'underlay — CONFIRMER=true

View file

@ -36,7 +36,7 @@
| 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 : 8 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 78 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,10.29.19.41/32,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, 81 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,10.29.19.41/32,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), 50 groupe(s), 79 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 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. |

View file

@ -54,6 +54,29 @@ def inventaire() -> dict:
if erreurs:
raise SystemExit("underlay.yml refuse :\n - " + "\n - ".join(erreurs))
# L'IDENTITE DU SITE. Un tenant tire `domaine_interne` et `organisation` de son plan,
# via `instancier`. Un site n'a pas de plan : il les declare dans sa carte, et c'est
# ici qu'ils deviennent des variables d'inventaire. Sans eux, un role partage comme
# `serveur_forgejo` construirait des noms d'hote a partir de rien.
site = u.get("site") or {}
communes: dict = {}
if site.get("domaine"):
communes["domaine_interne"] = site["domaine"]
if site.get("organisation"):
communes["organisation"] = site["organisation"]
# LE REBOND, DECLARE PLUTOT QUE PASSE EN LIGNE DE COMMANDE. Le poste n'a pas de route
# vers le segment du site ; la frontiere y est adjacente. Le jour ou une route directe
# existe, on retire `rebond:` de la carte et rien d'autre ne bouge.
# Le socle ne pose `/etc/resolv.conf` que si cette variable existe. Sans elle, les
# machines naissent sans resolveur — et l'echec ressemble a un probleme de reseau.
if site.get("dns_amorcage"):
communes["dns_amorcage"] = str(site["dns_amorcage"])
if site.get("cle_ssh"):
communes["ansible_ssh_private_key_file"] = site["cle_ssh"]
if site.get("rebond"):
communes["ansible_ssh_common_args"] = (
f"-o ProxyJump={site['rebond']} -o StrictHostKeyChecking=no")
par_reseau = {r.get("nom"): r for r in U.reseaux(u)}
hostvars: dict[str, dict] = {}
groupes: dict[str, list[str]] = {}
@ -73,6 +96,7 @@ def inventaire() -> dict:
# un site et un tenant peut avoir besoin de savoir de quel cote il tourne —
# par exemple pour ne PAS chercher un plan la ou il n'y en a pas.
"setops_site": True,
**communes,
}
groupes.setdefault(GROUPE_SOCLE, []).append(nom)
for service in m.get("services") or []: