Commit graph

437 commits

Author SHA1 Message Date
53d7b4c4c2 durcissement : cloud-init nait avec la VM et ne lui survit pas
cloud-init n est pas un logiciel d installation : c est une SOURCE DE
VERITE EXTERNE. Il se reveille a chaque demarrage et relit le lecteur
attache par l hyperviseur, qui peut redefinir comptes, cles SSH, mots de
passe et reseau. Sur une machine que le plan possede, c est un second
maitre — que le plan ne decrit pas, que make valider ne mesure pas, et
qui parle en premier.

Sa tache est finie a la premiere seconde : c est parce qu il a REUSSI a
poser l adresse et les cles qu Ansible a pu entrer.

TROIS MOITIES, ET ELLES SE DEFONT SEPAREMENT.
 - le GABARIT le garde : sans lui un clone n a ni adresse ni nom ;
 - le SOCLE ne l installe plus : le garder produisait un va-et-vient a
   chaque deploiement, deux changed par passage, idempotence perdue ;
 - le DURCISSEMENT le retire (roles/cloud_init_retrait, en dernier).
P63 garde les trois, plus le CONTENU du role : une coquille vide passerait
les trois premiers controles sans rien fermer. Quatre controles negatifs
rejoues.

CE QUI REND LE RETRAIT SUR EST MESURE, PAS SUPPOSE (obs-01, 2026-09-09) :
/etc/network/interfaces.d/50-cloud-init n appartient a aucun paquet — dpkg
-S ne le trouve pas — et le postrm ne le nomme jamais, meme en purge.
L adresse survit. Le role le verifie quand meme, avant et apres, et n
accuse que si le retrait l a emporte : une VM qui perd ce fichier ne se
plaint pas, elle repart sans adresse et plus personne ne peut entrer.

DEUX CHOIX DITS FRANCHEMENT. cloud-guest-utils reste (growpart : ni
service, ni port, ni source de donnees). Les ~29 paquets orphelins ne sont
pas retires par defaut : autoremove deciderait a partir des drapeaux dpkg,
et un durcissement ne doit pas pouvoir surprendre.

NON DEPLOYE : le code est ecrit, valide et prouve ; il n a pas ete
applique a la flotte. Essai a blanc sur obs-01 : cloud-init a retirer,
configuration reseau intacte.

make prouver : CONFORME, 62 OK, 0 echec, 1 saute (63 preuves).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 09:25:42 -04:00
2887b57f0c GUI : les six registres ont un formulaire genere, et la sauvegarde aussi
CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus
gros, sont passes au generateur — chargement, rendu et sauvegarde.

L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher
doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25
applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe
un formulaire genere d un formulaire qui en a l air — un champ visible a
l ecran et perdu en silence a l enregistrement serait le pire des deux
mondes. test_rendu_gui.py le mesure a chaque make prouver.

TROIS DEFAUTS TROUVES EN CHEMIN.

Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il
n existe aucun defaut fixe — deriver_ressources calcule depuis les roles
portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere
faux fait croire qu on connait la valeur. Le schema nomme le champ derive,
et l ecran montre la valeur reelle de cet hote. L option vide d un select
dit desormais ce qu elle produira : « (defaut : asgard) ».

Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs :
elle lisait encore data.nomenclature. Elle n avait jamais leve parce que
la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit
a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne
voit que les chemins vivants : verifier_gui.py fait donc aussi une
verification STATIQUE, qui voit ce qui dort.

La validation client s accrochait a data-v, pose a la main sur trois
champs. Le formulaire genere l aurait perdu et la validation serait passee
au vert sur ZERO champ. Le generateur marque chaque controle, et la
sauvegarde refuse si elle n en inspecte aucun.

DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la
matrice des integrations montre les universelles et les exemptions, et
l editeur de liens contraint le role a meta/liens.yml. Le generateur s
efface plutot que de remplacer un editeur qui en sait plus que lui.

LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur.

make prouver : CONFORME, 61 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:26:33 -04:00
935a64a1bf plan : sauvegarder n emportait plus quarante lignes de commentaire
En voulant generer deux formulaires de plus, j ai trouve pire que ce que
je cherchais.

CE QUI ETAIT DEJA LA. Les quatre ecrivains de registre ecrasaient le
fichier au safe_dump. Mesure sur les fichiers reels : domaines.yml 6->3,
applications.yml 27->5, serveurs.yml 18->3. Quarante lignes, detruites
par n importe quel clic sur Sauvegarder dans les vues Serveurs,
Applications ou Domaines. Parmi elles, celle qui explique pourquoi
backup-01 a ete retire, et celle qui dit dans quel ordre les deux roles
du runner s appliquent. C etait l incident du 2026-08-18, jamais corrige
pour les registres du plan. Les quatre passent par _ecrire_registre :
aller-retour a vide identique a l octet, sur les quatre fichiers.

TROIS ECARTS DE SCHEMA, trouves en confrontant le schema aux VALIDATEURS
et non aux seuls plans :
 - edge designe un GROUPE, pas un hote. Le schema disait serveurs : un
   formulaire genere aurait offert une valeur qu aucun hote ne reconnait,
   donc aucun SAN, donc la panne du 2026-08-25 reintroduite ;
 - exposition, entierement valide par le moteur, manquait au schema ;
 - liens etait items: {type: object} — une liste d objets sans forme.
Et mail, offert par la vue Domaines depuis sa creation, decrit ici comme
un booleen, saisi la-bas comme du texte, lu par rien : retire.

P62 garde tout ca. Elle separe l entite du reste mecaniquement : un
validateur lit son entite par des variables LOCALES, les autres registres
par ses PARAMETRES. Controle negatif rejoue.

LES FORMULAIRES. Serveurs de BD et Domaines sont generes, chargement et
sauvegarde compris. Quatre registres sur six. Le generateur a appris la
liste d objets.

LIMITE : restent serveurs et applications, les deux plus gros ; et je n ai
toujours pas ouvert ces pages dans un navigateur.

make prouver : CONFORME, 61 OK, 0 echec, 1 saute (62 preuves).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 17:54:49 -04:00
88033f37b8 GUI : la vue Nomenclature, et deux fautes que mes bancs ne voyaient pas
Some checks are pending
verifier / verifier (push) Waiting to run
LA VUE. La nomenclature etait le seul registre que le GUI ne savait pas
ecrire du tout : ajouter une fonction exigeait d ouvrir le YAML. Elle a
sa vue, et son formulaire est GENERE depuis le schema. Deuxieme registre
sur six. couverture_gui verifier passe : les 28 champs des plans reels
sont editables.

Elle n est pas un registre comme les autres : elle decrit la REGLE dont
VMID, VLAN, adresse et passerelle se derivent. Chaque fonction montre ce
qu elle derive et les VM qui la portent ; l index est montre mais pas
editable, parce qu il est alloue par le site ; valider_nomenclature
refuse de retirer une fonction encore portee, ou de designer une zone
non declaree.

DEUX FAUTES, ET POURQUOI MES BANCS NE LES VOYAIENT PAS.

Le formulaire des bases, livre la veille, etait casse dans un navigateur.
Il lisait data.schema, or il n existe aucun data global : c est une const
locale de charger(). ReferenceError a l ouverture, et zone morte dans
sauvegarderBases. Je l avais eprouve sous node EN LUI PASSANT data : le
banc reproduisait la fonction, pas sa portee. D ou test_rendu_gui.py, qui
charge le JS entier dans un DOM simule et dessine les douze vues, avec son
controle negatif.

Le schema decrivait reservations comme une table de zones ; le fichier
reel est un bloc plat. P61 comparait des NOMS aplatis, donc ne voyait
rien. Elle compare desormais aussi la FORME.

ECRIRE SANS DEPLACER UN COMMENTAIRE. _fusion_chirurgicale remplace le
bloc entier des qu une valeur change : quinze entrees compactes devenaient
42 lignes, et le commentaire du poste d exploitation se retrouvait en tete
du bloc, ou il affirmait que collab etait le poste d exploitation. Un
commentaire deplace n est pas laid, il est faux. _fusion_table edite les
tables ligne a ligne ; le diff fait trois lignes.

Au passage : sort_keys triait le schema, donc l ordre des cases a l ecran
(reserve_max avant reserve_min) ; et _ecrire_index_nomenclature ecrivait
encore par write_text, oubliee au passage des ecritures atomiques.

LIMITE : deux registres sur six sont generes, et je n ai toujours pas
ouvert cette page dans un navigateur.

make prouver : CONFORME, 60 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 17:08:45 -04:00
34b7ec3886 schema : le marqueur du champ requis, et l entree de CHANGELOG qui manquait
Some checks are pending
verifier / verifier (push) Waiting to run
Deux dettes des commits 0eaceb1 et 03c628d.

La premiere se voyait a l ecran : le marqueur « requis » se collait au
libelle et se lisait « Base requis », « Secret (Vault) requis ». Il
devient une asterisque discrete portant l infobulle « Champ requis ».

La seconde est une entorse a la regle 5 : les deux commits precedents
ont livre le schema derive et le premier formulaire genere SANS entree
de CHANGELOG. Elle est ecrite ici, avec sa limite dite franchement — un
registre sur six est genere, et le trou de la nomenclature reste ouvert.

make prouver : CONFORME, 60 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 16:34:25 -04:00
5bc3bceac1 documentation : la tournee des 74 documents, parce qu un balayage ne lit pas
Some checks failed
verifier / verifier (push) Has been cancelled
La revision a commence par un balayage par motifs — chemins morts, cibles make
absentes, comptes derives. Il a trouve une trentaine d ecarts et rate presque
tout le reste : un motif ne voit que ce qui s exprime en motif.

make hote-planifier en est l exemple. La cible EXISTE, donc le controle passait
au vert. C est une cible depreciee qui refuse et sort en 2, recommandee par
AGENTS.md, et qui contredit la REGLE D OR du meme fichier trois ecrans plus
haut. Il fallait lire pour la voir.

74 documents lus un par un. 66 corriges, 8 exacts.

CE QUI ETAIT FRANCHEMENT FAUX

AGENTS.md, la source d autorite, annoncait la flotte pas encore executee contre
des VM reelles. Elle a ete rasee et remontee depuis zero trois fois.
ecosysteme-chezlepro.md, le document montre a un client, portait la meme
phrase : il se sous-vendait gravement.

courriel-conception.md s ouvrait sur aucun role n est encore ecrit, au-dessus de
son propre paragraphe 1 qui les nomme. autorisation.md se terminait sur rien n
est construit alors qu il rapporte des mesures datees du role en fonctionnement.
hebergeur-exploitation.md disait rien n est fait d un depot qui existe.
filiation-emancipation.md se contredisait a deux ecrans de distance.

DES MODELES DECRITS D APRES UN MONDE ANTERIEUR

Le resolveur : cinq documents decrivaient un Unbound par VM en opt-in, trois le
donnaient en exemple d integration FACULTATIVE — il est universel depuis le
2026-08-24. L adressage de nomenclature-vm.md : reseau unique, VLAN 11-15, VMID
a cinq chiffres. Le nommage SDN de sdn-evpn.md contre le code : c est le wiki
qui avait raison.

CE QUI CASSE AU PREMIER ESSAI

Le nom du gabarit dore etait faux a quatre endroits, dont la procedure qui le
FABRIQUE et le critere R2 de l epreuve d operateur independant.
preparer-un-site-hebergeur.md avertissait qu une VM faite a la main serait
detruite : raser derive du plan, il ne la detruira jamais — le risque est l
inverse. Un mot de passe d essai en clair dans un depot public.

DEUX PREUVES ETENDUES, ET UNE QUI SE TROMPAIT ELLE-MEME

P57 couvre les groupes : elle a signale aussitot 29 groupes annonces au-dessus d
un tableau qui en cite 40. P29 confronte le tableau de authentification.md aux
declarations reelles : 12 annonces, 21 reels.

Et P57 imposait un chiffre faux — 56 preuves alors que le depot en porte 57, la
conditionnelle vivant hors de tout comptage. Un garde-fou qui fait respecter une
erreur ajoute l assurance a l erreur.

CE QUI RESTE, ET QU AUCUNE PREUVE NE TIENT

Deux comptes trouves a la main. Et une lacune reelle : rien ne garde les
meta/acces.yml — ni qu un service web-sso en porte un, ni que le groupe qu il
nomme existe. P29 tient les positions d authentification, personne ne tient les
habilitations.

make prouver : CONFORME, 56 OK, 0 echec, 1 saute. 0 lien mort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-06 16:18:23 -04:00
dcbb57db69 cles : la cle USB se suffit a elle-meme, et la restauration est prouvee
Some checks are pending
verifier / verifier (push) Waiting to run
Le jour ou l on s en sert, le poste est mort — et cloner le depot demande la cle
SSH qui est dans l archive qu on essaie d ouvrir. Une procedure rangee dans le
depot serait inaccessible exactement quand elle sert.

L export depose donc restaurer_cles.py et un LISEZ-MOI a cote de l archive. La
cle ne demande plus que gpg, python3 et la phrase de passe.

Le script remet chaque fichier a sa place selon son NOM (machine neuve, parfois
autre compte), repose les droits a 0600 — ssh refuse une cle privee lisible par
d autres, et son message ne dit pas qu il s agit d un droit — et refuse d
ecraser une cle presente, en regardant AVANT d ecrire.

Le LISEZ-MOI porte les trois commandes manuelles. Un outil peut avoir un defaut ;
gpg et tar seront la.

EPROUVE : export vers une cle, poste neuf vide, restauration depuis la cle SEULE,
empreintes comparees — identiques 4 sur 4, droits 700/600, et le refus d ecraser
tire. Le filet manuel passe au meme test separement, identiques 4 sur 4.

make cles-restaurer refusera puisque les cles sont en place — et ce refus est la
preuve que l archive s ouvre et que la phrase de passe est la bonne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 13:33:31 -04:00
bebdb84212 cles : sortir du poste ce qui n existe qu au poste
Some checks are pending
verifier / verifier (push) Waiting to run
Le code est replique trois fois (eregion, forge du site, patient 0) et les
voutes chiffrees y sont aussi — le coffre est solide. Les CLES qui l ouvrent
vivaient dans neuf fichiers, 1644 octets, sans copie ailleurs.

Poste seul : les mots de passe restic restent lisibles sur les machines vivantes,
donc recuperable mais douloureux. Poste + une machine : l etat de cette machine
devient illisible. Poste + site : terminal.

make cles-recenser montre ce qui sortirait sans rien ecrire — nom, taille,
empreinte, JAMAIS le contenu. make cles-exporter chiffre en AES256 puis
REDECHIFFRE ce qu il vient d ecrire et compare les empreintes une a une : une
sauvegarde de cles qu on n a pas rouverte n est pas une sauvegarde.

A lancer par l exploitant lui-meme : gpg demande une phrase de passe, elle ne
doit passer ni par un journal ni par le contexte d un assistant.

Trois refus, eprouves en les faisant echouer : destination dans l infrastructure
(un coffre dont la cle est dedans), archive existante (elle est peut-etre la
seule), archive illisible (supprimee). Le premier essai du premier refus etait
faux — le shell developpait HOME avant que je le remplace, l instrument mesurait
ailleurs que la cible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 09:42:01 -04:00
42becd0c02 paquets tiers : passer par le cache du controleur, plus par Internet
Some checks are pending
verifier / verifier (push) Waiting to run
Trois depots tiers etaient en HTTPS, et client_artefacts pose
Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et
aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait
pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait
les chercher sur Internet a sa naissance.

step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une
machine neuve n obtenait ni son client d autorite ni ses metriques — elle n
entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors
ligne, et il tenait dans un mot.

make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256
verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les
installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux
qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en
apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour
ce que le cache n a PAS fourni, et rien d autre.

Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli
desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache
et la machine obtient ses certificats. Zero echec.

Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son
index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et
un depot injoignable faisait continue AVANT d incrementer le total, si bien que
le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut
pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en
le faisant echouer.

Reste hors ligne : NTP externe, et l expedition des alertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 08:22:14 -04:00
872b5e91aa forgejo : laisser le premier demarrage finir avant de l interrompre
Some checks failed
verifier / verifier (push) Has been cancelled
Deuxieme reconstruction de validation de Chezlepro : 14/14 hotes, 0 echec,
make valider 0 echec sur 13. Les corrections de la veille ont toutes tenu —
la garde de clonage a laisse passer trois WARNINGS, client_artefacts a degrade
sur le cache du site puis bascule seul.

Un defaut que la premiere reconstruction n avait pas montre : le role DEMARRE
Forgejo, qui entame son initialisation de premier lancement (schema +
migrations, plusieurs dizaines de secondes), puis flush_handlers le REDEMARRE
parce qu app.ini vient de changer. Redemarre au milieu, il laisse la version
cible inscrite et le schema absent — 5 tables, version=305 — et boucle
indefiniment sur migration[v14a] :  la relation collaboration n existe pas .
Le service reste active, il reessaie, et n ecoute jamais son port.

Invisible aux deploiements suivants : la base est deja migree, le premier
demarrage est instantane, le redemarrage ne tombe au milieu de rien. Le defaut
n existe que sur une base VIERGE, et meme la il depend du timing.

La chaine de sauvegarde est prouvee sur une flotte nee il y a une heure : neuf
detenteurs deposent chez l hebergeur, chacun verifie SON depot avec la cle qu il
est seul a detenir, et le rapporte a sa propre supervision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 22:27:38 -04:00
6653f49f2e sauvegarde : la verification suit la cle, pas le depot
Some checks are pending
verifier / verifier (push) Waiting to run
serveur_backup verifiait pour tout le monde — juste tant que le depot vivait
dans l ecosysteme. Depuis qu ils deposent chez leur hebergeur, le site heberge
des octets chiffres COTE CLIENT : il ne peut ni les lire ni dire s ils valent
quelque chose. La verification revient donc au seul qui detient la cle, le noeud.

client_backup verifie SON depot distant — pas le fait d avoir lance sa
sauvegarde. Une unite verte sur un depot vide est ce qui a menti un mois.

serveur_icinga n exige plus un hote serveur_backup et se branche sur deux
modeles : depot local (services sur son hote, nommes sauvegarde: <noeud>) ou
pas de depot (services sur chaque noeud, nommes sauvegarde).

Le 404 qui n etait pas une absence : les noeuds recevaient  No objects found
alors que icinga2 object list montrait le service charge. Le filtre du compte
d API ne portait que la premiere forme de nom — c est la PERMISSION qui
refusait, avec les mots d une absence.

Aussi : ingress 5665 depuis client_backup, le pair ne nommait que
serveur_backup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 18:53:44 -04:00
4e12c3a802 supervision du site, et la panne qui dormait dans un mot
Some checks are pending
verifier / verifier (push) Waiting to run
Le site a son temoin : site-mon-01 (VLAN 36) porte PostgreSQL, Icinga et un
relais. Le depot lui rapporte, et son premier verdict fut un vrai defaut —
site-mon-01 n avait jamais depose son propre etat. Les trois sont au vert.

serveur_backup_verification_locale repasse a true sur le depot : le drapeau ne
dit plus  on renonce  mais  verifie ce que tu peux ouvrir . La liste des noeuds
attendus derive de l inventaire ou tourne le role, donc du site seul.

serveur_postfix gagne un mode relais : un site n heberge aucune boite, il
expedie. Directement par sa frontiere — emprunter le MTA d un locataire ferait
dependre l hebergeur d un ecosysteme qu il peut outvivre.

LA PANNE DE FOND : le modele de routes OPNsense lit enabled ; on lui envoyait
disabled=0, un champ ignore. enabled restait a son defaut, ETEINT. Chaque route
creee par Set-OPS depuis l origine l etait desactivee — invisible, parce qu une
route eteinte EXISTE dans le modele et compte  posee . Le rechargement lance
pour activer la nouvelle patte a fait reprendre au noyau sa table depuis le
modele : 14 routes sur 15 disparues, 11 machines de Chezlepro injoignables le
lendemain de sa reconstruction, et le devis toujours vert.

