diff --git a/AGENTS.md b/AGENTS.md index 1d650e5..543fa1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente ## Écrire, puis relire (D-68) `--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**. -C'est aussi ce que font les 74 preuves de `make prouver` : elles lisent le dépôt, sans le +C'est aussi ce que font les 75 preuves de `make prouver` : elles lisent le dépôt, sans le moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le dépôt annonce.** diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de5399..f330cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # CHANGELOG — Set-OPS +## 2026-09-13 (8) — Le plancher traversait trois modules et se perdait au troisieme + +Trouve en preparant la creation des quatorze machines de TechnoLibre, une commande avant +de les creer. `instancier` derivait bien `proxmox_memoire_min` dans l'inventaire ; le +playbook de clonage savait bien poser `balloon:` ; entre les deux, la table +`CHAMPS_PROXMOX` de `inventory_host.py` ne transmettait **rien**. + +### Pourquoi rien n'aurait echoue + +Trois silences qui s'enchainent : + +1. `$SETOPS_MEMOIRE_MIN` non defini vaut la chaine vide +2. le Makefile ne passe alors pas `-e proxmox_clone_memoire_min` +3. la garde `when:` de la tache saute proprement + +Quatorze machines seraient nees avec le ballooning **desactive**, sans une seule erreur. +C'est le patron d'une liste qui en suit une autre et prend du retard — deja rencontre +quatre fois. + +### La garde, ecrite en meme temps que le remede + +**P75** lit la cible `creer-vm` du Makefile, releve chaque `$SETOPS_X` qu'elle consomme, et +exige que la table qui les EMET le declare. Elle ne juge aucune valeur : elle refuse qu'un +maillon manque. Eprouvee dans les deux sens — elle mord quand on retire le maillon, elle +se tait quand il est la. + +La regle qui en sort : **quand une variable traverse trois modules, le troisieme s'ecrit en +meme temps que le premier, pas quand on s'en sert.** + +`test_inventory_host.py` a signale le changement de contrat de son cote, comme il l'avait +fait pour `SETOPS_DOMAINE` et `SETOPS_CLES_AMORCAGE`. + ## 2026-09-13 (7) — Un hote retire du plan laissait son pare-feu derriere lui En retirant `backup-01` du plan de TechnoLibre, `flux-genere/backup-01.nft` est reste sur diff --git a/docs/audit/preuve-2026-09-13.md b/docs/audit/preuve-2026-09-13.md index b253b36..ca2b87b 100644 --- a/docs/audit/preuve-2026-09-13.md +++ b/docs/audit/preuve-2026-09-13.md @@ -7,7 +7,7 @@ > [`docs/audit/affirmations.md`](affirmations.md). - **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml` -- **Verdict** : ✅ CONFORME (73 OK · 0 echec · 1 saute) +- **Verdict** : ✅ CONFORME (74 OK · 0 echec · 1 saute) ## Preuves @@ -69,7 +69,7 @@ | P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. | | P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 0. | | P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. | -| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (74 preuves, 68 roles, 41 groupes). | +| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (75 preuves, 68 roles, 41 groupes). | | P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. | | P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). | | P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `904ece3` (publie le 2026-09-12). | @@ -87,6 +87,7 @@ | P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. | | P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 5 intrant(s) du locataire concordent avec ce que le site expose. | | P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. | +| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. | ## Couverture des affirmations ✅ du registre diff --git a/docs/devis-services.md b/docs/devis-services.md index dfc7706..83ad558 100644 --- a/docs/devis-services.md +++ b/docs/devis-services.md @@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met ## Le trou qu'il comble -`scripts/prouver.py` porte 74 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le +`scripts/prouver.py` porte 75 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est cohérent **avec lui-même** — que les handlers existent, que les intrants ont un propriétaire, que rien n'est codé en dur. diff --git a/scripts/inventory_host.py b/scripts/inventory_host.py index 70848cb..6a2237e 100644 --- a/scripts/inventory_host.py +++ b/scripts/inventory_host.py @@ -167,6 +167,19 @@ CHAMPS_PROXMOX: tuple[tuple, ...] = ( ("SETOPS_NOEUD", "proxmox_noeud", False), ("SETOPS_COEURS", "proxmox_coeurs", False), ("SETOPS_MEMOIRE", "proxmox_memoire", False), + # LE PLANCHER : ce que l'hote a le droit de REPRENDRE a cette machine (`balloon`). + # + # OUBLIE ICI LE 2026-09-13, ET C'EST LE TROISIEME MAILLON. Le moteur derivait bien + # `proxmox_memoire_min` dans l'inventaire, le playbook de clonage savait bien poser + # `balloon:` — et entre les deux, cette table ne transmettait rien. Quatorze machines + # allaient naitre sans plancher, donc avec le ballooning DESACTIVE, sans qu'aucune + # etape n'echoue : `proxmox_clone_memoire_min` serait simplement reste indefini et sa + # garde `when:` aurait saute la tache en silence. + # + # C'est le patron d'une liste qui en suit une autre et prend du retard. La regle qui en + # sort : quand une variable traverse trois modules, la troisieme s'ecrit EN MEME TEMPS + # que la premiere, pas quand on s'en sert. + ("SETOPS_MEMOIRE_MIN", "proxmox_memoire_min", False), # Resolveur d'AMORCAGE, pose par cloud-init. Il ne sert qu'une fois : `client_resolveur` # bascule ensuite `/etc/resolv.conf` vers 127.0.0.1. Mais sans lui, la VM nait sans # resolution de noms et `apt` ne peut meme pas installer Unbound — l'amorcage diff --git a/scripts/prouver.py b/scripts/prouver.py index 8bf5cce..add3d93 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -2093,6 +2093,46 @@ def preuve_carte_dit_vrai() -> tuple[bool, str]: f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.") +def preuve_parametres_clone_traversent() -> tuple[bool, str]: + """Tout `SETOPS_*` que le Makefile transmet au clonage a bien une SOURCE. + + LE PLANCHER DE MEMOIRE A TRAVERSE TROIS MODULES ET S'EST PERDU AU TROISIEME + (2026-09-13). `instancier` derivait `proxmox_memoire_min` dans l'inventaire ; le + playbook de clonage savait poser `balloon:` ; entre les deux, `inventory_host.py` ne + transmettait rien. Quatorze machines allaient naitre avec le ballooning DESACTIVE. + + ET RIEN N'AURAIT ECHOUE. `$$SETOPS_MEMOIRE_MIN` non defini vaut la chaine vide ; le + Makefile ne passe alors pas `-e proxmox_clone_memoire_min` ; la garde `when:` de la + tache saute proprement. Trois silences qui s'enchainent et une flotte muette. + + CE QUE CETTE GARDE LIT, ET C'EST DELIBEREMENT MECANIQUE : chaque `$$SETOPS_X` que la + cible `creer-vm` consomme doit figurer dans `CHAMPS_PROXMOX`, la table qui les EMET. + Elle ne juge pas la valeur — elle refuse qu'un maillon manque. + """ + mk = (RACINE / "Makefile").read_text(encoding="utf-8") + ih = (RACINE / "scripts" / "inventory_host.py").read_text(encoding="utf-8") + + # Le bloc de la cible `creer-vm` : de son entete a la ligne vide qui la ferme. + m = re.search(r"^creer-vm:.*?(?=^\S|\Z)", mk, re.S | re.M) + if not m: + return False, "cible `creer-vm` introuvable dans le Makefile — la garde ne lit plus rien." + consommes = set(re.findall(r"\$\$\{?(SETOPS_[A-Z_]+)", m.group(0))) + if not consommes: + return False, "aucun `$SETOPS_*` lu dans `creer-vm` — la garde ne lit plus rien." + + # La table qui les emet, plus les variables posees a la main autour d'elle. + emis = set(re.findall(r'\("(SETOPS_[A-Z_]+)"', ih)) + emis |= set(re.findall(r"^\s*(SETOPS_[A-Z_]+)\s*=", ih, re.M)) + emis |= set(re.findall(r'"(SETOPS_[A-Z_]+)"\s*:', ih)) + + manquants = sorted(consommes - emis) + if manquants: + return False, ("transmis au clonage sans source dans `CHAMPS_PROXMOX` : " + + ", ".join(manquants) + + " — la VM naitra sans, et aucune etape n'echouera.") + return True, f"{len(consommes)} parametre(s) de clonage, tous emis par l'inventaire." + + def preuve_gabarit_minimal_et_repris() -> tuple[bool, str]: """Le gabarit ne porte que l'indispensable, et rien de retire n'est perdu. @@ -3730,6 +3770,8 @@ PREUVES: list[dict] = [ "refs": [], "func": preuve_locataire_suit_son_site}, {"id": "P74", "titre": "Gabarit dore : une seule declaration, au plan du site", "refs": [], "func": preuve_gabarit_une_seule_declaration}, + {"id": "P75", "titre": "Les parametres de clonage traversent les trois maillons", + "refs": [], "func": preuve_parametres_clone_traversent}, ] diff --git a/scripts/tests/test_inventory_host.py b/scripts/tests/test_inventory_host.py index ee34bf6..0cc6329 100644 --- a/scripts/tests/test_inventory_host.py +++ b/scripts/tests/test_inventory_host.py @@ -98,6 +98,10 @@ def test_hote_complet_avec_noeud() -> None: "SETOPS_NOEUD='noeud-a'", "SETOPS_COEURS='4'", "SETOPS_MEMOIRE='2048'", + # Ajoute le 2026-09-13 : le PLANCHER de memoire (`balloon`), ce que l'hote a + # le droit de reprendre. Vide ici — la fixture ne le declare pas — et c'est le + # point : ce test epingle le CONTRAT, pas les valeurs. Il a signale l'ajout. + "SETOPS_MEMOIRE_MIN=''", "SETOPS_DNS=''", # Ajoute le 2026-08-09 : le domaine de recherche cloud-init se derive de # `domaine_interne`, comme le DNS. Ce test epingle le CONTRAT de