gabarit dore : une seule declaration, et les deux souches se rejoignent

Le plan du site disait 9006 (modeleSetOPS-minimal), l underlay 99998 — que le plan
nomme lui-meme `precedent`. Le Makefile derive VMID_MODELE du PLAN : les locataires
clonaient 9006. `site_machines` lisait l autre : les machines du SITE clonaient 99998.

POURQUOI CA NE S EST JAMAIS VU. Les deux VMID designent un gabarit valide. Les deux
clonent, les deux demarrent, rien n echoue. La flotte etait issue de deux souches, et
aucun message ne pouvait le dire puisqu aucune operation n avait echoue.

`site_machines` lit desormais underlay.gabarit(), la meme source que tout le monde. Le
motif de la seconde declaration est preserve : cette source lit le PLAN DU SITE, jamais
celui du tenant actif, donc elle ne depend d aucun symlink `instance`.

materialisation.vmid_modele retire des deux cartes. `precedent:` reste au plan — il dit
d ou l on vient et n est jamais clone.

P74 refuse toute redeclaration. Eprouvee dans les deux sens.

SITE-Technolibre visait 99998, repris de la carte de Chezlepro au lieu de son plan.
Corrige avant son premier clonage.

make prouver : 73 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-13 17:54:56 -04:00
parent ab0e74d843
commit 94bfa224ff
6 changed files with 104 additions and 12 deletions

View file

@ -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 73 preuves de `make prouver` : elles lisent le dépôt, sans le
C'est aussi ce que font les 74 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.**

View file

@ -1,5 +1,36 @@
# CHANGELOG — Set-OPS
## 2026-09-13 (5) — Le gabarit dore etait declare deux fois, et les deux clonaient
`plan/10-intrants.yml` disait **9006** (`modeleSetOPS-minimal`). `underlay.yml` disait
**99998** — que le plan nomme lui-meme `precedent`.
Le Makefile derive `VMID_MODELE` de `underlay.gabarit()`, donc du PLAN : **les locataires
clonaient 9006**. `site_machines` lisait l'autre : **les machines du site clonaient 99998**.
### Pourquoi ca ne s'est jamais vu
Les deux VMID designent un gabarit valide. Les deux clonent, les deux demarrent, rien
n'echoue. La flotte etait simplement issue de **deux souches** — et aucun message ne
pouvait le dire, puisqu'aucune operation n'avait echoue.
### Ce qui a ete fait
`site_machines` lit desormais `underlay.gabarit()`, la meme source que tout le monde. Le
motif d'origine de la seconde declaration est preserve : cette source lit le PLAN DU SITE,
jamais celui du tenant actif — elle ne depend d'aucun symlink `instance`.
`materialisation.vmid_modele` est retire des deux cartes. `precedent:` reste au plan : il
dit d'ou l'on vient, et il n'est jamais clone.
**`P74`** refuse toute redeclaration. Eprouvee dans les deux sens.
### Au passage
`SITE-Technolibre` visait 99998 — l'ancien — parce que sa valeur avait ete reprise de la
carte de Chezlepro plutot que de son plan. Corrige avant son premier clonage.
## 2026-09-13 (4) — La cle USB ne savait pas relire ce qu'on venait d'y ecrire
`exporter_cles --support-chiffre` ecrit un TAR CLAIR quand le support est deja chiffre au

View file