Trois corrections empilees : la cause (enabled), la cecite (le plan lit la table
du NOYAU et signale absent ou eteint), l inaction (la reconfiguration ne se
declenchait que sur un changement du modele).

Aussi : un role du site peut enfin en appeler un autre a travers la frontiere —
le devis traitait tout pair nomme comme  l exterieur . Et nftables_admin_ssh se
DERIVE de la carte : recopie a la main, il retardait d une zone, et m a
verrouille dehors de la machine que je venais de creer.

Reste ouvert : le destinataire des alertes est encore root@localhost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 17:34:01 -04:00
cf9abe74b6 sauvegarde : le site protege enfin son propre etat
Some checks are pending
verifier / verifier (push) Waiting to run
L hebergeur protegeait l etat de tous ses locataires et pas le sien. Deux choses
qu il detient et que personne ne peut regenerer : la racine de son AC, et la
forge du genome. Le reste est reconstructible par le code.

Preuve faite, pas annoncee : sauvegarde reelle puis restic check et restitution.
13 fichiers pour l AC (root_ca_key et intermediate_ca_key compris), 835 pour la
forge.

Le site depose avec SON identite, sur le compte restic que serveur_backup lui
cree, separe des comptes des locataires par les memes permissions. La cible est
derivee du expose de l application qui porte serveur_backup_site : le nom du
service, pas une adresse — client_backup_repo la grave dans le chemin de chaque
instantane.

P36 ne lisait que le plan de l instance montee, donc jamais celui du site — qui
detient pourtant le plus. Elle lit desormais les deux. Verifiee en la faisant
echouer : integration retiree, la preuve tire.

Reste ouvert : personne ne verifie les sauvegardes du site. Le depot tourne avec
verification_locale a false (pose pour les locataires, dont il ne peut pas ouvrir
les depots) et le site n a pas de supervision. La sauvegarde existe et se
restaure ; c est son SILENCE qui n alerte pas encore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 11:21:52 -04:00
53c5f41a1a durcissement : le site recoit enfin serveur_durci
Some checks are pending
verifier / verifier (push) Waiting to run
Les six machines du SITE — racine de l AC, forge, cache, resolveur, depot de
sauvegarde, runner — ne recevaient que serveur_debian. Ni auditd, ni fail2ban,
ni apparmor, ni sysctl, ni pare-feu. Le commentaire au-dessus de GROUPE_SOCLE
affirmait pourtant le contraire, ce qui rendait l ecart invisible.

Le pare-feu du site n avait aucune des trois pieces qui le rendent applicable :
- aucune regle generee (resoudre_flux lit un hosts.yml ; le site a un inventaire
  dynamique) -> commande nftables-site, branchee sur make flux
- le chemin du jeu de regles sortait du depot (inventory_dir vaut scripts/ pour
  un inventaire dynamique) -> host_var explicite
- aucun nftables_admin_ssh : l exploitant administre PAR REBOND, la connexion
  arrive avec l adresse de la patte de frontiere DANS LA ZONE VISEE. Le
  generateur refuse desormais de produire des regles sans cet intrant.

Defaut attrape en LISANT le jeu de regles avant de l appliquer : la regle DNS de
site-dns-01 omettait 10.17.0.0/16. _supernets_voisins retire l instance montee —
juste chez un tenant, faux du cote du site, ou ca retire le locataire qu on
pilote. L appliquer aurait prive de DNS les quinze machines reconstruites la
veille.

MaxStartups du depot passe en host_var : dans le meta du role, il ne portait que
dans son play, et le passage de serveur_durci le remettait au defaut sans rien
dire. Un reglage qui depend de l ordre des plays revient en arriere.

Verifie depuis un locataire a travers le nouveau pare-feu : cache, forge, depot
(2 snapshots) et resolveur repondent ; l AC du site refuse, et c est correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 10:31:21 -04:00
0854b2a93c reconstruction : quatre defauts que seule une flotte rasee pouvait montrer
Some checks are pending
verifier / verifier (push) Waiting to run
Chezlepro detruite (15 VM, disques compris) et refaite depuis le gabarit
minimal. 15/15 hotes, 0 echec ; make valider passe, test de restitution
compris. Aucun defaut ne venait de la flotte ni du gabarit.

1. Un avertissement n est pas un echec. Proxmox rend WARNINGS: n pour une
   tache ABOUTIE ; la garde n acceptait que OK et declarait perdues cinq VM
   clonees a 100 pourcent. Le message parlait d etat stopped — celui de la
   TACHE, pas de la VM.

2. client_artefacts se contredisait : son commentaire disait de degrader, son
   code arretait. L autorite monte en premier, donc avant le cache du
   locataire : aucun ordre ne pouvait satisfaire la garde.

3. harden-below-nxdomain etendait le NXDOMAIN signe de la racine pour le TLD
   internal a toute la zone du site, sans jamais interroger l autoritatif.
   Declencheur : toute question sur un nom absent sous internal, y compris la
   zone d un autre locataire. Le cache contenait la bonne reponse ET un
   message negatif ; c est le negatif qui etait servi.
   aggressive-nsec: no avait semble marcher — c est le redemarrage qui vidait
   le cache, pas le reglage.

4. Un locataire doit savoir a qui demander la zone de son hebergeur, sans quoi
   il ne peut plus nommer son depot de sauvegarde. La derivation prenait
   dns_amorcage pour le resolveur du site : faux chez Technolibre, dont
   l amorcage est 9.9.9.9. P03 l a attrape avant tout deploiement.

Au passage : instancier tentait encore le mot de passe unique d avant la
separation des voutes ; comparer echouait en exit 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 09:59:15 -04:00
3ea0c95492 sauvegarde : l etat d un locataire quitte enfin sa propre flotte
Some checks are pending
verifier / verifier (push) Waiting to run
Chezlepro rangeait ses instantanes sur une VM DE SA PROPRE FLOTTE. Raser
l ecosysteme pour le reconstruire, c etait raser le filet avec.

Le site a son depot ; les neuf detenteurs d etat y deposent ; une
restitution est sortie (annuaire LDAP lisible, hors flotte).

Isolation par compte Unix, pas par convention : home 0700, cle exclusive,
racine partagee a root en 0711 (traversable, non listable). Les deux refus
constates. Le site heberge du chiffre : il ne peut ni lire ni ouvrir, d ou
la verification deplacee chez le locataire qui detient la cle.

Quatre defauts reveles par ce deuxieme usage :
- la racine des depots ne peut etre le home de personne (StrictModes rendait
  Permission denied publickey pour un refus de CHEMIN)
- la racine nie le TLD internal, et harden-below-nxdomain etendait ce non a
  toute la zone sans jamais interroger l autoritatif : aucun locataire ne
  pouvait nommer un service du site
- le gabarit transporte des fichiers de durcissement perimes, et les machines
  du site ne recoivent jamais ssh_hardening
- MaxStartups compte les connexions non authentifiees : un depot de site en
  voit la somme de ses locataires

Constat non corrige : les machines du site ne sont pas durcies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-01 22:16:20 -04:00
4abf875e5f gabarit : q35 n est pas un reglage, c est la raison de la procedure manuelle
Some checks are pending
verifier / verifier (push) Waiting to run
CONSTAT DE L EXPLOITANT, PAYE EN ANOMALIES : convertir une machine deja installee
d `i440fx` a `q35` produit une serie de pannes dont chacune ressemble a autre chose
qu a sa cause. Ce n est pas une correction, c est une transplantation.

LE MECANISME, ECRIT POUR QU ON NE LE REDECOUVRE PAS : `i440fx` est un chipset PCI,
`q35` est PCIe. La topologie des bus change, donc les NOMS D INTERFACES
PREDICTIBLES changent avec le chemin PCI (enp0s3 -> enp1s0) et la machine perd le
reseau ; les chemins de disques bougent ; l ordre d enumeration suit.

C EST AUSSI POURQUOI SET-OPS N UTILISE PAS L IMAGE CLOUD OFFICIELLE DE DEBIAN :
`genericcloud` est livree configuree pour `i440fx`. Une machine nait `q35`, ou elle
ne le sera jamais proprement — et c est ce que l installation depuis l ISO garantit.

Ces deux lignes de la procedure n etaient qu une ligne de tableau. Elles portent
maintenant leur pourquoi, et le SITE les declare comme DONNEES (cle `gabarit`),
plus seulement comme prose.

`make gabarit-etat` compare le gabarit reel a ce que le site declare de lui.

ON VERIFIE LA SOURCE, PAS CHAQUE COPIE. Ma premiere version gardait le CLONAGE : la
propriete s herite, donc verifier chaque clone coute a chaque creation sans rien
dire de plus que verifier le gabarit une fois. Retiree.

TROIS FOIS J AI DEVINE LA FORME DE LA REPONSE AU LIEU DE LA REGARDER — regex_search
a groupe qui rend None, proxmox_vm_info sans `config: current` qui ne rend que
l etat. La garde a declare « ? » sur une VM parfaitement conforme : une garde qui
crie toujours est pire qu aucune, on apprend a l ignorer.

A la demande et non dans `make prouver` : ce controle exige le cluster, que le
harnais ne suppose pas joignable. Meme nature que genome-etat et underlay-plan.
Controle negatif verifie : declarer i440fx fait echouer, rc=1.

make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-01 14:42:58 -04:00
9aef3614ab gabarit : refabrique, minimal, et puise aux ressources du SITE
Some checks are pending
verifier / verifier (push) Waiting to run
REFABRIQUE (VMID 9006, modeleSetOPS-minimal). Quatre roles au lieu de dix-sept :
qemu_guest_agent, cloud_init, sudo_ansible, ssh_baseline — des conditions
d existence, pas des choix d efficacite. Tout le reste vient du socle, et P56 refuse
qu un role retire ne soit repris par personne.

FABRIQUE CHEZ LE SITE, ET C EST LA DECISION QUI COMPTE. « Les ressources du SITE
font autorite pour tous ses artefacts ; elles servent les tenants jusqu a ce qu ils
s emancipent. »

Il se fabriquait DEHORS : `-i "<ip>,"` ne porte aucun group_vars, donc ni mandataire
ni resolveur. L ancien gabarit allait chercher ses paquets chez Debian et resolvait
chez l ancien LAN — 192.168.10.10, lu sur la VM 99998. Le site avait son cache et
son resolveur, et son propre artefact les ignorait.

Desormais la fabrication DERIVE ses ressources du plan du site (underlay --adresses)
et tourne sur le reseau du genome. PROUVE : dix requetes de 10.0.33.31 servies par
site-cache-01, resolveur pose a 10.0.34.11.

L IDENTITE DU GABARIT VIENT DU SITE, PLUS DU TENANT. `proxmox_clone_vmid_modele`
vivait dans les group_vars de l ecosysteme : deux tenants pouvaient cloner deux
gabarits differents sans que rien ne le dise, et un tenant decidait d un objet dont
descend chaque VM de chaque ecosysteme. Meme mouvement que l INDEX (2026-08-25) : le
site ALLOUE, le tenant RECOIT. Cle `gabarit` du plan du site, lue par
underlay --gabarit.

PIEGE FERME EN CHEMIN : creer-vm passait VMID_MODELE="$SETOPS_VMID_MODELE" a
cloner-vm, or inventory_host.py n emet PAS cette variable. Elle valait donc le VIDE,
et ce vide ECRASAIT la valeur derivee — le gabarit du tenant reprenait la main sans
bruit.

ET UN PIEGE DEJA DOCUMENTE, PAYE UNE TROISIEME FOIS : le chemin du controleur
contient une espace, et `lookup('pipe', ...)` le decoupait. Guillemets.

EPROUVE DE BOUT EN BOUT : VM clonee du gabarit minimal — nom, adresse, machine-id
neuf, cle d hote regeneree, agent invite actif ; puis socle applique dessus,
changed=5, 0 echec, auditd compris. VM d essai retiree.

make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-01 13:46:31 -04:00
2f2323bd3e gabarit : minimal — il etait un cache du socle, et il perimait sans le dire
Some checks are pending
verifier / verifier (push) Waiting to run
Il portait DIX-SEPT roles : exactement ceux du socle et du durcissement, que le
deploiement rejoue a l identique. C etait donc un CACHE — et comme tout cache, il
perimait sans le dire.

MESURE : derniere recapture le 2026-08-09, et SIX de ses roles avaient change depuis
— common_packages, cloud_init, ssh_baseline, ssh_hardening, auditd,
nftables_baseline. Rien ne le signalait : le deploiement masquait la derive en
reappliquant tout, donc personne ne pouvait la voir. Aucune preuve du harnais ne
regardait sa fraicheur.

IL NE GARDE QUE CE QUI DOIT EXISTER AVANT QU ANSIBLE PUISSE AGIR :

    qemu_guest_agent  l agent repond AVANT SSH — P52 s en sert
    cloud_init        le seul chemin vers la premiere seconde
    sudo_ansible      la porte par ou tout entre
    ssh_baseline      le serveur SSH

Ce ne sont pas des choix d efficacite, ce sont des conditions d existence.

CE QUE CA COUTE, ET QUI EST COUVERT : une VM neuve n est plus durcie a la naissance.
Elle nait cependant DERRIERE LE PARE-FEU DE L HYPERVISEUR, policy_in=REJECT arme au
clonage — verifie sur obs-01. La fenetre d exposition est fermee par la fabric, pas
par le gabarit. Mon objection initiale tombait devant la mesure.

P56 GARDE LES DEUX MOITIES. Qu il ne REGROSSISSE pas : un role ajoute recree le
cache, donc la peremption invisible. Et que rien de retire ne soit PERDU : un role
absent du gabarit ET du socle disparaitrait de toutes les machines neuves, sans
erreur ni trace, et la panne arriverait des mois plus tard sur une machine qu on
croyait durcie. Verifie : 14 retires, 14 repris, zero orphelin. Deux controles
negatifs.

make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-01 10:35:20 -04:00
0b653170fa emancipation : l instrument de la quatrieme ligne — couper, pas sonder
Some checks are pending
verifier / verifier (push) Waiting to run
docs/filiation-emancipation.md decrit quatre temps et n en outillait que trois. Le
quatrieme est celui qu on oublie : « une emancipation non prouvee est une
emancipation non faite ».

SONDER NE PROUVE RIEN. Verifier que le service local repond ne dit pas si l amont
sert encore — le depot le disait deja du cache : « tant qu internet repond, un apt
update qui reussit ne dit pas d ou vient l octet ». L instrument COUPE donc l amont
et refait marcher la chose.

LE MEME ESSAI REND LES DEUX VERDICTS, et c est ce qui le rend honnete :

    coupe, la fonction marche  ->  EMANCIPE, et c est prouve
    coupe, la fonction casse   ->  PAS EMANCIPE, dependance prouvee REELLE

Le second n est pas un echec de l outil, c est son CONTROLE NEGATIF rendu par la
meme commande. Une preuve d emancipation incapable de montrer la dependance qu elle
mesure ne prouverait rien le jour ou elle passerait au vert.

UN TEMOIN PRECEDE LA COUPURE : la fonction marchait-elle seulement avant ? Sans lui,
une panne preexistante se lirait comme une dependance.

LA COUPURE EST GARANTIE REVERSIBLE : une TABLE nftables dediee, jamais une regle
glissee dans une table existante — elle se retire d un geste et ne peut pas laisser
d etat partiel. Le bloc `always` la retire meme si la mesure echoue ou si le play
est interrompu, et une tache verifie ensuite qu elle a bien disparu.

MESURE LE JOUR DE SA NAISSANCE, les deux verdicts sur du vrai materiel :
  obs-01 / resolveur   PAS EMANCIPE — plus aucune resolution des la coupure
  forge-01 / artefacts EMANCIPE — apt installe, cache du site coupe

Ce second verdict a ete DOUTE puis verifie : apt aurait pu reussir en rejouant des
listes fraiches. Refait avec un dossier de listes NEUF, amont coupe : reussit
quand meme. Le cache sert vraiment son contenu.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-01 08:36:28 -04:00
89ca92b132 D-82 : patient 0 n est le parent de personne — le dilemme est tranche
Some checks are pending
verifier / verifier (push) Waiting to run
Ouvert le 2026-08-28, referme par les faits plus que par le raisonnement. Trois
d entre eux lui ont retire ce role un par un, et il fallait les regarder ensemble :

  D-81 a donne l autorite du genome a la forge du SITE. Son dernier lecteur,
  Chezlepro, a ete corrige le 08-26 ; Technolibre le 08-31. Il ne sert donc le
  genome a personne.

  Le denominateur commun qu il portait vit dans les modeles depuis le 08-24, sous
  le nom `origine`. Ce n est plus lui qu on copie.

  L ANCETRE ETAIT LOCATAIRE DE SON ENFANT : index 29 sur la fabric de
  SITE-Chezlepro, qui descend de lui. Il ne peut pas etre le chemin de reprise de
  son propre hote.

Des trois issues posees, la deuxieme l emporte — non parce qu elle etait la plus
elegante, mais parce que les deux autres avaient cesse d etre disponibles. Et le
depot l avait deja suivie sans le declarer : serveur_forge_site, puis
serveur_cache_site, puis serveur_resolveur_site. Trois services pretes, un patron.

CE QUE CA NE REGLE PAS, ET QUI EST ECRIT. Patient 0 existait pour eliminer un point
unique de defaillance — eregion, hors flotte, que Set-OPS ne deploie ni ne prouve.
Il ne l a pas elimine : IL A ETE PROMU. Le poste y pousse, la forge du site en tire.
La dette a change de proprietaire, pas de nature ; elle appartient au SITE. Un
objectif qu on abandonne sans le dire devient un objectif qu on croit atteint.

Ce qu il garde : sa place de pair dans la famille du genome, et sa forge de travail.
Ce qu il perd : le rang.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 20:57:25 -04:00
35f3881218 recette : un depot occupe n est pas une sauvegarde cassee
Some checks are pending
verifier / verifier (push) Waiting to run
make valider rendait ECHEC sur data-sql-01, et la meme commande rejouee a la main
restaurait 5 fichiers. restic verrouille son depot pendant qu il ecrit : une recette
qui croise la fenetre de sauvegarde lit un echec de RESTAURATION la ou il n y a
qu une attente. Verdict juste sur l instant, faux sur le fond, et DEPENDANT DE
L HEURE a laquelle on la lance.

ON REESSAIE, MAIS ON NE MASQUE PAS. Trois tentatives espacees couvrent un verrou
d ecriture ; au-dela, l echec est REEL et la recette rend la RAISON que restic a
donnee, au lieu d un -1 muet qui obligeait a se connecter pour comprendre.

CONTROLE VERIFIE SUR LA MACHINE : cle de dechiffrement retiree ->
« ECHEC apres 3 tentatives — restic dit : Fatal: Resolving password failed ».
La recette attrape toujours une vraie panne, et elle en donne la cause.

DEUX FAUTES A MOI EN CHEMIN, CORRIGEES.

`[ "$f" -lt 0 ] && printf ...` : la DERNIERE commande d un script decide de son code
de sortie, et un test faux rend 1. La tache echouait donc exactement sur les
machines ou la restauration avait REUSSI — trois hotes verts declares en echec.

Puis la ligne `raison=` n etait emise qu en cas d echec : le gabarit cherchait un
champ absent, rendait None, et explosait sur les DIX machines saines. Elle est
desormais TOUJOURS emise, vide en cas de succes. Un champ toujours present coute un
octet et supprime un cas.

