instancier : refuser une machine dont la fonction n'est pas declaree
deriver_nomenclature(...) or {} avalait l'echec : une fonction absente rendait
un dictionnaire vide, et la machine entrait dans l'inventaire avec
ansible_host: None. La generation se declarait reussie ; la panne serait
apparue au deploiement, sous une forme incomprehensible.
Revele en portant serveur_ops dans les modeles : presence-web range son socle
en zone 1 et n'a pas de categorie 4. Le defaut n'est pas apparu en ecrivant le
role ni en le deployant chez patient 0 -- il a fallu le porter ailleurs.
serveur_ops ne nomme plus patient 0 dans ses defauts : un ecosysteme distrait
aurait clone le genome d'un autre, en silence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
32e1f6fbcd
commit
e6c5f9f539
5 changed files with 185 additions and 5 deletions
54
CHANGELOG.md
54
CHANGELOG.md
|
|
@ -1,5 +1,59 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 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
|
||||
écosystème livré n'aurait su se relire lui-même** : il aurait détenu son génome en
|
||||
dépendant, pour l'exécuter, de la machine de quelqu'un d'autre. Les six modèles déclarent
|
||||
désormais la fonction `ops`, la machine `ops-01` et son application.
|
||||
|
||||
### Un défaut du rôle, corrigé avant qu'il ne serve
|
||||
|
||||
Les valeurs par défaut de `serveur_ops` nommaient **patient 0** :
|
||||
|
||||
```yaml
|
||||
serveur_ops_depots:
|
||||
- { depot: "ops-patient0", dest: "OPS-Patient0", role: "instance" }
|
||||
```
|
||||
|
||||
Tout écosystème déployant un poste sans déclarer ses propres dépôts aurait donc cloné le
|
||||
génome de patient 0 — silencieusement, en croyant piloter le sien. Le défaut ne nomme plus
|
||||
que le moteur, qui est le même pour tous ; le dépôt d'instance **doit** être déclaré, et
|
||||
la tâche d'exigence refuse de poursuivre sans lui.
|
||||
|
||||
### Le silence que `presence-web` a révélé
|
||||
|
||||
Ce modèle range tout son socle en zone 1 (« Fondations ») et n'a pas de catégorie 4. La
|
||||
machine `ops-01` y a donc été posée sans que sa fonction existe — et le moteur a produit
|
||||
un inventaire **en se déclarant réussi** :
|
||||
|
||||
```
|
||||
ops-01 -> adresse=None vlan=None
|
||||
```
|
||||
|
||||
`deriver_nomenclature(...) or {}` avalait l'échec : une fonction absente rendait un
|
||||
dictionnaire vide, et la machine entrait dans l'inventaire sans adresse. La panne ne
|
||||
serait apparue qu'au déploiement, sous une forme incompréhensible — Ansible tentant de
|
||||
joindre une adresse qui n'existe pas.
|
||||
|
||||
`instancier.py` refuse maintenant, en bloc et en nommant ce qu'il faut corriger :
|
||||
|
||||
```
|
||||
Machines sans adresse derivable — leur `fonction` n'est pas declaree :
|
||||
- ops-01 : fonction « ops »
|
||||
Fonctions connues de ce plan : data, infra-dns, infra-edge, infra-mail, infra-pki, …
|
||||
```
|
||||
|
||||
Contrôle négatif fait, puis contrôle positif sur les six modèles.
|
||||
|
||||
### Ce que ça dit de la méthode
|
||||
|
||||
Le défaut ne s'est pas montré pendant qu'on écrivait le rôle, ni pendant qu'on le
|
||||
déployait chez patient 0 — où la catégorie 4 existe. Il a fallu **porter la pièce dans un
|
||||
contexte différent** pour qu'il apparaisse. C'est la même leçon que Technolibre avait
|
||||
donnée en révélant six défauts moteur invisibles avec un seul écosystème : un moteur ne
|
||||
se prouve qu'au pluriel.
|
||||
|
||||
## 2026-08-23 — La source d'artefacts : la forge sert le code, il manquait qui sert les binaires
|
||||
|
||||
Pour poser une seule machine, un écosystème allait chercher chez **six serveurs
|
||||
|
|
|
|||
78
docs/audit/preuve-2026-08-24.md
Normal file
78
docs/audit/preuve-2026-08-24.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Preuve de conformite — Set-OPS — 2026-08-24
|
||||
|
||||
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (42 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | [0m[0m |
|
||||
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
|
||||
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 33 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 31 rôles, 82 flux, schéma + matrice OK. |
|
||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
| 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). |
|
||||
| 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. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||
| 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. |
|
||||
| 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). |
|
||||
| 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. |
|
||||
| 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)). |
|
||||
| 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. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 32 role(s) serveur/client tous nommes, 33 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 43 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||
|
||||
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||
|
||||
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||
comme declarations d'intention, non comme preuves :
|
||||
|
||||
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||
contre une flotte vivante.
|
||||
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||
|
||||
_Rapport genere le 2026-08-24._
|
||||
|
|
@ -51,9 +51,19 @@ serveur_ops_forge_organisation: "genome"
|
|||
# (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
|
||||
# `scripts/instances.py` découvre.
|
||||
#
|
||||
# AUCUN DÉFAUT NE NOMME UN ÉCOSYSTÈME PARTICULIER (2026-08-24).
|
||||
#
|
||||
# La première version listait ici `ops-patient0`. Tout écosystème qui aurait déployé un
|
||||
# poste sans déclarer ses propres dépôts aurait donc cloné le génome de **patient 0** —
|
||||
# silencieusement, et en croyant piloter le sien. C'est la faute que ce dépôt combat sous
|
||||
# tous ses déguisements : un défaut plausible qui rend un résultat faux sans rien dire.
|
||||
#
|
||||
# Le moteur est le seul défaut légitime : il est le même pour tous. Le dépôt d'instance,
|
||||
# lui, est propre à l'écosystème et DOIT être déclaré — la tâche d'exigence refuse de
|
||||
# poursuivre sans lui.
|
||||
serveur_ops_depots:
|
||||
- { depot: "set-ops-public", dest: "Set-OPS-public", role: "moteur" }
|
||||
- { depot: "ops-patient0", dest: "OPS-Patient0", role: "instance" }
|
||||
|
||||
# --- LES DEUX SYMLINKS (D-80) ------------------------------------------------
|
||||
#
|
||||
|
|
@ -68,7 +78,10 @@ serveur_ops_depots:
|
|||
# CE QUE LE POSTE N'AURA PAS POUR AUTANT : `underlay.vault.yml`, qui porte les secrets du
|
||||
# monde physique. Il est hors dépôt (gitignore), donc absent du génome. Le poste lit la
|
||||
# CARTE de la fabric, pas ses clés.
|
||||
serveur_ops_instance: "OPS-Patient0"
|
||||
|
||||
# Le dossier de l'instance pilotée, cible du lien `instance`. Sans valeur, le rôle
|
||||
# refuse : un poste qui ne sait pas quel tenant il pilote n'a rien à piloter.
|
||||
serveur_ops_instance: ""
|
||||
# Vide = le poste ne pose pas de lien `underlay.yml` (il configure, il n'engendre pas).
|
||||
serveur_ops_underlay: ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
---
|
||||
# UN POSTE QUI NE SAIT PAS QUEL ÉCOSYSTÈME IL PILOTE N'A RIEN À PILOTER.
|
||||
#
|
||||
# On exige explicitement un dépôt d'instance, plutôt que de retomber sur un défaut : le
|
||||
# défaut nommait patient 0, et un écosystème distrait aurait cloné le génome d'un autre
|
||||
# sans qu'aucune erreur ne le signale.
|
||||
- name: Exiger les intrants du poste d'exploitation
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- domaine_interne | default('') | length > 0
|
||||
- serveur_ops_depots | length > 0
|
||||
- (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
|
||||
fail_msg: >-
|
||||
serveur_ops exige `domaine_interne` et au moins un dépôt dans
|
||||
`serveur_ops_depots` (le moteur au minimum).
|
||||
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.
|
||||
|
||||
- name: Installer l'outillage du poste
|
||||
ansible.builtin.apt:
|
||||
|
|
|
|||
|
|
@ -172,6 +172,33 @@ def generer() -> dict:
|
|||
sdn = underlay_mod.routage_tenants(underlay_mod.charger()) == "sdn"
|
||||
index_tenant = nomenclature.get("index")
|
||||
|
||||
# UNE MACHINE SANS FONCTION DECLAREE N'A PAS D'ADRESSE — ET LE DISAIT PAS (2026-08-24).
|
||||
#
|
||||
# `deriver_nomenclature(...) or {}` avale l'echec : une fonction absente de
|
||||
# `nomenclature.fonctions` rend un dictionnaire vide, et la machine entre dans
|
||||
# l'inventaire avec `ansible_host: None` et `vlan: None`. La generation se declare
|
||||
# REUSSIE. La panne n'apparait qu'au deploiement, sous une forme incomprehensible —
|
||||
# Ansible tentant de joindre une adresse qui n'existe pas.
|
||||
#
|
||||
# Constate en ajoutant `ops-01` aux modeles : `presence-web` range son socle en zone 1
|
||||
# (« Fondations ») et n'a pas de categorie 4. La machine a ete posee, la fonction non,
|
||||
# et rien ne l'a signale.
|
||||
#
|
||||
# On refuse en BLOC plutot qu'a la premiere fautive : l'exploitant corrige son plan
|
||||
# une fois, pas une machine a la fois.
|
||||
sans_fonction = [
|
||||
nom for nom, srv in serveurs.items()
|
||||
if not (deriver_nomenclature(str(srv.get("fonction", "")),
|
||||
fonction_seq(nom)[1], nomenclature) or {}).get("adresse_ip")
|
||||
]
|
||||
if sans_fonction:
|
||||
connues = ", ".join(sorted((nomenclature.get("fonctions") or {}))) or "(aucune)"
|
||||
raise SystemExit(
|
||||
"Machines sans adresse derivable — leur `fonction` n'est pas declaree dans "
|
||||
"`nomenclature.fonctions` :\n"
|
||||
+ "".join(f" - {n} : fonction « {serveurs[n].get('fonction', '')} »\n" for n in sans_fonction)
|
||||
+ f"Fonctions connues de ce plan : {connues}")
|
||||
|
||||
children: dict = {
|
||||
"modeles_vm": {"hosts": {}},
|
||||
"hotes_actifs": {"hosts": {}},
|
||||
|
|
|
|||
Loading…
Reference in a new issue