@ -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 (72 OK · 0 echec · 1 saute)
- **Verdict** : ✅ CONFORME (73 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 14. |
| 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 (73 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 (74 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). |
@ -86,6 +86,7 @@
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| 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. |
## Couverture des affirmations ✅ du registre

View file

@ -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 73 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
`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
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.

View file

@ -3314,6 +3314,49 @@ def preuve_pool_du_site_nest_pas_celui_du_tenant() -> tuple[bool, str]:
def preuve_gabarit_une_seule_declaration() -> tuple[bool, str]:
"""Le gabarit dore est-il declare a UN SEUL endroit ?
C'est de lui que descend chaque VM de chaque ecosysteme. Il vivait a deux endroits :
`plan/10-intrants.yml` (`gabarit:`) et `underlay.yml`
(`materialisation.vmid_modele`). Les deux ont diverge — le plan disait 9006, l'underlay
99998, que le plan nomme justement `precedent`.
CE QUE LA DIVERGENCE PRODUISAIT, et pourquoi elle etait invisible : les deux VMID
designent un gabarit VALIDE, donc les deux clonent. Les machines du SITE naissaient de
l'ancien, celles des LOCATAIRES du nouveau — le Makefile derivait deja du plan. Aucune
erreur, aucun message : deux flottes issues de deux souches.
Le champ `precedent:` est ADMIS et meme utile — il dit d'ou l'on vient. Ce qui est
refuse, c'est une seconde declaration de ce qu'il faut CLONER.
"""
import yaml as _y
try:
sys.path.insert(0, str(RACINE / "scripts"))
import underlay as _u
c = _u.chemin()
if not c:
return True, "Aucun site monte : aucun gabarit a declarer."
carte = _y.safe_load(c.read_text(encoding="utf-8")) or {}
except Exception as e:
return True, f"Carte illisible ({type(e).__name__}) : rien a comparer."
mat = ((carte.get("underlay") or {}).get("materialisation") or {})
if "vmid_modele" in mat:
return False, ("`underlay.yml` redeclare `materialisation.vmid_modele` — le gabarit "
"se declare au PLAN du site (`gabarit.vmid`), et nulle part ailleurs. "
"Deux declarations divergent, et les deux clonent : la flotte se "
"retrouve issue de deux souches sans qu'aucun message ne le dise.")
g = _u.gabarit()
if not g.get("vmid"):
return False, ("Aucun `gabarit.vmid` au plan du site — chaque VM en descend, et il "
"ne peut pas etre decide par un tenant.")
p = f", precedent {g['precedent']}" if g.get("precedent") else ""
return True, (f"Gabarit declare une seule fois : VMID {g['vmid']} "
f"« {g.get('nom', '?')} »{p}.")
def preuve_locataire_suit_son_site() -> tuple[bool, str]:
"""Le locataire monte designe-t-il encore les services de son site REEL ?
@ -3685,6 +3728,8 @@ PREUVES: list[dict] = [
"refs": [], "func": preuve_annuaire_sans_compte_maitre},
{"id": "P73", "titre": "Le locataire designe les services de son site REEL",
"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},
]

View file

@ -47,17 +47,32 @@ def _taille(v) -> str:
def materialisation() -> dict:
"""Gabarit, stockage et mode de clonage — DECLARES par le site.
"""Gabarit, stockage et mode de clonage — le gabarit vient du PLAN du site.
Sans ce bloc, le playbook de clonage prenait ces valeurs dans les group_vars du
TENANT ACTIF : `make site-creer` aurait clone depuis un gabarit different selon le
symlink `instance`, sans rien dire. Un accident silencieux vaut moins qu'un refus.
Sans declaration du site, le playbook de clonage prenait ces valeurs dans les
group_vars du TENANT ACTIF : `make site-creer` aurait clone depuis un gabarit
different selon le symlink `instance`, sans rien dire.
LE REMEDE AVAIT CREE UNE SECONDE DECLARATION (corrige le 2026-09-13). Le VMID vivait
a la fois dans `plan/10-intrants.yml` (`gabarit:`) et dans `underlay.yml`
(`materialisation.vmid_modele`). Les deux ont diverge : le plan disait 9006, l'underlay
99998 — que le plan nomme justement `precedent`.
Consequence, invisible parce que les deux gabarits clonent : les MACHINES DU SITE
naissaient de l'ancien, les machines des LOCATAIRES du nouveau. Le Makefile, lui,
derivait deja `VMID_MODELE` de `underlay.gabarit()`, c'est-a-dire du plan.
On lit donc la meme source que tout le monde. Le motif d'origine est preserve :
`underlay.gabarit()` lit le plan DU SITE, jamais celui du tenant actif — il ne depend
d'aucun symlink `instance`.
"""
m = (U.charger() or {}).get("materialisation") or {}
if not m.get("vmid_modele"):
m = dict((U.charger() or {}).get("materialisation") or {})
g = U.gabarit()
if not g.get("vmid"):
raise SystemExit(
"underlay.yml : `materialisation.vmid_modele` est requis — sans lui, le site "
"clonerait le gabarit du tenant actif, ce qui depend du symlink `instance`.")
"plan/10-intrants.yml : `gabarit.vmid` est requis — c'est de lui que descend "
"chaque VM du site, et il ne peut pas etre decide par un tenant.")
m["vmid_modele"] = g["vmid"]
return m