make valider : rc=0. make verifier : vert. make prouver : 55 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 20:41:39 -04:00
0dd57ba995 artefacts : deux filets — un cache mort ne doit plus rendre un tenant irreparable
CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION. Toute la flotte y
prend ses paquets. S il pointe vers un amont mort, apt echoue sur les quinze
machines, donc le socle echoue, donc le deploiement n atteint jamais la couche qui
poserait le bon amont. LE CORRECTIF SE RETROUVE DANS LA COUCHE QUE LA PANNE EMPECHE
D ATTEINDRE, et il faut une main pour en sortir.

C est arrive le 2026-08-31 : l amont pointait sur le cache de patient 0, eteint la
veille pour liberer de la RAM. apt rendait « 503 Connection timeout » EN CITANT
L ADRESSE DU CACHE LOCAL, jamais celle de l amont manquant — la panne accusait le
maillon visible.

DEUX FILETS, SYMETRIQUES.

serveur_artefacts sonde l amont AVANT d ecrire, et refuse s il est muet : mieux
vaut un cache qui garde sa configuration precedente qu un cache qu on vient de
rendre inutilisable pour toute la flotte.

client_artefacts sonde la source AVANT de detourner apt vers elle. Ce fichier
ECRASE le plancher d amorcage pose par le socle — le poser sur un cache mort prive
la machine du cache du SITE, qui lui fonctionnait. En refusant, le plancher reste :
la flotte est degradee mais debout, et REPARABLE PAR UN DEPLOIEMENT.

`ignore_errors` ET NON `failed_when: false` — la difference est toute la garde.
failed_when: false REECRIT le verdict : la tache n est plus jamais failed, donc
`is not failed` est toujours vrai, donc l assertion ne peut pas tirer. Ecrite ainsi,
ma premiere version a declare « joignable » un amont mesure MUET la seconde d avant.
Une garde qui ne peut pas echouer ne garde rien.

Deux controles verifies sur la machine : amont eteint -> refuse ; amont reel -> pose.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 18:31:21 -04:00
e2bc235132 audit + timers : deux services en echec sur les quinze machines, muets depuis toujours
`make prouver` disait 15/15 a failed=0. Les machines, elles, portaient chacune trois
unites systemd en echec. Le rapport d Ansible n est pas l etat d une machine.

AUDITD : ONZE REGLES ARMEES, PERSONNE POUR COLLECTER.

Deux fichiers de regles identiques cohabitaient — 99-chezlepro.rules et
99-setops.rules, vestige du renommage du role. augenrules CONCATENE rules.d/ :

    Error sending add rule data request (Rule exists)
    There was an error in line 16 of /etc/audit/audit.rules

audit-rules echoue, et auditd ne demarre pas — c est sa dependance. Resultat :
auditctl -l affiche onze regles, ce qui donne toutes les apparences d un audit qui
fonctionne, et rien ne les enregistre.

Meme mue que ssh_baseline, meme registre : auditd_fichiers_perimes. On n y ajoute
que des noms qu on a REELLEMENT deposes un jour.

Et `failed_when: false` cachait la panne : quinze machines a failed=0 avec auditd
mort sur les quinze. Un service de securite qui ne demarre pas doit se VOIR.

TIMERS APT-DAILY : UN ETAT D ECHEC RESIDUEL.

Masquer le service pendant que son timer tourne lui fait perdre sa cible ; systemd
le note et le GARDE (Unit to trigger vanished). `state: stopped` n efface pas un
etat failed — seul reset-failed le fait.

Ce n est pas cosmetique : une supervision qui compte les unites en echec compte ces
deux-la pour toujours, et la vraie panne s y noiera. Meme defaut que le journal de
la frontiere noye sous 982 000 entrees.

DIAGNOSTIC FAUX, CORRIGE : j avais lu « masked enabled » dans list-unit-files comme
un etat contradictoire, et construit une reparation pour le defaire. Ces colonnes
sont ETAT puis PRESET — masque avec un prereglage constructeur active est normal.
La reparation a ete annulee avant d etre livree.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-31 16:16:13 -04:00
fde0903ed8 flux + postfix : un port symbolique dans le pare-feu, un rechargement qui n applique rien
Le deploiement depuis ops-01 passe de 3 plays a 21 : douze machines sur quinze
deployees completement. Deux defauts restaient.

UN PORT SYMBOLIQUE ECRIT TEL QUEL.

    /etc/nftables.conf:36: Could not resolve service: Servname not supported
        ip saddr { ... } udp dport derive accept   # serveur_powerdns

`port: derive` dit que le port depend du deploiement. Le devis de la frontiere le
resout depuis le 2026-08-25 ; le generateur nftables ecrivait le mot, et nft
refusait TOUT le fichier. Il resout desormais par le plan et n emet rien quand il
ne peut pas, en le DISANT — un flux tu en silence est une porte qu on croit
ouverte. Verifie que ca ne ferme rien : infra-dns-01 garde son 53 par
serveur_resolveur, et l omission de PowerDNS est juste puisqu il ecoute en loopback
derriere lui.

LA VALIDATION NE POSE PAS LA MEME QUESTION QUE L EMISSION. Ma premiere garde
refusait tout port non numerique et a fait echouer P09, qui valide les roles HORS
instance — la ou derive est legitime. PORTS_SYMBOLIQUES nomme le vocabulaire : le
mot passe a la validation, jamais dans un fichier, et un mot inconnu reste refuse
des deux cotes.

RECHARGER N APPLIQUE PAS UN CHANGEMENT D ECOUTE, deuxieme fois.

    warning: to change inet_protocols, stop and start Postfix
    fatal: :::submission: Address family for hostname not supported

main.cf porte inet_protocols, que Postfix refuse de changer a chaud. Le master garde
all, tente d ouvrir :::submission en IPv6 et meurt — APRES avoir accepte une
configuration valide. postfix check ne dit rien parce que la configuration EST
valide : c est la transition qui ne l est pas. Meme famille que nginx. main.cf
notifie desormais le redemarrage.

Diagnostic corrige en chemin : j ai d abord accuse postfix@-.service, dont
l assertion echouait — c est moi qui l avais declenche par un demarrage manuel,
postfix.service le declare en Conflicts.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-30 13:00:54 -04:00
fc76a4e252 step-ca : la cle de signature versionnee — plus aucun appel sortant pour l obtenir
Le deploiement de Chezlepro depuis son propre runner a franchi le socle et le
durcissement sur les quinze machines, et s est arrete sur une seule :

    infra-pki-01 : Echec temporaire dans la resolution du nom
    url: https://packages.smallstep.com/keys/apt/repo-signing-key.gpg

UN TENANT N A PAS DE DNS SORTANT, ET C EST VOULU. Il resout chez lui, sa requete ne
traverse jamais la frontiere. apt s en sort par le mandataire du cache, qui resout a
sa place ; get_url n a pas de mandataire. Mesure sur la machine : DNS bloque, 443
sortant OK par adresse, apt via le cache OK.

Les cinq reprises du role ne pouvaient rien : elles etaient ecrites pour un serveur
INTERMITTENT (2026-08-23), pas pour une resolution fermee. Une reprise ne repare que
ce qui est passager.

UNE DETTE NOMMEE, APPELEE. client_artefacts le disait : les depots tiers en HTTPS
vont en direct, la seconde voie est propre et RESTE A FAIRE. Elle a ete appelee le
jour ou un ecosysteme s est reconstruit derriere une frontiere qui fait son travail.

La cle est versionnee, avec son empreinte et sa procedure de rafraichissement. Meme
idiome que les collections et les roues : le depot porte, la cible n ouvre rien.
C est ce qui rend une lignee reproductible SANS INTERNET.

Cout ecrit : une rotation amont n est plus recuperee toute seule, mais elle ne passe
pas inapercue — apt refuse alors le depot, bruyamment.

TROUVE EN LA RECUPERANT : l URL publique REDIRIGE vers pkgs.infra.smallstep.com.
Sans suivre les redirections on obtient un fichier VIDE, et curl -f sans -L sort en
succes. La procedure le dit, parce que ce piege ne se voit qu au deploiement suivant.

Le correctif du pare-feu a tenu : plus une seule suspension, quinze machines a
ok=66 et 0 unreachable.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-30 12:13:39 -04:00
bb63865a37 cles : le terrain etait inoccupable — la cle du tenant nait avec ses machines
Le deploiement lance depuis ops-01 s est arrete au premier geste, sur les quinze
machines a la fois : Permission denied (publickey). Les VM neuves n acceptaient que
la cle de l exploitant. Celle du runner EST declaree au plan, mais c est le SOCLE
qui la depose — et le socle doit etre applique par quelqu un qui peut deja entrer.
Boucle fermee : le tenant recevait un terrain qu il ne pouvait pas occuper.

DEUX CLES, DEUX PORTEES :

    la cle du SITE     -> sur le SEUL runner du tenant     l insemination
    la cle du TENANT   -> sur TOUTES ses machines          il va les configurer

POSER UNE CLE AU CLONAGE N EST PAS ENTRER CHEZ LE TENANT. C est un parametre de
creation, au meme titre que l adresse ou le disque : le site ecrit les conditions
de NAISSANCE, il n ouvre aucune session. Le site n obtient aucun acces sur ces
machines ; seul le runner du tenant en obtient un.

Forme tranchee par l exploitant : le site renseigne le seul runner, qui se charge
de toute sa flotte. Le plancher /etc/hosts suit le meme chemin — ops-01 a le sien
depuis son insemination et resout ses quinze voisines par leur nom.

LA REVOCATION EST HONOREE A LA NAISSANCE : une entree a etat absent n est pas
reposee. Sans cette lecture, une cle retiree de la flotte serait ressuscitee sur
chaque VM creee ensuite — panne lente, silencieuse, invisible au plan.

P55 garde les deux moities : la cle du site ne nait que sur un porteur de
serveur_ops_tenant, et aucune machine ne reste sans celle de son tenant. Une
frontiere tenue a une seule couche n est pas tenue. Deux controles negatifs.

make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-30 08:54:17 -04:00
f2f6cccd3e clonage : l hyperviseur juge le demarrage, plus le chronometre du module
Quinze VM creees trois a la fois. L une a depasse le delai du module PENDANT QUE
PROXMOX GENERAIT ENCORE SON ISO CLOUD-INIT :

    Reached timeout while waiting for starting VM.
    Last line in task before timeout: generating cloud-init ISO

Elle a demarre juste apres. flotte-creer a rendu « au moins une VM n a pas ete
creee » alors que les quinze tournaient. CE FAUX ECHEC ARRETE UNE RECONSTRUCTION :
reconstruire enchaine flotte-creer puis deployer-tout.

DEUX CORRECTIONS, LA SECONDE EST LA VRAIE. Delai plus genereux, parce que la
generation d ISO sur stockage partage se met en file quand trois clonages tombent
ensemble. Mais un delai reste un pari : son expiration ne conclut plus rien.
L HYPERVISEUR JUGE, en repondant ce qu il fait tourner — meme principe que P52, ou
l agent invite juge la materialisation a la place d une reponse SSH.

Logique verifiee sur quatre cas : seul running passe ; VM arretee, introuvable et
reponse malformee echouent. Une reponse vide ne passe pas en silence. Rejoue sur
une VM deja en marche : idempotent.

CE QUE LA CREATION DE CETTE NUIT N A PAS PROUVE : les quinze VM ont ete
materialisees DEPUIS LE POSTE, pas depuis le runner du SITE. Le chemin existait —
make inseminer, ecrit la veille — et il n a pas servi. Le poste detient
legitimement la voute du site, donc ce n est pas une faute de pouvoir ; c est une
demonstration qui manque.

make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-30 08:28:13 -04:00
732aff9c70 sdn : le puits avalait le plan d administration — une route, et le chemin existe
CE QUE LA MESURE A ETABLI, saut par saut. Le poste de l exploitant atteint la
frontiere, qui LAISSE PASSER (rule 31/0 match, pass out vlan040). Asgard RECOIT le
paquet sur vlan40. La VM ne le voit jamais. Et le noyau dit pourquoi :

    ip route get 10.17.19.41 from 10.0.31.11 iif vlan40  ->  dev vrf_t17
    ip route get 10.17.19.41 from 10.17.0.17 iif vlan40  ->  Invalid cross-device link

LA SOURCE EST LE DISCRIMINANT, pas l interface. Le chemin de RETOUR, vu du VRF :

    vers 10.0.31.11  ->  via 10.0.4.1 dev vlan40
    vers 10.17.0.17  ->  Invalid argument          <- le puits

LE PUITS A SES RAISONS et on n y touche pas : sans lui, une adresse non attribuee
du supernet sort par le defaut, revient par la frontiere dans la table PRINCIPALE
et repart vers le reseau de gestion (mesure du 2026-08-09). Le defaut n est pas
qu il existe, c est qu il est TROP LARGE : il couvre la bande basse ou D-77 place
justement l underlay d un site. Deux regles justes separement, contradictoires
ensemble.

LA CORRECTION EST DERIVEE, PAS ECRITE : pour chaque tenant, les reseaux
d administration qu il DECLARE (nftables_admin_ssh) et qui tombent dans son propre
supernet recoivent une route vers la frontiere, plus specifique que le puits. Ceux
qui vivent dehors n en ont pas besoin — la route par defaut les joint deja.

    vrf_t17   ip route 10.17.0.0/24    ...     l admin de Chezlepro
    vrf_t23   (rien)                           le sien est hors de son supernet
    vrf_t29   ip route 10.29.19.41/32  ...     l admin de patient 0 : son ops-01

CE QUE CA REPARE AU-DELA DE L ACCES : les regles administration -> tenant de la
frontiere etaient VRAIES et INAPPLICABLES a la fois. Elles correspondaient, elles
laissaient passer, et le paquet mourait un saut plus loin. Un devis vert sur un
chemin qui ne pouvait pas aboutir.

make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 17:46:40 -04:00
90ea474745 inseminer : le geste sort de mes mains et entre dans le depot
Some checks failed
verifier / verifier (push) Has been cancelled
L insemination avait ete conduite A LA MAIN depuis le runner du site — hors du
depot, donc sans preuve. Elle a maintenant sa cible :

    make inseminer TENANT=OPS-Chezlepro

TENANT= PLUTOT QUE LE SYMLINK instance : le runner du SITE amorce PLUSIEURS
locataires ; pointer un lien global sur l un d eux le ferait se prendre pour ce
tenant. Il en NOMME un par commande. Ca borne aussi le couplage que creer-vm
imposait en silence — rien ne disait sur quels tenants ce lien pouvait pointer.

L HOTE SE DERIVE : celui qui porte serveur_ops_tenant. Meme critere que le flux
d insemination et que la cle SSH du runner — le meme mot borne les trois pouvoirs.

P54 GARDE LA LIGNE DE PARTAGE la ou elle glisserait sans bruit. Les deux couches
retenues sont les seules qui ne reclament aucun secret. Le jour ou l on en
ajouterait une, le deploiement echouerait chez le tenant sur une valeur vide, et ce
message ne dirait pas qu un POUVOIR a ete franchi. Controle negatif : ajouter
client_pki, la couche suivante, fait echouer la preuve.

UN GARDE-FOU EXISTANT A INTERCEPTE UNE INSEMINATION MAL DIRIGEE. Le make parent
exporte SETOPS_INVENTAIRE ; ma resolution en heritait et visait l inventaire d un
AUTRE ecosysteme. Le refus vient d inventory_rules, pas de la cible — exactement
l erreur qu un runner servant plusieurs locataires commettrait en silence.

make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 17:14:37 -04:00
0595bf03c1 sonder : rapporter ce qui distingue, au lieu d un echec
Some checks are pending
verifier / verifier (push) Waiting to run
Trois faux diagnostics en une journee, tous dus a l instrument et aucun au
composant. curl et bash /dev/tcp ecrasent quatre causes incompatibles dans le meme
mot : ouvert, une POLITIQUE qui refuse, une machine ABSENTE, et la frontiere muette.

LE MEME CODE DIT DEUX CHOSES SELON LE DELAI, et c est la distinction qui a coute le
plus cher : EHOSTUNREACH immediat = pas de route ; le meme apres trois secondes =
il n y a pas de machine, c est l ARP qui renonce. Les confondre a fait appliquer un
pare-feu pour reparer un vide.  est pure, donc gardee par un test qui
exige que les deux ne se lisent jamais pareil.

LA GARDE ECHOUAIT DU COTE SILENCIEUX. L outil dit par quelle SOURCE le paquet part
et refuse de conclure sur une passerelle anycast — le piege qui m a fait declarer
muet un REJECT qui emettait bien ses RST. Ma premiere version rendait « pas
anycast » quand inventory_rules manquait, c est-a-dire sur un hyperviseur ou l on a
copie le seul fichier : precisement la ou le piege se produit. Une garde qui echoue
doit crier, pas se taire.

ET « NON CONCLUANT » EST UN RESULTAT : sur une source anycast et un silence, l outil
ne tranche pas, il dit quoi faire — compter les paquets du cote qui refuse, ou
sonder depuis une VM.

TCP seulement, et c est dit : UDP n a pas de poignee.

Valide contre le reel depuis ops-01 : trois cas sur quatre, le quatrieme attendant
deux machines vivantes dans un meme tenant.

make verifier : vert. make prouver : CONFORME, 53 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 17:02:20 -04:00
c918a50257 journal : le REJECT est instantane — quatre RST en quatre secondes
Some checks are pending
verifier / verifier (push) Waiting to run
J avais presente 3,05 s comme le signal d un refus. Faux, et l exploitant a eu
raison de le contester : c etait l ARP qui abandonne pour une machine inexistante.

La mesure qui tranche n est pas le delai mais le COMPTEUR : tcp-reset 17 -> 21
pendant une sonde de 4 s, un RST par retransmission du SYN. Le refus part
immediatement, chaque fois.

Ce qui manquait etait le RETOUR : ip route get rendait src 10.17.21.1, une
passerelle ANYCAST. Le RST revient au noeud qui porte cette adresse dans le VRF,
pas a la socket emettrice. Troisieme instrument fautif de la journee, et celui-ci
etait deja documente.

Entre deux VM d un tenant — le cas reel — la source est une adresse propre et le
routage inter-zone ne la reecrit pas : le refus arrive instantanement. L artefact
ne vaut que depuis un hyperviseur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 16:51:43 -04:00
4beb7e0724 flux : l interne refuse a voix haute, la bordure se tait (P53)
Some checks are pending
verifier / verifier (push) Waiting to run
Decision de l exploitant : block vers l Internet, reject a l interieur, parce que
c est prudent. Ce n est pas le refus qui informe, c est CE QU IL FAIT AU SILENCE :
sous drop partout, un timeout voulait dire aucune machine, aucune route, ou une
politique. Quand la politique parle, il n en reste qu une.

    nftables par hote   policy drop + reject with icmpx type admin-prohibited
    pare-feu Proxmox    policy_in = REJECT  (POLITIQUE_VM, source unique)
    frontiere OPNsense  block  — INCHANGE, et c est la condition

admin-prohibited ET NON tcp reset : un RST est indiscernable d un port ferme sans
service. La chaine forward reste muette : elle porte le trafic qui TRAVERSE l hote,
et y repondre ferait parler cette machine au nom d une destination qui n est pas
elle.

POURQUOI C EST PRUDENT : l obscurite etait deja nulle a l interieur (chaque machine
porte un /etc/hosts qui liste ses voisines), et la bordure protege le reject —
rien d indeclare ne franchit le perimetre, donc il ne repond jamais a l Internet.
982 000 entrees par jour a la frontiere, dont 82 % un balayage VNC.

LA MESURE A CORRIGE LA MESURE, DEUX FOIS.

Ma preuve interdisait le litteral DROP et a fait echouer un code JUSTE : la
detection d une politique posee AU DATACENTER, qui est un garde-fou. Une preuve qui
interdit un mot au lieu de mesurer une propriete finit par accuser ce qu elle
devrait proteger.

