Le controle de dependances a REFUSE le deploiement de patient 0 : client_journal requiert
serveur_loki, client_metrique requiert serveur_prometheus, serveur_forgejo requiert
serveur_postgresql et serveur_postfix. Quatre refus, une seule racine — le moteur suppose
que tout ecosysteme porte tous les services. Apres les intrants (P32) et les bases (P35),
troisieme manifestation en cinq jours. Et le mur que rencontrerait toute offre plus petite
que l'ecosysteme de reference.
UNE INTEGRATION UNIVERSELLE A BESOIN D'UN INTERLOCUTEUR. « Tout hote est mesure » est vrai
dans un ecosysteme qui porte un Prometheus ; ailleurs, la meme phrase pose sur chaque
machine un client qui n'a personne a qui parler. La regle est desormais DERIVEE : le
service central d'une integration est celui que le registre des dependances lui donne
deja. Rien de neuf a tenir a jour, donc rien de neuf a oublier.
Chezlepro -> diff VIDE (tous ses services existent, rien ne change)
patient 0 -> client_backup, client_pki, client_unbound
UNE EXIGENCE N'EST PAS TOUJOURS ABSOLUE. Deux notions manquaient au registre :
`sauf_si` l'exigence tombe sous condition (Forgejo + SQLite)
`utilise_si_present` un agrement, jamais bloquant (Forgejo notifie SI un MTA existe)
Les confondre obligeait une forge a deployer une pile courriel entiere pour exister.
ET LA LECON D'HIER A SERVI : trois lecteurs avaient besoin le meme jour de lire une
variable d'instance (P35, les clauses sauf_si, le generateur). Trois copies auraient
recommence ce qu'on venait de refermer. Il y en a UNE, dans inventory_rules.
make verifier 41/41 ; make ci 41/41 ; inventaire de Chezlepro identique.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cinq jours, cinq defauts, tous de la meme famille : « quelle instance, quel inventaire ? »
Neuf modules portaient chacun leur reponse.
- 18 aout : P03 comparait chaque instance a l'inventaire d'une AUTRE ;
- 19 aout : verifier_ports codait `principal/` en dur ; verifier_intrants et
_frontiere_absente lisaient le symlink au lieu de la variable ;
- 20 aout : devis_placement rendait un verdict juste sur le mauvais tenant ;
- 22 aout : P35, puis P36 — la dixieme, trouvee par la preuve elle-meme.
Aucune n'etait une faute d'inattention : chacune avait ete ecrite de bonne foi, a un
moment ou le besoin semblait local. C'est le mode de panne de la duplication — pas
l'erreur, mais la DERIVE, invisible depuis l'interieur d'un fichier.
LA RESOLUTION UNIQUE. `inventory_rules` porte instance_courante(), inventaire_de(),
dossier_inventaire() et plan_de(). Trois niveaux de repli, dont le TROISIEME manquait a la
moitie des copies : un hosts.yml existant, puis un REPERTOIRE existant (instance neuve —
c'est ce qui faisait echouer `make instancier` sur le modele public), puis le defaut.
Vingt-huit modules y sont branches.
CE QUI REND CE REFACTOR SUR : avant de toucher quoi que ce soit, chaque module a ete
interroge sur ce qu'il resolvait, pour les DEUX ecosystemes. Apres refactor, meme mesure :
17 modules x 2 instances, diff VIDE. Aucune resolution n'a change — prouve, pas suppose.
P41 echoue des qu'un module reintroduit une copie. Eprouvee en negatif : une copie
replacee dans genome.py est signalee avec son numero de ligne. Trois exemptions nommees :
instances.py et inventory_gui.py manipulent le SYMLINK lui-meme (bascule d'instance), et
devis_opnsense lit deliberement quelle instance est ACTIVE. Elles parlent du lien, pas de
la resolution.
make verifier 41 OK, 0 echec, 0 saute ; make ci idem ; lint vert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Trouve en validant une mise a jour du CHANGELOG. Deux invocations de la meme preuve,
deux verdicts : `make prouver` -> NON CONFORME (« lab : 17 hotes avec ecart »),
`python3 scripts/prouver.py` -> CONFORME 37/37. Le lab n'avait aucun ecart.
DEUX VARIABLES DESIGNENT LA CIBLE, ET LA SECONDE GAGNE. Le Makefile exporte
SETOPS_INVENTAIRE (ligne 13), derive de l'instance ACTIVE ; instancier.py:68 lui fait
FORCER la cible par-dessus SETOPS_INSTANCE. P03 (prouver.py:505) ne redirigeait que
SETOPS_INSTANCE : elle generait le plan de CHAQUE instance federee et le comparait a
l'inventaire applique de la SEULE instance active.
LE ROUGE N'ETAIT PAS LE PROBLEME, LE VERT L'ETAIT. Sous `make`, l'inventaire applique
de lab et de Technolibre n'etait JAMAIS lu — l'angle meme pour lequel P03 a ete ecrite
le 2026-08-12 (un tenant qu'on ne regarde pas imposant ses vieilles adresses au pare-feu
partage). La preuve etait aveugle a son propre cas, par l'invocation documentee. Les
rapports du 13 et du 14 sortent de cette invocation-la. Signature visible sans lire le
code : les hosts.genere.yml de lab et de Technolibre ne bougeaient pas.
CORRECTIF, cinq sites : env.pop("SETOPS_INVENTAIRE") partout ou l'on redirige
SETOPS_INSTANCE — P03 et P15, plus les trois applicateurs (opnsense, proxmox_fw, sdn) qui
pointent vers l'HEBERGEUR. Ces trois sont sans effet tant qu'hebergeur et tenant actif
coincident, c'est-a-dire jusqu'au second site. Le geste existait deja (modeles.py:96).
GARDE, pour que la classe cesse d'etre silencieuse : inventory_rules.inventaire_force()
REFUSE une cible hors de l'instance visee, en nommant les deux valeurs. Eprouvee dans les
deux sens (contradiction -> code 1 ; cible legitime dans l'instance -> passe). Branchee
sur les quatre resolutions de _inventaire (instancier, serveurs, applications,
config_proxmox). Le GUI garde la sienne : il ne redirige jamais SETOPS_INSTANCE pour un
fils et resout par symlink a chaque requete.
make prouver : 37 OK, 0 echec, 0 saute — et les hosts.genere.yml des TROIS instances
portent l'horodatage du passage. make test 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
instancier.py comparer affichait l'ecart puis renvoyait TOUJOURS 0. P03
« Diff-vide du plan » ne pouvait donc pas echouer : elle est passee au vert
pendant que quatorze hotes divergeaient du plan.
Deux appelants, deux besoins — d'ou un MODE plutot qu'un changement de
comportement : `make instancier` est une INSPECTION (voir le diff avant de
decider ; un code d'erreur y transformerait la lecture en panne), P03 est une
AFFIRMATION (« le plan reproduit l'inventaire »).
prouver sort desormais a 1, et c'est EXACT : depuis le retrait du decalage de
+10, le plan derive 10.17.x.x tandis que l'inventaire applique — et les quatorze
VM qui tournent — portent 10.27.x.x. Le depot est sciemment dans cet etat
jusqu'au renumerotage.
Une preuve rouge qui dit vrai vaut mieux qu'une verte qui ne regarde rien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keycloak ne demarrait pas : pg_hba n'autorisait que 10.11.0.0/16 pour un tenant
en 10.27.0.0/16. La valeur etait figee dans group_vars, sous un commentaire
« AJUSTER au sous-reseau reel » que personne n'a suivi.
Postfix portait la meme valeur perimee et aurait echoue plus tard sur le
courriel. Technolibre aussi (10.12.0.0/16 pour un tenant en 10.21.0.0/16).
Quatre fichiers, une seule faute, repetee parce que recopiee.
`setops_supernet` se derive comme le VMID et l'adresse ; les quatre fichiers le
consomment. Chezlepro -> 10.27.0.0/16, Technolibre -> 10.21.0.0/16, sans saisie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le pont n'était pas seulement non portable, il était faux. proxmox_clone_pont
faisait naître les VM sur vmbr1 avec une étiquette VLAN — l'ancien monde. En SDN
une VM appartient à son VNet ; c'est ce qu'il a fallu corriger à la main sur
infra-pki-01, et les treize suivantes auraient suivi.
deriver_nomenclature() expose désormais la zone de sécurité, instancier en dérive
proxmox_pont et une étiquette VIDE — le VNet porte déjà le tag, en poser un
second donnerait un double étiquetage. La chaîne va jusqu'à make creer-vm :
SETOPS_PONT='t11appl', SETOPS_VLAN=''.
Trois pièges. Un doublon dans le Makefile passait PONT_PROXMOX deux fois dans la
même cible, la seconde vide aurait écrasé la valeur dérivée. Un repli naïf sur
proxmox_vlan aurait fait revenir l'étiquette en SDN : le repli ne s'applique que
si la clé est ABSENTE, jamais si elle est présente et vide. Et le test unitaire
est tombé, à raison — il couvre maintenant cette distinction.
D-55 : le dépôt réseau porte le contrat entre l'Alliance et ses hébergeurs, et
abstrait le matériel en encapsulant chaque tenant dans sa zone EVPN. Mesuré : un
tenant est à deux valeurs de la portabilité complète (noeud, stockage).
D-57 : l'interface sysadmin d'un hyperviseur (vmbr0, 10.0.0.41/.43/.47) n'a pas
de route par défaut ; celle-ci vit sur vlan40, vers la frontière. On n'atteint
l'administration que depuis son propre domaine de diffusion. Ça tranche la
question de la sortie des nœuds laissée ouverte ce matin — option A, mais sur une
interface dédiée, ce qui lève l'objection qui la bloquait.
D-58 : un hôte déclare par quelle interface (`via`) chaque réseau lui arrive ; le
devis en dérive un port par interface et son type — trunk 11,40 sur bond3, accès
VLAN 10 sur vmbr0. Sans ça, ajouter le VLAN 10 le remettait sur le trunk du
transport, soit le domaine qu'on venait d'en sortir.
D-59 : un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont.
Régression créée puis corrigée : le modèle public, qui ne déclare aucun
hyperviseur, n'émettait plus rien pour ce port. Il émet maintenant tout
l'underlay en disant que c'est un repli.
30 preuves OK, 4 tests unitaires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants.
1. Intégrations universelles (D-33/D-34, P26)
Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à
quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être
oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01
n'étaient ni supervisés, ni journalisés, ni certifiés.
Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le
plan ne garde que les vrais choix et refuse la recopie. Les exemptions se
dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle
pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace.
Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la
voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations
universelles auraient cessé d'être exigés et P18 serait passé au vert sur une
voûte incomplète.
Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41
lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas
client_pki sur infra-pki-01.
2. Vue Intégrations : la matrice
La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas
été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x
intégrations : colonnes de politique en lecture seule, facultatives cochables sur
place, ligne de couverture n/N qui rend le motif visible sans le juger.
3. Propriété des intrants (D-35/D-36, P27)
Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière.
Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de
stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont
dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive —
l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et
ses défauts de placement.
Le panneau nomme désormais le propriétaire de chaque section : éditer une section
« hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas.
26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit
a cote du moteur echappait au controle. Trois preuves ferment ces angles morts :
- P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle.
SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve
6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles).
- P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets
que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie
voute : compare des noms.
- P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable
par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu).
GUI :
- champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes
deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml).
Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main.
- champ « WebSocket » (Collabora).
- CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19.
Garde-fou de fond : valider_applications refuse une application posee sur un hote non
declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI.
liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le
validateur, le GUI et instancier.py (dont la copie locale est retiree).
Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident,
DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le dépôt fait / explique / prouve ce qu'il affirme, vérifiable en une commande.
- Phase 1 : docs/audit/affirmations.md — 54 affirmations publiques tracées vers
une commande de preuve et un statut (✅/🟡/❌/⚪).
- Phase 2 : CLAUDE.md réduit à un pointeur mince ; contradiction SSH levée (le
code applique déjà PasswordAuthentication no + AuthenticationMethods publickey,
conforme à AGENTS.md) ; section AGENTS « Codex » → « agents IA ».
- Phase 3 : parcours démarrage réparé (QUICKSTART renvoyait à un modèle absent,
chemins de voûte faux, commandes make périmées) ; make verifier vert
(ansible-lint 33 → 0 : site.yml généré nommé, pipefail, name[template]) ;
voûte Proxmox unifiée lue par le clonage (all/vault.yml).
- Phase 4 : make prouver → docs/audit/preuve-<date>.md, harnais rejouable qui
rappelle l'outillage existant (aucune validation réimplémentée).
- Phase 5 : parcours QUICKSTART prouvé hors-ligne sur le socle ; modèle socle
rendu valide (autorite interne → auto-heberge) ; split-brain d'inventaire
corrigé (repli sur le répertoire existant, pas principal/).
make prouver : 15 OK, 0 échec, 1 sautée (voûte). ansible-lint : 0 failure.
Écarts découverts en cours de traitement (AFF-097..100) : tous résolus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Élimine 3 gotchas du spike Nextcloud/Collabora :
- instancier dérive sans_exposition par edge (FQDN exposés du plan) → le cert edge
(client_pki_sans) se met à jour tout seul, plus de liste manuelle en group_vars ;
- champ 'websocket' sur une application → le vhost d'exposition ajoute la map
$http_upgrade + en-têtes Upgrade/Connection + timeout long (Collabora, éditeurs live) ;
- (plancher /etc/hosts : le mécanisme d'alias existait déjà, publier_expositions=true).
Prouvé sur le lab : cert edge couvre les 6 expositions, bureau 200 en WebSocket.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une application déclare ses liens ([{vers, role}]) dans applications.yml ;
chaque rôle décrit les liens acceptés dans meta/liens.yml. instancier
résout la cible (FQDN interne dérivé de la nomenclature) et injecte les
variables en host_vars du consommateur.
Migration prouvée : les liens mail Postfix→Dovecot (mailstore) et
Postfix→rspamd (milter) passent de group_vars codés en dur à des liens
déclaratifs — make instancier donne DIFF VIDE (mêmes vars générées).
Cf. docs/bindings-conception.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une voûte chiffrée group_vars/all/vault.yml fait échouer
ansible-inventory --list (comparaison du plan) sans mot de passe →
« Appliquer le plan » cassé dès qu'une voûte existe. instancier utilise
maintenant ~/.config/setops-vault-pass si ANSIBLE_VAULT_PASSWORD_FILE
n'est pas déjà fourni. Validé : plus d'exit 4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le moteur ne code plus en dur inventories/lab|production : un inventaire
par instance, détecté de façon rétro-compatible (principal > production >
lab) et surchargeable par SETOPS_INVENTAIRE. Makefile (définitions seules,
pas les 47 usages), inventory_gui, instancier, config_proxmox, serveurs,
applications. Les instances lab/production existantes marchent à
l'identique ; les nouvelles adoptent inventories/principal/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les cœurs/RAM/disque d'une VM sont estimés depuis l'empreinte des rôles
hébergés (roles/<rôle>/meta/empreinte.yml) sommée au socle SE, au lieu
d'hériter des specs du golden template. Le générateur écrit
proxmox_coeurs/memoire/disque_taille ; le clonage les passe à Proxmox
(omit si absent → aucune régression). Override par hôte dans le plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>