From d4614926238a671ea30789a75e33aabd7235fa2c Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sun, 20 Sep 2026 22:46:43 -0400 Subject: [PATCH] adressage : le site est une flotte, et P20 le regarde enfin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'exploitant a demande si l'adressage derive ne valait pas pour toute flotte, site compris. Oui — decision du 12 septembre, index 37 pour le site. J'avais affirme le contraire a l'entree (11) du CHANGELOG, en recopiant un commentaire perime de huit jours au lieu de mesurer. Un commentaire perime se propage : celui-la s'etait aussi loge dans la docstring de ConsoleSite. P20 s'appelle « Adressage 100% derive du seed (aucun stocke) » et sa portee etait « l'instance liee + les modeles » : elle ne regardait jamais le site, qui stockait sous_reseau et passerelle pour ses sept zones. Elle juge desormais les deux natures d'un underlay et refuse une entree qui ne declare pas la sienne — on ne peut pas juger ce qu'on ne sait pas lire. Controle negatif joue : une zone remise a 10.36.31.0/24 est refusee, en nommant l'index dont elle aurait du descendre. Le loader derive sous_reseau et passerelle a la lecture, pour que rien ne change chez les consommateurs. Mesure : 14 valeurs retirees du fichier, zero ecart sur ce qu'ils lisent, et make underlay, devis-sdn et devis-reseau passent. Valide : make test a 0 echec, P20 verte sur 2 nomenclatures et 7 zones de site. P02 et P60 restent, pour les raisons deja consignees. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++ scripts/inventory_gui.py | 9 ++++--- scripts/prouver.py | 41 ++++++++++++++++++++++++++++- scripts/underlay.py | 51 +++++++++++++++++++++++++++++++++-- 4 files changed, 152 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1747c7e..c4bc8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # CHANGELOG — Set-OPS +## 2026-09-20 (12) — Une décision appliquée à moitié se croit tenue + +**83 preuves, `make test` à 0 échec.** L'exploitant a demandé : « n'avions-nous pas décidé +d'un plan d'adressage dérivé pour chaque flotte, qu'elle soit tenant ou site ? » Oui. La +décision est écrite dans l'underlay du site, datée du 12 septembre : + +> *« sites et locataires se partagent la classe A, chacun avec SON index. Le site prend +> 37, le locataire garde 17. `make instances` les compte ensemble depuis ce jour. »* + +### Ce que l'entrée (11) affirmait, et qui était faux + +Elle disait qu'un site *« déclare ses adresses, il n'a aucun seed dont les faire +descendre »*. C'était la recopie d'un commentaire en tête de `SITE-Chezlepro/plan/serveurs.yml` +— **périmé depuis le jour où le site a reçu son index**, et lu au lieu d'être mesuré. + +Un commentaire périmé se propage : celui-ci s'est retrouvé le même jour dans la docstring +de `ConsoleSite` et dans une entrée de CHANGELOG. C'est exactement ce que +`CARTE-DU-SITE.md` décrit chez un locataire — **inerte et crédible** : le moteur ne le lit +pas, donc rien ne le corrige ; un humain le lit, et le croit. + +### La mesure + +Les sept zones du site suivaient `10...0/24` **à la lettre** — et étaient +pourtant **écrites** : `sous_reseau` et `passerelle` stockés pour chacune. C'est +précisément ce que P20 interdit à un locataire, sous le titre *« Adressage 100 % dérivé du +seed (aucun stocké) »*. Et la portée de P20 était *« l'instance liée + les modèles »* : +**elle ne regardait jamais le site**. + +Les valeurs concordaient parce qu'un humain les avait tapées juste, ce jour-là. Rien ne le +garantissait : une zone écrite `10.36.x` serait passée sans un mot. + +### Deux natures dans une seule liste + +`reseaux:` mélangeait ce qui peut dériver et ce qui ne le peut pas. Chaque entrée déclare +maintenant sa nature : + +| `nature: fabric` | grappe, iSCSI, Ceph, transit, VXLAN, management | adressage dicté par les participants d'un lien physique (D-02, D-04) — il reste **écrit** | +| `nature: site` | les sept zones du site | elles **descendent de son index**, et ne stockent plus rien | + +Le loader dérive `sous_reseau` et `passerelle` à la lecture, pour que les consommateurs ne +voient aucune différence. **Quatorze valeurs stockées ont disparu du fichier, et ce que +les consommateurs lisent est identique — écart mesuré : zéro.** `make underlay`, +`make devis-sdn` et `make devis-reseau` passent. + +P20 juge désormais les deux natures, et refuse une entrée qui ne déclare pas la sienne : +on ne peut pas juger ce qu'on ne sait pas lire. Contrôle négatif joué : une zone remise à +`10.36.31.0/24` est refusée, en nommant l'index dont elle aurait dû descendre. + +### Ce qui reste écrit, et qui est une dette nommée + +Les **machines** du site gardent leurs `ip`, `vmid` et `noeud` dans son plan. Ce n'est plus +une doctrine — « le site est le terrain, il n'a rien dont faire descendre » — puisque le +seed existe maintenant pour elles aussi. C'est une dette, et la nommer est le minimum : +*un objectif qu'on abandonne sans le dire devient un objectif qu'on croit atteint.* + +**P02 et P60 restent**, pour les raisons déjà consignées. + ## 2026-09-20 (11) — Deux fonctions qui doivent rendre la même forme finissent par ne plus la rendre **83 preuves, `make test` à 0 échec, 8 tests de rendu.** La console avait deux diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index a474cf3..06b5490 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -774,9 +774,12 @@ class ConsoleLocataire(Console): class ConsoleSite(Console): """Elle MATERIALISE : cree et detruit des VM, pose le reseau, avec la voute du site. - SON PLAN EST DECLARE, PAS DERIVE. Un site est le terrain : il ecrit `ip`, `vmid` et - `noeud`, parce qu'il n'a aucun seed dont les faire descendre. Son inventaire n'est - pas un fichier mais un SCRIPT. + SES ZONES DERIVENT, SES MACHINES SONT ENCORE ECRITES. Un site a son propre index + depuis le 2026-09-12 — le 37 pour celui-ci — et ses zones en descendent comme celles + d'un locataire descendent du sien (P20 le garde). Ce qui reste ecrit dans son plan, + ce sont les `ip`, `vmid` et `noeud` de ses MACHINES : une dette nommee, pas une + doctrine. La docstring disait le contraire : elle recopiait un commentaire perime de + huit jours au lieu de mesurer. Son inventaire, lui, n'est pas un fichier mais un SCRIPT. ELLE A UN PLAN, ET ON LE LUI SERT. La charge du site servait cinq registres vides « pour ne pas fabriquer un faux plan de tenant ». Le site n'a pas de faux plan : il a diff --git a/scripts/prouver.py b/scripts/prouver.py index f522453..0f6414e 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -946,9 +946,48 @@ def preuve_nomenclature_derivee() -> tuple[bool, str]: for z, c in (n.get("categories") or {}).items(): for k in interdits_zone & set(c or {}): fautes.append(f"{rel}: categorie {z}.{k} stocke") + # ── ET LE SITE, qui est une flotte comme une autre (2026-09-20) ──────────────── + # + # CETTE PREUVE NE REGARDAIT QUE « l'instance liee + les modeles ». Le site a recu son + # propre index le 2026-09-12 — decision ecrite dans son underlay : « sites et + # locataires se partagent la classe A, chacun avec SON index » — et son adressage a + # continue d'etre ECRIT : `sous_reseau` et `passerelle` pour chacune de ses sept + # zones, exactement ce que les lignes ci-dessus interdisent a un locataire. + # + # Les valeurs concordaient parce qu'un humain les avait tapees juste. Rien ne le + # garantissait : une zone ecrite `10.36.x` serait passee sans un mot. Une decision + # appliquee a moitie est plus dangereuse qu'une decision non prise — on la croit tenue. + # + # LES DEUX NATURES NE SE JUGENT PAS PAREIL. Un lien de fabric PHYSIQUE (grappe, iSCSI, + # Ceph, transit, VXLAN) tient son adressage des participants du lien, pas d'un seed : + # D-02 et D-04 le disent, et il reste ecrit. Seules les zones du site derivent. + import underlay as _u + carte = _u.chemin() + zones_site = 0 + if carte: + brut = (yaml.safe_load(carte.read_text(encoding="utf-8")) or {}).get("underlay") or {} + index = brut.get("index") + for reseau in brut.get("reseaux") or []: + nature = reseau.get("nature") + nom = reseau.get("nom", "?") + if nature not in ("site", "fabric"): + fautes.append(f"{carte.name}: {nom} ne declare pas sa nature " + f"(site|fabric) — on ne peut pas juger ce qu'on ne sait pas lire") + continue + if nature != "site": + continue + zones_site += 1 + for champ in ("sous_reseau", "passerelle"): + if champ in reseau: + fautes.append(f"{carte.name}: zone {nom}.{champ} stocke alors " + f"qu'elle derive de l'index {index}") + if reseau.get("vlan") is None: + fautes.append(f"{carte.name}: zone {nom} sans vlan — rien dont deriver") + if fautes: return False, f"{len(fautes)} champ(s) d'adressage stockes : " + " | ".join(fautes[:4]) - return True, f"{len(cibles)} nomenclature(s) : adressage 100% derive du seed index." + return True, (f"{len(cibles)} nomenclature(s) et {zones_site} zone(s) de site : " + f"adressage 100% derive du seed index.") def preuve_pas_lab_code_en_dur() -> tuple[bool, str]: diff --git a/scripts/underlay.py b/scripts/underlay.py index c0fb632..cb91948 100644 --- a/scripts/underlay.py +++ b/scripts/underlay.py @@ -88,13 +88,60 @@ def chemin() -> Path | None: return p if p.exists() else None +# --- LES ZONES DU SITE DERIVENT DE SON INDEX, COMME CELLES DE N'IMPORTE QUELLE FLOTTE --- +# +# LA DECISION EST DU 2026-09-12, SON APPLICATION DU 2026-09-20. Ce jour-la, l'exploitant a +# tranche : « sites et locataires se partagent la classe A, chacun avec SON index ». Le +# site a pris 37. Mais son `underlay.yml` a continue d'ECRIRE `sous_reseau` et +# `passerelle` pour chacune de ses sept zones — exactement ce que P20 interdit a un +# locataire, sous le titre « Adressage 100% derive du seed (aucun stocke) ». +# +# Les valeurs concordaient, parce qu'un humain les avait tapees juste ce jour-la. Rien ne +# le garantissait : une zone ecrite `10.36.x` serait passee sans un mot, P20 ne regardant +# que « l'instance liee + les modeles ». Une decision appliquee a moitie est plus +# dangereuse qu'une decision non prise : on la croit tenue. +# +# DEUX NATURES DANS UNE SEULE LISTE, et c'est ce qui brouillait la lecture : +# +# nature: fabric les liens PHYSIQUES — grappe, iSCSI, Ceph, transit, VXLAN. Leur +# adressage est dicte par les participants du lien (D-02, D-04) : il +# ne descend d'aucun seed, et il reste donc ECRIT. +# nature: site les zones du site lui-meme. Elles descendent de son index, comme +# celles d'un tenant descendent du sien. +# +# CE QUI SE DERIVE, ET RIEN D'AUTRE : `sous_reseau` = `10...0/24` et +# `passerelle` = `.1` de ce reseau. Les deux valeurs etaient deja deductibles de deux +# champs presents — c'est le propre d'une valeur stockee pour rien. + +def _resoudre_reseaux(underlay: dict | None) -> dict | None: + """Rend l'underlay avec ses zones de site RESOLUES. Idempotent. + + Une zone qui porte deja ses valeurs les garde : la garde (P20) refusera qu'elle les + porte, mais ce module ne doit rien casser sur un site pas encore converti. + """ + if not underlay: + return underlay + index = underlay.get("index") + if index is None: + return underlay + for reseau in underlay.get("reseaux") or []: + if reseau.get("nature") != "site": + continue + vlan = reseau.get("vlan") + if vlan is None: + continue + reseau.setdefault("sous_reseau", f"10.{index}.{vlan}.0/24") + reseau.setdefault("passerelle", f"10.{index}.{vlan}.1") + return underlay + + def charger() -> dict | None: - """Le bloc `underlay` du fichier, ou None si absent/vide.""" + """Le bloc `underlay` du fichier, RESOLU, ou None si absent/vide.""" c = chemin() if not c: return None data = yaml.safe_load(c.read_text(encoding="utf-8")) or {} - return data.get("underlay") or None + return _resoudre_reseaux(data.get("underlay") or None) # --- LE PLAN DU SITE, a cote de sa carte ---------------------------------------