Et l absence parlait deja : EHOSTUNREACH en 3,05 s pour une machine inexistante,
timeout a 6 s pour un refus de la frontiere. Mes deux erreurs de diagnostic ne
venaient pas du drop mais de ma SONDE — curl et bash /dev/tcp ecrasent les deux
dans un meme echec.

Applique : 6 VM en REJECT, 0 creee, 0 retiree. Rien ne se ferme.
Trois controles negatifs verifies.

make verifier : vert. make prouver : CONFORME, 53 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 16:43:22 -04:00
89c911ef05 journal : l insemination aboutit — un runner de tenant, ne d un runner de site
Some checks are pending
verifier / verifier (push) Waiting to run
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 16:21:39 -04:00
ceb832eeac journal : l amorcage des artefacts, et deux diagnostics faux corriges par la mesure
L explication du commit precedent avait ete mangee par le shell (backticks), et le
garde-fou du genome a refuse l amend — l histoire de la forge fait foi (D-81). Elle
vit donc ici, avec ce que la mesure a corrige : un port muet lu comme un pare-feu
alors qu il mesurait une absence, et un acces cru perdu qui n etait qu une exception
retiree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:08:35 -04:00
45aaf2808d runner : armer un runner est un acte declare, jamais un defaut
La separation faite, la distribution devient sure. `serveur_ops_site` et
`serveur_ops_tenant` deposent desormais LA CLE de la voute qu'ils deposaient deja
chiffree. Le runner du site l'a recue : une seule cle chez lui, en 0600, et il
ouvre sa voute tout seul. La fabric, pas un tenant.

`false` PAR DEFAUT, ET CE N'EST PAS DECORATIF. Armer est le moment ou un humain
remet la cle — le seul geste que la reproduction exige de lui. Un defaut a `true`
armerait des machines sans que personne ne l'ait decide. Les deux roles exigent
une declaration au plan et refusent d'armer sans qu'on dise AVEC QUELLE cle :
poser un fichier vide laisserait un runner se croire arme.

ECRIRE PUIS RELIRE, contre la faute SYMETRIQUE de celle de la voute : la on
craignait un chiffre devenu clair, ici on craint une cle qui serait une voute, ou
vide — Ansible accepte un mot de passe vide et n'ouvre rien. Existence, taille et
mode mesures apres ecriture.

CE QUE CA CHANGE : le mot de passe se tapait a chaque deploiement. Un geste repete
vingt fois par semaine ne prouve rien, et il rendait tout deploiement non
interactif impossible sans blocage. Il se remet une fois, et c'est un evenement.

P32 A TROUVE LA MOITIE MANQUANTE : le role du tenant exigeait
`serveur_ops_tenant_cle_source`, absente du plan de Chezlepro. Dit avant tout
deploiement, dans les bons termes.

make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 14:46:29 -04:00
377462b141 voutes : une voute, une cle — separer avant de distribuer
Decision de l'exploitant : chaque runner est maitre de sa voute et en detient la
cle. C'est ce qui rend un runner autonome, donc ce qui rend l'emancipation
atteignable. Elle exigeait un prealable, mesure ce matin : UN SEUL mot de passe
ouvrait les SIX voutes de la flotte, celle de la fabric comprise.

DISTRIBUER AVANT DE SEPARER AURAIT ETE PIRE QUE LE STATU QUO : poser « la » cle
sur chaque runner rendait chaque runner capable d'ouvrir les autres. Compromettre
le plus petit locataire donnait les secrets de l'hebergeur.

Cinq cles, une par ecosysteme, sous ~/.config/setops-vault-<depot>. Verification
croisee apres rechiffrement : la diagonale, et rien qu'elle. L'ancienne cle
maitresse n'ouvre plus aucune des six.

UN SEUL MOT DE PASSE NE POUVAIT PLUS SUFFIRE, et pas pour la raison qu'on croit :
`cloner_vm_debian.yml` charge la voute du TENANT puis celle de l'UNDERLAY dans la
meme execution. `ANSIBLE_VAULT_IDENTITY_LIST` en porte plusieurs et les essaie
toutes — un seul export suffit pour les 28 appels a ansible-playbook, sans en
toucher un seul. La liste se derive dans scripts/voutes.py.

CE QUI BORNE LE POUVOIR N'EST PAS LA LISTE MAIS LA PRESENCE DES FICHIERS. Sur le
poste, toutes les cles sont la — c'est l'humain qui les detient toutes, et P03 lit
les inventaires de tous les freres. Sur un runner, une seule existe. Le code est
identique, le pouvoir ne l'est pas.

DEUX PREUVES ONT DIT CE QUI MANQUAIT. P03 est tombee des la separation : elle lit
les inventaires voisins, donc il lui faut leurs cles — c'est elle qui a etabli que
la liste devait couvrir le voisinage. P16 s'est mise a SAUTER : sa garde ne
connaissait que ANSIBLE_VAULT_PASSWORD_FILE. Une preuve sautee se lit trop
facilement comme une preuve passee.

COUT ASSUME : le chiffre et sa cle cohabiteront sur la meme machine des que les
runners recevront la leur. Le pari tient parce que le perimetre est borne.

Les six voutes sauvegardees avant rechiffrement. L'ancienne cle maitresse reste
sur le poste : la retirer est une decision, pas un nettoyage.

make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec, 0 saute — sans
ANSIBLE_VAULT_PASSWORD_FILE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 14:23:33 -04:00
ef832d11d6 insemination : la cle d'amorcage, bornee au meme groupe que le flux
Le flux etait declare et applique ; il manquait l'IDENTITE. Le runner du SITE
atteignait la porte de ops-01 sans avoir de cle.

LE PIEGE COMPTE PLUS QUE LE CORRECTIF. La cle s'injecte au CLONAGE, et `creer-vm`
cree TOUTES les machines d'un tenant. L'injecter a chaque clonage aurait donne a
l'hebergeur un acces SSH a la flotte entiere de chaque locataire, en silence — ca
aurait defait a la couche IDENTITE ce que le pare-feu venait de borner a la couche
RESEAU. Le meme critere gouverne donc les deux : porter `serveur_ops_tenant`.
`SETOPS_CLES_AMORCAGE` est vide partout ailleurs. Elle S'AJOUTE a celle de
l'exploitant, elle ne la remplace pas : c'est l'humain qui arme.

La cle vient du PLAN DU SITE, pas du disque local : materialiser depuis le poste
et depuis le runner doit produire la meme VM.

DEUX COUCHES MANGEAIENT LES ESPACES. Proxmox rendait `SSH public key validation
error` — message muet sur la cause. Isole par un CONTROLE (rejouer sans la cle :
la tache passe), puis par la mesure de ce qui arrivait au module :

    "sshkeys": "ssh-ed25519"     <- le premier mot, rien d'autre

J'ai accuse `make` d'abord ; c'etait `ansible-playbook -e cle=valeur`, qui decoupe
AU SHLEX. D'ou l'environnement pour le transport et `-e '{...}'` en JSON pour
l'entree. (Un scalaire YAML plie ne produit pas non plus de saut de ligne.)

TROIS TESTS QUI NE GARDAIENT RIEN. `test_inventory_host` inscrit ses tests dans une
liste explicite ; mes deux nouveaux n'y etaient pas — definis, jamais joues. La
garde d'exhaustivite ajoutee en a trouve un TROISIEME le jour meme,
`test_etiquette_vlan_repli_et_vide_explicite`, jamais inscrit depuis sa creation :
inscrit, il levait un KeyError sur une fixture qu'il lisait mal. Un test non
inscrit est pire qu'un test absent : on croit l'avoir.

PREUVE, AVEC SON CONTROLE NEGATIF :

    runner du SITE -> ops-01        ops-01  10.17.19.41/24   entre
    runner du SITE -> 10.17.19.21   Connection timed out     refuse

make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:45:44 -04:00
d3ba520500 insemination : declarer le lien, l'emettre d'un seul cote, et un test rouge
L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux
declarations, aux deux bouts, et rien d'autre :

    serveur_ops_site     egress  22/tcp -> serveur_ops_tenant
    serveur_ops_tenant   ingress 22/tcp <- runner_site         partage: true

ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme
ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute
la flotte du tenant.

L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable :
`creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer
son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree
pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui
reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant.

LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le
pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote
site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une
seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle
utile. La declaration du tenant pose sa regle nftables, et elle seule :

    ip saddr { 10.0.31.11 } tcp dport 22 accept

L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain
deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25.

Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE.

P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les
trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue.

DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le
sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a
refuse d'emettre vers un alias vide.

UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec
un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par
`bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne
joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans
`bande_basse_de`, aucun chevauchement n'est tolere.

make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:09:28 -04:00
c6f2cb1dd9 filiation : nommer l'insemination, et rendre a l'humain l'acte d'emancipation
La parente est la seule exception au zero-confiance, et elle est asymetrique :
un ecosysteme neuf ne peut pas s'amorcer lui-meme. Le SITE materialise le
terrain et amorce `ops-01` ; le tenant acheve le reste. Ce geste n'avait pas de
nom, et un geste sans nom ne se borne pas.

LE PARTAGE D'INTELLIGENCE QUI LE REND POSSIBLE, plus net que « le SITE ignore les
roles » : les deux runners portent le meme moteur et n'en consomment pas la meme
face. Le SITE lit `meta/flux.yml` — ce que les roles exigent du RESEAU — et en
tire SDN, pare-feu Proxmox, frontiere. Le tenant lit tasks/templates/integration
— ce qu'ils exigent de la MACHINE. C'est ce partage qui permet au SITE de poser
les bonnes regles sans jamais lire la configuration d'un tenant.

LE COUPLAGE EXISTAIT DEJA, NON DECLARE. `creer-vm` exige `_instance-requise` :
le runner du SITE a du basculer son symlink `instance` sur OPS-Chezlepro pour
materialiser ses VM, alors que son plan pose `serveur_ops_instance: ""`. Rien ne
borne sur quels tenants il porte ni jusqu'a quand.

L'EMANCIPATION EXIGE LA GOUVERNE D'UN HUMAIN. J'avais propose une condition
d'extinction MESUREE — le lien se ferme quand la machine constate l'autonomie.
Erreur symetrique de celle qu'on corrigeait, et le document disait deja pourquoi.
Le fruit de l'emancipation est livre a quelqu'un ; sans quelqu'un pour le
recevoir, il n'y a pas de livraison, seulement un lien qui tombe. Une
emancipation automatique serait une expulsion. Quatre lignes distinctes : le
moteur porte le lien, la machine INSTRUIT, l'humain ACTE, la machine PROUVE.

DEUX LIMITES NOMMEES PLUTOT QUE TUES.

Qui est le parent ? D-81 vide patient 0 de sa raison d'etre — plus un seul
lecteur depuis le 26 — et la machine hors flotte qu'il existe pour remplacer
alimente la forge qui fait autorite : le SPOF n'a pas ete elimine, il a ete
promu. Trois issues posees, aucune tranchee, aucune bloquante.

La separation des voutes est ORGANISATIONNELLE, pas cryptographique : les
fichiers sont separes (site-ops-01 ne detient que underlay.vault.yml) mais un
seul mot de passe ouvre les trois.

make prouver : CONFORME, 52 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 12:31:57 -04:00
35853ff5fb journal : les cinq commits du 27 n'avaient pas d'entree
Some checks are pending
verifier / verifier (push) Waiting to run
La regle 5 exige une entree de CHANGELOG par changement. Les cinq commits du
2026-08-27 n'en ont aucun : la session s'est interrompue avant. Trois entrees
les couvrent — P52 (materialiser sans entrer chez le tenant), P51 (les quatre
defauts de portabilite reveles par le runner, epinglage compris) et P50 (le
devis de la frontiere sait refuser sans consigner).

La carte comptait 28 pieces d'audit ; le rapport du jour en fait 29. P48 l'a vu,
comme la veille — c'est exactement ce qu'on lui demande.

make prouver : CONFORME, 52 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 10:55:59 -04:00
561c034eb4 flux : P49 — le registre des flux avait derive sans bruit
Some checks failed
verifier / verifier (push) Has been cancelled
`docs/registre-flux.md` est GENERE depuis les `roles/*/meta/flux.yml`, et c'est le
document qu'un humain lit pour savoir ce que le pare-feu laisse passer. Son
EXISTENCE etait verifiee depuis longtemps ; sa FRAICHEUR ne l'etait pas.

Il avait derive : la garde d'administration y portait encore `10.0.0.0/24` alors
que le reseau d'administration vaut `10.17.0.0/24`, deux flux `client_resolveur`
ajoutes depuis n'y figuraient pas, et un hote manquait des listes de sources. Un
lecteur y aurait lu un pare-feu qui n'existe plus.

L'inventaire avait deja sa garde — P03, le diff-vide du plan. Le registre des flux
est le meme genre d'artefact : genere, versionne, lu par un humain. Il lui
manquait la meme. `generer_registre` etant une fonction PURE, P49 la rejoue en
memoire et compare — une preuve qui repare ce qu'elle mesure ne mesure plus rien.

Controle negatif ideal, et il ne s'invente pas : la version commitee elle-meme.
Restauree, la preuve echoue ; regeneree, elle passe.

CE QUE CETTE DECOUVERTE CORRIGE AUSSI DANS MA TETE. J'avais decrit le symptome
comme « le runner salit ses propres clones » — une contradiction structurelle
entre un depot-clone et un repertoire de travail. C'etait faux, et la question de
l'exploitant l'a mis au jour. Regenerer un artefact DOIT produire un diff quand
les sources ont change ; ce qui manquait n'etait pas une architecture, c'etait une
garde. Un symptome observe depuis un seul endroit ressemble toujours a une
propriete de cet endroit.

Ce commit emporte aussi la regeneration elle-meme : le registre du moteur, et les
quatorze fichiers nftables de Chezlepro, remis en accord avec leurs sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:58:17 -04:00
8890c997af runner : serveur_ops_tenant — le runner d'un tenant recoit enfin sa voute
Some checks are pending
verifier / verifier (push) Waiting to run
La doctrine des runners decrit trois portees depuis le 2026-08-22 :

  calculer      plan -> inventaire        aucune voute        serveur_ops
  configurer    roles sur ses machines    voute du TENANT     <- revendiquee, jamais recue
  materialiser  creer/detruire des VM     voute du SITE       serveur_ops_site

La deuxieme ligne etait un trou. RIEN ne deposait jamais la voute d'un tenant sur
son runner. Un runner pouvait deriver son inventaire et ne rien pouvoir en faire :
chaque role qui demande un secret echouait sur son assertion, et l'echec ne disait
pas qu'il manquait un FICHIER, seulement que les valeurs etaient vides.

Decouvert en preparant la reconstruction de Chezlepro. Le runner du SITE peut
materialiser ses quinze machines ; il ne peut pas les configurer, parce que
Chezlepro a sa PROPRE autorite de certification — donc `client_pki` y reclame un
secret de Chezlepro. Le runner du site ne l'a pas, et NE DOIT PAS l'avoir : c'est
la ligne qui rend l'hebergement mutualise defendable.

`serveur_ops_tenant` est le symetrique exact de `serveur_ops_site` : un MARQUEUR,
pas un installateur. Il depose la voute `decrypt: false`, puis RELIT l'en-tete du
fichier depose — une voute dechiffree par accident est une fuite silencieuse. Le
dossier d'inventaire (`principal` ou `production`) se DECOUVRE sur la machine
plutot que d'etre ecrit.

Pourquoi un role a part et non une option de `serveur_ops` : donner sa voute a un
runner est un POUVOIR, pas un reglage. Le declarer au plan force a repondre a
« cette machine a-t-elle le droit de configurer cet ecosysteme ? » — une option
activee par defaut y repondrait a notre place.

CHEZLEPRO LISAIT ENCORE SON GENOME CHEZ PATIENT 0.

Trouve au passage, et bloquant pour la reconstruction : `serveur_ops_forge_amont`
pointait sur `10.29.16.11` — la forge de patient 0, l'amont d'avant que le site
ait la sienne. D-81 a tranche depuis. Laisse tel quel, Chezlepro se serait
reconstruit depuis un moteur perime, sur un reseau que le decoupage en zones a de
toute facon deplace.

Corrige vers la forge du site, PAR SON IP — `forge.genese.internal` appartient a
la zone souveraine du site, et un tenant ne resout que la sienne ; le certificat
SERVI porte bien `IP Address:10.0.33.11`. La racine de l'AC du site est desormais
versionnee a cote de la carte de la fabric a laquelle elle appartient, et son
chemin se DERIVE du symlink `underlay.yml` : il vaut depuis le poste comme depuis
un runner.

Le role est inscrit dans les quatre registres qui l'exigeaient — couches de
deploiement, graphe des dependances, catalogue des services, carte d'orientation.
Ce sont les preuves P08, P38 et P48 qui l'ont reclame, chacune a son tour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 15:30:51 -04:00
9e8679215d carte : P48 — l'index du mainteneur ne peut plus mentir
Some checks are pending
verifier / verifier (push) Waiting to run
`docs/carte-set-ops.md` est l'index du MAINTENEUR : l'ordre de lecture du corpus,
et surtout le catalogue des MECANISMES TRANSVERSES avec, pour chacun, OU IL VIT
DANS LE CODE. Son but est ecrit en toutes lettres : « ne plus re-deterrer ce qui
existe ».

Elle n'avait aucune garde, alors que `catalogue-services.md` a la sienne depuis
P38. Ses sept chiffres etaient faux — 54 roles annonces contre 60, 34 documents
contre 38, 15 pieces d'audit contre 27, 70 decisions contre 78.

Le defaut couteux n'est pourtant pas la. C'est le POINTEUR MORT : la carte dit ou
vit un mecanisme, quelqu'un ne l'y trouve pas, et le reimplemente a cote —
exactement la panne qu'elle existe pour prevenir. Aucun de ces nombres ne fait
travailler personne ; mais un document dont les faits verifiables sont faux cesse
d'etre consulte, et c'est alors ses pointeurs qu'on perd.

P48 verifie les deux : 84 chemins cites existent, et 7 chiffres correspondent a
la mesure. Controle negatif verifie sur les DEUX moities — un chiffre fausse, un
pointeur casse, la preuve echoue dans les deux cas.

QUATRE DISTINCTIONS ont du etre ecrites pour qu'elle ne soit pas fausse dans
l'autre sens : un gabarit de nom (`preuve-<date>.md`) decrit une forme, pas un
fichier ; un chemin hors depot (`~/.config/setops-vault-pass`) vit sur le poste de
l'exploitant, et c'est tout l'interet de la doctrine des voutes ; un fragment
(`meta/acces.yml`) vaut comme SUFFIXE, parce qu'un index se lit ainsi ; et un
artefact GENERE (`hosts.yml`) n'a pas a exister dans le moteur. Les quatre sont
nommees dans le code plutot que sautees en silence.

Le tableau « Le depot en chiffres » remplace les comptes en prose : ce qu'on
n'entretient pas, on ne l'affirme pas — ou bien on le fait recompter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 13:56:58 -04:00
87716cefc7 genome : la forge du SITE fait autorite (D-81), et make genome-etat le verifie
Some checks are pending
verifier / verifier (push) Waiting to run
Decision de l'exploitant : la forge du SITE fait autorite pour le genome. Toute
autre copie — y compris celle d'ou le moteur a ete pousse jusqu'ici — est un
MIROIR.

Un ecosysteme se reproduit depuis la forge de son site : c'est de la qu'il clone
son moteur, ses plans, ses modeles. Si l'autorite est ailleurs, cette forge
devient un cache qu'on croit a jour — et le 2026-08-26 elle etait quatre commits
en arriere sans que rien ne le signale, dont le correctif qui desarme le pare-feu
Proxmox.

UNE AUTORITE QU'ON NE VERIFIE PAS EST UNE AUTORITE QU'ON SUPPOSE.

`make genome-etat` confronte, depot par depot, ce que le poste porte a ce que la
forge porte. Il REFUSE en cas d'ecart plutot que de le signaler : un ecart connu
et tolere redevient un ecart oublie, et la commande qui le corrige tient en trois
mots. Il dit aussi quand la copie locale n'est pas propre — des commits pas
encore faits sont une autre forme de retard.

Mesure au passage, et traitee plutot qu'ignoree : le premier contact avec la
forge echoue une fois sur six — poignee TLS expiree, puis cinq reponses de suite.
Ce n'est pas le chemin, qui est prouve ; c'est l'acceptation TLS apres un temps
d'inactivite. Les deux cibles reessaient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:17:24 -04:00
6a5a49f924 site : le runner travaille, et le genome remonte chez lui
Some checks are pending
verifier / verifier (push) Waiting to run
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.

DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.

`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.

Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.

Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.

LE CERTIFICAT COUVRE LES NOMS DU SERVICE.

`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.

Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.

`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.

La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.

Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.

Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.

Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.

Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:52:37 -04:00
e5841ea684 dns : quatre zones inverses pour le site, et rien de plus
Some checks are pending
verifier / verifier (push) Waiting to run
Le site ne servait aucun PTR. `serveur_powerdns_zone_inverse` derivait d'un
supernet /16 — la forme d'un TENANT, qui tire tout de son index. Un site ne
derive pas : il declare plusieurs /24 et n'a pas de supernet unique, si bien que
la derivation rendait une chaine vide et qu'aucune zone n'etait generee.

Le site revendique desormais exactement ce qu'il occupe :

  31.0.10.in-addr.arpa   32.0.10.in-addr.arpa
  33.0.10.in-addr.arpa   34.0.10.in-addr.arpa

Revendiquer `0.10.in-addr.arpa` d'un seul geste aurait ete plus simple et faux :
cette zone couvre aussi la frontiere, le transit et les hyperviseurs, qui ne sont
pas a lui. Une autorite qu'on s'attribue sans l'exercer est une panne differee —
le resolveur repondrait NXDOMAIN pour des adresses qu'un autre sait nommer.

LA DERIVATION VIT DANS UN FILTRE (`zones_inverses`) parce qu'elle a DEUX
appelants : `serveur_powerdns` ecrit ces zones, `serveur_resolveur` les delegue a
l'autoritatif. Deux calculs separes finiraient par diverger, et la divergence ne
se verrait qu'au premier PTR interroge. Le nom d'une zone dit sa profondeur —
trois etiquettes numeriques valent un /24, deux valent un /16 — et le modele en
deduit seul la forme du PTR.

DEUX CHEMINS MORTS TROUVES EN ROUTE.

Les zones etaient servies, et personne ne les demandait. `serveur_resolveur`
deleguait la zone directe par une `stub-zone` mais pas les inverses : `dig -x`
rendait vide depuis les cinq machines alors que la meme requete posee directement
a l'autoritatif repondait juste. Un service correct derriere un chemin que rien
n'emprunte.

Puis, les stubs poses, Unbound repondait toujours NXDOMAIN avec le drapeau `aa` —
une reponse AUTORITAIRE, sans jamais consulter le stub. Il embarque des
`local-zone` pour tout l'espace RFC1918 inverse. `nodefault` n'y change rien : ce
mode n'agit que si le nom correspond EXACTEMENT a une zone par defaut, et la
sienne est `10.in-addr.arpa`, le /8 entier. C'est `transparent` qui laisse la
requete suivre son cours — pour nos quatre zones seulement, la ou
`unblock-lan-zones` aurait ouvert tout l'espace prive.

Rien ne distinguait ce blocage d'une absence : le meme NXDOMAIN qu'un nom qui
n'existe pas.

AUSSI : les zones inverses sont desormais validees par `named-checkzone` comme la
directe (un fichier mal forme etait refuse en silence par PowerDNS), et une zone
qu'un ecosysteme cesse de revendiquer est retiree du repertoire.

VERIFICATION. Les cinq PTR resolvent depuis les cinq hotes par le resolveur, les
huit noms directs par le plancher ET par le DNS, et les deux roles sont
idempotents (changed=0).

P47 evalue la derivation sur cinq cas — un site a quatre zones, un tenant a une,
deux machines d'un meme /24 qui n'en font qu'une, aucune adresse, une adresse
illisible. Controle negatif verifie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 20:58:50 -04:00
a74e35bf21 site : le plancher survit au redemarrage, et la zone dit les vraies adresses
Some checks are pending
verifier / verifier (push) Waiting to run
Le decoupage du site en quatre zones a deplace cinq machines. Ni le plancher
/etc/hosts ni la zone DNS n'avaient suivi. Quatre defauts, tous dans le moteur.

LE PLANCHER ETAIT EFFACE A CHAQUE DEMARRAGE — ET LE PREMIER CORRECTIF N'EN
ETAIT PAS UN.

`hosts_statiques` posait `99-setops-hosts.cfg` avec `manage_etc_hosts: false`,
pendant que `cloud_init` posait `99_setops.cfg` avec `true`. Dans `cloud.cfg.d`
l'ordre est LEXICAL et le dernier gagne : `-` vaut 0x2D, `_` vaut 0x5F. On a
donc retire la cle de `cloud_init` — le role qui POSSEDE le fichier decide —
puis renomme notre fragment `zz-` pour passer apres le `99_chezlepro.cfg` du
gabarit dore.

Et ca ne suffisait toujours pas. Redemarrage d'epreuve : plancher encore efface.
La cause reelle est ailleurs — Proxmox inscrit `manage_etc_hosts: true` dans la
USER-DATA de son lecteur cloud-init, et la user-data prime sur `cloud.cfg.d`
tout entier. Aucun fragment ne pouvait gagner ; renommer pour parler en dernier
ne servait a rien, le dernier mot n'appartenant pas a ce repertoire.

Ce que cloud-init regenere, il le regenere depuis `hosts.debian.tmpl` — le
gabarit le documente lui-meme. `hosts_statiques` le pose desormais avec le MEME
contenu que /etc/hosts, et une garde compare les deux a chaque passage.
Redemarrage d'epreuve : les neuf entrees sont la.

La garde precedente affirmait « conforme » en mesurant l'ordre lexical — vrai,
et sans rapport avec ce qui se passait. Une garde qui mesure la mauvaise chose
est pire qu'aucune.

LA ZONE DNS NE PUBLIAIT PAS LES NOMS DE SERVICE.

`forge.genese.internal` et `pki.genese.internal` — des noms que les certificats
portent et que les clients appellent — n'avaient aucun enregistrement. Seul le
plancher savait les resoudre. Trois causes empilees :

  - le plan du site coupait `serveur_powerdns_publier_expositions`, au motif que
    « le site n'a pas d'edge » : ca confondait PUBLIC et EXPOSE ;
  - `expositions_des_applications` rendait `domaine: None` faute de
    `domaines.yml`, et le modele de zone ecarte les expositions dont le domaine
    n'est pas la zone. Repli ajoute, symetrique de celui deja ecrit pour `edge` :
    sans domaine public declare, le domaine est celui que porte le FQDN ;
  - `serveur_powerdns` exigeait les deux registres et echouait si `domaines.yml`
    manquait — le meme tout-ou-rien que `hosts_statiques` a corrige le meme jour.

Puis `named-checkzone` a refuse la zone : `dns.genese.internal` heritait d'un
CNAME par defaut du role ET d'un A par exposition. La garde a bien joue son role
— elle a arrete une zone cassee avant qu'elle soit servie. Le plan l'emporte
desormais sur le defaut du role.

Un service ne doit pas dependre d'un plancher pour etre joignable : le plancher
est un filet, pas le sol.

VERIFICATION. Les huit noms — cinq machines, trois services — resolvent vers les
bonnes adresses depuis les cinq hotes, par le plancher ET par le DNS, et le
plancher survit au redemarrage.

P46 refuse desormais deux choses : plus d'un role ecrivant `manage_etc_hosts`,
et l'absence du gabarit maitre. Controle negatif verifie.

RESTE NOMME, PAS CORRIGE : la zone INVERSE. `serveur_powerdns_zone_inverse`
derive d'un supernet /16 — la forme d'un tenant. Un site declare plusieurs /24
et n'a pas de supernet unique : aucune zone inverse n'est generee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 20:31:01 -04:00
ac48b7650b proxmox : le pare-feu ne s'arme que dans le SDN — et 45e preuve
Some checks are pending
verifier / verifier (push) Waiting to run
Le decoupage du site en quatre zones a revele un defaut qui dormait dans le
moteur. `firewall=1` sur l'interface d'une VM fait passer tout le trafic ponte
par conntrack. Sur un VNet SDN c'est sans consequence : en EVPN le routage
inter-VNet se fait dans le VRF, SUR LE NOEUD, et le flux ne quitte jamais
l'hyperviseur. Sur un pont classique route par une frontiere externe, deux VM du
MEME noeud dans deux VLAN differents ne se parlent qu'en EPINGLE : la trame sort
par le lien physique, la frontiere la route, elle revient sur le meme pont. La
meme table conntrack voit alors les deux moities de la connexion, classe le
retour INVALID, et PVEFW-FORWARD le jette.

La mesure a tranche :

  ops(asgard)  -> pki(asgard)     0/8    dns(gandalf) -> cache(gandalf)  0/8
  ops(asgard)  -> forge(vishnu)   6/8    dns(gandalf) -> pki(asgard)     6/8
  ops(asgard)  -> cache(gandalf)  8/8    dns(gandalf) -> forge(vishnu)   8/8

Toutes les paires intra-noeud echouent, toutes les paires inter-noeuds passent.
Douze tentatives faisaient monter le compteur `ctstate INVALID` de +112 sur
asgard et +116 sur gandalf ; `firewall=0` pose, il ne bouge plus — 0 sur les
deux, pour le meme trafic.

Le defaut se deguise en panne reseau : la poignee TCP ABOUTIT, et ce sont les
paquets de DONNEES qui disparaissent. L'AC le disait dans son propre journal
(`TLS handshake error ... i/o timeout` : elle accepte et attend un ClientHello
qui n'arrive jamais). Ecartes un par un, par la mesure : regles identiques champ
par champ, alias corrects, assignation des interfaces confirmee, ARP et routes
saines, IPS desactive, shaper vide, NAT source limite a `wan`, MTU a 1500 de
bout en bout — et la taille sans effet, 100 octets se perdant comme 1460.

L'INTENTION DECLAREE NE SUFFIT PAS : un tenant declare
`proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme
valeur PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui
retombe sur `vmbr1` naitrait arme sur un pont classique. `cloner_vm_debian.yml`
croise donc l'intention avec le pont REELLEMENT utilise, et le dit quand il
desarme — un desarmement muet serait le meme piege, en silence.

P45 EVALUE l'expression du playbook sur quatre cas plutot que d'en lire le
texte, dont un qui DOIT rendre vrai : sans lui, une expression constamment
fausse passerait la preuve sans rien garantir. Controle negatif verifie — le
drapeau remis a plat fait echouer la preuve.

Ce commit emporte aussi le desarmement d'`unattended-upgrades` dans
`common_packages`, jusqu'ici applique sur les machines mais pas versionne : le
verrou dpkg tenu 48 minutes par une vague de maj automatiques a coute deux
deploiements.

Le site a revele ce defaut parce qu'il a ete le premier a porter plusieurs
zones. Ce n'est pas une particularite du site : un tenant derive les siennes du
meme principe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:50:53 -04:00
1a7c042e5b site : quatre zones d'autorite, et l'ordre inscrit dans les integrations
Le site n'est plus un /24 plat. Une zone par nature d'autorite — pilotage, autorite,
genome, service — chacune son VLAN et sa patte sur la frontiere. L'inversion corrigee,
mesuree : les cinq VM du site n'avaient AUCUN filtrage est-ouest, contre policy_in=DROP
sur une machine de tenant. La plus autoritaire etait la moins protegee.

Filtrage nord-sud par choix de l'exploitant : un seul point de police, un seul devis.
90 -> 117 regles. Chaque flux `flotte` produit une regle par zone SOURCE, destination
nommee — ce qui etait gratuit devient police.

L'ORDRE FAIT PARTIE DE L'INTEGRATION. client_pki tournait en parallele sur tous les hotes ;
sur celui qui porte l'autorite il recharge step-ca, et les quatre autres echouaient dans
cette fenetre sur `TLS handshake timeout` — un message qui accuse le reseau. Chaque
integration declare desormais sa dependance dans meta/integration.yml, les playbooks en
sont le miroir genere, et P44 refuse l'ecart (4 controles negatifs). `serveur: ~` est une
reponse valable : client_metrique pose un exportateur qu'on vient LIRE.

Autres defauts du meme soir :
  - le plancher /etc/hosts venait APRES le premier apt, qui vise le cache par son NOM :
    boucle fermee des que les adresses changent. Il ne s'installe pas, il rend installable.
  - dns_amorcage ecrit en dur a eu tort deux fois ; il se derive de serveur_resolveur.
  - l'ACL du resolveur derivait d'un seul sous-reseau : trois zones refusees sur quatre.

ET UNE ERREUR A MOI : j'ai diagnostique un trou noir de MTU et declare 1450. Faux — pas de
VXLAN sur ce chemin, tout est a 1500 de bout en bout. Ma mesure etait reelle, mon
interpretation non : je venais de debrancher la carte a chaud, et chaque `ip link set mtu`
reconfigurait l'interface. C'est la reconfiguration qui debloquait, pas la valeur.

44 preuves vertes, ansible-lint profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:31:07 -04:00
158c3b314a preuve : P43 — la frontiere voit-elle les machines du site ?
Couvre ce qui a failli coûter 36 objets ce soir : le devis de la frontiere avait cesse de
voir le site et proposait de retirer tous ses alias et toutes ses regles. Harnais vert,
lint vert — seule la lecture manuelle du plan avant application l'a attrape.

La premiere version etait inutile, et c'est instructif : elle lisait le plan par
`devis_opnsense._machines_du_plan_site()`, la fonction meme dont la panne etait a
detecter. Eprouvee sur la regression reelle, elle SE TAISAIT — les deux voyaient le vide,
et elle concluait « rien a prouver ». Une preuve qui partage la source de ce qu'elle
verifie ne verifie rien.

La version retenue lit plan/serveurs.yml et plan/applications.yml DIRECTEMENT et confronte
au devis produit. Eprouvee sur la regression reelle : elle refuse et nomme la cause.

43 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:11:39 -04:00
ea67b75a15 dns : le site resout chez lui, et le socle cesse de le defaire
Mise au point de l'exploitant : la frontiere n'a pas de service DNS actif et gere. Un
Unbound y tourne — il repondait, ce qui m'a induit en erreur — mais un processus n'est pas
un service. La delegation de zone que j'y avais posee est retiree.

Et le site a son propre DNS : `dns_amorcage` pointait encore sur la frontiere, valeur d'un
moment ou site-dns-01 n'existait pas. Elle vaut desormais 10.0.3.51.

Deux defauts revelés au passage :

  - devis_opnsense lisait encore underlay.machines(), vide depuis le deplacement du plan.
    Il proposait de RETIRER 36 objets — tous les alias et regles du site. Aucune preuve ne
    couvre ce devis : c'est le plan avant application qui l'a attrape.
  - le socle defaisait la bascule de client_resolveur a chaque passage. Sa garde ne
    protegeait que l'hote du resolveur (127.0.0.1). Invisible chez un tenant, ou
    dns_amorcage vaut l'adresse du resolveur : la coincidence masquait le defaut. Ca ne
    s'est vu qu'en retirant la regle de pare-feu — les cinq machines ont perdu la
    resolution d'un coup.

L'amorcage ne s'applique plus que si rien de sense n'est en place. Verifie : socle
`changed=0`, les cinq machines restent sur 10.0.3.51.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:57:48 -04:00
b60946f19f forgejo : verifier l'etat du compte, ne pas faire confiance au drapeau
Toute l'API rendait 403 — ni 401, ni message de droits : jeton valide, identite reconnue,
requete rejetee. La cause etait `must-change-password` sur le compte d'administration.

Le role passait DEJA `--must-change-password=false`, corrige le 2026-08-23 sur la premiere
forge de patient 0. Forgejo 16 a ignore l'option : le compte de la forge du SITE est ne
avec le drapeau pose malgre elle. La correction d'il y a deux jours ne protegeait plus
rien, et rien ne le disait.

Le role ne demande donc plus a la creation de bien se comporter : il CONSTATE l'etat voulu
et le retablit, par une tache idempotente sans effet quand la creation a tenu parole.

Le genome vit desormais sur la forge du site : six depots, 606 commits, chaque tete
confrontee entre le poste et la forge — identique. L'ancienne forge reste comme second
remote : « une famille, pas un maitre ».

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:33:10 -04:00
43666cff6e site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler
J'ai repete toute la journee qu'un SITE n'est pas un plan. C'etait faux : j'en
reconstruisais un morceau par morceau dans underlay.yml sans le nommer. Ce qui est vrai,
c'est qu'un site ne DERIVE pas — il declare ses adresses parce qu'il EST le terrain — et
ne passe donc pas par `instancier`. Mais ne pas deriver n'est pas ne pas avoir de plan.

SITE-Chezlepro/plan/{10-intrants,serveurs,applications}.yml ; underlay.yml ne garde que
la fabric. Ce qui est MESURE d'un cote, ce qui est VOULU de l'autre. Les groupes viennent
du registre des applications, les gardes ont suivi le plan (4 controles negatifs).

Huit defauts, tous invisibles chez un tenant parce qu'il a toujours tout :
  - client_pki codait `infra-pki-01` EN DUR — vrai par coincidence de nomenclature ;
  - aucun moyen pour un service non-root de lire la cle (Forgejo tourne en `git`) ;
  - le certificat, PUBLIC par nature, restait en 0600 ;
  - l'unite Forgejo n'avait pas d'ExecReload : un cert renouvele aurait ete servi perime ;
  - le role ne savait pas servir TLS lui-meme (il y avait toujours un edge) ;
  - le cert ne couvrait pas le nom de SERVICE, faute d'`expose:` ;
  - les registres se chargeaient en tout-ou-rien : sans domaines.yml, plancher vide ;
  - le flux declarait 3000 en dur.

Le message accusait presque toujours autre chose : le DNS quand c'etait un nom faux, une
permission de fichier quand c'etait un port privilegie, rien du tout quand le plancher
s'ecrivait vide.

ROOT_URL est gravee dans les URL de clonage : la forge sert desormais sur 443, avec
CAP_NET_BIND_SERVICE, et son URL n'a plus de port.

Verifie depuis le reseau : Verify return code 0, https://forge.genese.internal/ -> 200.

42 preuves vertes, flux coherents (34 roles, 92 flux).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:15:34 -04:00
add94f2cbd underlay : un SITE n'a pas d'index — le vestige est retire
`index: 17` ne disait pas « voici mon adressage » : un site ne derive rien, ses machines
vivent sur un reseau de fabric hors de toute derivation. Il disait UNE seule chose — quel
supernet de tenant est le mien — pour autoriser un reseau du site a en occuper la bande
basse (D-77). Un reste de la coincidence hebergeur<->tenant chez Chezlepro, qui est les
deux a la fois ; ailleurs il aurait fallu inventer un index a un hebergeur qui n'heberge
pas son propre ecosysteme.

L'exception se declare desormais sur le RESEAU qui en a besoin, et elle NOMME le tenant
dont elle occupe la bande basse (`bande_basse_de: OPS-Chezlepro`). Plus vrai : un seul
reseau est concerne. Plus verifiable : le nom se resout dans le registre `tenants:`, donc
une faute de frappe est refusee au lieu de passer.

Quatre controles negatifs, tous refuses — tenant inconnu, exception absente, debordement
sur la bande des zones, et le mauvais tenant nomme.

42 preuves vertes, les quatre devis du panneau OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 12:11:20 -04:00
467b05cbc0 site : un ecosysteme complet — PKI, DNS, forge, cache, runner
Le site portait trois services sur les sept du modele `origine`. Sa forge servait LE
GENOME EN CLAIR et aucune de ses machines n'avait de certificat — donc aucun chiffrement
est-ouest, ce que la doctrine zero-confiance interdit. site-pki-01 (step-ca) et
site-dns-01 (PowerDNS + resolveur colocalises) rejoignent les trois autres.

Quatre defauts que le site a fait tomber, chacun invisible chez un tenant :

  - DNS bloque par notre propre default-deny. Un tenant a son resolveur DANS son reseau
    et ne traverse jamais la frontiere ; le site interroge la sienne. La regle est derivee
    de `site.dns_amorcage`, destination declaree, jamais `any`.
  - serveur_cache_site n'installe rien : il marque un cache et lit les variables de
    serveur_artefacts. Les defauts d'un role ne sont en portee que dans le play qui
    l'inclut — une dependance de role regle l'ordre ET la portee.
  - resoudre_idp partait meme avec OIDC desactive, et exigeait un plan. La resolution
    suit desormais l'usage.
  - le plancher /etc/hosts etait VIDE : `hotes_actifs` n'existait pas dans l'inventaire
    du site. Un role qui reussit en n'ecrivant rien est la pire forme d'echec.

Une machine du site peut desormais se configurer (`variables:`), appliquee en dernier :
ce qu'une machine declare d'elle-meme prime sur ce que le site declare pour toutes.

Verifie et non suppose : systemd disait `active` mais rien n'ecoutait sur 443 — step-ca
sert sur 8443. La zone souveraine resout et la recursion marche, mesurees sur la machine.

42 preuves vertes, ansible-lint profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 12:00:17 -04:00
a530d5f70e federation : c'est le SITE qui determine l'index d'un tenant
SITE et OPS sont deux classes distinctes. Le site decide de la fabric et du reseau et
PRODUIT les intrants ; le tenant les consomme et n'a d'intelligence que sur ses
applications, leur configuration et leurs integrations. Une valeur reseau qu'un OPS decide
est une valeur mal placee.

L'index est l'intrant reseau par excellence — supernet, sous-reseaux de zone, VLAN, VMID,
noms de VNet en descendent. Il etait declare DEUX FOIS : dans la nomenclature du tenant et
dans l'underlay du site. Et le site ne declarait meme pas qui il heberge : la cle
`tenants:` existait dans le code, jamais dans la carte — la decouverte se faisait par
balayage des dossiers freres.

`tenants:` devient un registre d'allocation (nom -> index). Le site alloue, le tenant
recoit, la nomenclature n'est plus que la copie verifiable d'une decision prise ailleurs.
Quatre gardes neuves, chacune eprouvee par un controle negatif, sous P23.

Un tenant qui s'emancipe recoit un index NEUF de son nouveau site : l'index n'est pas une
propriete du tenant, c'est une place sur une fabric.

42 preuves vertes, quatre devis du panneau OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 10:42:14 -04:00
fcde343ef0 devis reseau : un segment non etiquete n'appartient a aucune fabric de switch
`segment_physique: true` retire la cle `vlan`. devis_reseau.py ecrivait `r['vlan']` en une
vingtaine d'endroits : KeyError, /api/devis-reseau en 500, et LA VUE RESEAU DU PANNEAU
RESTAIT VIDE — une panne a deux couches de sa cause.

Filtre a la source plutot que colmatage : devis_reseau definit son propre
`reseaux_de_fabric` qui ecarte les reseaux sans etiquette, et les dix appels y passent.
Colmater les vingt occurrences aurait laisse la vingt-et-unieme.

Le bloc de gestion d'un switch echappait au filtre (il lit son reseau directement). Sans
etiquette, aucune `interface VlanN` n'existe : l'adresse va sur l'interface de gestion
native du boitier. Le devis le DIT au lieu d'inventer une syntaxe.

Expose au passage que bifrost-3 et bifrost-4 declarent leur gestion sur un segment qui ne
les traverse pas, a des adresses qui n'ont jamais repondu.

Les quatre devis du panneau repassent. 42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 09:51:57 -04:00
a6713ffdcd doctrine : patient 0 n'est pas un tenant — corriger la raison, garder le geste
Le commit dc92f01 justifiait le retrait des roles de site de patient 0 par « patient 0
est un tenant comme les autres, c'est meme tout ce qu'il prouve ». C'est FAUX.

Un tenant ordinaire existe POUR SES GENS : Chezlepro heberge de l'identite, du courriel,
de la collaboration. Patient 0 n'heberge que LA LIGNEE. Il est l'ecosysteme d'origine et
le detenteur du genome, et il le reste.

Ce qui le quitte, ce sont deux responsabilites de SITE qu'il tenait faute d'un SITE
capable de les porter — a l'epoque un site n'etait qu'un fichier de carte, sans machines.
Le SITE existe desormais comme objet a part entiere : le geste etait donc juste, seule sa
raison ne l'etait pas.

Le texte est corrige plutot qu'efface, dans le CHANGELOG comme dans les fichiers : une
doctrine juste appuyee sur une raison fausse finit toujours par se retourner.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 09:34:02 -04:00
0b6c036f8c ssh : retirer les drop-ins d'une nomenclature abandonnee
Mesure sur forge-01 (par l'agent invite, sans dependre du reseau) : quatre drop-ins SSH
coexistent la ou il devrait y en avoir deux. Les roles se sont appeles `chezlepro` avant
de porter le nom du moteur ; le renommage a change le fichier DEPOSE sans retirer le
precedent.

Et les paires divergent : MaxSessions 2 contre 10, MaxStartups 5:30:20 contre 10:30:60.
sshd retient la PREMIERE valeur rencontree pour chaque mot-cle et lit les drop-ins dans
l'ordre lexical — c'est donc l'ANCIEN fichier qui gagne. Une configuration qu'on croit
avoir remplacee reste aux commandes, en silence.

ssh_baseline et ssh_hardening retirent chacun le fichier de la nomenclature qu'ils ont
abandonnee, AVANT de deposer le leur, et notifient la meme validation. La liste est
declarative et ne contient que des noms reellement deposes un jour : supprimer un fichier
qu'on n'a jamais ecrit serait effacer la configuration d'un autre.

PAS ENCORE EPROUVE : les seules machines portant les anciens fichiers sont celles de
patient 0, hors d'atteinte depuis le poste. Ecrit, linte, reste a le voir agir.

42 preuves vertes, ansible-lint profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 03:40:24 -04:00
6715f0d5c5 clonage : quatre defauts que la premiere VM placee hors du noeud du gabarit a reveles
Toutes les VM naissaient sur le noeud du gabarit puis migraient. site-cache-01 est la
premiere a etre placee ailleurs, et elle a fait tomber quatre defauts enchaines :

1. l'URL de clonage visait le noeud de DESTINATION, alors que l'API veut celui qui DETIENT
   le gabarit ; la destination se dit par `target`. D'ou un 500 sur tout clonage
   inter-noeuds. Le noeud du gabarit se decouvre dans l'inventaire du cluster.
2. ce 500 etait avale par failed_when: false + no_log: true. Une assertion le releve
   desormais, message de l'API compris.
3. l'attente interrogeait nodes/<destination>/tasks/<UPID> ; la tache vit sur le noeud du
   gabarit. 60 tentatives x 10 s pour un clonage termine en 87 s, puis la suite qui reprend
   sans un mot. Le noeud se lit dans l'UPID lui-meme.
4. la garde d'apres-attente retombait sur `exitstatus | default('OK')` : une attente qui
   n'avait rien observe passait pour un succes. Elle exige d'avoir VU la tache s'arreter.

Et une taille de disque porte toujours son unite : `disque: 40` etait lu comme un
retrecissement, erreur toleree a raison — la machine naissait donc avec les 16 Go du
gabarit au lieu de 40, sans que rien ne le dise.

Le site declare enfin ce qu'il materialise (materialisation: gabarit, stockage,
clone_complet) au lieu de l'heriter du tenant actif.

Chrono : clonage reel 59 s et 87 s pour 3,3 Gio alloues.

42 preuves vertes, ansible-lint profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 02:44:21 -04:00
dc92f01fe4 frontiere : le devis voit le site — fabric, alias et regles
Un mot manquait au vocabulaire des flux. Le runner de SITE declarait son API Proxmox en
`externe`, qui se rend par !SETOPS_INTERNES — or les hyperviseurs SONT en RFC 1918 : la
regle les aurait exclus tout en ayant l'air d'ouvrir le flux. D'ou `fabric`.

Deux flux manquaient :
  - serveur_cache_site n'avait aucun egress : la chaine de caches se terminait sur un
    cache vide, et ca ne se serait vu qu'au premier apt update d'un ecosysteme neuf ;
  - serveur_ops_site n'avait que l'API : le shell des noeuds et l'API de la frontiere
    sont deux autres pouvoirs, declares a part.

Le devis ignorait les machines du site — dans aucun plan de tenant, donc invisibles a sa
boucle, zero regle rendue SANS RIEN SIGNALER. Il emet desormais SETOPS_SITE,
SETOPS_FABRIC, SETOPS_ADMIN_SITE et un alias par role, sur opt2 (arrivee reelle du
trafic) et lan pour le SSH, plus le NAT sortant du site.

Le socle s'applique au site : site_inventaire.py range ses machines dans serveur_debian.

Patient 0 ne declare plus serveur_ops_site ni serveur_cache_site : il est un tenant
comme les autres, c'est meme tout ce qu'il prouve. Inventaire regenere.

42 preuves vertes. Devis : 26 regles a creer, 5 a retirer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 00:49:04 -04:00
bf35b4f528 site : les machines de l'hebergeur sur leur propre reseau de fabric
VLAN 30 / 10.0.3.0/24, pont vmbr3 (bond3, sur les trois noeuds), passerelle 10.0.3.1
portee par la frontiere (OPT2). site-ops-01 en .11, site-cache-01 en .21.

Le repli intermediaire etait grappe-controle (192.168.11.0/24) : porte par un vrai pont,
mais de l'herite — des adresses sans avenir, derriere une passerelle qui n'est meme pas
la frontiere. Le VLAN 30 leve les deux : adressage de fabric coherent avec le transit
(vlan 40 -> 10.0.4.0/24), et sortie PAR LA FRONTIERE, donc policee.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 00:32:55 -04:00
6e126dd8a9 site : le chemin qui materialise les machines de l'hebergeur
- site_machines.py : traduit une declaration d'underlay en SETOPS_*, que cloner-vm
  consomme deja. Le clone reste le seul chemin eprouve.
- site_inventaire.py : inventaire DYNAMIQUE. Un site ne derivant de rien, sa declaration
  est deja sa forme finale — un hosts.yml genere ne rendrait rien plus inspectable.
  Les groupes sont les services : playbooks/groupes/<role>.yml trouve ses hotes seul.
- cibles site-decrire / site-inventaire / site-creer (CONFIRMER=true) / site-appliquer,
  toutes independantes d'une instance montee : le site existe avant tout tenant.
- playbook de groupe manquant pour serveur_cache_site, et son classement en couche.

Le premier garde-fou de site-appliquer refusait un GROUPE vide : il ne pouvait jamais
se declencher (GROUPE a un defaut global). Le vrai risque, observe en le testant : un
playbook de tenant contre l'inventaire du site ne matche aucun hote et sort avec 0 — un
succes qui n'a rien fait. Refus desormais de tout groupe absent de cet inventaire.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:11:32 -04:00
93dde5b4f2 site : un SITE n'est pas un plan — ses machines vivent dans l'underlay
La branche « adressage declare » du generateur de tenants est retiree. Un tenant se
derive de son index ; un site n'a pas d'index et ne derive de rien. Les faire passer par
la meme moulinette donnait une nomenclature de site vide de sens, et un site exclu des
devis par ABSENCE d'index plutot que par nature.

Les machines de l'hebergeur se declarent desormais dans underlay.yml, a cote des switches
et des hyperviseurs qui les portent. Six gardes neuves, chacune eprouvee par un controle
negatif.

Mesures qui ont corrige la carte :
  - 10.17.0.0/24 n'a pas d'etiquette VLAN (segment physique sur igb0 de la frontiere) ;
    le VLAN 10 de vmbr1 est l'ancien plan 10.0.0.0/24, vide.
  - aucun pont d'hyperviseur ne porte ce segment : trois sondes muettes, temoin positif
    reussi. Une VM y naitrait sourde — le validateur le refuse.
  - les machines du site vont donc sur grappe-controle (vmbr0), seul plan de l'hebergeur
    porte par un pont reel, avec passerelle et sortie.

Role d'hote neuf : passerelle_amont — un routeur reel que nous n'administrons pas.

42 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:02:05 -04:00
9960cfd68e instancier : accepter un adressage declare, pour les plans sans index
Un SITE decrit les machines de l'hebergeur : pas d'index, pas de cohabitation
avec les tenants, il vit dans le reseau d'administration. Rien ne peut donc
deriver d'un seed inexistant.

L'explicite (`ip`, `vmid`, `vlan`) gagne sur le derive, et les deux chemins se
rejoignent sur un seul jeu de hostvars. La garde reste entiere : une machine
sans adresse -- ni declaree ni derivable -- est toujours refusee.

Revele en preparant le plan du site : l'underlay ne dit PAS quel pont Proxmox
porte quel reseau. Les tenants ne s'en apercevaient pas, leur pont etant un VNet
derive de leur index. Un SITE n'en a pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:35:52 -04:00
85ef6e107e voute : demander le mot de passe une fois, pas quinze
flotte-creer appelait `make creer-vm` une fois par hote, et chaque appel
ajoutait --ask-vault-pass. Quinze machines = quinze invites, quatre en
parallele, avec la sortie redirigee vers des journaux : l'exploitant est harcele
par des invites qu'il ne voit meme pas.

L'idiome existait DEJA dans deployer-tout, et je ne l'avais pas cherche : ma
premiere correction inventait une seconde facon de manipuler un secret. Deux
resolutions d'une meme question finissent par diverger -- la lecon de P41,
appliquee a un mot de passe.

Les deux cibles partagent maintenant un bloc unique, VAULT_UNE_FOIS.

Et une garde que ma premiere version n'avait pas : `-t 0`. Sans elle, un appel
non interactif restait bloque sur une invite que personne ne lit -- constate en
verifiant ma propre correction, qui a pendu deux minutes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:14:08 -04:00
da211bd81b modeles : extraire le denominateur commun de patient 0
Patient 0 conflait trois roles : le denominateur commun, l'ecosysteme de
l'hebergeur de SITE-Chezlepro, et le detenteur du genome. Seul le premier est
generique -- il devient le modele `origine`.

Un modele n'a ni index reel, ni voute, ni parente, ni machines. Patient 0 avait
les quatre : c'est ce qui prouvait la confusion.

`origine` ne porte PAS serveur_ops_site ni serveur_cache_site : ce sont les
roles de l'hebergeur, et un client qui les recevrait aurait un pouvoir sur ses
voisins.

Trouve au passage : les six modeles prives portaient encore setops_plan_dir en
dur sur `instance/plan`. Corrige chez les instances il y a deux jours, il avait
survecu ici -- un deploiement par SETOPS_INSTANCE y aurait lu le plan d'une
autre instance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 18:17:07 -04:00
bf8e27ef85 pare-feu est-ouest : applique, et le port derive enfin compris des deux cotes
verifier_ports traitait depuis toujours un port non numerique comme « pas une
ecoute fixe ». Le generateur est-ouest l'envoyait tel quel a l'API Proxmox :
« invalid port 'derive' », six regles refusees. Une meme notion, comprise d'un
cote et pas de l'autre.

Sauter est la bonne reponse : depuis que le resolveur est la seule porte,
PowerDNS n'ecoute que sur 127.0.0.1:5300 -- aucune regle est-ouest n'a d'objet
pour lui. Les trois groupes t*-srv-powerdns sont retires.

Mais un flux qu'on n'applique pas doit SE VOIR : le devis recense et affiche les
ports sautes avec leur raison. Sans cette note, sauter proprement serait devenu
un trou silencieux.

Les deux devis sont clos. Flotte verifiee : DNS, Internet, apt, cache joignable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:58:54 -04:00
a1997774be frontiere : appliquee, et la contrainte de nommage d'OPNsense consignee
Devis clos : 0 a creer, 0 a retirer, 65 inchange + 15 routes. Flotte verifiee
apres coup -- DNS interne, Internet et apt sans erreur sur les cinq hotes.

OPNsense refuse un alias de 32 caracteres ou plus. La contrainte n'etait ecrite
nulle part et se manifestait a l'APPLICATION, pas au devis. nom_alias abrege
desormais (SERVEUR_ -> SRV_, comme le pare-feu est-ouest) et REFUSE bruyamment
si le nom deborde encore : un devis qui promet un objet que la cible rejettera
n'est pas un devis. Le role devient serveur_cache_site.

« RIEN N'EST APPLIQUE » signifiait « pas encore recharge », pas « rien ecrit » :
les objets etaient dans la config, le pare-feu en marche les ignorait. J'avais
lu le message a l'envers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:51:29 -04:00
fa7e656844 artefacts : le chainage des caches, et la regle qui appartient au SITE
Une regle inter-tenant n'appartient ni a l'emetteur ni au recepteur : c'est le
site qui autorise un flux entre deux de ses tenants, et son runner qui prepare
le terrain.

Deux silences fermes dans le generateur de frontiere. flux_frontiere() ne
retenait que les flux `externe` -- or deux tenants vivent sur des VLAN routes
par la frontiere, leur trafic la traverse. Et un egress vers un voisin recevait
!SETOPS_INTERNES en destination : le port ouvert vers l'INTERNET. La destination
est desormais nommee.

Deux roles parce que meta/flux.yml est statique : un role unique aurait declare
l'ingress pour TOUS les caches -- maillage complet, visible au devis.
serveur_artefacts_site porte l'ingress, serveur_artefacts l'egress vers l'amont.

Debian est desormais telecharge une fois pour toute la fabric. Le cache du site
ne voit que des requetes agregees, jamais quelle machine installe quoi.

Rien n'est applique : le devis se lit avant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:40:33 -04:00
6547eb2fac flux : le mot voisins_site, et l'aveu de ce qui manque pour s'en servir
Le registre ne savait pas dire « les autres tenants de ma fabric ». ingress +
externe signifie DEPUIS L'INTERNET : declarer ainsi un cache partage l'aurait
publie au monde.

voisins_site rend les supernets des tenants que CE SITE heberge, en reutilisant
devis_reseau.decouvrir_du_site() plutot qu'en ecrivant un second recensement.
Ma premiere version lisait un `federe` absent comme « non federe » et excluait
Chezlepro et Technolibre en silence -- la decouverte canonique dit l'inverse.

LE CHAINAGE DES CACHES N'EST PAS LIVRE. Aucun generateur ne rend ce mot : zero
regle 3142 au devis de frontiere. Les declarations ont donc ete RETIREES plutot
que laissees a moitie -- un flux declare que personne n'applique est le piege
que ce depot traque.

La difficulte est structurelle : les regles OPNsense s'evaluent sur l'interface
d'ARRIVEE, donc une regle inter-tenant doit etre posee sur l'interface du
VOISIN. Le generateur construit tenant par tenant, sur les interfaces de ce
tenant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:15:19 -04:00
db2d6f6bf0 resolveur : un par tenant, et non plus un par machine
Mesure avant de decider : ~21 Mo de RSS par VM pour 28 a 189 requetes servies.
Le gain de cache est negligeable ; ce qu'on recupere, c'est un demon au lieu de
cinq et un endroit a regarder au lieu de cinq.

Pas sur la frontiere : un resolveur de SITE devrait connaitre la zone interne de
chaque tenant, et un tenant dont le resolveur vit chez l'hebergeur ne peut plus
s'emanciper avec. La recursion est generique, la zone interne ne l'est pas.

L'autoritatif se replie sur 127.0.0.1:5300 -- derive de la colocalisation, pas
declare a la main. Son port devient `derive` dans meta/flux.yml : les deux lient
53 mais sur des adresses differentes.

Deux pieges. Unbound refuse d'interroger une loopback par defaut : sans lever
do-not-query-localhost, toute la zone rendait SERVFAIL. Et le plancher
/etc/hosts MASQUAIT la panne -- getent repondait, dig disait SERVFAIL. La tache
de validation du role avait raison contre moi.

client_unbound n'installant plus Unbound, son nom mentait : client_resolveur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:59:34 -04:00
70e57076e8 collabora : en natif, la derniere exception conteneurisee tombe
Le role lancait collabora/code dans Docker -- seule exception de la flotte au
principe « logiciel libre en natif ». Desormais : paquet coolwsd du depot amont,
systemd, derriere l'edge nginx. community.docker est retiree de requirements :
plus aucun role n'a besoin de Docker.

Eprouve avant d'ecrire, sur une Debian 13.6 reelle et sans rien installer :
apt-get install --simulate coolwsd resout jusqu'a « Conf coolwsd (26.04.3.1-1) »,
libgcc1 est fourni par libgcc-s1, et le depot CODE-deb est PLAT.

La configuration passe par un fragment systemd (--o:) : le coolwsd.xml livre,
439 lignes commentees, reste intact.

Trois outils du harnais ont pese. voute.py lisait les COMMENTAIRES : documenter
le nom d'une clef suffisait a l'exiger -- corrige, controle negatif fait. Et
verifier_intrants avait raison : une garde ecrite en deux morceaux promettait un
secret pour une console fermee ; reecrite en implication, elle dit le vrai
contrat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:23:35 -04:00
1363ebff59 acces : le runner peut enfin entrer, et trois silences fermes
ssh_baseline gere les cles d'administration declarees au plan, avec un `etat`
par entree : revoquer devient un changement de plan, pas une visite sur chaque
machine. Pas d'exclusive -- il effacerait la cle de cloud-init et fermerait la
flotte a tout le monde.

cloud-init reecrivait /etc/hosts a chaque demarrage et effacait le plancher de
resolution. Constate sur infra-dns-01 apres un redemarrage : six entrees
perdues, revelees deux jours plus tard par un apt update qui ne resolvait plus.

requirements.yml ne declarait pas ansible.posix ni community.docker, pourtant
utilisees. Ca marchait chez le mainteneur, pas sur un runner. Et le cache des
collections suivait l'existence du fichier au lieu de son contenu.

Enfin : deux `when` sur une meme tache, c'est un seul -- le dernier. Le
check-mode avait disparu en silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:50:37 -04:00
2b55761fba runner : separer le pouvoir de configurer de celui de materialiser
La ligne de partage est celle des voutes. serveur_ops calcule et configure --
voute du tenant, SSH chez lui. serveur_ops_site materialise -- voute du SITE,
API de l'hyperviseur, jamais de SSH chez un tenant.

Un runner par tenant qui materialiserait mettrait la voute du SITE en N
exemplaires. Un runner unique qui ferait tout traverserait le default-deny
inter-tenant et rendrait l'emancipation impossible.

Le role depose la voute CHIFFREE et relit l'en-tete apres avoir ecrit : sans
`decrypt: false`, Ansible dechiffre la source quand il detient le mot de passe
-- constate le jour meme, 776 octets en clair au lieu de 3465. Controle negatif
fait, la garde mord.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:30:03 -04:00
f248bb084f filiation : nommer les trois ages d'un ecosysteme
Un ecosysteme nait fils, peut rester mutualise par choix, et s'emancipe quand il
le decide. Le moteur avait rencontre ce motif trois fois sans le nommer :
client_artefacts_actif, serveur_ops_forge_externe, client_backup_cible.

serveur_ops_forge_externe etait citee par le registre des dependances ET par le
README, definie nulle part : l'exemption ne pouvait jamais s'appliquer. Elle
existe maintenant, avec un amont obligatoire.

Consequence pour les modeles : quatre n'ont pas de forge, ce ne sont pas des
lacunes mais des ecosystemes au premier age. Ils le declarent.

Reste a faire : l'instrument qui PROUVE qu'une emancipation a coupe le lien.
Sans lui, on croirait s'etre emancipe en restant dependant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 14:10:10 -04:00
e6c5f9f539 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>
2026-08-24 11:42:32 -04:00
32e1f6fbcd artefacts : la forge sert le code, il manquait qui sert les binaires
Some checks failed
verifier / verifier (push) Has been cancelled
serveur_artefacts (apt-cacher-ng) + client_artefacts, integration universelle
qui s'eteint quand aucun hote ne porte le service et RETIRE la direction posee.
Chez patient 0 : colocalise sur forge-01 -- la forge est la source, du code et
des binaires.

La preuve est le mode hors ligne : paquet en cache servi en 0 o/s, paquet absent
refuse par un 503. Tant qu'internet repond, un apt update qui reussit ne dit pas
d'ou vient l'octet.

Trois lecons : apt fait heriter Acquire::https::Proxy de la valeur HTTP (d'ou
403 CONNECT denied sur les depots tiers, et smallstep injoignable) ; un service
ne doit pas dependre de lui-meme pour se reparer (l'apt update du role passait
par le cache hors ligne) ; et rediriger 2>/dev/null, c'est choisir de ne pas
voir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:41:56 -04:00
a3f496a496 resolveur : patient 0 ne pose plus ses questions a personne
Some checks are pending
verifier / verifier (push) Waiting to run
Les hotes interrogeaient Quad9 alors que l'ecosysteme fait tourner son propre
autoritatif. Le mecanisme n'etait pas absent -- client_unbound etait desarme.

En l'armant, la zone s'est revelee FAUSSE : ni ops-01, ni forge.genese.internal,
le nom que l'ecosysteme publie. Meme cause que le vhost nginx et le plancher :
setops_plan_dir pointait le mauvais plan. Un service que personne n'interroge
n'est pas surveille, il est muet.

Quatre hotes bascules, forward-addr = 0 : Unbound recurse depuis la racine, il
ne renvoie a aucun tiers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:37:05 -04:00
3ec3768799 miroirs : les quatre depots utiles du genome suivent leur amont
Some checks are pending
verifier / verifier (push) Waiting to run
Le jeton de lecture seule porte read:repository SEUL -- mesure : organization,
package, user et admin rendent tous 403 -- et lit malgre tout les depots prives
d'organisation. La question laissee ouverte est tranchee par la mesure.

Eprouver un miroir authentifie demande le bon instrument : le justificatif n'est
pas dans le git config, Forgejo le range en base. Un ls-remote a la main rend
"could not read Password" et ne prouve rien. Le juge est mirror_updated, et il a
avance pour les deux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:10:31 -04:00
d94fca490f poste : le second symlink, celui qui separe exploiter d'engendrer
Some checks are pending
verifier / verifier (push) Waiting to run
Le poste ne clonait que le moteur et son plan : il savait configurer des machines
existantes, pas en creer. Placer une VM demande de savoir sur quelle fabric la
poser.

Patient 0 n'a pas d'underlay a lui -- il est TENANT de SITE-Chezlepro. Son poste
porte donc les deux symlinks de D-80, et quatre depots : le moteur, son plan, la
fabric qui le porte, les modeles.

underlay.vault.yml reste hors du genome : le poste lit la CARTE du monde
physique, jamais ses cles. Mesure depuis ops-01 : `make instancier` rend un diff
vide sans aucun secret, `make underlay-plan` refuse faute de voute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:24:28 -04:00
665b07ba82 serveur_ops : la difference entre une archive et une matrice
Some checks are pending
verifier / verifier (push) Waiting to run
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.

Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.

setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.

Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.

Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
8e125b71cb plancher : nommer ce qui est hors de l'ecosysteme
Some checks are pending
verifier / verifier (push) Waiting to run
Le plancher /etc/hosts ne savait nommer que ce que l'ecosysteme contient. Or un
ecosysteme enfant doit atteindre la forge dont il descend, et ce nom-la resout
vers l'adresse publique depuis l'overlay -- pas vers l'adresse du LAN, que la
frontiere bloque a juste titre.

hosts_statiques_externes accueille ces declarations. Le fichier etant regenere
integralement a chaque passage, une entree posee a la main ne tiendrait pas.

La poignee TCP disait "ouvert" sur l'adresse bloquee alors qu'aucune donnee ne
passait -- seule la livraison compte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 10:09:08 -04:00
7d71f37528 genome : les cinq depots vivent sur la forge de patient 0
Some checks are pending
verifier / verifier (push) Waiting to run
La boucle est fermee. Set-OPS-public (avec son etiquette SIGNEE v2026.08.21),
SITE-Chezlepro, OPS-Chezlepro, OPS-Patient0 et Set-OPS-Modeles sont sur la forge de
l'ecosysteme que le moteur vient de fabriquer. La filiation reste donc verifiable depuis
l'enfant, sans rien demander au parent.

Le genome existe desormais en TROIS exemplaires vivants et independants : eregion, le poste
de l'exploitant, patient 0. C'est le seuil a partir duquel reecrire l'histoire suppose de
convaincre plusieurs temoins — la propriete recherchee, obtenue sans blockchain, comme
effet secondaire de la lignee.

LE CHEMIN A DU SE PLIER A LA POLITIQUE. Le port 3000 n'est pas ouvert depuis le poste, et
le SSH de forge-01 refuse le transfert de ports (durcissement). Versement par paquets git
deposes sur l'hote, puis pousses depuis lui a travers l'API locale — donc par les crochets
de la forge, comme n'importe quel push. Aucune regle assouplie pour la commodite.

LE COMPTE DE SECOURS NE SECOURAIT RIEN. Forgejo exige par defaut un changement de mot de
passe au premier acces et refuse toute requete d'API tant qu'il n'a pas eu lieu. Or ce role
DESACTIVE la connexion locale (SSO d'abord) : aucun chemin n'existait pour ce changement.
Le compte administrateur etait donc inutilisable des sa creation, sur toutes les forges
deployees. `--must-change-password=false` est pose ; le mot de passe vient de la voute et
tourne deja par empreinte.

Cinquieme defaut revele par le meme ecosysteme. Aucun n'etait visible sur une flotte
debout : il fallait en construire une AUTRE, et s'en servir.

make verifier 41 OK, 0 echec, 0 saute ; lint vert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 03:09:34 -04:00
15cdb7d454 patient 0 debout — et quatre defauts que seul un ecosysteme DIFFERENT pouvait montrer
Some checks are pending
verifier / verifier (push) Waiting to run
Quatre machines, zero echec, la forge repond (service actif, ecoute 3000, HTTP 200).
forge-01 121 taches, infra-pki-01 109, infra-edge-01 92, infra-dns-01 84.

1. UN TIERS INTERMITTENT ARRETAIT TOUT. `packages.smallstep.com` repond une fois sur deux ;
   `get_url` abandonne a 10 s sans reprise. Mesure AVANT d'accuser le reseau : DNS
   resolvait, la poignee TLS aboutissait, 138 Ko depuis deb.debian.org passaient en 0,09 s,
   et le chemin acceptait 1450 octets en refusant 1500 — l'attendu exact en overlay. Le
   reseau n'y etait pour rien. Reprises posees sur les trois telechargements du chemin
   critique (serveur_step_ca, client_pki, serveur_forgejo). Une dizaine d'autres restent
   sans reprise : liste dans le CHANGELOG.

2. UN `register` A ECRASE UN CHEMIN DE FICHIER. En posant la reprise, j'ai enregistre dans
   `client_pki_cle`, nom que le role utilisait deja pour la cle privee. L'ecart s'est vu 70
   taches plus loin : `step ca certificate` recevait un dict serialise a la place du
   fichier et refusait « too many positional arguments ». Un register ecrit dans l'espace
   de noms de TOUT le role.

3. LE SSO SE DECLARAIT AU LIEU DE SE DERIVER. `serveur_forgejo_oidc_actif: true` en dur
   faisait cabler une source OAuth2 vers un Keycloak inexistant. Derive desormais de
   l'inventaire. Quatrieme manifestation en deux jours de la meme hypothese — le moteur
   supposait l'ecosysteme COMPLET — apres les intrants (P32), les bases (P35) et les
   dependances causales.

AUCUN de ces defauts n'etait visible sur Chezlepro, qui porte tout et tournait deja. Ils ne
pouvaient apparaitre qu'au premier ecosysteme DIFFERENT.

make verifier 41 OK, 0 echec, 0 saute ; make test inchange.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:53:44 -04:00
4af1fcd0e2 frontiere : un boitier injoignable etait lu comme un boitier vide
Some checks are pending
verifier / verifier (push) Waiting to run
L'exploitant lance `make frontiere-appliquer` dans son terminal : « a creer 0, inchange
53 + 15 routes — la frontiere dit deja ce que le devis dit ». Elle etait DEJA conforme.

J'avais annonce quelques heures plus tot « 89 a creer, 0 inchange, donc les regles
heritees sont invisibles a l'API ». FAUX. L'intrant `opnsense_api_url` pointait sur
10.0.0.1, l'adresse d'avant la migration du boitier : chaque lecture rendait
{"_erreur": ...}, le plan lisait `.get("rows")`, n'y trouvait rien, et concluait au vide.
Avec CONFIRMER, on aurait pousse une politique entiere EN DOUBLE.

TROISIEME FOIS EN UNE SOIREE, meme confusion — « pas de reponse » pris pour « rien » :
  devis_placement      iterait un dict d'erreur comme une liste
  devis_underlay       declarait morts les reseaux qu'il ne joignait pas
  appliquer_opnsense   lisait un boitier injoignable comme un boitier vide
Toutes les lectures de la frontiere passent desormais par une garde qui refuse en nommant
l'hote, la cause et le piege evite. Eprouvee contre l'ancienne adresse : elle refuse.

CE QUE CA DIT DE LA METHODE : ce n'est ni le harnais ni moi qui avons trouve, c'est
l'exploitant, en lancant la commande la ou il VOIT la sortie. Mes commandes s'executent
dans ma session ; il n'en voit rien. Une commande lente ressemble a un blocage, et un
blocage a une commande lente — j'ai conclu deux fois a tort avant qu'il ne regarde.

make verifier 41/41 ; le plan reel reste « rien a faire ».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 00:56:30 -04:00
5be3fbd5e1 dependances : un ecosysteme minimal n'est pas un ecosysteme incomplet
Some checks are pending
verifier / verifier (push) Waiting to run
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>
2026-08-22 14:54:00 -04:00
c18debc25e resolution d'instance : une seule, partagee — au lieu de neuf copies
Some checks are pending
verifier / verifier (push) Waiting to run
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>
2026-08-22 14:19:55 -04:00
bd1b897815 forgejo : apprendre SQLite, et retirer ce qui ne servait pas
Some checks are pending
verifier / verifier (push) Waiting to run
Doute de l'exploitant sur patient 0 : « je doute de la pertinence de pgsql ». Mesure
plutot que discussion.

REDIS NE SERVAIT A RIEN : le role serveur_forgejo ne le mentionne ni dans son app.ini, ni
dans ses defauts, et ne declare aucun lien. Heritage du modele `forge`. Retire du plan.

POSTGRESQL ETAIT EXIGE PAR LE ROLE : DB_TYPE = postgres en dur, resoudre_base sans
condition. Le doute etait fonde, le moteur ne savait pas faire autrement.

INTERRUPTEUR `serveur_forgejo_bd: postgres|sqlite`. En sqlite la base devient un FICHIER
sous serveur_forgejo_data. Ce que ca change ailleurs : rien. Le job de sauvegarde
`serveur_forgejo` emporte deja ce dossier ; PGSSLROOTCERT etait deja conditionne au mode
TLS ; et P35 lit desormais l'interrupteur (convention `<role>_bd`, group_vars de
l'instance puis defaut du role), donc n'attend aucune entree de registre. Une valeur
inconnue est REFUSEE au debut du role plutot que de retomber en silence sur PostgreSQL.

PATIENT 0 PASSE DE SIX A QUATRE MACHINES (Dovecot, Redis, PostgreSQL et sa VM). Sur la
machine dont tout descend, chaque service en moins est une chose de moins a defendre, a
sauvegarder et a rebatir. Et l'effet depasse patient 0 : une offre `forge` pour un petit
organisme cesse d'exiger une VM PostgreSQL.

LA NEUVIEME. En verifiant P35 sur patient 0, elle a rendu un verdict JUSTE SUR LE MAUVAIS
ECOSYSTEME : `plan = RACINE / "instance" / "plan"`, le symlink en dur. Neuvieme resolution
d'instance codee en dur en cinq jours. Ce n'est plus une serie de bogues, c'est une piece
manquante : une resolution unique et partagee, a faire en une fois et de tete reposee.

Enseigne : SQLite au glossaire (P39 l'exige desormais), et le README du role documente
l'interrupteur et ce qu'il ne change pas.

make verifier 40/40 ; make ci 40/40 ; lint et syntaxe du role verts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 13:10:05 -04:00
79461fdc38 filiation : signer, inscrire la parente, et compter les temoins
Some checks failed
verifier / verifier (push) Has been cancelled
L'exploitant : « j'ai une intuition : blockchain ». L'intuition visait le bon probleme —
une memoire partagee, verifiable, sans centre — mais la reponse etait deja dans git.

GIT EST DEJA UNE CHAINE DE HACHAGE : chaque commit porte l'empreinte de son parent, un
arbre de Merkle. Ce qui manquait n'etait pas la chaine mais l'AUTEUR : `user.name` est
declaratif, et toute la soiree du 20 des commits ont porte « Daniel Allaire » sans qu'aucune
preuve ne les lie a une cle (verifie : 8 commits, 0 signature, 0 etiquette).

POSE AUJOURD'HUI :
- signature par cle SSH (celle que la forge connait deja), etiquettes signees par defaut ;
  premiere etiquette v2026.08.21, verifiee par `git verify-tag` ;
- `.git-allowed-signers` VERSIONNE : qui clone verifie sans rien demander a la forge, et
  sans lui faire confiance. Retirer une ligne revoque pour la suite ; le passe signe reste
  verifiable ;
- `scripts/genome.py` + trois cibles make : les QUATRE depots sans lesquels un ecosysteme
  ne renait pas (moteur, instance, hebergeur, modeles), DERIVES et non declares ;
- `parente.yml` par ecosysteme : de quel moteur il descend, a quel commit, sous quelle
  etiquette. Patient 0 descend de 742bcbf, etiquette v2026.08.21 ;
- P40 : la parente est inscrite, chaque depot se retrouve, chaque commit inscrit EXISTE
  encore (une histoire reecrite se voit la), chacun porte un remote. Sautee proprement
  quand l'instance n'est pas un depot git — le modele jetable de la CI.

POURQUOI PAS DE BLOCKCHAIN. Elle resout : qui ecrit ensuite, quand personne ne fait
confiance a personne et qu'il y a de l'argent en jeu. Aucun des trois ici. Et la
multiplicite qu'elle achete cher, la lignee la produit comme effet secondaire : chaque
enfant porte une copie du code dont il descend, donc reecrire l'histoire suppose de
convaincre TOUS les descendants. Le jour ou l'Alliance certifiera, ce sera un JOURNAL DE
TRANSPARENCE (Certificate Transparency, Sigstore), pas une chaine.

ENSEIGNE, pas seulement pose : nouvelle unite « Filiation, signatures et temoins » (moule
en quatre temps), onze termes au glossaire, et P39 les exige desormais.

make verifier 40 OK, 0 echec, 0 saute ; make ci idem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:56:29 -04:00
1f47e9bca8 glossaire : le metier n'etait explique nulle part
Some checks are pending
verifier / verifier (push) Waiting to run
Demande de l'exploitant apres une soiree passee a croiser « strophe FRR », VRF, VNet et
nexthop-vrf : cet ecosysteme doit rester pilotable par un humain, idealement un seul ;
que chaque notion sous-jacente soit ENSEIGNEE.

MESURE AVANT D'ECRIRE : 40 termes employes par le depot et absents du glossaire — LDAP
184 fois, playbook 165, underlay 106, EVPN 66, VRF 33, LMTP 25. Le glossaire expliquait le
vocabulaire propre a Set-OPS (plan, index, voute, zone) et laissait dehors tout ce qui
vient du metier. Or c'est le metier qui perd le lecteur.

CE N'EST PAS UN DEFAUT DE REDACTION. La regle fondatrice du depot est qu'un humain pilote
sans IA. Chaque mot obscur retire une personne a la liste de celles qui peuvent reprendre
le systeme : un vocabulaire non explique est un defaut de CONCEPTION.

- Glossaire reecrit : 67 termes groupes par famille (plan, machines, Ansible, reseau,
  noms, confiance, identite, courriel, etat et preuve). Chaque entree dit ce que c'est ET
  pourquoi ce depot s'en sert, avec renvoi vers l'unite qui developpe.
- Unite d'apprentissage manquante : « Le reseau des tenants ». Dix-sept des quarante
  termes y vivaient sans domicile. Elle suit l'ordre ou les problemes se sont poses : deux
  clients sur un cable -> VLAN -> ses deux limites -> encapsulation -> pourquoi 1450 ->
  EVPN -> le VRF, qui n'est pas une interdiction mais une ignorance structurelle.
- Navigation : la nouvelle unite est au sidebar ; le plan de recette regenere (P22 l'a
  exige des l'ajout de la page — le harnais a mordu).

P39 verifie : chaque terme du jargon a une entree ; chaque lien du glossaire mene a une
page existante ; chaque page du wiki est atteignable depuis la navigation.

LA LISTE EST DECLAREE, ET C'EST UN CHOIX MESURE. La derivation automatique a ete essayee :
153 acronymes dans le wiki et le README, dont la moitie sont des mots francais en
capitales (AUCUNE, AVANT, TOUS). Un controle qui exige une entree pour « AUCUNE » finit
desactive, et une preuve desactivee ne garde rien. La preuve dit elle-meme cet angle mort.

EPROUVEE EN NEGATIF contre le glossaire d'avant : 49 termes manquants, nommes un par un.

make verifier 39 OK, 0 echec, 0 saute ; make ci idem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:15:55 -04:00
61488777ee placement : le devis validait un pont que la flotte n'utilise pas
Some checks are pending
verifier / verifier (push) Waiting to run
Remarque de l'exploitant en preparant patient 0 : « le pont ne me semble pas approprie du
tout, depuis qu'on cree des VNets pour des tenants ». Juste, et plus grave que cosmetique.

`make placement-plan` confrontait `proxmox_clone_pont` (vmbr1) au cluster. Ce n'est PAS la
que les VM de la flotte atterrissent : `instancier` pose dans chaque hote le pont DERIVE
de sa zone (le VNet du tenant), et `make creer-vm` le passe au clone en ecrasant ce
defaut. vmbr1 n'est que le repli des clones MANUELS, hors plan. Le devis mesurait donc un
objet qui ne sert pas, et ignorait celui qui sert.

SUR PATIENT 0 : avant, « pont vmbr1 existe -> CONFORME ». Apres, « reseaux VM : t29appl,
t29donn, t29fron, t29serv INTROUVABLE — passer `make sdn-appliquer` AVANT de creer les
VM ». Aucun de ses quatre VNets n'existe sur le cluster : le devis d'avant-vol declarait
conforme un tenant dont les VM n'auraient eu nulle part ou naitre.

D-80 avait pourtant ete corrigee le 2026-08-13 — la liaison de placement est noeud,
stockage et gabarit, le pont se derive. Le devis continuait de compter quatre objets et de
nommer le mauvais : une doctrine corrigee dans un document ne se propage pas toute seule
dans le code qui l'applique.

MESURE MAINTENANT : en `sdn`, les VNets derives confrontes a /cluster/sdn/vnets ; en
`switch`, les ponts du noeud retenu. Avec le geste correctif quand il en manque.

NON-REGRESSION sur l'ecosysteme de reference : ses six VNets existent, conforme, code 0.
Quatre tests (Cluster simule, aucun reseau touche), harnais 38/38.

Au passage, dans patient 0 : le commentaire annoncait « les QUATRE valeurs qui rattachent
un tenant a une fabric » — trois, et le pont n'en est pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:39:26 -04:00
254268d5f2 devis de placement : mourir n'est pas un diagnostic
Some checks are pending
verifier / verifier (push) Waiting to run
Soir de reconstruction, VPN pas encore monte. `make placement-plan` — le devis qu'on lance
AVANT quarante minutes de deploiement — rendait `AttributeError: 'str' object has no
attribute 'get'` : dix lignes de trace Python pour dire « le nom `asgard` ne se resout pas
d'ici ».

En panne, `Cluster.__call__` rend {"_erreur": "..."} — un DICT. Le devis l'iterait comme
une liste, et un dict itere rend ses CLEFS. `Cluster.rate()` existait pour ca et n'etait
appele nulle part ici. Les quatre appels passent desormais par une garde qui nomme la
cause, l'hote interroge et le geste a tenter (resolution, VPN, jeton).

ET LE DEVIS MESURAIT LE MAUVAIS TENANT. `placement_du_tenant()` lisait `instance/` en dur :
viser patient 0 avec SETOPS_INSTANCE mesurait en silence le placement de l'instance
montee. Le verdict etait juste — pour l'autre tenant. Les deux portaient les memes quatre
valeurs, ce qui est exactement la circonstance ou l'erreur ne se voit pas. C'est la
HUITIEME resolution d'instance ou d'inventaire codee en dur trouvee en trois jours ; a ce
compte ce n'est plus une serie de bogues, c'est une piece manquante.

L'en-tete annoncait aussi « tenant instance » — le nom du lien, pas celui du tenant.

TROIS TESTS, aucun reseau touche (Cluster simule) : une panne devient un refus lisible ;
une reponse qui n'est pas une liste est refusee — c'est le cas silencieux, celui qui
franchirait la premiere garde ; le cas nominal traverse sans gene. Branches sur `make test`.

Verifie ensuite contre le cluster reel : le devis nomme « OPS-Patient0 » et confirme ses
quatre objets (asgard, TrueNAS, vmbr1, gabarit 99998).

make test OK ; make verifier 38/38 ; make ci 38/38.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:26:32 -04:00
a68b9cd10e CI : le harnais ne se declenchait que par memoire
Some checks are pending
verifier / verifier (push) Waiting to run
Trente-huit preuves, des tests, un lint — et RIEN ne les executait sans qu'un humain tape
`make`. Le meilleur atout du depot dependait de ne pas oublier. Il a desormais une CI
(.forgejo/workflows/verifier.yml) et une cible qui la rejoue a l'identique : `make ci`.

CE QUE LA CI A TROUVE AVANT D'EXISTER. Ecrire le workflow supposait de repondre a une
question jamais posee : est-ce qu'un depot PUBLIC, seul, se tient ? Mesure sur un clone
nu : non, a cinq endroits.

- `make instancier` echouait sur le modele public — le tout premier geste du QUICKSTART.
  Le Makefile forcait `principal/hosts.yml` alors que le modele vit en `production/` ; sa
  precedence suit desormais celle du code (fichier, puis REPERTOIRE existant, puis defaut).
- P32 parcourait les 54 roles sans regarder ce que l'instance deploie. Elle passait sur
  l'ecosysteme de reference PARCE QU'IL PORTE TOUT. Or les modeles sont des OFFRES : toute
  offre plus petite que l'ecosysteme complet echouait son propre harnais, pour des services
  qu'elle ne vend pas. Le perimetre se lit maintenant du plan (groupes de l'inventaire,
  puis roles composes par leur playbook).
- P24 : le modele public ne declarait aucun reseau d'administration — une flotte qu'on
  construit et ou l'on n'entre plus. `nftables_admin_ssh` est pose, avec le pourquoi.
- P33 : verifier_ports.py codait `instance/inventories/principal/hosts.yml` en dur.
- P32 et P24 lisaient le symlink `instance/` au lieu de SETOPS_INSTANCE.

Toutes de la MEME FAMILLE que P03 avant-hier : une resolution d'inventaire recopiee, une
variable d'environnement qui deborde de sa portee. Le depot en compte SEPT ; deux de plus
sont corrigees ici, et la septieme le dit en commentaire plutot que de le taire.

`make ci` NE TOUCHE AUCUN SYMLINK : le modele public est monte comme instance jetable,
vise par SETOPS_INSTANCE/SETOPS_UNDERLAY, detruit en sortant. Deux details mesures parce
que devines faux d'abord : l'instance jetable est un DOSSIER FRERE (la federation se
decouvre ainsi ; ailleurs, quatre preuves tombent) ; et SETOPS_UNDERLAY n'est pose QUE
pour la verification, sinon l'inventaire est ecrit avec une fabric et regenere avec une
autre — la commande fabriquait l'ecart qu'elle denonce.

RESULTAT : clone nu sans instance ni frere -> 38 OK, 0 echec, 0 saute. Depot de
l'exploitant avec ses 3 instances -> 38 OK, 0 echec, 0 saute. Aucun residu.

Et le lint du depot a refuse mon propre fichier de CI avant qu'il ne tourne une seule fois
(`on:` lu par YAML comme le booleen vrai). Le harnais mordait deja.

A AJUSTER AU PREMIER PASSAGE, ecrit en tete du workflow : l'etiquette `runs-on` doit
correspondre a un runner Forgejo enregistre, et le runner a besoin du reseau pour pip et
ansible-galaxy. Le vert de cette CI dira que le moteur et son modele public se tiennent —
pas que la flotte va bien : aucune VM jointe, aucune voute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 19:29:46 -04:00
f01f06df5e catalogue : la carte des services avait quatre mois de retard sur le moteur
`catalogue-services.md` est le document qu'on lit pour savoir ce que Set-OPS FAIT :
l'hebergeur d'un second site, un futur client, un mainteneur qui arrive. Verifie role par
role contre roles/, voici ce qu'il disait de faux.

- « Capacites futures encore a implementer : collaboration (Nextcloud/Collabora) et couche
  web (frontal/dorsal) » — les quatre roles existent, collab-01, web-frontal-01 et
  web-dorsal-01 sont ACTIFS, et les deux roles web sont codifies depuis les spikes du
  2026-07-05.
- « La federation LDAP n'est pas automatisee dans le role ; Keycloak n'est pas expose » —
  serveur_keycloak/tasks/federation-ldap.yml existe, et le plan declare
  `expose: auth.<domaine>`.
- `infra-mail-01` : « Sendmail MTA » — c'est Dovecot ; Sendmail est retire depuis le
  2026-07-04. La table des hotes datait d'avant la separation edge-mta / mailstore.
- `client_supervision` annonce comme integration — n'a JAMAIS eu ni role ni playbook. La
  supervision ne pose rien sur les hotes : controles actifs depuis le coeur, resultats
  passifs pousses par l'API (c'est backup-01 qui rapporte l'etat de ses depots).
- Une colonne « Role » decorative inventait des noms (`nextcloud`, `client_metriques`) : le
  role porte le nom du GROUPE. Colonne retiree.

Et NEUF roles vivants ne figuraient dans aucune table — le socle, toute la pile courriel,
les sauvegardes, Icinga Web 2, oauth2-proxy, Unbound. Deux (`serveur_backup`,
`client_backup`) n'etaient nommes NULLE PART.

P38 — CE QUE P31 NE POUVAIT PAS VOIR. P31 verifie que tout est nomme et atteignable, pas
qu'un document dise vrai : une carte peut etre complete et perimee. P38 confronte le
catalogue au code dans les deux sens, et c'est la TABLE qui fait foi des deux cotes : tout
role figure dans une ligne de table (la prose ne suffit pas — la pile courriel y etait
racontee et introuvable pour qui lit un index), et tout groupe cite en table existe
reellement (role, ou playbook de groupe pour `serveur_durci`, qui en compose onze).

Deux exemptions nommees : la prose peut citer les roles RETIRES, sinon on ne peut plus
ecrire d'ou l'on vient ; et P38 ne juge pas si une description est JUSTE — cela se revoit
contre le CHANGELOG, le mecaniser serait se mentir.

EPROUVEE EN NEGATIF : rejouee contre la version d'avant, elle echoue en nommant les neuf
roles absents et les trois cases fantomes.

Le catalogue dit aussi desormais ou il s'arrete : la reconstruction prouve qu'une machine
nue atteint l'etat voulu, pas la tenue sous charge ; et l'usage reel de Nextcloud n'est pas
consigne comme preuve. Lacune nommee au passage : la dependance causale
serveur_web_frontal -> serveur_nginx n'est toujours pas declaree.

make prouver : 38 OK, 0 echec, 0 saute. make test inchange.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:07:59 -04:00
98ab74d047 underlay : les tenants declares sont confrontes aux dossiers reels
Suite du filtre de portee : `underlay.tenants` nomme des DOSSIERS FRERES, et une faute de
frappe y etait invisible — le tenant disparaissait des trois devis du site, qui restaient
« conformes » sur ce qu'il en restait.

Sur un site a UN SEUL tenant — le cas de la prochaine implantation — la faute rend un
devis VIDE : une frontiere sans regle, un commutateur sans VLAN. Rien dans le mot
« conforme » ne dirait qu'on vient de dessiner le vide.

L'ecart est lisible sans toucher au materiel : d'un cote une liste de noms, de l'autre
les dossiers presents. Il se dit donc a `make underlay` (D-75). Quatre situations, quatre
messages distincts : dossier absent ; dossier sans plan/nomenclature.yml ; nomenclature
non federee (index absent, categories vide, federe: false) ; plus aucun nom qui
corresponde.

CE QU'UN GABARIT NE DOIT PAS SUBIR. Un modele decrit du materiel, pas un site deploye :
sans garde, tout modele portant un exemple de `tenants` echouerait chez quiconque n'a pas
ce dossier, et P17 deviendrait rouge sur la machine du voisin. La distinction existait
deja : modeles.py passe des reperes de tenants EXPLICITES (gabarit), le site les laisse
deriver. La verification ne s'applique qu'au second cas.

Trois tests dans test_adressage_derive.py — nom introuvable, clef absente, gabarit
epargne — avec un nom absurde pour qu'aucun test ne depende des dossiers de la machine.

make test 15 + 9 ; prouver 37 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:55:49 -04:00
3445ccb836 portee : les trois devis d'un site partagent enfin la meme regle
Le commit du 2026-08-14 nommait lui-meme ce qui restait : « meme hypothese ailleurs, non
corrigee — devis_sdn et devis_reseau partent du meme decouvrir(). A traiter quand ils
serviront sur un second site. » C'est fait AVANT, pas pendant la visite.

Les trois devis equipent le MATERIEL d'un site : la frontiere (regles, routes), le
commutateur (VLAN, SVI, routes) et le SDN de l'hyperviseur (zones, VNets). Un tenant
d'ailleurs y ajoutait des objets que le materiel accepte, qui ne correspondent jamais a
rien, et que rien ne signale.

UNE SEULE FONCTION AU LIEU D'UN FILTRE RECOPIE TROIS FOIS :
`devis_reseau.decouvrir_du_site()` = decouvrir() restreint par `underlay.tenants`, la
doctrine ecrite une fois. Le filtre inline de devis_opnsense est retire au profit d'elle.
`admin_tous_tenants()` la suit : le routeur d'un site n'a pas a savoir revenir vers le
plan de gestion d'un tenant qu'il ne porte pas.

EPROUVE dans les trois situations : underlay sans la cle -> les deux tenants, comme avant ;
underlay du second site -> OPS-Technolibre seul ; nom declare qu'aucun dossier ne fournit
-> ATTENTION et le reste est retenu ; filtre qui ne retient rien -> refus, code 1.

SANS EFFET SUR LE SITE ACTUEL : l'underlay de Chezlepro ne declare pas `tenants`, et cle
absente = toute la federation (verifie : decouvrir() et decouvrir_du_site() rendent la
meme liste ici).

ET LA CLE EST ENFIN DOCUMENTEE — c'etait le vrai trou. `underlay.tenants` existait depuis
le 14 sans figurer ni dans underlay.yml.example ni dans l'annexe du runbook
d'implantation : indecouvrable pour qui monte un second site.

prouver 37 OK, 0 echec, 0 saute ; make test inchange.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:38:30 -04:00
577c78e659 correction : 94 lignes de commentaire perdues, pas 121
Le 121 etait le total des lignes SUPPRIMEES au diff — il comptait aussi les lignes de
valeur deplacees par le retri des clefs. Compte des seules lignes de commentaire, les
quatre fichiers de l'enregistrement de 13:48 : 129 -> 35, dont ce qui reste est l'entete
que le panneau reecrit lui-meme. Soit 94.

Rien ne change au correctif ni a sa mesure (41 -> 41 lignes, 30 -> 30 commentaires, diff
d'une ligne). Un depot qui mesure ce qu'il affirme ne garde pas un chiffre approximatif.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:06:03 -04:00
ed84494c40 GUI : le panneau d'intrants effacait la memoire ecrite du depot
Un enregistrement du panneau « Intrants de base », a 13:48, a emporte 121 lignes
d'explications dans quatre fichiers — dont celle qui disait POURQUOI la valeur qu'on
venait de changer avait ete choisie (le plan de gestion reste en 10.0.0.0/24 tant que la
frontiere ne sait pas classer un second CIDR, D-61). Ces phrases sont la seule trace de
raisonnements qu'aucun code ne redit. `safe_dump` les effacait toutes a chaque
sauvegarde, en retriant les clefs au passage.

LE DEPOT CONNAISSAIT DEJA LE GESTE JUSTE. `_ecrire_intrants_fabric` (underlay.yml) et
`_ecrire_index_nomenclature` remplacent LA LIGNE sans toucher au reste ; leur commentaire
dit meme « un safe_dump les effacerait toutes ». Les quatre fichiers d'intrants n'avaient
jamais recu ce traitement. Ce qui manquait pour l'etendre : savoir remplacer une valeur
de LISTE, qui tient sur plusieurs lignes.

`_fusion_chirurgicale`, trois regles :
- une clef dont la valeur ne change pas n'est PAS reecrite (zero bruit au diff) ;
- les commentaires internes a un bloc remplace sont conserves, jamais juges ;
- une clef absente du fichier est ajoutee a la fin, jamais inseree au hasard.

La deuxieme est assumee : une explication devenue fausse survit a la valeur qu'elle
explique. Corriger une phrase est un geste humain ; l'effacer parce qu'un champ a bouge,
non. Meme principe que la fusion des clefs posee le 2026-08-10.

EPROUVE SUR LE FICHIER REEL, pas sur un exemple : l'enregistrement de 13:48 rejoue sur la
version d'avant (tiree de git) rend 41 lignes -> 41, 30 commentaires -> 30, 0 perdu, et un
diff d'UNE ligne. Neuf tests dans scripts/tests/test_gui_intrants.py, branches sur
`make test`.

DEUX FOIS LE MEME GESTE DESTRUCTEUR, SUR LE MEME CHEMIN : le 10 aout ce panneau perdait
des CLEFS (dns_amorcage, amorcage_acces_courriel — une VM qui nait sans resolution), le
18 des COMMENTAIRES. La premiere fois avait valu une fusion, pas un test. C'est le test
qui manquait.

Non touche, et dit comme tel : les registres (bases, applications, serveurs, domaines)
passent toujours par safe_dump. Ils sont structures et n'ont pas perdu de commentaires au
meme enregistrement — a reprendre si l'un d'eux en porte un jour.

make test 9/9 sur le nouveau fichier ; prouver 37 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:05:12 -04:00