Compare commits

...

437 commits

Author SHA1 Message Date
479ec3cc7d journal : l'arret de Loki attend un envoi d'Alloy (10 s), pas la copie
Correction de (109) : la liaison des morceaux fonctionne (copie ~60 ms) ; les 10,75 s viennent de l'arret propre de Loki, qui attend un envoi d'Alloy jusqu'a son delai de 10 s. Aucune perte mesuree ; laisse tel quel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:54:14 -04:00
9a4b63544e loki : les morceaux sont lies, pas copies ; chunks/index reste copie
Copier 6 550 morceaux arretait Loki 13 s au site ; les lier prend 40 ms. Ils sont adresses par leur contenu ; chunks/index loge delete_requests.gz, qui se reecrit, et reste vraiment copie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:44:04 -04:00
3e2846cc5f journal : le site recoit l'outillage de restauration et la copie a froid de Prometheus et Loki
Les machines sauvegardees du site n'avaient pas setops-restaurer. client_backup deploye au site : 0 echec ; Prometheus arrete 523 ms, un releve manque ; Loki 13 s, aucun journal perdu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:34:24 -04:00
cca22f47ca journal : Prometheus et Loki deployes chez les deux locataires, un seul releve manque
Frontiere sans changement ; pare-feu Proxmox et nftables de mon-01 ouverts a obs-01 ; premier depot : Prometheus arrete 85 a 151 ms, Loki 582 a 614 ms, ecart maximal 30 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:14:52 -04:00
4660a8892a prometheus et loki : copie a froid, restauration et temoins
Les metriques et les journaux repartaient de zero a chaque reconstruction. setops-copie-a-froid arrete le service le temps de copier et le relance quoi qu il arrive ; les roles remettent la copie avant de demarrer ; les temoins jugent la couverture, pas les fichiers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:57:04 -04:00
6141bc4b2f journal : le gabarit 99998 sort des declarations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:17:00 -04:00
e8907285d5 publier : les releases voyagent jusqu'aux deux forges ; wiki publie sur la forge du site
Le colis du genome ne portait que la branche : les etiquettes de release
n'arrivaient jamais sur la forge du site. Il les porte desormais, le runner les
pousse sans forcer, et la forge est relue par son API. publier.py pousse aussi
les etiquettes vers eregion. Le wiki, jamais publie sur la forge du site, l'est.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:04:31 -04:00
e9215a2141 journal : les deux locataires reconstruits sur 2cbfa2d, l'historique d'Icinga traverse
Technolibre puis Chezlepro, 43 min chacun, temoins sans ecart. L'AC d'Icinga
restauree garde l'environnement ; l'historique d'avant reste, 0 ligne orpheline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:12:05 -04:00
2cbfa2df9b journal : client_backup et serveur_icinga deployes, les temoins par cle voient l'historique perdu
Sur les deux locataires : l'AC d'Icinga est desormais sauvegardee. Rejoues sur
Chezlepro, les temoins par cle voient les 412 lignes d'historique perdues que le
comptage cachait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:35:35 -04:00
f04e790d2c icinga : l'AC et l'historique survivent a la reconstruction ; temoins par cle
L'historique d'Icinga DB etait exclu de la restauration (2026-09-30) : son
environnement derive de l'AC d'Icinga, recreee a chaque reconstruction. L'AC est
desormais un jeu de sauvegarde, remis avant api setup ; la base d'Icinga est
restauree, et son schema n'est importe que sur une base vierge.

Les temoins comparaient PostgreSQL par nombre de lignes et n'ont pas vu 412
lignes d'historique remplacees par 380 neuves. Ils comparent desormais par cle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:18:40 -04:00
12caff1cff devis de placement : le gabarit et le stockage que le clonage utilise (ceux du site)
Le devis lisait le gabarit du tenant : il validait chez Technolibre un 99998 que
plus rien ne clone, et refusait Chezlepro, qui suit la regle et n'en declare plus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:24:39 -04:00
543791e859 temoins : toutes les bases, modeles compris (template1 dite perdue a tort)
Premier essai reel sur Technolibre : pg_dumpall --clean recree template1, et
les bases vivantes etaient lues sans les modeles. Le test reproduit le filtre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:55:53 -04:00
11b5bb5733 temoins : l'instantane d'avant rasage est garde, et l'etat remis lui est compare
M4 a reconstruit Technolibre par le site qui la nomme ; les 7 jeux sont revenus
de l'instantane de 11:34, mais le premier depot d'apres reconstruction l'a chasse
par --keep-daily le jour meme : plus rien a quoi comparer.

Le depot d'avant rasage est etiquete avant-raser et garde jusqu'a la
reconstruction suivante. Le bilan dit ce qui a ete restaure et si c'est encore
au depot. setops-restaurer temoins et make temoins-etat comparent l'etat vivant
a cet instantane : une perte ou une identite changee est un ecart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:48:00 -04:00
a204ead8ab pre-vol M4 : raser et le devis de placement nomment le locataire designe
Sur le chemin TENANT=, raser annoncait un ecosysteme monte (rien n'est
monte sur le runner du site) et le devis de placement un tenant « ? ».
Les deux en-tetes disent desormais ce qui est vise et d'ou vient la liste.
Le pre-vol lui-meme est consigne : 13 VM, aucun conflit, placement conforme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:16:04 -04:00
e7f1a74040 contexte : materialisation M3, la reconstruction nomme son locataire
reconstruire_locataire lance locataire-raser et locataire-creer TENANT= sur
le runner du site, sans monter le depot du locataire comme instance.

Trou de M2 corrige avant tout usage : le sous-make cloner-vm nommait le pool
par l'instance montee (hors de tout pool sur le site, pool de l'autre
locataire sur le poste). devis_proxmox_pools.py --pool-du-locataire le
derive de la face.

test_appels_locataire.py : un faux ansible-playbook consigne la ligne qui
part ; 26 machines, ancien chemin, site et poste identiques a l'argument
pres. Temoin : l'ancienne ligne de pool fait echouer le test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:27:25 -04:00
8294e5dc71 contexte : materialisation M2, les commandes du site nomment leur locataire
locataire-creer, locataire-raser et placement-plan TENANT= lisent la face
reseau du locataire, sans monter son depot comme instance. flotte-creer,
creer-vm et raser acceptent TENANT= ; sans lui, l'ancien chemin est inchange.

P94 : creer, raser et placer par la face visent les memes machines, VMID,
valeurs et ponts que l'ancien chemin, chacun mesure par sa commande lancee
a part. 116 controles dans test_contexte.py, temoins compris.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:08:21 -04:00
4532a98707 contexte : materialisation M1, la face publie les parametres de clonage (P93)
Chaque machine porte ce que parametres-proxmox rend, par la meme fonction.
P93 compare a la commande lancee a part pour les 26 machines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:22:43 -04:00
40af2d1ec6 contexte : etape 3, les pools et les tunnels lisent la face reseau
devis_proxmox_pools et vpn_admin prennent machines, VMID, etat, pairs et
index dans la face publiee. Sorties identiques a un worktree complet de
la version precedente ; temoins d'alteration verts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:59:29 -04:00
2e9b31bef7 contexte : corriger le devis des pools vide (face sans fonctions)
La face publie aussi fonctions ; les pools retrouvent leurs VM. La
comparaison a l'ancien code est refaite dans un git worktree complet :
les sept sorties du site sont identiques. Garde de regression ajoutee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:49:24 -04:00
d0a6002b87 contexte : etape 3, le site decouvre ses locataires par leur face reseau
decouvrir_du_site part de underlay.tenants et prend index et zones dans
la face publiee. Les sept sorties du site sont identiques a HEAD, a
l'octet pres ; frontiere-plan ne voit rien a faire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:10:39 -04:00
013c1b5279 contexte : etape 3, la frontiere lit la face reseau
devis_opnsense prend dans la face publiee les adresses des groupes,
l'intrant d'administration, les verdicts, le tunnel et les zones
publiees. Devis identique a l'octet pres, a HEAD comme par le repli.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:02:03 -04:00
8c1e8c725b contexte : etape 3, le pare-feu Proxmox lit la face reseau
La face publie le verdict des flux conditionnels ; devis_proxmox_fw y lit
inventaire, verdicts et administration. Devis identique, octet pour octet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:51:37 -04:00
44c24e62e4 contexte : etape 3, le locataire publie sa face reseau, le site la lit (P92)
face-reseau.yml publiee chez chaque locataire (make face-reseau-publier) ;
les comptes de sauvegarde et le DNS public du site la lisent au lieu des
fichiers internes. Inventaire du site identique, octet pour octet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:39:28 -04:00
2beef04dc6 contexte : etape 3, l'instancier lit la fiche du site (P91)
Le site depose fiche-site.yml chez ses locataires (make fiches-site-
deposer) ; l'instancier y lit temps, delegation DNS et routage. Sans le
site, l'inventaire genere est identique au versionne, a l'octet pres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:23:17 -04:00
d6591cb271 contexte : etape 2 terminee, les sorties (P90)
resoudre_flux publie les sorties vers l'Internet de chaque machine ;
verifier_sorties les confronte aux regles de sortie de la frontiere.
Le contrat site <-> locataire est entierement publie et prouve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:21:37 -04:00
92a6fb0ead nftables_baseline : la passerelle n'est pas une porte (garde 8006)
La sonde connectivite essaie l'API Proxmox (8006) sur la passerelle par
defaut : chez un locataire en SDN, c'est l'hyperviseur dans son VRF, hors
de la frontiere. Ouvert -> CRITIQUE. Temoin : CONNECTIVITE_PASSERELLE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:12:37 -04:00
ce84d0e1ea frontiere : l'administration (P89) ; serveur_ops ne declare plus le 8006
La face reseau dit quelles entrees publiques sont ouvertes au poste ;
verifier_administration confronte gestion, VPN et tunnel. La sortie 8006
de serveur_ops vers externe ne laissait passer aucun paquet (hyperviseurs
en plages privees) et affirmait un pouvoir qu aucun locataire n a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:14:14 -04:00
56033afd00 serveur_ops : retirer la sortie 443 vers voisins_site (passage inter-locataires)
Ecrite quand la forge du genome vivait chez un voisin ; elle est au site.
Vue d un locataire, voisins_site designe les autres locataires : la
frontiere ouvrait le 443 de chaque runner vers le supernet de l autre.
Le clonage passe par la regle de serveur_forge_site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:38:18 -04:00
3c16ef45cd contexte : etape 2, la frontiere second temps, les entrees publiques (P88)
La face reseau porte le port de son tunnel ; verifier_entrees_publiques
confronte regles WAN, redirections et tunnel aux entrees que la face
ouvre a tous. Aucun ecart ; quatre alterations vues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:14:32 -04:00
78c2c25761 contexte : etape 2, la frontiere premier temps, les identites (P87)
La face reseau porte ses zones ; verifier_frontiere confronte supernet,
administration, tunnel, alias de groupe, routes et traduction sortante a
la face reseau et a la fiche du site. Aucun ecart ; cinq alterations vues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:28:06 -04:00
3d01208a68 contexte : etape 2, les flux de chaque machine (P86)
La face reseau porte les flux resolus par le locataire ; verifier_flux
les confronte au devis Proxmox, port par port. resoudre_flux publie les
sources_declarees d un port aussi public, que le locataire jetait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:10:12 -04:00
077c6c91b4 contexte : etape 2b, la face reseau du locataire (les faits)
locataire.face_reseau() publie index, machines, groupes, administration,
zones publiques et cle de sauvegarde ; verifier_face la confronte aux
consommateurs du site (decouverte, devis Proxmox, frontiere, inventaire
du site). P85 : aucun ecart ; huit alterations vues en ecart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:02:29 -04:00
8251022148 contexte : etape 2a, la fiche du site et la preuve qu elle dit vrai
site.fiche_pour(locataire) reunit attribution, offre, racine et les
trois lectures directes de l instancier ; verifier_fiche la confronte
aux copies, a la racine et a l inventaire du locataire. P84 : aucune
ecart sur les deux couples ; sept alterations vues en ecart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:16:55 -04:00
c891d7fb26 contexte : etape 1, le tronc commun et les deux classes (inutilises)
scripts/contexte.py : Ecosysteme, Site, Locataire, contexte actif
(fichier `contexte`, sinon les indices d avant via instance_courante
et underlay.chemin). 40 controles dans make test. Corrige aussi P34 et
P48, rouges depuis le commit de la page de conception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:43:20 -04:00
42e23885cd conception : un tronc commun, deux classes (SITE et LOCATAIRE)
docs/conception-contextes.md, arretee avec l exploitant : le releve
des devinettes de contexte et des echanges site <-> locataire, le
contrat en deux fiches, le poste selecteur, le chemin en neuf etapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:26:32 -04:00
0d7578b367 journal : release v2026.10.04, les deux locataires reconstruits sur f42d30b
Technolibre et Chezlepro, 42 min chacun, sans arret ni echec ; (73),
(74) et les corrections de (76) relus sur les machines neuves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:12:31 -04:00
f42d30b38a validation avant release : test casse par (74), trois defauts d outillage
test_restauration fournit client_backup_attente_verrou et exige
--retry-lock ; test_frontiere_refus lit le WAN sous if_wan ; prouver
--verifier nomme les preuves en echec ; le Makefile n exporte plus une
liste de cles de voute vide (valeur identique sur le poste).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:46:43 -04:00
5d7d2a8918 exporter : archives datees, et rien d ecrit si les voutes n ont pas change
Le nom par defaut etait fixe : le second export butait sur le premier.
Il porte maintenant la date (l heure en plus le meme jour) ; un export
de voutes identiques a la derniere archive n ecrit rien. Le LISEZ-MOI
et la doc restaurent la plus recente archive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:37:07 -04:00
1167cc12e0 client_backup : un depot verrouille n est plus une panne
Les quatre scripts qui touchent au depot appellent restic avec
--retry-lock (client_backup_attente_verrou, 30 min) : deux minuteurs
partis ensemble ne fabriquent plus de faux critique (aucun instantane,
depot corrompu). Eprouve sur un depot jetable, dans les deux sens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:27:11 -04:00
7976df08d5 client_backup : un instantane vide dit pourquoi
Devant un instantane vide, les sondes sauvegarde et restauration comptent
ce que les chemins sauvegardes contiennent sur le noeud : rien (RIEN A
SAUVEGARDER, avertissement), arrive apres l instantane (avertissement),
ou deja la a sa prise (critique : la sauvegarde manque ses donnees).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:06:31 -04:00
6570b757a4 journal : les deux locataires reconstruits, les correctifs tiennent
Technolibre en 44 min, Chezlepro en 42 min, sans arret ni failed ;
7 jeux d etat restaures chez chacun ; versions, epingles, vigie, vues
metier, filtres Alloy et synthese relus sur les machines neuves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:01:04 -04:00
5340220192 vigie : vues métier par clientèle interne, dérivées du plan
- metier: sur chaque sonde (47) ; catalogue des vues et services dans
  serveur_icingaweb2 ; vues Mes outils / Services rendus / Exploitation
- contrôles calculés comme serveur_icinga (sauvegardes, socle, matériel,
  frontière) ; vue vide non posée ; ancien exemple supervision retiré
- CHANGELOG 71

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:26:11 -04:00
055aed665e grafana : la synthèse comme page d'accueil
- setops.conf : GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH vers setops-synthese.json
- CHANGELOG 70 complété

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:43:33 -04:00
05ec7a8743 grafana : un tableau de synthèse, au site et chez les locataires
- tableau-synthese : faut-il agir, marge, activité, où regarder ensuite ;
  hyperviseurs au site seulement ; chaque panneau dit quoi faire
- CHANGELOG 70

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:12:00 -04:00
b3debd6ca9 grafana : le tableau Matériel dit quoi faire
- materiel.py : textes accentués et champ action par mesure (seuils inchangés)
- tableau-materiel : mode d'emploi en tête ; descriptions en trois temps
  (ce que ça mesure, seuils, quoi faire)
- CHANGELOG 69

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:49:28 -04:00
49f3d28865 forge et nuage : Forgejo 16.0.5, Nextcloud 34.0.4 ; Nextcloud sait monter
- serveur_nextcloud : tasks/monter.yml (maintenance, ancien code mis de côté,
  data/config/ajouts repris, occ upgrade) ; signature PGP vérifiée
- serveur_forgejo : 16.0.5 ; une montée se simule
- serveur_artefacts : le site sert aussi la signature de Nextcloud
- CHANGELOG 68

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:09:03 -04:00
16675037f5 identité : Keycloak 26.7.5, oauth2-proxy v7.15.5 ; les rôles savent monter
- serveur_keycloak : une montée remplace la distribution (arrêt, retrait,
  extraction) au lieu de construire les anciens fichiers sous le nouveau repère
- serveur_oauth2_proxy : repère = version du binaire ; empreinte SHA-256
  épinglée, archive refusée et retirée du cache si elle diffère
- CHANGELOG 67

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:32:10 -04:00
715f9de84d paquets tiers : les épingles tiennent, cache ou pas
- épingles posées en préférences apt (990) par chaque rôle et par le socle
  avant son dist-upgrade ; garde : paquet plus récent ou épingle inopérante
- quatre épingles relevées (step-cli, icingadb-redis, icinga-php-*)
- paquets_tiers : --force-confold aux mises à jour ; une mise à jour compte
  comme un changement
- CHANGELOG 66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:52:25 -04:00
badaf753f8 paquets tiers : épingles Grafana relevées sur ce que portent les locataires
- alloy 1.20.1-1, grafana 13.2.3, loki 3.7.8 (les runners avaient tiré
  la dernière version publiée ; le cache du poste aurait rétrogradé)
- CHANGELOG 65

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:33:31 -04:00
4ee149a0c6 vigie : psycopg2 sur l'hôte de la vigie ; base appliquée chez les locataires
- serveur_icingaweb2 : python3-psycopg2 avant la première requête vers sa base ;
  au site il venait de PostgreSQL, co-localisé, chez un locataire il manquait
- CHANGELOG 64 : déploiement relu chez Chezlepro et Technolibre

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:17:38 -04:00
f11bb11ad7 vigie : sa base dans tous les modes ; trois restes du site corrigés
- serveur_icingaweb2 : base de console résolue et utilisée en SSO comme en db
  (config_backend db) ; le badge de migrations la réclame sans condition
- serveur_icingaweb2 : php8.4-fpm et nginx avant les paquets tiers ; apache2
  retiré s'il part seul (apt-get -s purge)
- serveur_nginx : ssl_protocols de Debian neutralisé (doublon à chaque reload)
- site_inventaire : les hyperviseurs ne reçoivent plus le cache du site ni ses
  amorçages, qu'ils n'atteignent pas
- exemple de voûte : vault_bd_icingaweb2 dans tous les modes ; CHANGELOG 64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:04:51 -04:00
2f275d8a7d make : appliquer refuse un GROUPE non donné ; locataires corrigés et mesurés
- appliquer, deployer-groupe et site-appliquer refusent quand GROUPE vient
  du défaut du Makefile ($(origin GROUPE) = file) ; l'ancienne garde -z ne
  pouvait jamais se déclencher, et un make appliquer seul avait appliqué
  serveur_debian à tout Technolibre
- CHANGELOG 63 : déploiement chez les deux locataires relu sur 26 machines ;
  journal -77 % / -86 %, audit -72 % / -80 %, edge-mta-01 -96 % / -98 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:08:21 -04:00
b4ae92c03f journaux : le bruit de sonde du courriel filtré ; make appliquer accepte ARGS
- client_journal : sept stage.drop de plus pour Postfix (connect, lost
  connection after CONNECT, disconnect commands=0/0, SSL_accept error) et
  Dovecot LMTP ; phrase exacte, seulement depuis une machine sondeuse
- éprouvé sur une heure réelle de Chezlepro (69 % jeté, 96 % sur
  edge-mta-01, le scanner externe reste visible) et avec le binaire Alloy
  1.20.1 (9 lignes de sonde jetées, 7 légitimes reçues)
- Makefile : appliquer passe ARGS, comme site-appliquer
- CHANGELOG 63 : mesure des locataires ; cache de paquets du poste plus
  vieux qu'eux (alloy 1.19.2 / loki 3.7.7 contre 1.20.1 / 3.7.8)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:01:07 -04:00
8d40c687e1 site : journaux allégés, porteur de santé retiré des hyperviseurs
- client_sante : tasks/retirer.yml, joué sur hyperviseurs:!client_sante ;
  le minuteur resté depuis le 2026-09-10 visait 10.0.36.11 et échouait
  toutes les 15 min (une unité en échec permanente par hyperviseur)
- site_inventaire : plus de client_sante_icinga_url pour les hyperviseurs
- client_journal : loki.process jette le bruit de la sonde connectivite,
  phrase exacte et seulement depuis une machine de client_sante
- serveur_loki : log_level warn (Loki réingérait ses propres requêtes)
- auditd : règle never pour adjtimex de node_exporter (35 % de l'audit) ;
  nouveau handler augenrules --load, un restart d'auditd ne rechargeait
  pas les règles sur Debian 13
- audit : rapport de preuves du 2026-10-03, carte à 51 pièces

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:58:47 -04:00
8b008a8cf4 nextcloud : le groupe d'habilitation redevient admin apres une restauration
L'etape tournait avant la remise du Nextcloud restaure, sur une base ou le
groupe n'existait pas encore : la base d'avant revenait avec son etat d'avant.
sysadmin etait admin chez Technolibre, pas chez Chezlepro. Sortie dans
admin.yml et rejouee apres la remise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 20:26:03 -04:00
9414dca750 journal : la frontiere refuse a voix haute a l'interieur
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:10:40 -04:00
6a8fc53084 frontiere : les interfaces internes refusent a voix haute, le WAN reste muet
Un reject final, journalise, sur chaque interface interne (zones du site,
gestion, transit, WireGuard) : un flux mal declare entre deux zones echoue
au lieu d'expirer. P53 affinee, P50 ne compte que les block, test du devis.
La sonde classe ECONNREFUSED en avertissement : le RST de la frontiere est
indiscernable d'un port ferme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:39:46 -04:00
a4b6565056 journal : Chezlepro reconstruite en 41 minutes, sans arret
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:49:03 -04:00
7a2f179f39 repertoires partages : un seul mode par repertoire ; le depot de binaires du site sert de nouveau
/var/lib/setops etait tenu par quatre roles (0750 contre 0755) : apt-cacher-ng
ne le traversait plus, le depot rendait 403 et chaque runner reconstruit allait
chercher 500 Mo sur Internet. /etc/setops (0700 contre 0755) et /srv/restic au
site portaient le meme desaccord. test_repertoires_partages.py le refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:01:54 -04:00
c1408be9bb journal : aucune exception, la soumission suit son interrupteur
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:30:05 -04:00
c2fd6d17ca postfix : la soumission (465/587) n'est ouverte que la ou elle existe
Conditionnee a serveur_postfix_submission_actif, que seuls les locataires
levent. Le relais du site fait sortir le courriel des machines par le 25 ;
il n'a ni utilisateurs ni annuaire, et n'offre pas la soumission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:27:17 -04:00
e858fe60d3 journal : la frontiere reconciliee (8 regles et 2 alias retires)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:11:18 -04:00
ff518e18c2 journal : seulement_si, la sonde au site, la frontiere en attente
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:07:26 -04:00
25fd4fc6f1 flux : seulement_si non_vide ; forge 3000 et PowerDNS 5300 conditionnels
La forge du site sert son propre TLS (443) : le flux 3000 derriere un edge
ne vaut que si serveur_forgejo_tls est faux. L'AXFR 5300 ne vaut que la ou
l'instance publique existe (dns_public_site non vide) : pas au site.
Absente partout = vide ; une expression Jinja garde le statu quo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:01:46 -04:00
0318412a9e audit : rapport de preuves du 2026-10-01, carte a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:01:41 -04:00
85932d948c connectivite : la liste hors de /etc/setops, le chemin du site, la simulation
Au site, l'inventaire est dynamique : le chemin de la liste pointait hors du
depot (meme surcharge que le ruleset). /etc/setops a deja ses gardiens : la
liste, qui n'a rien de secret, va dans /usr/local/lib/setops. L'activation
du minuteur ne s'execute plus en --check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:59:22 -04:00
34bfd43e06 flux : seulement_si — un flux peut dependre d'un reglage de l'ecosysteme
La vigie (8080) et la console (8090) n'ecoutent sur le reseau qu'en mode
locale ; en SSO, nginx est lie a 127.0.0.1 et la regle ne menait a rien.
Evaluee par ecosysteme dans les trois generateurs (nftables, Proxmox,
frontiere). Locataires : 8 regles Proxmox et 4 nftables en moins. Site :
inchange.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:45:48 -04:00
209730b62a journal : premiere reconstruction sans arret ; parefeu par la sonde connectivite (135 s / 94 s)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:08:41 -04:00
8603ec4c19 connectivite : ignorer ses propres adresses ; un flux sans service est une information
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:50:35 -04:00
d2e2bb8990 connectivite : une sonde a la minute remplace la matrice du pare-feu
make flux ecrit, a cote de chaque .nft et depuis les memes regles, la
liste de ce que chaque VM doit joindre. La sonde (nftables_baseline) la
teste chaque minute avec la cause (rejete, delai, personne n'ecoute) ;
client_sante la porte en mode minute ; Icinga a une fraicheur par sonde.
eprouver_parefeu --sondes : tout activer d'un coup, juger par Icinga.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:46:49 -04:00
49a7e12850 journal : second arret de Technolibre, OOM sur l'AC, run_once
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:26:20 -04:00
b42bc51752 client_pki : l'empreinte de la racine se derive une fois, pas une fois par hote
Treize modules Python en parallele sur l'AC (765 Mo, 1 vCPU) : le noyau
a tue celui de collab-01 (rc=137) a la reconstruction de Technolibre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:25:08 -04:00
b5fa0e8d23 common_packages : apt-get attend le verrou dpkg lui aussi
lock_timeout ne protege que la verification du module ; apt-get n'attend
pas (APT 3.0 ne l'accorde qu'a la commande apt). Verrou repris par
unattended-upgrades sur mon-01 de Technolibre, ne une minute plus tot.
DPkg::Lock::Timeout pour tout appel d'APT, en premiere tache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:18:13 -04:00
2bf94acb96 reconstruire-locataire : les sous-commandes ecrivent sans tampon
L'etape parefeu est restee muette douze minutes : ecrivant dans un tuyau,
le Python enfant gardait sa sortie par blocs. PYTHONUNBUFFERED pour tous
les enfants, -u pour eprouver_parefeu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:25:14 -04:00
f425fb6cb3 journal : troisieme reconstruction de Chezlepro, arret au pare-feu, cause et correction
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:11:37 -04:00
20a5e44803 client_backup : le premier rapport a Icinga part d'un marqueur a lui ; le pare-feu ne compte que ses critiques
La copie de l'AC d'Icinga est deposee aussi par client_sante, plus tot :
sur une flotte neuve elle ne changeait jamais ici, et rien ne partait
(reconstruction de Chezlepro). client_backup retient desormais l'empreinte
de l'Icinga qui l'a entendu, apres le rapport. eprouver_parefeu ne s'arrete
plus sur les critiques presents avant l'activation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:08:55 -04:00
249d37d2c4 reconstruire-locataire : le nom de l'ecosysteme n'est plus demande deux fois
TENANT= le designe en toutes lettres ; le nom qu'exige raser en est
derive par raser.nom_court (une seule derivation). raser seul garde
son garde-fou.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:56:42 -04:00
a5a21ee4c5 reconstruire-locataire : le runner du site tire ses trois depots avant toute etape
Moteur, plan du locataire, depot du site (deduit d'underlay.yml), en
avance rapide seulement ; refus devant une modification locale. Vaut
aussi pour une reprise DEPUIS=creer ou inseminer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:35:23 -04:00
5a6d0f28f6 reconstruire-locataire : eprouve d'armer a bilan ; le suivi ne duplique plus le journal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:24:28 -04:00
14bf977bd6 reconstruction : make reconstruire-locataire conduit tout depuis le poste
Sauvegarder, raser/creer/inseminer (runner du site), armer (pause sauf
ARMER=oui), monter-flotte (runner du locataire), pare-feu, bilan.
Arret a la premiere etape en echec, reprise par DEPUIS=, journal dans
<locataire>/logs/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:14:12 -04:00
cc906af918 reconstruction : monter-flotte et parefeu-*-flotte entrent dans le code
La sequence du runner (flux, socle, deployer-tout, valider) devient
make monter-flotte ; reconstruire s'appuie dessus. eprouver_parefeu.py
--flotte : toutes les VM une a une, runner en dernier, arret au premier
refus ; les reseaux des IPSet (zone d'administration) comptent enfin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:23:27 -04:00
9167f67c7b client_backup : le web frontal quitte le catalogue des detenteurs d'etat
Il relaie et ne sert rien lui-meme. Un noeud qui n'a plus rien a
sauvegarder perd aussi ses verifications de depot et de restauration,
qui rapportaient sinon a des services qu'Icinga ne declare plus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:46:19 -04:00
b135b09488 client_backup : un Icinga neuf recoit le premier rapport sans attendre dimanche
La copie de l'AC d'Icinga change quand Icinga ou le noeud est neuf : elle
declenche deposer, verifier le depot, verifier la restauration. Eprouve
sur web-frontal-01 de Technolibre ; second passage sans effet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:40:20 -04:00
844568af08 journal : Chezlepro reconstruite par les runners, l'etat est revenu a l'identique
AC (meme racine), sysadmin, annuaire, Nextcloud, DKIM, bases : empreintes
identiques avant et apres. Un defaut trouve et corrige (annuaire vierge).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:33:56 -04:00
d9a8905fc7 openldap : la mesure d'un annuaire vierge ne meurt plus sur lui
Sur un annuaire vraiment vierge, grep -v ne gardait aucune ligne et
sortait en 1 : pipefail faisait echouer la mesure dans le seul cas
qu'elle doit reconnaitre. Comptage par awk. Trouve par la
reconstruction de Chezlepro.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:56:42 -04:00
515aac415a restauration : la section d'une base passe a psql par l'entree standard
postgres ne lit pas le repertoire jetable de root ; trouve en repetition
sur data-sql-01 de Technolibre. Le test l'exige desormais.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:51:19 -04:00
8a9da0c31a restauration : la reconstruction remet l'etat de l'incarnation precedente
Chaque role proprietaire (AC, bases, annuaire, Nextcloud, rspamd/DKIM,
courriel, forge, web) remet son etat au moment ou il le creerait neuf,
depuis le dernier instantane anterieur a la naissance de la machine.
La sauvegarde refuse de deposer tant qu'un etat d'avant attend.
Outil de noeud setops-restaurer ; cibles sauvegarder-maintenant,
restauration-etat, restauration-renoncer ; test_restauration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:39:22 -04:00
d3437a6308 journal : etat de Technolibre remis en place sans reconstruire
La reconstruction ne reinjecte aucun instantane : annuaire, Keycloak,
Nextcloud et courriel remis depuis restic-tech (23:50) sur les machines
existantes. Le runbook coupe aussi la section sur DROP DATABASE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:16:27 -04:00
502feff6e0 journal : reconstruction de Technolibre par les runners, quatre defauts trouves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:46:35 -04:00
bdbf7d8317 keycloak : un client absent ne fait plus echouer les mappers de groupes et de roles
Sous set -euo pipefail, grep sans correspondance sortait AVANT la branche client absent.
Trouve par la reconstruction de Technolibre (plus de client forgejo).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:12:03 -04:00
f3e8883083 flux : sans voir son site, un ecosysteme qui en depend ne regenere pas ses pare-feux
Sur le runner d un locataire (ni underlay.yml ni plan du site), make flux reecrivait des
regles amputees de tout lien au site : AXFR du DNS public, collecte de la fabric,
insemination. Le generateur s abstient et le dit ; les regles committees font foi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:33:27 -04:00
be06def899 armement : le runner retrouve son identite SSH depuis la voute
Le runner fabriquait sa paire a sa naissance : reconstruit, il avait une identite que la
flotte ne connaissait pas (refuse sur les 13 machines de Technolibre). La cle privee vit
dans la voute du locataire ; serveur_ops_tenant la remet et exige que sa cle publique soit
declaree au plan (garde eprouvee en defaut).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:23:49 -04:00
cc3a4eec30 journal : 404 du frontal de Technolibre
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:12:30 -04:00
5f23201075 frontal : page 404 du locataire pour les noms qu il ne publie pas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:37:00 -04:00
f818143b49 journal : page du PSPBT sur essai.chezlepro.ca
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:28:22 -04:00
b01de94493 waf : le mode dit en clair par la sonde ; journal du passage en blocage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:09:02 -04:00
4678ca0223 journal : plancher de Chezlepro avec essai.chezlepro.ca
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:03:16 -04:00
80bd8e88ac dorsal : confiance limitee a la forge du site ; chemin public eprouve de bout en bout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:34:44 -04:00
8186389309 domaine public -> frontal ; noms publics vers l adresse du locataire ; dorsal refuse les inconnus
Zone publique : les expositions pointent vers ip_publique (les NS restent au site).
client_pki : l AC interne ne signe que le domaine interne. Dorsal : serveur par defaut 444.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:14:56 -04:00
6d31842b83 journal : frontal proxy + WAF et dorsal des sites, poses chez les deux locataires
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:57:33 -04:00
cac0b75b3a web frontal : server_tokens deja actif chez Debian, pas redeclare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:53:08 -04:00
0cd08663e7 web frontal : le lien du vhost se simule au premier deploiement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:50:23 -04:00
2fde5c7ae7 wiki publie : temoin a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:48:43 -04:00
3fe7e3cea3 web frontal = reverse proxy + WAF ; le dorsal porte les sites
Le frontal derive ses vhosts des expositions publiques (meme derivation que l edge),
filtre par ModSecurity v3 + OWASP CRS (DetectionOnly d abord, eprouve), refuse les noms
inconnus, et ne sert plus rien lui-meme. Les sites statiques passent au dorsal, relaye
par l edge et par le frontal. Sondes : frontal (nouvelle), sites-servis suit le dorsal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:48:42 -04:00
2903d75831 journal : frontiere appliquee, NAT des locataires prouve dans les deux sens
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:22:24 -04:00
5eca6ff4f8 journal : services publics des locataires prets ; frontiere en attente d accord
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:14:34 -04:00
37753984a7 services publics des locataires : web frontal 80/443, soumission 587/465 ; externe ouvre a tous chez les locataires
Le web frontal devient la porte publique (80/443 externe) ; Postfix publie la soumission
587 et ajoute le 465 (TLS direct). Le generateur nftables ouvrait un flux externe combine
a une source nommee a cette seule source : l Internet etait refuse par l hote. Corrige chez
les locataires ; au site, rien ne s elargit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:09:31 -04:00
e9b03399b9 frontiere : NAT des locataires par leur adresse publique (devis)
Sortie traduite avec l adresse attribuee par le site ; flux externe rediriges depuis cette
adresse vers la machine du role. Gardes : pas de redirection en double, adresse du pool.
Non applique : en attente de l accord de l exploitant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:54:36 -04:00
e84b155a5f pool d adresses publiques : le site attribue a chaque locataire la sienne
Declare dans opnsense.yml du site, rendu par le contrat (ip_publique), valide (une par
locataire, jamais celle du site), compare aux alias du WAN par frontiere-plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:24:09 -04:00
712a7da1fd frontiere : les flux admin traduits chez les tenants ; edge retire de l Internet
Le devis OPNsense ne rendait le chemin du poste chez un tenant qu au travers des flux
externe ; il traduit desormais les flux admin (gestion, VPN). Applique : 4 retraits
(WAN -> edges), 18 ajouts, 0 ecart. sonde_tcp : une fermeture propre apres la requete
est une reponse de la destination ; la mesure de la frontiere est conforme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:05:23 -04:00
81cdb84f66 journal : pare-feu Proxmox et nftables des edges poses ; route du poste vers le site
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:09:29 -04:00
2695013b32 proxmox-fw : les types ICMP au vocabulaire de Proxmox (fragmentation-needed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:05:22 -04:00
e68f315625 wiki publie : temoin a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:58:11 -04:00
1493059271 edge ferme a l Internet ; le pare-feu Proxmox recoit les flux externe
L edge n expose qu a sa zone d administration (plus de pair externe) ; le public passera
par le web frontal. Le devis Proxmox recoit les flux externe depuis +t<i>-internet (RFC 1918
exclus en nomatch) et depuis l admin quand le poste en est client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:58:01 -04:00
1bd211d62b resolution_interne : entre machines du site, chaque nom mene a son service
L edge expose les interfaces web aux personnes ; les VM du site vont directement au
service. Declare par domaine (service | edge, defaut edge) ; plancher, zone et certificat
du service suivent. Pose au site : 35 planchers sur 35 conformes ; locataires inchanges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:25:37 -04:00
efb1b3d334 wiki publie : temoin a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:34:49 -04:00
eee6851c3b sonde plancher ; une exposition sans port n est plus attribuee a l edge
La zone publie l empreinte de ce que le plancher doit porter ; chaque machine la compare
a son /etc/hosts. Au site, elle a revele que sauvegarde, pki et dns.genese.internal
pointaient vers l edge (regression de mon deploiement du matin) : la derivation ne donne
plus a l edge ce qui n a pas de port. Zone du site corrigee, SFTP des locataires verifie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:34:47 -04:00
ba2020d486 powerdns : une zone plus recente que sa version servie est rechargee ; zones le voit
PowerDNS servait chez les deux locataires une zone anterieure a son fichier (console
absente) : le handler de rechargement avait ete abandonne. Reconciliation a chaque
passage (bind-reload-now) et controle dans la sonde zones. Plancher /etc/hosts rejoue
sur les deux locataires ; console se resout depuis les edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 05:29:45 -04:00
fbd4094d3f wiki publie : temoin a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:30:45 -04:00
2bbe12299f sonde passerelle : le trajet d un visiteur jusqu a la page de connexion
Apres /ping, /oauth2/start doit renvoyer vers l emetteur configure pour ce client, et
l IdP, joint depuis la passerelle, doit servir sa page de connexion. Le secret du client
n est pas teste (journal du realm). Port derive de l ecoute ; archive oauth2-proxy
plus retransferee a chaque deploiement. Fin de la revue des sondes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:30:43 -04:00
5404c947d8 wiki publie : temoin a jour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:55:59 -04:00
1653319ef1 sonde vigie : elle eprouve ce que la vigie lit, pas seulement qu elle repond
Apres check_http, la sonde lit la configuration deployee d Icinga Web 2 et eprouve
chaque ressource utilisee avec ses identifiants : base du moteur (hotes), annuaire ou
base des comptes, Redis. Au vert sur les trois ; quatre mises en defaut critiques.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:55:58 -04:00
8ac7909d49 wiki publie : temoin a 4e07b52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:03:37 -04:00
4e07b52291 sonde vigie : la sonde d Icinga Web 2 ne s appelle plus console
Le vocabulaire des plans dit vigie pour Icinga Web 2 et console pour la console
d exploitation Set-OPS (sonde console-ops). Renommee et posee sur les trois
supervisions ; client_sante a retire l orphelin console.sh ; vigie au vert partout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:01:21 -04:00
ca1f1557c4 wiki : fiches des roles regenerees
Rattrape les sondes declarees aujourd hui (disque, fabric, genome-a-jour, tableaux,
filtrage) et les flux de la PR fusionnee (serveur_dns_public, console-ops, icingaweb2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:17:46 -04:00
9e6f33d1cf sonde identite : Keycloak doit atteindre l annuaire ; l archive Keycloak ne voyage plus pour rien
La sonde demande a Keycloak de s authentifier aupres de LDAP avec la federation
enregistree (testLDAPConnection, secret masque). Eprouvee sur les deux locataires ;
mise en defaut par parametre. Le repere d archive deja posee est le marqueur de
construction de la version, pas /tmp : changed=0 au redeploiement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:17:46 -04:00
6b6a56ab3f sondes filtrage et edge : elles verifient le service rendu ; l edge refuse les noms inconnus
filtrage soumet GTUBE ; edge interroge chaque exposition en local. Sans serveur
par defaut, un nom inconnu recevait Keycloak : 000-defaut.conf (444, rejet TLS).
Garde test_sondes_syntaxe : chaque gabarit de sonde se rend en bash valide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:40:26 -04:00
b938897e17 sonde tableaux : la sante de chaque source de donnees de Grafana
Elle etait verte pendant que les tableaux etaient vides. Eprouvee avec une
source recreant le defaut du jour, puis retiree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:51:53 -04:00
8f699eb3ce grafana : l URL de Loki suit son TLS ; les tableaux des locataires se remplissent
Loki servait en HTTPS, Grafana lui parlait en clair : la variable host restait
vide et aucun panneau ne s affichait. URL derivee de serveur_loki_tls_actif.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:40:41 -04:00
cf08e51c38 wireguard : pairs de Technolibre appliques ; un renommage se fait sur place
Le meme appareil renomme garde sa cle : creer puis retirer echouait (cle en
double) et laissait le tunnel sans pair dans la configuration. rapprocher le
reconnait a la cle et modifie le pair existant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:33:04 -04:00
3063bd4bf3 sonde genome-a-jour eprouvee ; libelle ; journal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:28:32 -04:00
5c638675cb sonde genome-a-jour : la forge du site porte-t-elle ce que le poste a publie ?
Filet sous make publier : compare la forge du site a eregion (repere seulement,
depots lisibles sans identifiant), tolere l ecart d une publication en cours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:23:16 -04:00
91dc90f22b publier : eregion et la forge du site en un seul geste, verifie
La forge du site fait autorite et n etait nourrie que par un geste a part,
genome-pousser, qu on oublie. make publier pousse, porte, et verifie ; il refuse
si eregion porte des commits absents du poste.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:20:06 -04:00
974fabc40a restauration : premier passage sur 19 noeuds ; journal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:11:45 -04:00
b1c4775afe sauvegardes : un controle hebdomadaire de restauration, rapporte a Icinga
restic check en relisant 10 pourcent des donnees, puis restauration reelle du
dernier instantane avec --verify et comptage des fichiers. Service restauration,
fraicheur de 8 jours, filtre du compte d API complete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:04:28 -04:00
bcf62bafe7 sonde fabric en service ; journal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:01:08 -04:00
6edb8ac7d2 sonde fabric : detail lisible
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:57:57 -04:00
79fcff3274 sonde fabric : le pare-feu, le SDN, la frontiere et les acces disent-ils le plan ?
Un minuteur horaire sur le runner du site joue les plans de lecture et
consigne l ecart ; la sonde lit le constat, et signale un constat perime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:56:17 -04:00
a401481ae9 administration : la frontiere garde l intrant, l est-ouest y ajoute le tunnel
Ajouter le tunnel a admin_de l avait fait classer WAN par le devis de la
frontiere : une regle SSH sur le WAN qui ne correspondrait jamais.
admin_avec_tunnel pour Proxmox et l epreuve ; admin_de redevient l intrant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:54:32 -04:00
22fffdbb6f sonde disque : declaree et posee par client_sante, P64 conforme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:51:38 -04:00
40b38467e8 sonde disque : espace et inodes de chaque systeme de fichiers, sur tout noeud
Seuls le cache apt et les depots de sauvegarde etaient surveilles. Declaree par
serveur_debian, posee par client_sante (pas par common_packages, qui ferait un
upgrade full de la flotte pour deposer un script). Seuils 80/90 pourcent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:23:40 -04:00
5e7534d87f pare-feu proxmox : la procedure d activation prouvee entre au depot
eprouver_parefeu.py, make proxmox-fw-eprouver et proxmox-fw-activer-vm :
matrice du devis, flux observes, avant/apres, Icinga ; sans objet derive de
ce qui ecoute, VMID du bon locataire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:09:22 -04:00
d243773b45 pare-feu proxmox : les 26 VM des locataires filtrees, plan conforme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:58:39 -04:00
aac2d35e06 devis courriel et identite : ils nomment le compte du service qu ils verifient
Depuis le 13 septembre, resoudre_annuaire exige un compte par consommateur ;
ces deux devis retombaient sur inconnu et echouaient avant toute connexion.
courriel-plan et identite-plan : CONFORME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:54:01 -04:00
67fd3ab6b7 administration : nftables et Proxmox admettent les memes sources
Le devis Proxmox ne lisait que nftables_admin_ssh ; nftables y ajoutait le
tunnel du locataire. Filtre par Proxmox, Technolibre refusait le SSH de son
propre tunnel. Une derivation, tunnel_admin_de, pour les deux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:39:37 -04:00
8804d67dbf pare-feu proxmox : Technolibre entierement filtre, VM par VM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:36:41 -04:00
d673f97ac9 pare-feu proxmox : deuxieme lot de Technolibre, flux observes et matrice 100/100
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:10:08 -04:00
58432fa6e1 pare-feu proxmox : quatre VM de Technolibre filtrees, matrice 19/19 et controle negatif
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:06:36 -04:00
35831608d0 pare-feu proxmox : un flux ICMP devient une regle, au lieu d etre saute
Le type ICMP (echo-request) etait range parmi les ports derives et le flux
saute : le ping de supervision n avait pas de regle, et web-frontal-01 passe
en REJECT est devenu mort pour Icinga. icmp_type dans le devis, icmp-type
vers l API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:01:04 -04:00
4ce296f3d5 pare-feu proxmox : un flux ICMP devient une regle, au lieu d etre saute
Le type ICMP (echo-request) etait range parmi les ports derives et le flux
saute : le ping de supervision n avait pas de regle, et web-frontal-01 passe
en REJECT est devenu mort pour Icinga. icmp_type dans le devis, icmp-type
vers l API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:29:38 -04:00
3676e69cd9 pare-feu proxmox : poser les objets, puis activer VM par VM
--objets-seulement et --vm <vmid> : aucune des 26 VM des locataires n avait son
pare-feu actif ; tout activer d un coup ouvrait 26 pannes possibles a la fois.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:26:18 -04:00
4f5a0b5696 pare-feu proxmox : poser les objets, puis activer VM par VM
--objets-seulement et --vm <vmid> : aucune des 26 VM des locataires n avait son
pare-feu actif ; tout activer d un coup ouvrait 26 pannes possibles a la fois.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:25:29 -04:00
0156596f01 P02 : une table du plan ne se coupe plus a son premier commentaire
_fusion_table traversait mal les commentaires interieurs a une entree :
chezlepro.ca (DNSSEC) etait coupee et ses enregistrements pris pour des entrees.
make verifier : CONFORME, 83 OK, 0 echec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:17:52 -04:00
ffc171a2cb cache d artefacts : l amont suit artefacts_amorcage, plus une adresse morte
serveur_artefacts_amont portait 10.0.33.21, l adresse du cache du site avant
son index. Inerte aujourd hui (aucun hote ne porte serveur_artefacts ; apt passe
par artefacts_amorcage), mais un piege le jour d une emancipation. Derive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:04:52 -04:00
5f52dcb2b7 wireguard : rotation des cles de daniel-portable, sans afficher de privee
cle-appareil et config --cle/--vers ecrivent les secrets en 0600 ;
--portee restreint appliquer au tunnel vise. Nouvelles cles en service,
anciennes retirees, Technolibre intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:57:15 -04:00
4855cda294 voutes : elles sortent du poste, dans leur propre archive, relues
exporter_voutes.py et make voutes-exporter : les six voutes (gitignorees, sur
aucune forge) sont sur la cle USB, a cote des cles ; restauration eprouvee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:53:14 -04:00
2bd761b85f exportateur postgresql : la promesse dit ce que fait Prometheus
Vide, le secret ne pose pas l exportateur, mais Prometheus derive quand meme sa
cible : la collecte rougit, et c est voulu. La phrase promettait l inverse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:48:29 -04:00
415189ceff journal : les journaux de la frontiere sont couverts, contrairement a ce qu il disait
L affirmation venait d un diff, qui ne montre que ce qui change. Mesure : la
sonde site-mon-01!journaux-frontiere est verte, 748 lignes en 10 min.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:43:10 -04:00
0551c4b4de exportateur postgresql de chezlepro pose ; les voutes ne sont pas sur les forges
vault_pg_exportateur ajoute a la voute de Chezlepro (hors depot), role redeploye,
pg_up 1 : plus aucun critique. La voute a ete redeposee sur le runner. Deux
phrases qui disaient les voutes repliquees sur les forges sont corrigees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:41:14 -04:00
f5adffb0de sondes conditionnelles : attendues seulement la ou elles sont posees
seulement_si dans meta/supervision.yml, lu par le gabarit Icinga des sondes ;
garde test_sondes_conditionnelles. journaux-frontiere ne rougit plus a vie chez
les locataires ; Technolibre sans critique.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:31:17 -04:00
185b000c60 icinga : un service passif qui n a jamais rien recu passe au rouge
Gabarit setops-rapport-attendu : actif, dummy critique, check_interval = le
ttl que le noeud envoie. Prouve a t+60 s sur site-mon-01, deploye sur les trois
Icinga ; la garde test_fraicheur_icinga lie seuils et ttl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:05:15 -04:00
831c7d5cf4 sauvegardes des locataires : aucun des deux ne deposait hors de sa flotte
Technolibre frappait au compte du site ; Chezlepro n avait client_backup que
sur un noeud sur onze depuis le redeploiement du 12 septembre. Les deux deposent
maintenant, restaurations lues. Le trou qui l a cache : un service passif jamais
alimente reste en attente, pas au rouge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:56:44 -04:00
51b8165221 icinga : deux temoins de sauvegarde, et non l un ou l autre
Au site, la verification que chaque noeud fait de son depot visait un service
que le gabarit ne creait que sans depot dans l ecosysteme : 404 toutes les 4 h
depuis le 20 septembre sur site-forge-01, site-mon-01 et site-pki-01. Les
deux temoins existent maintenant ; six services au vert, relus dans Icinga.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:18:32 -04:00
fe9d47c4a6 eregion n est pas sur le chemin du genome : le SPOF n existait pas
D-82, D-83 et filiation-emancipation disaient que le poste pousse sur eregion
et que la forge du site en tire. Le chemin mesure : genome-pousser porte les
commits en bundle au runner, qui pousse sur la forge du site. eregion est une
forge heritee, porte publique des contributions, hors Set-OPS pour toujours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:02:44 -04:00
3a379f6344 patient 0 : ses depots distants et la cle de sa voute n existent plus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:50:04 -04:00
478aab3eb8 pare-feu Proxmox : force dans l URL, et le retrait de t29 est pose
Proxmox refuse un corps sur DELETE (501) : aucun IPSet n etait jamais retire
par l applicateur. SDN, frontiere et pare-feu Proxmox ne portent plus rien de
l index 29 ; le journal dit comment, et ce qui reste (strophe FRR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:08:22 -04:00
badbcd3971 pare-feu Proxmox : un tenant retire sort du perimetre, ses objets aussi
Les IPSets et groupes t29- restaient poses : le perimetre ne retenait que
les prefixes des tenants presents. Les etiquettes des zones retirees
(ANCIEN_NOMMAGE du SDN) y entrent, pour etre retirees. Et un cluster muet
arrete le plan au lieu de se lire vide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:58:42 -04:00
fde604c2a5 pare-feu et pools Proxmox : les tenants du site, pas toute la federation
Les deux devis balayaient tous les dossiers freres. Le runner du site, qui
gardait un clone de patient 0, proposait de recreer ses groupes t29 ; le poste
comptait un dossier de CI comme tenant. La frontiere et le SDN filtraient deja
par underlay.tenants : ces deux-la suivent maintenant la meme liste.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:57:06 -04:00
e1a4dc62bb wiki republie depuis 6a71882 ; le journal nomme le correctif sdn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:49:45 -04:00
6a7188290f sdn : un cluster muet n est pas un cluster vide, et t29 se retire
Le poste coupe du site, sdn-plan annoncait a creer : 33 — toutes les zones,
Chezlepro comprise. Une lecture en echec rendait un dict d erreur, que rep or []
parcourait comme une liste vide. La lecture s arrete maintenant, comme la frontiere.

t29 rejoint l ancien nommage : sortie du devis sans cela, la zone aurait ete
lue comme etrangere et laissee en place, avec ses VLAN 1291-1296.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:34:20 -04:00
c0f610be33 patient 0 efface : l index 29 est libere, et le site n ouvre plus rien a 10.29.0.0/16
Ses machines n existaient plus depuis le 2026-09-06 (D-83), mais son plan
restait sur disque : la federation lui reservait l index 29 et quatre machines
du site lui ouvraient SSH, apt, DNS et HTTPS. Les commentaires et documents
vivants gardent leur lecon sans le nommer ; les archives restent telles quelles.

Pas encore sur le reseau : les regles regenerees attendent le runner du site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:55:27 -04:00
5f6ffda5e8 Merge pull request 'runbooks : la recette tranche, le registre ne se compare plus a lui-meme' (#4) from contrib/runbooks-json into main
Reviewed-on: #4
Reviewed-by: Daniel Allaire <danallaire@noreply.forge.alliance-boreale.ca>
2026-09-26 08:59:37 -04:00
1d07a6cf80 runbooks : la recette tranche, le registre ne se compare plus a lui-meme
Un outil tiers veut n'offrir de ce moteur que ce qui n'agit pas. Le seul champ
qui le lui dise est `nature`. Il fallait donc qu'il ne mente pas, et il mentait
dans les deux sens.

`filiation` → `emancipation-prouver` se declarait « mesure » en portant
`fixes: {CONFIRMER: "true"}`. La cible tranche elle-meme — son refus dit
« cette preuve COUPE l'amont quelques secondes pour mesurer ». Un constat
rapporte ne rend pas inerte le geste qui l'obtient : l'etape passe a
`ecriture`.

Dans l'autre sens, trois cibles — flotte-creer, deployer-tout, reconstruire —
refusent sans confirmation sans que le registre le declare : un assistant les
lancait telles quelles, et sortait en 2. Comparer `nature` a `fixes` ne les
voyait pas, car c'est comparer deux champs ecrits par la meme main. `verifier`
lit desormais la RECETTE, qui ne ment pas.

Effet de bord traite : une etape qui agit barre celles qui la suivent dans la
console, alors qu'une « mesure » se debloque d'office. `depots-perimes` est
marque `facultative` — il nettoie ce que la filiation a laisse, il n'en est pas
le prealable.

`lister --json` rend le registre assemble d'un bloc, et le drapeau est refuse
hors de son action : rendre la prose humaine a qui demande du JSON est pire
qu'un refus. L'affichage humain ne bouge pas, et la preuve mesure ce qu'il
PORTE — un identifiant, un libelle, les marques de nature — et non seulement
qu'il n'est pas du JSON.

Valide : `verifier` a 0 ecart, test_runbooks.py 25/25, et sept des neuf
epreuves de `make test` ; test_raser et test_raser_resultat echouent sur
« Aucune instance montee », avant comme apres. Mesure : 127 etapes, dont 84 de
nature « mesure » — la surface qui n'agit pas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbXr5vv4Rat6PCPGG9GoG8
2026-09-25 17:54:03 -04:00
47283256d8 wiki republie : l accueil porte la ligne des assistants
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 12:20:14 -04:00
4e0b9caa45 wiki : l accueil signale les assistants de la console
La republication du 21 n avait touche que la page du GUI : l accueil, dont la source
n avait pas change, gardait sa date du 14 et rien n y disait qu il y avait du neuf. Une
ligne dans « Par ou commencer », a cote de « Reprendre l ecosysteme » : elle s adresse au
meme lecteur, celui qui doit exploiter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 12:20:02 -04:00
3b8559063f wiki republie : la vue Assistants est en ligne, P60 au vert
Le wiki publie datait du 14 septembre (392da4d). Un commit avait touche wiki/ depuis —
f33b5be, qui documente la vue Assistants — et P60 le signalait. Republie depuis d461492
vers le wiki du depot public sur eregion, l'adresse que le temoin precedent avait consignee.

Relu apres ecriture : un clone du wiki distant porte bien la section Assistants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:34:09 -04:00
d461492623 adressage : le site est une flotte, et P20 le regarde enfin
L'exploitant a demande si l'adressage derive ne valait pas pour toute flotte, site compris.
Oui — decision du 12 septembre, index 37 pour le site. J'avais affirme le contraire a
l'entree (11) du CHANGELOG, en recopiant un commentaire perime de huit jours au lieu de
mesurer. Un commentaire perime se propage : celui-la s'etait aussi loge dans la docstring de
ConsoleSite.

P20 s'appelle « Adressage 100% derive du seed (aucun stocke) » et sa portee etait
« l'instance liee + les modeles » : elle ne regardait jamais le site, qui stockait
sous_reseau et passerelle pour ses sept zones. Elle juge desormais les deux natures d'un
underlay et refuse une entree qui ne declare pas la sienne — on ne peut pas juger ce qu'on
ne sait pas lire. Controle negatif joue : une zone remise a 10.36.31.0/24 est refusee, en
nommant l'index dont elle aurait du descendre.

Le loader derive sous_reseau et passerelle a la lecture, pour que rien ne change chez les
consommateurs. Mesure : 14 valeurs retirees du fichier, zero ecart sur ce qu'ils lisent, et
make underlay, devis-sdn et devis-reseau passent.

Valide : make test a 0 echec, P20 verte sur 2 nomenclatures et 7 zones de site. P02 et P60
restent, pour les raisons deja consignees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 22:46:43 -04:00
cd0f94a50b console : un tronc, deux branches — assembler deux fois est ce qui fait deriver
Il y avait deux assembleurs de charge, un par sorte de console. Ils ont derive deux fois le
meme jour : en forme (serveurs valait [] d'un cote et {} de l'autre, et charger() levait),
puis en contenu (cinq registres servis vides alors que le site a SON plan — 9 serveurs,
21 applications, 2 bases, 1 domaine, tous invisibles).

Console assemble, une seule fois, et fixe les clefs et leurs formes. Les branches ne
decident que de ce qui leur appartient : d'ou vient le plan, d'ou vient l'inventaire, quels
pouvoirs elles portent. ConsoleLocataire configure, ConsoleSite materialise et sert
desormais son propre plan, ConsolePoste herite du locataire et sait en plus sur quelle
fabric poser.

Le role et la portee ne se confondent pas : le role est une propriete de la classe, la
portee se calcule depuis les pouvoirs. Un poste prive de la voute du site reste l'atelier du
mainteneur et n'engendre pourtant rien. Le decoupage a montre un trou aussitot : ConsolePoste
heritait du refus d'un locataire — « elle ne sait pas sur quelle fabric poser » — alors qu'il
monte la carte ; ce qui lui manque est la voute, et accuser la mauvaise absence fait chercher
au mauvais endroit.

Le jugement des assistants remonte dans le tronc : il etait ecrit dans la route qui liste ET
dans celle qui execute, et celle qui se trompe est toujours celle qui execute.

Valide : make test a 0 echec, 8 tests de rendu sous node dont deux neufs, console lancee pour
de vrai (13 serveurs, 24 applications, 17 runbooks, 127 etapes, 0 ecart). make verifier a
aussi attrape une faute que j'avais laissee passer sur depots_perimes.yml — risky-shell-pipe,
corrige. P02 et P60 restent, et P60 demande une republication du wiki.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 21:55:49 -04:00
859ac07a54 console de site : un vide doit avoir la forme de ce qu'il remplace
La console du runner de SITE-Chezlepro ne montrait rien. Elle a douze machines.

inventaire_api sert serveurs en LISTE, inventaire_api_du_site le servait en TABLE. Les deux
sont vides et la page ne les lit pas pareil : (data.serveurs || []).map trouve {} — truthy,
sans .map — et charger() leve. La console d'un site mourait avant sa premiere vue, et le
message accusait une methode manquante plutot qu'une forme qui ment.

Et la vue regardait au mauvais endroit : le registre vide est voulu, mais la page en tirait
le message d'un locataire sans serveurs et les douze machines servies dans hotes n'etaient
dessinees nulle part. Une console de site dessine desormais SES machines, en lecture seule,
avec leur adresse, leur VMID, leurs roles et leur point de vie ; le panneau droit dit ce
qu'elle peut et renvoie aux Assistants.

C'est le defaut du 16 septembre par une autre porte : la premiere fois l'inventaire etait
vide et la page dessinait ce vide comme un plan vide ; cette fois il est plein et c'est la
vue qui regarde ailleurs.

Ce qui a trouve la cause n'est pas une lecture mais le BANC : en donnant pour la premiere
fois a test_rendu_gui la charge d'une console de site, il a leve sur charger(). Sans lui
j'aurais livre une vue juste par dessus un charger() qui leve.

Valide : node --check sur le bloc script, 7 tests de rendu dont deux neufs, make test a 0
echec, runbooks.py verifier a 0 ecart, P81 et P83 vertes. P02 reste en echec pour la raison
anterieure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:36:00 -04:00
7ef7ca196a depots perimes : ce qui n'existe qu'ici ne se detruit pas depuis ici
serveur_ops clone ce que le plan declare et ne retire rien : le runner de
Chezlepro-locataire portait encore le clone de SITE-Chezlepro, la carte de son hebergeur,
longtemps apres que son plan ait cesse de la declarer.

Le retrait n'entre pas dans le role. Un role qui efface des dossiers a chaque passage est
une grenade degoupillee : une faute de frappe dans serveur_ops_depots suffirait a perdre du
travail local. C'est donc un geste separe, qui regarde par defaut et n'efface que sur
CONFIRMER=true. Trois choses ne sont jamais retirees, meme confirmees : ce qui n'est pas un
depot git, ce qui porte des modifications non validees, ce qui porte des commits qu'aucun
distant ne porte.

La garde a servi au premier essai : le releve a nomme SITE-Chezlepro et venv, et venv a ete
ecarte parce que ce n'est pas un depot git. Une version naive aurait efface l'environnement
Python du runner en se disant satisfaite.

Passe deux fois sur le runner reel : regarder (changed=0), puis confirmer (changed=1) avec
relecture. Le runner ne porte plus que Set-OPS-public et OPS-Chezlepro ; sa console rend
portee=tenant, materialiser=False, fabric=False — le pouvoir de LIRE la fabric est tombe
avec la carte, et c'est juste.

Valide : syntax-check du playbook, runbooks.py verifier a 0 ecart (la cible neuve est portee
par le runbook Filiation), make test a 0 echec. P02 reste en echec pour la raison anterieure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:18:59 -04:00
4188cf5b6f la console mesure le pouvoir sur la voute, pas sur la carte
Le document des responsabilites attache chaque pouvoir a une voute : calculer n'en demande
aucune, configurer demande celle du tenant, materialiser celle du site. Le code lisait les
symlinks, c'est-a-dire les cartes. Le runner de Chezlepro-locataire montait la carte du
site sans en avoir jamais eu la voute : sa console se declarait poste et offrait 126 etapes
sur 126. Ces gestes seraient partis puis tombes sur un secret vide — un echec au milieu du
chemin, la ou un refus net aurait dit la verite avant de commencer.

contexte() derive desormais les pouvoirs des voutes presentes, et la portee decoule des
pouvoirs au lieu de les preceder. On ne prouve pas qu'une voute s'ouvre, le mot de passe se
tape a l'execution ; mais son absence est decisive et se mesure sans rien ouvrir. Lire une
carte reste permis : le pouvoir fabric suit toujours le symlink, consulter un miroir n'est
pas engendrer.

serveur_ops retire aussi le lien quand la fabric n'est plus declaree — il ne retirait rien,
et un runner gardait le pouvoir que son plan ne lui donnait plus. Il ne retire qu'un lien,
jamais un fichier : une vraie carte a cette place n'a pas ete ecrite par ce role, et il le
dit plutot que de detruire ce qui n'est pas le sien.

Valide : syntax-check et ansible-lint sur le role (profil production, 0/0), make test a 0
echec, P81 et P83 vertes. Six tests montent quatre faux disques et exigent la portee qui
leur revient, dont le defaut lui-meme : carte presente, voute absente, portee tenant.

Limite : P02 reste en echec pour la raison anterieure deja consignee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:12:18 -04:00
d3a8124777 assistants : la portee se pese a l'etape, pas a la sequence
Mesure sur la console de TechnoLibre, portee tenant : 6 runbooks conduisibles sur 17, et
parmi les onze fermes, locataire-deployer et machine-une — c'est-a-dire le travail
quotidien d'un locataire. La cause : flotte-creer et creer-vm engendrent des VM et
exigent la fabric, et une portee declaree pour toute la sequence faisait basculer avec eux
des etapes voisines qui ne demandent que ce que le locataire possede deja.

Le runbook ne donne plus que le defaut ; l'etape qui exige davantage le declare. Le
locataire conduit sa sequence et bute precisement la ou il faut : sur la machine a
engendrer, pas sur le deploiement qui suit. La page ferme l'etape seule avec sa raison, et
la garde de la route lit la portee de l'etape visee par son index.

Un droit calcule sur l'ensemble se trompe toujours dans le meme sens : il refuse a
quelqu'un ce qu'il a le droit de faire, et le refus parait fonde puisqu'il nomme un vrai
manque. Il a fallu une console de locataire reelle pour le voir — sur le poste, qui porte
les deux liens, les dix-sept sequences s'affichaient conduisibles.

Valide : runbooks.py verifier a 0 ecart, make test a 0 echec (4 tests neufs, dont un qui
nomme le cas exact), P83 verte. P02 reste en echec pour la raison anterieure deja consignee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 19:55:02 -04:00
f33b5be151 assistants : cent trente-deux cibles, et aucune ne disait dans quel ordre
La console offrait des boutons sans sequence. Rien n'y apprenait que site-creer precede
forge-amorcer, que le premier passage de site-deployer-tout s'arrete sur une forge vide
sans que ce soit un echec, ni que rien n'est pret avant valider : cet ordre vivait en
prose dans des documents que la console ne porte pas.

La vue Assistants conduit 17 runbooks et 126 etapes. Les 132 cibles documentees y sont,
chacune portee par un assistant ou exemptee avec son motif — une exemption muette est
refusee. Le registre ne recopie pas le Makefile : il declare l'ordre, la nature, la portee
et le pourquoi, et le libelle de chaque etape est lu dans le Makefile au moment de servir.

P83 est ecrite en meme temps que la liste, pas apres, parce qu'une liste qui suit une
autre prend du retard. Onze tests lui presentent des registres faux, un par forme de
retard, et exigent qu'elle les refuse.

Le navigateur ne nomme pas une commande, il nomme une place : la route lance ce que le
registre declare a cet index-la, avec les seules variables declarees. L'index compte, le
premier jour d'un site jouant site-deployer-tout deux fois. Une etape qui ecrit attend que
la precedente ait reussi ; une mesure reste toujours offerte, parce que mesurer apres un
echec est exactement ce qu'on fait ensuite.

Valide : runbooks.py verifier a 0 ecart, make test a 0 echec, les 83 preuves rejouees, et
la console lancee pour de vrai — 17 runbooks servis, six requetes malformees refusees une
a une, une etape de mesure executee de bout en bout avec son journal.

Limite, anterieure a ce travail : P02 (test_ecriture_plan) echoue sur domaines.yml, a
l'identique sur une copie de HEAD. Ajouter ou retirer un domaine public depuis la vue
Domaines leverait a l'enregistrement. Non corrige ici.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:31:16 -04:00
bf94a304ff la console du site redemarre a la poussee : la derniere limite est levee
Premiere execution du correctif de genome_pousser.yml, et premier declenchement :
f9a20b0 -> f958739 sur la forge, tache en changed, console du site repartie a 15h46m27
contre 15h16m45, sonde a 0. Les cinq autres depots, reconnus immobiles, n'ont rien
redemarre — la condition ne reagit qu'au moteur. make genome-etat rend failed=0 sur les six.

Les trois correctifs de la journee ont maintenant ete vus fonctionner sur la machine que
chacun concerne : le role chez les deux locataires, le playbook de poussee chez l'hebergeur.
Cette entree ne corrige qu'un paragraphe devenu faux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:47:19 -04:00
f95873915f genome pousse : celui qui pousse avance son propre clone, et sa console l'ignore
Le correctif de serveur_ops a eu sa preuve en conditions reelles : clone passe de 6367d85
a f9a20b0 chez les deux locataires, tache en changed, consoles reparties a 15h33m33 et
15h38m02 contre 15h16, sondes a 0. En la donnant, il a montre le cas qu'il ne couvre pas.

genome_pousser.yml fait avancer le clone du runner du SITE sans qu'aucun role ne passe :
c'est lui qui pousse, donc il recoit d'abord. Quand serveur_ops passera, le clone sera
deja a jour et la tache s'abstiendra a juste titre — l'hebergeur gardait le defaut corrige
chez ses locataires. Le playbook nomme deja la transition dans son verdict ; il en tire
maintenant la consequence et redemarre la console du site quand c'est le MOTEUR qui a
bouge. Un plan pousse ne coupe pas les pages ouvertes, et l'unite systemd est verifiee
avant de toucher au service.

Valide : --syntax-check contre l'inventaire du site et celui d'un locataire, ansible-lint
sur le playbook (profil production, 0 echec, 0 avertissement), condition de declenchement
eprouvee sur quatre cas dont le mode check.

Limite : le redemarrage de la console du site demande une poussee posterieure a ce commit,
il n'est donc pas encore observe. Son disque porte f9a20b0, sa console sert le code
charge a 15h16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:39:06 -04:00
f9a20b0870 console des runners : le code sur le disque n'est pas le code servi
Les trois consoles portaient le moteur du jour et servaient celui du 16 septembre. Le
clone du genome ne notifiait rien et la tache de service demande state: started, qui ne
fait rien quand le service tourne deja ; inventory_gui.py lit son code au demarrage, et
seulement la. Le role retient desormais si le MOTEUR a avance — designe par
serveur_ops_depot_moteur, jamais un chemin recopie — et redemarre la console dans ce seul
cas : un plan qui avance ne coupe pas les pages ouvertes.

La sonde console-ops criait au vestibule ouvert sur les deux runners de locataire, sur
des consoles fermees. Elle frappait la boucle locale, seul endroit d'ou un 200 est le
resultat attendu en mode oidc, ou nginx est reduit a 127.0.0.1 pour qu'on ne contourne
pas la passerelle SSO. Elle connait maintenant son mode : en locale le verdict ne bouge
pas, en oidc la serrure mesuree est l'adresse d'ecoute. Le refus de la passerelle reste
l'affaire de la sonde passerelle de serveur_oauth2_proxy, sur le meme hote.

Valide : --syntax-check sur playbooks/groupes/serveur_ops.yml, ansible-lint sur le role
(profil production, 0 echec, 0 avertissement, 14 fichiers), le gabarit rendu dans ses deux
modes sans reste Jinja et bash -n propre, le filtre d'ecoute eprouve sur cinq formes
d'adresses, l'expression du when sur cinq cas dont le mode check. Le role est applique aux
trois runners, failed=0, et la sonde rejouee sur chacun rend 0.

Limite : le redemarrage automatique ne s'est pas declenche, les clones etant deja au
niveau de la forge — la tache s'est correctement abstenue. La preuve du declenchement
viendra au prochain genome pousse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:27:50 -04:00
6367d851e8 histoire du depot : une chronique de 641 commits, figee a une revision
promo/histoire.html raconte huit etapes du 24 juin au 17 septembre a partir des commits
accessibles depuis 05b85eb, avec l'archive recherchable des titres originaux et un
graphique d'activite tire du meme corpus. Le corpus est fige : les chiffres decrivent cet
instantane, pas l'etat courant du depot. Page autonome, sans police ni service distant,
lisible sans JavaScript ; ses styles sont isoles sous .history pour ne pas toucher les
quatre pages commerciales. La page Capacites y renvoie.

Le CHANGELOG consigne aussi la revision des dossiers de livraison et le comparatif SOC 2,
qui vivent hors de ce depot : aucun role, playbook ni reglage n'est modifie ici.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 14:24:27 -04:00
05b85ebd27 tunnel d'administration : TechnoLibre servi, et l'oeuf et la poule du premier pair
`pair-nouveau --locataire` refusait de proposer une adresse a un ecosysteme sans aucun pair,
soit exactement le premier. Le kit remis au client derive tout ce qu'il affiche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 16:08:39 -04:00
be8c3917e5 acces d'administration : un tunnel par locataire, declare par lui, borne a lui
plan/acces.yml chez le locataire (cles publiques), reseau/port/instance derives de l'index.
La garde du devis refuse qu'un tunnel de locataire vise autre chose que son supernet : sans
elle, un ecosysteme s'ouvrirait un acces chez un voisin depuis son propre plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 16:01:54 -04:00
2a276fb6b4 tunnel d'administration : le SSH du site et la console de la frontiere manquaient
Eprouve en production : les locataires repondaient, le site non — son SSH vient de `flotte`
et vivait du rebond par la frontiere. Et rien ne designait la frontiere elle-meme, si bien
que monter le tunnel faisait perdre le moyen de le corriger. Une regle par port : OPNsense
refuse deux ports dans un champ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:26:23 -04:00
d90513ca91 acces d'administration : un tunnel WireGuard nominatif, pas le runner en rebond
Le runner detient la voute et les cles SSH : en faire la porte des humains reunirait deux
pouvoirs que le depot separe. Instance `admins` a cote du tunnel site-a-site, un pair par
personne et par appareil, cles publiques seules au plan. Le reseau du tunnel est un reseau
d'administration : pare-feux d'hote, contrat des locataires et regles de bordure en derivent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:42:48 -04:00
ee386dca00 materiel : la frontiere expose ses temperatures, seul le Prometheus du site les lit
os-node_exporter sur la patte de supervision, declare dans la carte du boitier : cible et
hote Icinga en derivent. Les flux vers la frontiere ne s'ouvrent plus a tous pour un role
qui n'est pas le socle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:20:46 -04:00
49ceb81a4d materiel : un catalogue de capteurs, un tableau Grafana, un verdict Icinga
Les hyperviseurs exposaient temperatures, ventilateurs, SMART et usure NVMe sans que rien
les regarde. scripts/materiel.py porte capteurs et seuils ; Grafana en tire le tableau
Materiel, Icinga un service materiel par hyperviseur. Premier verdict : le disque sda de
gandalf (OSD Ceph) a 56 secteurs en attente, stables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:13:43 -04:00
8ac9f53f05 pdns public : la boucle locale n'obtient pas la zone sans TSIG (mesure), et comment l'imprimer
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:02:41 -04:00
0eefc5cf4d DNS public expose : mesure depuis l'exterieur, rien ne manque sauf le second serveur de noms
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:25:35 -04:00
c1d0d9b1a7 frontiere : publier un service du site, port public et redirection
Un flux externe porte port (la machine) et port_public (l'Internet). Le devis emet la
redirection et sa regle WAN, garde qu'elles aillent ensemble ; l'applicateur reconcilie
d_nat et encode les champs imbriques. DNS public : WAN:53 -> site-dnspub-01:1053 (dnsdist).
Plan lu : 4 objets a creer, rien d'autre ; pas encore applique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:38:39 -04:00
53cfe71c7e DNS public : dnsdist devant PowerDNS, debit et refus eprouves
ANY UDP tronque, AXFR/NOTIFY/UPDATE refuses depuis dehors, debit par /24 (rafale 300 ->
70 reponses, 230 tronquees). PowerDNS garde le 53 pour les primaires ; le frontal ecoute
1053, ou la frontiere redirigera. Sonde par le frontal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:29:19 -04:00
712ad9f630 DNSSEC : le locataire signe, avec la cle de sa voute
Cle CSK ECDSA P-256 tiree dans la voute du locataire, importee par le role, DS calcule
sans la machine. Refus de changer la cle ou de retirer la signature tant qu'un DS est
publie. SOA-EDIT EPOCH : INCEPTION-EPOCH aurait laisse expirer les signatures du site.
CAA sur chezlepro.ca, sonde des signatures au site, P18 deplie vault_dnssec_<zone>.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:09:21 -04:00
63780645a7 zones publiques : le courriel dans la zone, et un devis avant de basculer
Une zone publique ne portait que des A d'exposition ; basculer chezlepro.ca aurait coupe
son MX, son SPF et son DMARC. Les enregistrements se declarent au plan, valides et rendus ;
le serveur de noms porte le nom que le site declare (dns1.chezlepro.ca), parce que
ns1.chezlepro.ca existe deja en production. make dns-bascule-devis compare le plan au DNS
en service : 0 perdu sur les deux zones, deux prealables restants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:54:49 -04:00
19bbadfb11 DNS public en production : cinq fautes que seule la production montrait
Applicateur de frontiere : ecrire n est pas charger, le chemin rien a faire charge
desormais. site-verifier compare enfin playbooks/site.yml, regenere avec le groupe.
Base TSIG dans un repertoire a pdns et pdnsutil en pdns (journal SQLite). Flux UDP 5300 :
le secondaire demande le SOA avant de transferer, garde dans P82. socket-dir de
pdns@public, et le failed_when qui taisait la notification est retire. Eprouve sur les
machines : 2 zones sur 2 tirees automatiquement, NOTIFY compte, zone interne refusee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:35:36 -04:00
788c5073dc DNS public, phase 1 : le locataire ecrit, le site sert
Role serveur_dns_public (secondaire public, transfert signe TSIG, aucune zone interne) ;
serveur_powerdns exerce enfin autorite primaire-cache dans une instance pdns@public a part,
pour que le site ne puisse jamais interroger la zone .internal. Relations derivees des plans
des locataires, mots de flux dns_public_site et primaires_dns_locataires. Eprouve avant
d ecrire : allow-axfr-ips et TSIG sont alternatifs, le primaire notifie aussi ses NS, le
serial fige aurait gele le secondaire. P82 refuse l exposition sans DNSSEC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:08:20 -04:00
8456d298bf la page cesse de proposer ce que le serveur refuse
Un seul entonnoir pour les quatre gestes d hote (lancer), POUVOIR_PAR_MODE qui
rattache chaque mode a son pouvoir, et des boutons qui demeurent mais inertes,
avec leur raison en infobulle. Les panneaux de fabric sont marques lecture seule
chez un locataire : ils viennent d une copie locale qui avait deja diverge. P81
refuse en plus une page qui aurait perdu POUVOIR_PAR_MODE.

Deux fautes attrapees en route : le banc de rendu a vu un TypeError a l ouverture
(ma garde adoptait pour un contexte tout ce qu on lui donnait) la ou node --check
restait vert ; et une couleur illisible, vue sur une capture, pas dans le code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:57:39 -04:00
e46fa198fd la console dit sa portee : un site n affichait aucune de ses machines
Servie par le runner d un SITE, la console montrait zero serveur sans une erreur :
serveur_ops retire le lien instance sur un hebergeur, charger_yaml rend un
inventaire vide sur un fichier absent, et la page dessinait ce vide comme un plan
vide. La portee se DERIVE des deux symlinks (instance = je configure, underlay =
je materialise), jamais d un reglage declare. Une console de site sert desormais
son inventaire dynamique ; POUVOIR_REQUIS exige un pouvoir pour chaque route POST
et le refus dit pourquoi ; la page nomme la console. P81 refuse une portee sans
source d inventaire, un site sans machines, et toute route qui echapperait a la
table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:36:59 -04:00
a5152a5162 responsabilites : deduites des pouvoirs, et lues par les documents remis
Qui peut, doit ; qui ne peut pas, ne peut pas etre tenu. Vingt lignes derivees des
trois portees du moteur (calculer, configurer, materialiser), chacune citant son
mecanisme. Deux coupures qui ne se devinaient pas : la verification des sauvegardes
suit la CLE (l hebergeur heberge des octets qu il ne peut pas ouvrir), et l acces de
secours par sudo est dit plutot que tu, avec sa date de fin. Le tableau est canonique
dans le depot ; les chartes remises le LISENT entre ses ancres.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:37:18 -04:00
0cbb9fdb4a remise au client : deux temps, un outil, une garde
Livrer se terminait par une phrase — tes cles te seront remises separement — et
rien n ecrivait la suite. Temps 1 l identite (sa cle de voute, sa voute, sa racine
d AC), temps 2 la machine a echeance (sa cle entre, la notre sort, voute re-cletee,
secrets tournes). scripts/remise.py refuse une destination interne, un paquet sans
racine d AC, et tout ce qui n est pas l ecosysteme monte. Le registre remise.yml
declare enfin le responsable designe (D-18). P80 refuse un registre incomplet, un
second temps echu, un second temps declare fait sans revocation au plan, et un
secret dans un fichier versionne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:16:27 -04:00
85c7f9710b P79 : une derivation qui ne trouve rien ne passe plus pour un succes
Sept replis des deux derniers jours, un temoin chacun, lu hors de la derivation
qu'il juge : jumeaux d'amorcage, patte de zone, edge d'exposition, certificat
bouchon, rechargement, sortie vers l'Internet, regles d'hote par instance.
Eprouvee par reinjection de chaque faute ; a trouve deux flux-genere perimes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 18:37:38 -04:00
ae212c0252 les trois consoles repondent, chacune derriere sa propre serrure
genese 401 (vestibule, pas d annuaire), chezlepro et technolibre 302 vers leur
Keycloak. Pile identique partout : le GUI et son vestibule en boucle locale,
seule la passerelle publiee.

La regle edge -> ops-01:4180 a manque deux fois pour la meme raison : make flux
ecrit pour l INSTANCE MONTEE, et je l ai lance avec Chezlepro monte. Chez un
locataire ce flux ne traverse pas la frontiere — le SDN de Proxmox tient les
passerelles de zone — donc c est le pare-feu d hote qui le porte. Au site,
c etait la frontiere.

Deux de mes mesures ont menti : un tls=1 qui venait d une AC de 0 octet (le ssh
qui devait la lire avait echoue sans que je regarde son code), et les cles
d hote de TechnoLibre changees a sa reconstruction — je n ai pas touche au
known_hosts de l exploitant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 12:31:51 -04:00
e93e8f4c1d console chez les locataires, collision de noms, et un fichier ecrase
Les deux locataires declarent leur console derriere oauth2-proxy. En oidc le
vestibule n ecoute que la boucle locale : le gabarit annoncait cette protection
en s en remettant au pare-feu, qui ouvrait le port depuis l edge — la console
etait joignable sans passer par la passerelle.

COLLISION REELLE : serveur_oauth2_proxy est mono-instance par machine, et la
table des noms publics etait indexee par GROUPE. La passerelle de la vigie se
croyait la console ; son URL de retour OIDC aurait vise l autre machine. Une
application nomme un COUPLE (machine, role). P67 ne le voyait pas parce qu elle
indexait comme la derivation qu elle garde — une garde qui reproduit le
raisonnement qu elle verifie ne verifie rien.

ET J AI ECRASE group_vars/serveur_ops.yml sans le regarder : 68 lignes
detruites, deux preuves tombees, et une explication fausse construite dessus. Le
git diff qui m avait rassure portait un glob developpe par le shell parent : il
n a rien matche et n a rien dit. Une commande qui ne trouve rien et une commande
qui trouve que rien n a change rendent le meme silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 11:47:50 -04:00
0529951381 la console d exploitation est allumee, et publiee par l edge
console.genese.internal : 401 sans justificatif, la console avec. Le site est le
premier ecosysteme a servir son GUI par un nom, en TLS verifie.

Le vestibule reste, et c est le contraire d une contradiction avec la veille :
Icinga Web 2 a une page de connexion, donc on lui a retire le sien ; le GUI de
Set-OPS sert sa page a qui la demande, jeton inclus, donc le vestibule EST sa
seule serrure. Verifie sur la machine : le GUI n ecoute que 127.0.0.1:8765, seul
le 8090 du vestibule est publie.

Controle dans les deux sens — une serrure ne se prouve qu en la forcant et en
l ouvrant. Et la sonde console-ops exige un REFUS sur une requete anonyme, parce
qu un 200 y serait la pire des reponses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 10:32:48 -04:00
504840f378 console : deux bases confondues, et un etat sans domicile
serveur_icingaweb2 appelle resoudre_base deux fois — moteur puis comptes — et le
second appel ecrase les faits du premier. Les gabarits lisaient donc la seconde
base partout : la console cherchait icingadb_schema dans la base des comptes et
rendait une trace PHP a chaque page, pendant que les 66 tables du moteur etaient
intactes a cote. Celui qui appelle un role partage deux fois doit NOMMER ses
resultats avant de le rappeler.

Et le cadre de migration veut une instance de base quoi qu il arrive :
config_backend db + config_resource icingaweb_db. L erreur des preferences
n apparaissait qu A LA CONNEXION — un journal muet ne prouvait rien tant que
personne n avait ouvert de session, d ou un controle qui en ouvre une vraie.

J ai attribue a tort la fin des erreurs a un rechargement manuel : les horloges
disent que le handler du role avait deja redemarre php-fpm sept minutes plus
tot. Mes fenetres de mesure enjambaient le correctif.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 10:22:53 -04:00
51dae506fb icingaweb2 : le backend natif remplace un vestibule de trop
L exploitant : Icinga Web 2 permet nativement de gerer des comptes ; ce genre
d intervention n est pertinente que pour integrer Icinga a Keycloak chez les
tenants.

Le mode locale posait un auth_basic nginx devant le backend external. Ca
marchait, et ca reinventait une page de connexion devant une application qui en
a une — en privant l exploitant de la gestion des comptes dans l interface. Un
vestibule n a de sens que devant une application qui ne sait pas
s authentifier ; le GUI de Set-OPS est dans ce cas, Icinga Web 2 non.

Mode db : backend natif, groupes natifs, base a elle (les tables du moteur sont
reecrites par ses migrations). Le moteur pose UN compte d amorcage et ne
l ecrase jamais. D-66 redevient applicable sans annuaire, les groupes vivant en
base.

Deux pieges du renommage : une garde ecrite en negation a cesse de garder, et le
bloc de la base pose apres le rendu des .ini a produit un echec CENSURE par
no_log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 08:49:32 -04:00
95dc225037 l edge publie : trois noms en TLS verifie de bout en bout
expositions n etait resolu nulle part — ni par le pare-feu d hote, qui ne filtre
pas l egress, ni par le devis de frontiere. Il n avait jamais eu besoin de
l etre : chez un locataire le trafic edge vers amont ne traverse pas le boitier.
Au site il le traverse. Une declaration peut dormir des mois avant que la
premiere fabric ne la reveille.

Une regle par amont, avec son port. Jamais une regle large : ouvrir l edge sur
tout le site annulerait ce qu une zone de publication separee cherche a obtenir.

La meme faute qu hier, avalee de la meme facon : U au lieu de underlay_mod, et
un except Exception large a transforme le NameError en note plausible. Resserre
a OSError/ValueError — une faute de frappe doit faire du bruit.

Le schema de proxy_pass suit maintenant le port, et l amont TLS est VERIFIE
contre l AC interne : relayer en TLS sans verifier ne fait que deplacer la
confiance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-15 07:50:21 -04:00
fae3cc8838 l edge servait du snakeoil, et deux ecritures d une meme regle
Le certificat bouchon de Debian sur les trois noms. serveur_nginx l a pour
defaut, avec un commentaire d avant la PKI ; un locataire le remplace en
group_vars, l inventaire du site est dynamique et n en a pas. Les autres replis
rendaient un service muet — celui-la rend du TLS qui RESSEMBLE a du TLS : le
cadenas s affiche et rien n est prouve.

site_inventaire construisait sa propre liste d expositions, avec edge = le
groupe de l application. Vrai tant que le site n avait pas d edge. client_pki
retient un FQDN si son edge est un groupe de cet hote : aucun ne correspondait,
et le certificat ne portait que le nom de la machine. La boucle est remplacee
par un appel a expositions_des_applications.

Et la cicatrice de site-forge-01 — un cert renouvele n atteint personne tant que
son consommateur n est pas recharge — a ete refaite sur la machine suivante.

Les 6 noms sont maintenant servis et verifies contre l AC du site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 21:44:03 -04:00
4f68182ac6 l edge du site est debout, et quatre replis silencieux l ont retarde
Les quatre defauts ont la MEME forme : un repli qui rend un succes au lieu d un
refus.

1. Le gabarit dore porte l adresse du cache d avant le renumerotage, et
   site_inventaire derivait dns_amorcage sans deriver son jumeau
   artefacts_amorcage. Le site fournissait cette valeur a ses locataires sans se
   la donner a lui-meme.

2. opnsense_if_zones ignorait la zone neuve, et _if_de retombe sur l ancienne
   patte PLATE : 20 regles posees sur vlan030, correctes et jamais rencontrees.
   Le fichier documentait deja le meme incident un mois plus tot.

3. serveur_nginx chargeait domaines.yml sans condition — un SITE ne publie rien
   a l Internet et n a pas ce registre. Il releve maintenant ce qui existe.

4. Sans registre de domaines, l edge d une exposition retombe sur le GROUPE DE
   L APPLICATION, jamais serveur_nginx : le vhost genere faisait 43 octets et le
   deploiement rendait vert. Une derivation qui ne trouve rien ne se distingue
   pas d une derivation qui n a rien a trouver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 20:57:09 -04:00
09a6a49089 le site gagne un edge, et P23 a nomme le geste qui manque
Le site servait trois applications web sans jamais les publier : observatoire,
vigie et console ne se joignaient que par adresse:port, en clair, depuis le plan
d administration.

Zone site-publication (VLAN 37) a elle seule. Les autres zones separent ce qui
agit de ce qui est agi ; celle-ci separe ce qui est adresse de l exterieur de ce
qui ne doit jamais l etre. Un edge est la machine qu on attaque en premier : elle
ne partage pas son voisinage.

site-edge-01 : 2 vCPU, 2 Go, 20 Go, sans client_backup — un relais ne porte aucun
etat. Elle ne s expose pas elle-meme : les expose des AUTRES applications
deviennent ses vhosts et les SAN de son certificat.

P23 a refuse une passerelle fantome. appliquer_opnsense pose des regles et des
routes, pas des interfaces : declarer la patte 10.37.37.1 rend le geste manuel
visible et verifiable, et le harnais le relit a chaque passage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 19:42:44 -04:00
627ef00ec7 la console d exploitation devient un service, et elle n avait aucune serrure
Le GUI n a aucune authentification : GET / sert la page a qui la demande, jeton
ecrit dedans, et POST /api exige ce jeton que la page vient de donner. Le jeton
garde contre le CSRF, pas contre un visiteur — la seule serrure est
--hote 127.0.0.1. Ce qu il offre a qui entre : deployer, creer, raser, editer le
plan. La fabric entiere.

Le service reste donc sur la boucle locale. Ce qui est publie est un nginx local
qui authentifie d abord : oidc par defaut (oauth2-proxy, donc un groupe
d annuaire qu on revoque sans deploiement), locale en repli pour un ecosysteme
sans annuaire. L authentification est posee au niveau du server, pas d un
location.

La sonde console-ops mesure une SERRURE : une requete anonyme doit etre REFUSEE.
Un 200 y est la pire des reponses, et il ne fait echouer personne.

P54 a attrape une contrainte ratee : serveur_ops est insemine par le SITE, qui
ne detient pas la voute du locataire. Le role ne nomme donc aucune voute — il
declare un parametre, et la couche qui detient le secret le remplit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 19:27:28 -04:00
5ecce4282a une console pour le site, et un repli qui ouvrait vers l Internet
Icinga Web 2 gagne un troisieme mode, locale : nginx authentifie en HTTP Basic
et pose REMOTE_USER, l application le croit. Un SITE n a ni annuaire ni Keycloak
— ce sont des services d ecosysteme. Ni backend LDAP ni backend de groupes : un
backend qui vise une ressource inexistante fait echouer chaque ouverture de
session. L habilitation nomme alors une personne, entorse a D-66 ecrite plutot
que contournee.

Deux pieges en chemin : resoudre_annuaire etait appele sans condition et tombait
sur NoneType has no len (default sans son second argument ne remplace pas None),
et le flux du role ne nommait que l edge — le site n en a pas, donc personne ne
pouvait entrer. serveur_grafana portait deja la reponse.

LE DEFAUT DU DEVIS : une sortie vers un role ABSENT de l ecosysteme retombait
sur !SETOPS_INTERNES, la forme de vers l Internet. La frontiere aurait autorise
la console a parler LDAPS a n importe quelle machine du monde, pour joindre un
annuaire qui n existe pas. C est une source vide ouvre le port, cote
destination. Trois regles du meme defaut etaient DEJA posees pour postfix.

La frontiere n est pas ecrite : elle porte la production, et le devis attend un
mot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:11:22 -04:00
c308c9c5b0 TLS des bases : le consommateur suit son serveur, il ne le devine plus
La supervision du SITE etait morte depuis 11:10 et rien ne le disait. icingadb
refuse par pg_hba — hostssl impose cote serveur, connexion en clair cote client.
icinga2 tournait, redis tournait, les sondes poussaient, et rien n atteignait la
base : les verdicts se calculaient dans le vide.

La cause est une seconde liste tenue a la main. tls_force allume hostssl ; chaque
consommateur avait SON interrupteur a allumer dans les group_vars. Chezlepro
avait les trois, le site avait le premier. serveur_forgejo disait pire que rien :
sslmode disable ecrit en dur, le contraire de ce que le serveur imposait.

resoudre_base expose resoudre_base_db_tls_force, lu dans les hostvars de la
machine qui PORTE la base. Les trois interrupteurs en derivent. Un serveur qui
ne declare rien ne force rien : on ne casse pas un ecosysteme qui n a pas
bascule.

P78 refuse une valeur ecrite chez un consommateur, et nomme les deux roles sans
reglage TLS plutot que de rendre un vert muet sur eux.

Apres : icingadb active, TLSv1.3 vu par PostgreSQL, 16 hotes et 88 services en
base. Les cinq sondes des marqueurs du site, INCONNU faute de deploiement,
rapportent leur phrase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 18:42:21 -04:00
d4bc3e48b7 nom public : la garde ne regardait qu un cote de la cloture
Le plan du site expose observatoire.genese.internal ; serveur_grafana devinait
grafana.<domaine>. Grafana fabriquait son root_url, ses liens d alerte et son
URL de retour OIDC avec un nom que rien ne sert.

C est mot pour mot le defaut du 2026-09-10, quatre jours plus tard, de l autre
cote : instancier derive <groupe>_hostname depuis ce jour-la et P67 le garde,
mais tous deux ne connaissent que l instance montee — donc un locataire.
L inventaire du site n en derivait aucun. Quatre devinettes sur cinq tombaient
juste par convention, et c est ce qui rendait le defaut invisible.

site_inventaire.py derive les cinq noms du plan. P67 lit maintenant les deux
inventaires — celui du site est dynamique, donc elle l EXECUTE au lieu de lire
sa source. Eprouvee dans les deux sens : 10 services sur 2 inventaires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 17:47:36 -04:00
bb88f1f981 voute du site : vault_pg_exportateur, et la chaine est complete
Le gabarit de voute reclamait cette cle pour serveur_postgresql ; la voute reelle
du site ne l avait pas. Le gabarit disait quoi mettre, personne ne l avait mis.

40 caracteres alphanumeriques : le mot de passe entre dans une URI de connexion
PostgreSQL, et un @ ou un / y couperait l URI en deux — l echec dirait mauvais
mot de passe au lieu d URI mal formee.

Gardes passees : copie de surete, dechiffrement vers un FICHIER jamais vers un
tube, relecture du YAML, en-tete verifie apres, empreinte comparee, 15 cles
relues, clair passe au shred. Le premier rechiffrement produisait du 1.2
etiquete la ou la voute portait du 1.1 — refait sans etiquette, parce que le
runner du site ouvre avec un unique fichier-cle.

Compte setops_metriques : ni superuser, ni createdb, ni createrole, membre de
pg_monitor et rien d autre.

Les huit panneaux des deux tableaux derives repondent, mesures sur le site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 17:16:23 -04:00
b02d3c900e temoin du wiki
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 16:35:03 -04:00
392da4d3f5 fiche : les panneaux etaient caches derriere l exportateur
Le tableau des panneaux vivait DANS la condition de l exportateur. Le premier
role a declarer des panneaux sans exportateur — client_metrique, dont le job
node est universel — affichait « aucun exportateur declare » et cachait ses
quatre panneaux.

Une fiche qui tait ce qu un role declare est pire qu une fiche absente : elle
affirme que rien n existe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 16:34:51 -04:00
311f33d6f6 grafana : les panneaux declares deviennent des tableaux
La moitie qui manquait. Les roles declaraient leurs panneaux, Prometheus en
derivait ses cibles, les expressions repondaient, et rien ne les assemblait.

serveur_grafana lit les memes meta/metriques.yml que serveur_prometheus et en
assemble un tableau par role — la raison du panneau devient sa description dans
Grafana. Moisson des tableaux orphelins, prefixe setops-role- pour ne jamais
toucher un tableau ecrit a la main, JSON valide avant d etre pose (Grafana ne
tombe pas sur un tableau illisible : il le saute en silence).

client_metrique declare quatre panneaux sans exportateur — le cas symetrique.
Celui qui compte : la memoire disponible, depuis que le ballon est actif.

Eprouve sur le site : les deux tableaux charges dans le stockage unifie, un
temoin orphelin retire, les quatre panneaux de flotte a 10 series chacun.

Un panneau etait faux — /var/lib/lxcfs rapporte toujours zero octet libre et un
min() en meurt. Corrige en liste blanche : un montage inconnu manque au graphe,
ce qui se voit, au lieu de l ecraser.

P77 garde la table des unites : une unite inventee retombe sur short, lisible et
fausse. 76 OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 16:32:30 -04:00
baa9d12054 wiki : WIKI_REMOTE etait documente cinq fois et jamais lu
La cible lisait $$remote — une variable de shell que rien ne definit — au lieu
de $(WIKI_REMOTE). L echappatoire que le message d erreur proposait lui-meme
n existait pas.

WIKI_BRANCHE, deux cibles plus bas, etait ecrit correctement depuis le debut.
Une option qui se lit autrement que sa voisine est l endroit ou regarder.

Wiki publie sur eregion : 95 pages, les huit fiches neuves portent leur sonde —
verifie en clonant la forge, pas en lisant le temoin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 15:46:43 -04:00
aac74f6043 supervision : les huit derniers roles, et deux defauts que l epreuve a trouves
Les 33 roles serveur_* declarent maintenant une sonde. Les huit qui manquaient
sont ceux dont la verite ne ressemble pas a « ce service repond-il ».

Quatre marqueurs du site : ce que tasks/main.yml verifie UNE FOIS au deploiement
cesse d etre vrai sans que rien ne tombe. La racine du cache se retrouve chainee,
la forge du genome repond en n ayant plus rien dedans, un locataire n est plus
admis a resoudre, l isolation d un depot glisse.

serveur_ops_site ne sert rien : il detient un pouvoir. La sonde verifie que la
carte est la, que la voute du site est chiffree et que sa cle est en 0600 — sans
lire le contenu d aucun des trois.

serveur_icingaweb2 surveille la vitrine de la supervision elle-meme : si la
console meurt, tout reste vert et l exploitant est aveugle.

DEFAUT 1 — quatre gabarits qu Ansible aurait refuse de rendre. Jinja lit le
{# de ${#tableau[@]} comme un debut de commentaire. Le depot connaissait le
remede et l appliquait la ou quelqu un s etait fait prendre, nulle part ailleurs.
P76 rend desormais chaque gabarit de role, avec les delimiteurs qu Ansible en
tirerait — pas une recherche de motif.

DEFAUT 2 — la doctrine promettait 54 greffons et citait check_pgsql. La flotte a
monitoring-plugins-basic : 53, sans check_pgsql ni check_dns ni check_ldap. Le
paquet qui les porte traine samba et snmp sur chaque machine. Un greffon absent
sort en 127, qui n est pas un code Nagios.

21 controles negatifs sur les machines reelles du site. ansible-lint production
0/91, harnais 75 OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 15:43:52 -04:00
4290b566b4 la decision de la forge appliquee a toute la flotte
Trois plans de locataire et cinq modeles perdent leur forge ou leur cache
d artefacts. Une machine de moins par ecosysteme neuf : origine passe de cinq
a quatre, et tout ecosysteme neuf en descend.

Retirer un service, ce sont cinq points d attache — le service, sa machine, sa
base, son client SSO, sa configuration de role. En oublier un laisse un
inventaire qui se genere et un deploiement qui echoue plus tard.

Preuve statique apres coup : 74 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 15:03:59 -04:00
f307a23345 le wiki suit le genome : un locataire n en est pas depositaire
Decision : seuls les SITES portent la forge, le cache APT et les artefacts. Meme
geste que le retrait de serveur_artefacts d un plan de locataire, un cran plus
loin — le site fournit tout ce dont un tenant a besoin pour venir au monde.

La generalisation de forge_amorcer a un locataire est revenue en arriere : elle
implementait le modele ecarte, et la garder en ferait un piege.

ma_forge ne derive plus MA forge mais la forge qui porte MON genome. Le signal
est deja au plan : serveur_ops_forge_externe dit je lis mon genome ailleurs. Qui
le declare consulte, il ne republie pas.

Reste vrai : une forge de locataire peut exister pour LE CODE DE SES GENS —
c est l offre Atelier. Ce n est pas l endroit ou vit le moteur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 14:41:23 -04:00
8574b7b540 wiki : chaque runner publie pour SA forge
J allais centraliser — un controleur poussant vers N forges, un temoin portant
une liste. Ca aurait demande un acces sur chaque forge depuis un seul poste, et
fait du temoin un fait global que personne ne detient.

Le modele suit la ligne du reste : chacun sert les siens. Sans WIKI_REMOTE,
l adresse se derive du nom que le plan monte expose pour serveur_forgejo.

Deux notions a ne pas confondre : la forge AMONT ou l on LIT le genome, et MA
forge ou l on SERT les siens. Le runner d un locataire lit chez son hebergeur et
sert chez lui.

WIKI_REMOTE l emporte toujours : le poste du mainteneur n est le runner d aucun
ecosysteme et publie vers le domicile public du projet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 14:26:29 -04:00
70193af24d wiki : publie, et le temoin pointait une forge vide
make wiki-publier a refuse : le wiki vise par le temoin — la forge du SITE —
n a aucun commit. Le wiki vivant est sur eregion, 26 pages, branche main. Le
temoin affirmait depuis le 12 avoir publie sur une forge dont le depot wiki n a
pas survecu a sa reconstruction.

C est ce que P60 annonce d elle-meme : un temoin dit ce qui est PARTI, jamais ce
qui est ARRIVE. La preuve etait verte et la documentation n existait nulle part
a l adresse qu elle nommait.

Le garde-fou a tenu : publier sur un wiki vide aurait invente un nom de branche.

95 pages en ligne, dont les 68 fiches et leur index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 14:14:07 -04:00
d76ce574f0 fiches de role au wiki : 68 generees, l index, et trois gardes qui ont mordu
Generees depuis ce que chaque role declare, avec un schema mermaid et la raison
de chaque ligne. Une section vide est une information : l index recompte ce que
la flotte ne declare pas — 36 sans flux, 40 sans sonde, 67 sans metrique.

La charte du wiki disait de ne pas recopier le depot, pour eviter la derive. Le
motif ne vaut pas pour une page qui relit sa source ; l exception est nommee
plutot que prise en silence.

La navigation exigeait une citation DIRECTE alors que son motif parle
d atteignabilite. L exigence litterale interdisait toute page d index — la garde
forcait a degrader ce qu elle protegeait. Elle suit maintenant les liens de
proche en proche, et son motif reconnait le souligne : un motif trop etroit ne
rend pas une garde prudente, il la rend aveugle.

Et le compte du wiki serait passe de 27 a 96 : une fiche generee n est pas une
unite d apprentissage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 14:09:13 -04:00
1b71884871 make fiches : les 68, et la cible qui les regenere
Une preuve a mordu : un script qu aucune cible n appelle est du code mort. Elle
avait raison.

68 fiches generees. Le vide y est une information — la carte de ce qui reste a
faire, tenue a jour toute seule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 13:14:01 -04:00
a4a8fc6065 fiche_role : la fiche d un role, generee depuis ce qu il declare
68 roles. Autant de pages faites a la main seraient perimees avant la fin du
mois — c est le defaut que ce depot traque partout sous le nom une liste qui
suit une autre prend du retard.

La fiche lit meta/flux, supervision, metriques, empreinte, authentification. Un
schema mermaid au centre, puis les tableaux, et chaque ligne porte la RAISON que
le role a declaree.

Une section vide est une information, pas un defaut de la fiche : elle devient
la carte de ce qui reste a faire, tenue a jour toute seule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 13:12:06 -04:00
818a100ded docs : metriques-conception, l autre moitie d une phrase deja ecrite
supervision-conception disait deja qu une metrique a seuil appartient a
Prometheus et Grafana. Ce document est l autre moitie.

Il porte le critere des panneaux — une serie a sa place si elle PRECEDE un
verdict ou si elle n en aura JAMAIS — le compte de service au moindre droit, la
chaine de connexion hors ligne de commande, et la dette du chiffrement ecrite
plutot que tue.

La carte nomme desormais les deux ensemble : deux questions, deux fichiers meta,
un meme patron.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 13:05:15 -04:00
a431c13025 metriques : la chaine complete, de la declaration au graphe
Declaration, flux, installation, 657 series servies, job derive par Prometheus,
cible up, et les trois panneaux interroges pour de vrai : 11 connexions, cache a
0,9982, 84 Mo de bases.

Le crochet cibles_supplementaires n est plus vide pour la premiere fois.

Au passage, la voute a rappele sa ligne : vault.yml est gitignore, aucun secret
ne transite par la forge. Le runner avait saute l exportateur sans rien dire
(changed=0) parce que sa voute datait. Le geste d armement est le seul transport
de secret du systeme, et il se refait a chaque changement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 11:46:43 -04:00
f4d57009ab meta/metriques.yml : le role declare son exportateur, le moteur derive
Aucune metrique de SERVICE n etait collectee — seulement du systeme. Le crochet
serveur_prometheus_cibles_supplementaires existait, documente, et personne ne le
remplissait.

Le pendant de meta/supervision.yml et son contraire : une sonde rend un verdict
avec un TTL, un exportateur expose une serie. Est-ce casse, contre depuis quand
et vers ou.

Le critere des panneaux : une serie a sa place si elle PRECEDE un verdict ou si
elle n en aura JAMAIS. Le taux de succes du cache n en aura jamais — quand les
donnees depassent shared_buffers, rien ne casse et tout devient lent.

Compte en lecture seule (pg_monitor), et flux en clair avec sa dette inscrite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 11:35:14 -04:00
8eb482e0c6 site-deployer-tout : l orchestration existait, rien ne la lancait
playbooks/site.yml ordonne les couches pour n importe quelle instance. Mais
deployer-tout ne vise que l inventaire d un locataire, et le site n avait que
site-appliquer GROUPE=<un seul>.

Un hebergeur qu on ne peut remonter qu en enchainant onze groupes de memoire
n est pas reconstructible : il est reparable par quelqu un qui se souvient.

Et quatre gardes que --check rendait folles, toutes de la meme famille : une
garde qui compare contre ce qu une tache du meme play vient de produire n a rien
a dire tant que rien n a ete ecrit. Sept machines en echec sur un site sain, en
suivant le conseil du Makefile lui-meme.

Apres correction : 7/7, 0 echec, de 174 a 309 taches par machine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 11:02:37 -04:00
52b9d31a5d sonde depot : prouvee, et le chemin qu il a fallu ouvrir pour y arriver
Depot sain code 0 avec 4 depots et 374G libres. Chemin non inscriptible code 2.
Chemin inexistant code 2. Zero temoin residuel dans le vrai depot.

Pour y arriver il a fallu que le runner du site puisse entrer chez lui — ce
qu il ne savait pas faire. Trois causes empilees, dont une que j ai ecrite en
corrigeant les deux autres.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 10:23:42 -04:00
9709817be6 site : le rebond ET la cle decrivent le controleur, pas le site
Deux corrections, et la premiere est une faute que j ai ecrite ce soir.

La fonction appelait underlay_mod — un nom qui n existe pas dans ce fichier, le
module y est importe as U. Python levait un AttributeError a chaque appel, et
mon except Exception: return False le rendait MUET. La fonction repondait donc
toujours je ne suis pas une machine du site, le rebond etait toujours pose, et
rien ne le disait. Une garde qui se tait ne garde rien.

Le except ne couvre plus qu un plan illisible. Une faute de programmation
remonte.

Et cle_ssh subit le meme traitement que le rebond : il vaut le chemin de la cle
sur le POSTE. Impose au runner, il designe un fichier absent chez lui. Les deux
decrivent COMMENT ON ARRIVE, pas ce vers quoi on va.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 10:20:34 -04:00
b7884217ce le rebond se decide par le NOM, pas par l adresse
Troisieme formulation, et les deux premieres etaient fausses.

Suis-je dans un RESEAU du site : le poste porte 10.37.0.17, donc il y est, et il
aurait perdu son rebond alors qu il en a besoin. Etre dans un reseau ne dit rien
de ce qu on atteint.

Mon adresse est-elle dans underlay.hotes : hotes ne contient que les EQUIPEMENTS.
Les machines du site vivent dans son PLAN, et le module le dit lui-meme. La
comparaison portait sur une liste vide et rendait toujours False, sans rien
signaler — une garde muette qui ne gardait rien.

Le runner s appelle site-ops-01, et c est une cle du plan du site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 10:15:55 -04:00
63437b5726 le site pouvait inseminer un locataire, pas se configurer lui-meme
Deux defauts qui s additionnaient, et un message qui accusait le mauvais.

1. Le site n avait AUCUN moyen d autoriser une cle d administration. Un
   locataire declare ssh_baseline_cles_admin ; cote site, rien ne transmettait
   cette liste. Ses machines n autorisaient que la cle de cloud-init.

2. Le rebond etait pose sans condition. Depuis le poste c est juste — aucune
   route directe. Depuis le runner du site, qui vit DANS le site, c est un
   detour qui casse : il doit s authentifier aupres de la frontiere ou sa cle
   n est pas autorisee.

Et OpenSSH rend alors Host key verification failed / Connection closed by
UNKNOWN port 65535 — un message qui accuse les cles d HOTE alors que l echec
est une AUTHENTIFICATION, et sur le SAUTEUR, pas sur la cible.

Le critere du rebond est desormais es-tu une MACHINE du site, pas es-tu dans un
RESEAU du site : le poste porte 10.37.0.17, donc il est dans le reseau
management, et il aurait perdu son rebond alors qu il en a besoin.

L amorcage est circulaire et se rompt par le poste : lui seul entrait, il pose
la cle, le runner est ensuite autonome. Verifie : 5/5 machines du site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 10:08:50 -04:00
7348eb93d1 serveur_backup : ce qu un depot peut affirmer sans pouvoir lire
Les instantanes sont chiffres cote client : ce serveur heberge des octets qu il
ne peut pas ouvrir, donc pas juger. La verification suit la cle, et
client_backup la fait deja depuis chaque noeud.

Ce que la sonde ajoute : elle voit TOUT DE SUITE, et depuis la cause, ce que les
clients ne decouvriront qu a leur prochaine execution. Lecture seule apres une
erreur disque, volume plein, droits derives — le depot refuse alors tout le
monde, et neuf rouges epars ne designent pas une cause commune.

Elle ECRIT vraiment, sous l identite qui depose. Un test -w ment sur un montage
en lecture seule et sur un quota atteint.

Corrige aussi l en-tete de setops-sauvegardes.conf.j2, qui nommait encore
backup-01 comme pousseur — retire le 2026-09-02, et c est tout le sujet. Le code
avait suivi la decision, l en-tete non.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:58:54 -04:00
07216a5d30 supervision : les trois controles negatifs rejoues sur TechnoLibre
edition, filtrage, passerelle : vert code 0 sur le sain, rouge code 2 avec une
chaine introuvable. Services intacts apres l essai.

Une sonde qu on n a jamais vue echouer n est pas une sonde, c est une habitude.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:37:37 -04:00
f815b069f7 supervision : le lot check_http, et les greffons qui manquaient a la flotte
La doctrine dit qu un greffon Nagios EST une sonde valide et compte sur les 54
de monitoring-plugins. Mesure : aucun n etait installe. Le document decrivait
une possibilite qui n existait pas, et chaque role ayant besoin d un controle
HTTP n avait d autre choix que d ecrire du shell.

Le PORTEUR les installe desormais — c est son affaire de pouvoir executer des
sondes, et les poser role par role en ferait autant de copies de la decision.

Trois sondes, trois enveloppes qui delegue a check_http et rendent son code tel
quel :

  collabora    edition     /hosting/discovery — l adresse que Nextcloud interroge
                           lui-meme ; sans elle le bouton ouvrir meurt et
                           Nextcloud reste vert
  rspamd       filtrage    un filtre muet ne bloque pas le courrier, il le laisse
                           passer
  oauth2_proxy passerelle  ce qui tombe avec elle n est pas elle : les services
                           derriere restent debout et deviennent injoignables

Chacune exige une CHAINE que seul le bon service produit — un 200 peut venir
d une page d erreur. Cette chaine est aussi la mise en defaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:34:31 -04:00
af93a4129e redis : le controle negatif rejoue, et trace
Etat sain vert aux deux passages, Redis arrete rouge, seuil impossible rouge.
Sur data-sql-01 de TechnoLibre, service remis en etat apres l essai.

Une sonde qu on n a jamais vue echouer n est pas une sonde, c est une habitude.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:22:42 -04:00
050b8b267e serveur_redis : la sonde qui manquait, et la panne qu elle voit
Redis est le premier des treize roles serveur_* sans supervision declaree.

Une seule sonde, parce qu il n y a qu une cause d action : le cache ne sert
plus. Elle s atteint par deux chemins — il ne repond pas, ou il repond et n a
rien a dire. Le second est le couteux : borne a maxmemory avec allkeys-lru, un
Redis plein n echoue JAMAIS, il evicte. Les sessions disparaissent, les gens
sont deconnectes au hasard, et le service reste vert. La panne se presente
comme un defaut d application.

Le compteur d evictions est cumulatif : la sonde en fait un DEBIT, sinon une
seule mauvaise journee laisserait le voyant rouge pour toujours.

Pas d occupation memoire ni de taux de succes ici — ce sont des series, elles
appartiennent a Prometheus. Icinga repond a une seule question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:20:10 -04:00
f199888f86 redis : la correction d un fait faux, et la methode qui l a produit
L entree du 2026-09-13 affirmait aucun maxmemory configure. Faux : le grep
visait /etc/redis/redis.conf alors que le role ecrit setops.conf. Interroge la
ou la verite vit, redis repond maxmemory 268435456 et allkeys-lru.

La conclusion ne change pas — redis reste hors de la table des planchers — mais
sa raison change : il est borne a 256 Mo, il ne peut pas prendre la machine.

Un fichier de configuration n est pas la configuration ; c est une de ses
sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 09:16:33 -04:00
0b848fddc9 postfix : l ordre des handlers decidait de la panne
Ansible declenche les handlers dans l ordre ou ils sont DEFINIS, jamais dans
celui ou les taches les notifient. Recharger etait ecrit en premier.

Sur un premier deploiement, main.cf passe inet_protocols de all a ipv4 et
notifie un redemarrage ; les cartes LDAP notifient un rechargement. Le
rechargement partait d abord, sur un maitre portant encore l ancienne valeur :
to change inet_protocols, stop and start Postfix — puis fatal sur :::submission.

Le role documentait le piege depuis le 2026-08-30 et notifiait correctement.
Seul l ordre des handlers le rendait inoperant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 00:31:19 -04:00
af3fb1c0f1 sondes : deux roles ecrivaient dans un repertoire qu ils ne creaient pas
Vingt-trois roles sur vingt-cinq assurent /usr/local/lib/setops/sondes avant d y
deposer quoi que ce soit. auditd et chrony etaient les exceptions, et ils s en
tiraient parce qu un role plus precoce l avait cree.

Une dependance d ordre ecrite nulle part : elle tient tant que la couche complete
passe dans l ordre habituel, et casse des qu on applique un groupe SEUL.
Destination directory does not exist, sur une machine et pas les treize autres.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 23:43:46 -04:00
220e1e3e28 deployer-tout : le genome est-il a jour avant de poser quoi que ce soit
Un runner tire ce qu on pousse, il ne le recoit pas. Trois fois ce soir un
genome perime a menace de rebatir un etat depasse — et aucun des deux runners
n aurait echoue : un deploiement depuis un genome perime REUSSIT. Il applique
fidelement un etat qui n a plus cours, et se presente en vert.

En retard : refus. En avance ou diverge : note et on continue. Forge
injoignable : note et on continue — une forge en panne ne doit pas immobiliser
une exploitation, le silence serait la faute.

FORCE=1 passe outre. Eprouvee dans les trois sens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 23:32:16 -04:00
8a9ac18462 intrants du site : la table comparait cinq valeurs sur sept
Le controle rapportait CONFORME sur un locataire dont le plan d administration
pointait les bouts de WAN d un AUTRE site. nftables_admin_ssh et
passerelle_sortie n etaient compares par personne.

Quatorze machines debout, le runner insemine, et l exploitant bloque a la porte
sans qu aucune garde n ait rien eu a dire.

nftables_admin_ssh est une liste : la comparaison normalise, parce que l ordre
d une liste de sources ne porte aucun sens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 22:52:19 -04:00
db1cb652b7 insemination : le runner verifie SA cle avant de partir
La cle d amorcage du plan est une transcription de la cle publique du runner du
site. Reconstruire le runner lui donne une paire neuve ; la transcription, elle,
ne bouge pas. Meme algorithme, meme commentaire, materiel different : rien ne
distingue les deux a l oeil.

La garde ne demande rien au reseau — le runner du site EST la machine qui
insemine, sa cle est sous sa main. Elle ne compare que si les deux cles nomment
le meme hote : lancee depuis le poste d un exploitant, elle se tait.

Elle dit aussi ce que la correction seule ne suffit pas a reparer : une machine
deja creee porte la cle perimee, il faut la RECREER.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 22:04:41 -04:00
4b759110bf pages publiees : la liste entiere, et la page qui affirme un prix
Le registre ne couvrait que la serie des flux. Quatre pages publiees n en
faisaient pas partie, dont la seule qui affirme un ETAT et un PRIX. Une page
absente de cette liste vit sur une adresse que personne ne devine, et vieillit
sans que quiconque s en apercoive.

La page commerciale a ete reprise sur mesure et non de memoire :

- collaboration et couche web passent de la feuille de route au service ;
  Nextcloud, Collabora et les deux briques web tournent sur des machines reelles
- Maison OBNL, Cabinet et Presence Web quittent le chantier
- les chiffres recomptes : 51 roles -> 68, 16 preuves -> 75, 54 affirmations ->
  70, 8600 lignes -> 12600
- huit piliers sous un titre qui en annoncait sept — le defaut que P59 garde
- une section neuve sur le modele hebergeur/locataires, qui manquait entierement
- les sections Honnetete vendaient des reserves devenues fausses ; elles portent
  maintenant celles qui sont vraies : aucune reprise exercee, aucun depart
  accompagne, une seule destination de sauvegarde

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 20:32:02 -04:00
224ac2fe32 clonage : le plancher se perdait au troisieme maillon
instancier le derivait, le playbook savait le poser, et la table qui les relie
ne transmettait rien. Quatorze machines allaient naitre avec le ballooning
desactive sans qu une seule etape echoue : variable vide, extra-var non passee,
garde when: qui saute. Trois silences en file.

P75 lit la cible creer-vm, releve chaque $SETOPS_X qu elle consomme et exige
que la table qui les emet le declare. Eprouvee dans les deux sens.

74 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 20:10:24 -04:00
77cf63f1cf flux : un dossier genere doit aussi savoir supprimer
flux-genere/ ecrivait sans jamais retirer. Un hote sorti du plan y laissait son
ruleset complet, indiscernable des vivants. Le dossier cessait d'etre une image
du plan pour devenir le cumul de tous les plans successifs.

P53 voyait l'orphelin — un ruleset perime n'a pas de refus audible — mais
designait le fichier, pas la cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 20:03:58 -04:00
bd0e40753b memoire : ce que l hote a le droit de reprendre, et pourquoi il ne le pouvait pas
61 Go declares a 21 machines, 12 reellement utilises. balloon valait 0 partout,
ce qui ne desactive pas le ballooning : cela RETIRE le peripherique de la ligne
de commande QEMU. Le moniteur repond "No balloon device has been activated".

Le moteur derive desormais un plancher par machine sur les deux chemins de
creation, et le playbook de clonage le pose a cote de memory.

La table des planchers a ete corrigee deux fois par la mesure : shared_buffers
vaut 128 Mo et non une part de la RAM, une JVM tient 605 Mo, Redis 16 Mo. Ces
machines tiennent du cache de pages, pas un jeu de donnees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 19:57:11 -04:00
94bfa224ff gabarit dore : une seule declaration, et les deux souches se rejoignent
Le plan du site disait 9006 (modeleSetOPS-minimal), l underlay 99998 — que le plan
nomme lui-meme `precedent`. Le Makefile derive VMID_MODELE du PLAN : les locataires
clonaient 9006. `site_machines` lisait l autre : les machines du SITE clonaient 99998.

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

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

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

P74 refuse toute redeclaration. Eprouvee dans les deux sens.

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 17:54:56 -04:00
ab0e74d843 schemas de flux : l index, et la regle qui les gouverne
Neuf pages expliquent comment les choses sont configurees et interconnectees. Leurs
adresses n existaient nulle part dans le genome : elles vivaient dans une conversation.

L index les range en trois familles — la serie des huit flux, la page qui les relie
(la filiation), et celles qui visent quelqu un d autre que le mainteneur.

LA REGLE D ECRITURE Y EST ENONCEE, parce que c est elle qui manquait : garder le
mecanisme et sa raison, retirer l anecdote, la date, la duree, le nombre de machines
touchees. Un schema sert a voir comment c est branche ; relater un incident est le
travail du CHANGELOG et du registre des decisions.

Les chiffres restent admis quand ils donnent un ordre de grandeur utile, pas quand ils
racontent une soiree particuliere.

Une derniere section dit QUAND les relire : un service prete ajoute ou retire, un
renumerotage, une bascule, un role neuf avec une sonde.

Carte d orientation : 40 -> 41 documents. P34 a exige que ce document declare son
lecteur, comme tous les autres.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 17:41:33 -04:00
a154893fc1 restauration : lire aussi l archive en clair, pas seulement la chiffree
`--support-chiffre` ecrit un tar clair quand le support est deja chiffre au repos.
Ajoute cote EXPORT le 12 septembre, et pas cote RESTAURATION : `restaurer_cles.py`,
qui est ce qui rend la cle USB autonome, ne savait lire que du GPG.

Constate en EPROUVANT l export, pas en le supposant reussi. Et le message etait le
pire possible :

    gpg: aucune donnee OpenPGP valable n a ete trouvee.
    ECHEC du dechiffrement. Phrase de passe erronee, ou archive abimee.

Il accusait une phrase de passe qu on n avait jamais posee, sur une archive saine. Il
envoyait chercher la faute la ou elle n est pas — au moment ou l on restaure, c est-a
-dire au moment ou l on sait le moins.

`tarfile.is_tarfile` reconnait la forme. Pas de drapeau a passer : ce serait une chose
de plus a savoir le jour ou tout a brule.

Verifie sur la cle reelle : l archive du 13 se liste, dix fichiers, chacun avec sa
destination. Le garde-fou a par ailleurs refuse d ecraser celle du 12 — « on n ecrase
pas une sauvegarde de cles : elle est peut-etre la seule » — donc la nouvelle est
datee et l ancienne reste.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 16:32:07 -04:00
025776064a intrants du site : ce qu un locataire doit savoir pour l habiter, derive
Un locataire ECRIT les adresses des services de son site — resolveur, cache, forge,
depot de sauvegarde, plan d administration, sortie. Une copie se perime, et deux
l avaient fait en deux jours avec la meme forme : `serveur_ops_forge_amont` visait
10.0.33.11 quand la forge sert en 10.37.33.11, et `ac-racine-site.crt` portait la
racine d avant la reconstruction du site. Rien ne les relisait.

`make site-intrants` lit le plan du site et rend le contrat — sept valeurs, toutes
derivees. `make site-intrants-verifier` les confronte a ce que le locataire declare,
et P73 en fait une preuve. Elle a trouve le defaut de la forge des sa premiere
execution.

Le contrat se DERIVE du plan du site, pas d une liste tenue a part : ajouter un
service prete au site l ajoute au contrat, sans qu on ait a y penser.

P69 RESTREINTE AU COUPLE MONTE. Elle balayait tous les depots OPS-* et les comparait
au site monte. Elle avait raison tant qu un seul site existait : une adresse en
10.x.3z ne pouvait designer que lui. Deux sites decoupent leurs zones de la meme
facon — c est le but, un locataire doit pouvoir habiter l un ou l autre sans se
renumeroter. Le troisieme octet a cesse de distinguer « mon site » d « un autre
site » : 10.31.34.11, juste pour un locataire de TechnoLibre, etait declare faux
parce que Chezlepro etait monte.

Un locataire n appartient a aucun site — il en habite un, choisi par le symlink au
deploiement. La seule paire jugeable est celle qui est montee. Meme portee que P73.

Une marche payee : la premiere version de site_intrants recopiait la resolution
d instance au lieu de la partager. P41 a mordu — neuf modules avaient deja porte
chacun leur copie, et cinq defauts en etaient sortis en cinq jours.

make prouver : 72 OK, 0 echec, 1 saute. ansible-lint : 0 failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 16:18:12 -04:00
b4e3520561 annuaire : un compte de service par consommateur, et la porte se ferme
Keycloak, Dovecot, Postfix et Icinga Web 2 se liaient TOUS avec cn=admin, le compte
d administration de la base. C est le rootDN : slapd lui fait contourner toutes les
ACL. Un seul secret, quatre services, tous les droits sur l arbre — pour ce qui est,
trois fois sur quatre, une simple lecture.

Et les droits livres par Debian etaient intacts : `to * by * read`. Sur ldap://, sans
s authentifier, une machine du reseau enumerait tous les comptes et toutes les
adresses. Des comptes a droits mesures n auraient rien valu tant que cette ligne
restait : on aurait ferme la porte en laissant la fenetre.

L indice etait deja dans le depot. `validatePasswordPolicy` existe parce que slapd
n applique pas ses controles de qualite au rootDN : la consequence etait compensee,
la cause intacte.

- ou=services, un compte par consommateur, secret propre en voute
- sept regles d acces posees EN ENTIER (state: exact) : l ordre est la regle, et
  inserer c est parier sur ce que le paquet aura mis avant nous
- amorcage_acces garde le compte d administration, NOMME comme l exception : il ne
  consomme pas l annuaire, il le provisionne depuis la socket locale
- la sonde passe de -x a -Y EXTERNAL : elle lisait en anonyme et aurait annonce un
  annuaire VIDE sur un annuaire parfaitement sain
- la rotation du compte d administration devient possible (elle n etait posee qu a
  l installation, par debconf : la voute et slapd divergeaient en silence)

Quatre marches payees en chemin :
1. un cinquieme appelant oublie, dont l echec etait masque par no_log — la garde
   refuse desormais SANS no_log : elle nomme la cle absente, jamais son contenu
2. la federation Keycloak ne reecrivait son bindDn que si l URL ou le mode changeaient
   — nouveau secret, ancien nom, error code 49
3. la rotation placee APRES les taches qui se lient en administrateur
4. ansible-vault et son tube : sortie non bloquante = echec silencieux, la voute
   paraissait tournee et etait identique a l octet

P72 exige que tout role incluant resoudre_annuaire NOMME son compte, et qu aucun sauf
amorcage_acces ne nomme admin. Eprouvee dans les deux sens.

Verifie sur l infrastructure : chaque compte lit ce qu il doit, aucun ne voit les
autres, la lecture anonyme rend 0 entree, et les quatre services repondent (doveadm
user, postmap -q, decouverte OIDC 200, portier SSO 200).

vault_openldap_admin et vault_ldap_bind_postfix renouveles : les deux avaient transite
en clair par une session d exploitation. Les anciennes valeurs rendent Invalid
credentials (49).

make prouver : 71 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 13:36:38 -04:00
db4223904d pools : le genome ne nait plus chez un locataire
`Chezlepro-17` contenait VINGT ET UNE VM : les quatorze du locataire ET les sept du
genome. `OPS-Chezlepro` et `OPS-Technolibre`, crees a la main, etaient vides.

La cause : `site-creer` appelle `cloner-vm`, qui derive son pool par `--pool-actif`,
c est-a-dire le pool du TENANT lie. Les machines du site heritaient du locataire
courant. Range a la main, ca se serait defait au prochain `site-creer` — sans un mot,
parce que la VM est bien creee, bien nommee, bien adressee. Seule son appartenance
est fausse, et rien ne la regarde.

- `--pool-site` rend le nom invariable du pool du genome. Option DISTINCTE, pas un
  drapeau sur la premiere : une fonction qui repond aux deux questions finit par se
  tromper d appelant.
- `cloner-vm` accepte une surcharge POOL= ; `site-creer` la nomme. Substitution au
  niveau MAKE, pas shell : POOL arrive du sur-make comme variable make, et $${POOL}
  ne l aurait jamais vue.
- P71 exige que `site-creer` nomme son pool. Eprouvee dans les deux sens : passe sur
  le Makefile sain, tire des qu on retire l argument.

Applique au cluster : Site-OPS 9 VM (7 du site + 2 gabarits), OPS-Chezlepro 14,
OPS-Patient0 5. Chezlepro-17, Patient0-29 et Set-OPS supprimes une fois vides. Les
pools anterieurs a Set-OPS et les quinze VM hors pool n ont pas ete touches.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 04:12:27 -04:00
5812e0c6ca depot de binaires : le site tient ce que les runners allaient chercher
Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils
ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH.

Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire
monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et
download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul
paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu.

Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL
signee valable une heure, differente a chaque requete. Un cache qui la prend pour
cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre.

Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie
un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service,
aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre
exactement ce chemin.

Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs.
Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le
depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache
existante n a change.

P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit
une autre prend du retard ; celle-ci est nee avec sa garde.

Deux marches payees en chemin :
- failed_when: false REECRIT le verdict, donc la premiere garde de signature ne
  gardait rien. Elles mesurent le fichier desormais.
- file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas
  en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier.

Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site
ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256
sont identiques a celles qui ont construit Chezlepro ; second passage changed=0.

make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.

Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle
ne reprend rien (304 Not Modified, size 0, attempts 5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 23:45:00 -04:00
56d202a8bb pools nommes comme les depots, et les cles sortent du poste
LE NOM D UN POOL EST CELUI DE SON DEPOT. Chezlepro-17 devient OPS-Chezlepro :
le seed se lisait dans le nom, ce qui obligeait a connaitre le codage — et
surtout le nom CHANGEAIT si l index changeait, ce que la renumerotation du
site a montre le jour meme.

Site-OPS ne derive de rien, et c est le point : les machines du genome ne
dependent d aucun index, elles sont l infrastructure SUR laquelle les index
vivent. Sans ce bloc elles restaient hors de tout pool.

P69 — l amorcage d un tenant designe-t-il le site REEL ? dns_amorcage et
artefacts_amorcage sont ecrits a la main, volontairement : au moment ou ils
servent la machine ne resout aucun nom. Mais ils designent des machines DU
SITE, et n ont pas suivi son renumerotage. La reconstruction du locataire
s est arretee sur Failed to update apt cache, a quinze couches de sa cause.
La preuve ne juge que les valeurs qui PRETENDENT designer le site : viser
9.9.9.9 est un choix, pas un oubli.

LES CLES SORTENT DU POSTE, EN CLAIR, ET C EST RAISONNE. Support perdu : LUKS
s en charge. Poste compromis : la seconde couche n aide pas, les originaux
sont dans ~/.config sur ce meme poste. Elle coutait une phrase de passe
stockee nulle part — le seul point que la procedure ne couvre pas. Option
--support-chiffre explicite ; le defaut reste GPG, parce qu un support non
chiffre est le cas le plus frequent.

Et ma note qui disait les cles sorties depuis le 5 septembre etait fausse :
le support ne portait que le depot hors site du 1er.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 21:37:28 -04:00
b9b0d87db2 trois reconstructions : trouver, verifier, prouver — 37 min 24 au tour 3
Tour 1  8 passages  ~2 h 30   16 defauts
Tour 2  3 passages   53 min    2 defauts
Tour 3  2 passages   37 min    0

LE TOUR 2 EST CELUI QUI COMPTE LE PLUS. J avais annonce deux passages ; il en
fallait trois, parce que les deux blocages connus sont EN SERIE : on ne peut
pas amorcer une forge qui ne tourne pas, et elle ne tourne qu apres la
convergence de la cle. Un runbook ecrit d apres un recit de depannage
contenait une erreur qu aucune relecture n aurait montree — il fallait le
SUIVRE pour la voir.

DEUX MURS DE PLUS, invisibles au tour 1. site-creer rend la main avant que
les machines repondent (3 min 20 mesurees). Et les cles d hote changent a
chaque reconstruction : accept-new couvre la premiere rencontre, jamais un
changement.

LE CYCLE DE LA CLE EST DENOUE. Aucun ordre de couches n y pouvait rien — les
certificats doivent venir tot. C est la propriete du geste qui a change de
main : serveur_forgejo revendique la cle au moment ou il cree le groupe.
client_pki garde la sienne et la repose a chaque passage, parce que step
reecrit la cle a chaque renouvellement. Gain : un passage entier et 300
secondes d attente perdue.

forge-amorcer purge desormais l entree perimee de l hote que GIT va
contacter, lu dans git remote get-url — pas celle de l API, qui peut etre un
tunnel. Et il rend les quatre premieres lignes de stderr : n afficher que la
derniere m a coute un aller-retour, git terminant toujours par un message
generique.

Le runbook porte les six etapes du tour 3 avec leurs durees reelles et les
dix-huit murs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 20:55:59 -04:00
bef0655112 le site reconstruit depuis zero : 7/7, 0 echec, et seize corrections
SITE-Chezlepro n avait jamais ete rase. La limite qu on repetait partout —
l infrastructure d accueil n a jamais ete reconstruite depuis zero — se
lisait comme de la prudence. C etait seize defauts que rien d autre n aurait
pu reveler.

Un locataire naît dans un monde deja peuple : le site lui fournit paquets,
noms, genome, heure et depot. Un site n a personne au-dessus, sauf sa
frontiere. Onze des seize murs viennent de la.

DEUX CAPACITES QUI N EXISTAIENT PAS. make site-raser — rien ne detruisait les
machines du site, donc la limite etait un trou d outillage. make
forge-amorcer — la forge naît vide et le runner y clone ; l amorcage part du
poste, seul endroit qui detienne alors le genome.

TROIS GARDES QUI VERIFIAIENT LA FORME. La plus couteuse des familles : elles
donnent l apparence d une verification. Le resolveur comparait des adresses
au lieu de mesurer si la resolution aboutit, et protegeait ainsi l etat
casse. L administration etait reconnue a son port. Un flux a deux paires
n obtenait qu une branche.

UN ECART DE SECURITE. Le PostgreSQL du site servait le certificat auto-signe
de Debian, sans reseaux autorises ni hostssl — invisible tant qu aucun client
n exigeait la verification. Le defaut n a pas casse la construction : la
construction a revele le defaut.

UN ACCES ACCIDENTEL. Celui de l exploitant tenait au chevauchement d
adressage que le renumerotage a supprime. Separer les index n a pas cause le
probleme, il a retire le hasard qui le masquait.

La sequence du premier jour est ecrite : runbooks §9, avec les seize murs et
ce que chacun enseigne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 19:08:31 -04:00
0c55aafc40 le site prend l index 37, et la capacite de le raser existe enfin
SITE-Chezlepro passe de 10.0.31-36 (tapes a la main, derives de rien) et
10.17.0.0/24 (emprunte au supernet du locataire) a son PROPRE index : gestion
en 10.37.0.0/24, zones en 10.37.31-36. OPS-Chezlepro garde 17.

CE QUE L OPERATION A REVELE. Il n existait aucun moyen de raser le site :
raser.py ne vise que l instance active, un locataire. Ce n est donc pas que
personne n avait essaye de le reconstruire depuis zero — l outil n en offrait
pas le moyen, et la limite se lisait partout sans que sa cause soit nommee.
make site-raser reprend les quatre verrous de raser.py.

TROIS FOIS LE MEME DEFAUT, attrape par l exploitant. La declaration decrit la
CIBLE pendant que l outillage s en sert pour joindre l EXISTANT : renumeroter
le rebond avant de bouger la patte a rendu le site injoignable. Regle posee :
le renumerotage declaratif vient APRES la derniere operation qui a besoin de
l ancienne infrastructure.

nftables_admin_ssh etait une liste a la main qui devait suivre le plan
d administration. Elle a pris du retard le jour meme.

Le plan de frontiere n a propose AUCUNE creation ni suppression de regle —
seulement douze contenus d alias. Les regles visent des alias par leur nom :
un renumerotage complet se reduit a changer ce que les noms designent.

Routes des trois hyperviseurs refaites, declarees ET vives, avec sauvegarde
de /etc/network/interfaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 16:32:06 -04:00
5419ee2d71 un site prend son propre index, et la garde les compte enfin
Decision de l exploitant : sites et locataires se partagent la classe A,
chacun avec son propre index. L exception du guide — un site derive du meme
index que son tenant — disparait.

Ce qu elle cachait : le plan d administration du site vit dans 10.17.0.0/24,
a l interieur du supernet du locataire OPS-Chezlepro. Pas dangereux, mais
10.17.0.0/16 designait deux choses. Et les zones du site ne derivaient de
rien — le site etait la seule partie du systeme sans seed.

La seconde liste nait avec cette decision : un site et un locataire peuvent
desormais reclamer le meme nombre, et make instances ne voyait que les depots
OPS-*. La decouverte lit maintenant SITE-*/underlay.yml et son champ index.
Un site sans index declare reste hors du compte.

SITE-Technolibre : squelette du deuxieme site pour la visite du 14. Un seul
noeud Proxmox en version 9, meme forme que Chezlepro, adresse depuis l index
31. COLLECTE.md liste les 35 valeurs a relever.

Reste a l exploitant : OPS-Chezlepro passe a 37, ce qui libere 17 pour le
site qui l utilise deja.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-12 14:31:51 -04:00
904ece3004 les correctifs de securite reviennent au quotidien, sans personne
Decision de l exploitant, et elle corrige un mauvais jugement de ma part :
j avais decrit la situation correctement — la seule operation de la flotte
qui exige un humain — puis propose un minuteur maison plutot que le mecanisme
que Debian fournit exactement pour ca.

Le desarmement d aout avait un motif reel (48 minutes de verrou dpkg apres un
rattrapage) mais une contrepartie ecrite sans etre mesuree. Ce qui rend la
coexistence tenable existe maintenant : _attendre-hote attend apt-daily et le
verrou, lock_timeout est pose partout, unattended-upgrades ne traite que
l origine -security, et Automatic-Reboot reste false.

Rearmer ne se deduit pas de ne plus desarmer : passer le drapeau a false
saute la tache, il ne demasque rien. Une tache de rearmement explicite est
donc posee le jour meme ou la decision s inverse.

ET LA PREMIERE VERSION A REJOUE LA COURSE. state: started sur les trois
unites lance le rattrapage en plein deploiement : dix machines sur vingt et
une, avec l erreur meme qui avait motive le masquage. Le travail quotidien ne
passe pas par ce service — le timer appelle apt.systemd.daily directement. On
ARME sans declencher : les minuteurs demarres, le service seulement active.

21 machines, minuteurs armes, prochain passage demain vers 06h, zero unite en
echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-11 19:02:10 -04:00
6786d15068 l heure vient de la frontiere : la derniere dependance vivante tombe
Mesure sur les quatorze : aucune sortie TCP vers une adresse publique, apt
par le cache, noms autoritaires en local, unattended-upgrades masked. Et
quatre pairs NTP publics par machine. Le role chrony posait le fuseau et
installait le demon sans jamais toucher a ses sources : le defaut de Debian
tenait depuis le premier jour, herite et jamais choisi.

L autorite est la frontiere, par decision de l exploitant. Elle etait deja
stratum 2 et ecoutait en 123 ; il ne manquait que le passage. 9 anciennes
regles port 123 vers !SETOPS_INTERNES retirees, 9 regles nommees vers
SETOPS_FRONTIERE posees : le changement resserre autant qu il centralise.

Deux chemins parce que la topologie en a deux. Un tenant n atteint pas la
frontiere par sa passerelle de zone — tenue par le SDN — mais par le lien de
transit. Une machine du site a la frontiere pour passerelle directe. Les deux
valeurs sont derivees de l underlay, via reseau_transit() plutot que d un
prefixe d adresse qui aurait menti chez le prochain hebergeur.

La patte face aux tenants manquait a opnsense_if_zones, pour la meme raison
que grappe-controle la veille.

Sonde horloge ecrite en meme temps : synchronisee ET contre la source
DECLAREE. Une machine peut etre parfaitement a l heure contre quatre serveurs
publics — c est exactement l etat d avant.

14 tenant + 7 site, toutes disciplinees, sources publiques = 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-11 12:08:13 -04:00
989398f3cf expositions-etat : le certificat SERVI, pas celui du disque
Renommer une exposition touche cinq choses. Quatre suivent au deploiement ;
la cinquieme est celle que le navigateur regarde. serveur_nginx pose le vhost
et laisse le SAN en arriere — c est client_pki qui reemet, et rien ne le
reclame. Mesure deux fois le 2026-09-10.

Le controle compare trois listes : les expositions du plan, les noms du
certificat servi, et le code que rend le vhost. Dans les deux sens : un nom
reste dans le SAN apres avoir quitte le plan continue d etre authentifie.

Piege trouve en l ecrivant : client_pki met toujours le FQDN et le nom court
de la machine dans le SAN. Les compter comme vestiges faisait crier le
controle a chaque execution — et un controle qui crie toujours ne se lit plus.

Deux formes de terminaison, une seule question : le locataire termine sur un
edge, le site sur la machine elle-meme. Le porteur se derive de l une ou de
l autre.

Ce n est pas une preuve : rien de statique ne peut lire un certificat servi.
Les quatre controles a la demande n etaient nommes nulle part comme famille ;
ils ont maintenant leur section dans les runbooks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-11 10:21:19 -04:00
5f5af70a9c audit : la piste sort de la machine auditee, et quelqu un regarde
auditd tournait, onze regles etaient armees, et rien de tout cela ne servait
a grand-chose. Quatre manques, mesures avant d etre combles.

1. Rien ne sortait. Alloy ne contenait aucune reference a l audit et les cinq
greffons etaient inactifs. Il lit maintenant audit.log et le pousse vers Loki
— sans toucher une permission, alloy etant deja dans le groupe adm. On ecarte
le greffon syslog : journald limite le debit, et une rafale d evenements est
exactement le moment qui compte.

2. Aucune sonde. Elle mesure la COLLECTE, pas l armement : le nombre de
regles est precisement le chiffre qui restait bon pendant la panne du
2026-08-30. Il part en perfdata, jamais en verdict.

3. Retention non declaree. Mesuree a 4,6 Mo/jour au repos ; portee de 40 a
128 Mo. Depuis (1), le local n est plus l archive mais un tampon.

4. Les regles ignoraient la cle privee de l hote. -w /etc/step/ partout, et
-w /etc/step-ca/ sur la seule autorite : auditctl refuse un watch vers un
chemin absent et fait echouer le chargement entier.

Verifie sur l autorite seule d abord, puis 14/14 : regles armees, auditd
actif, sonde a 0, et les quatorze hotes presents dans Loki sous job=audit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-11 09:00:28 -04:00
1b89e83b0c reconstruction a froid : les noms derives naissent justes, et une cle vide tombe
Quatrieme reconstruction depuis zero, 32 min 14 s, 14/14. Elle avait un but
precis : trois roles dependent desormais d une variable que le generateur
pose. A froid, si instancier ne la posait pas, le defaut du role reprendrait
la main en silence — juste pour forge et cloud, faux pour observatoire.

Le certificat ne a froid porte observatoire et vigie, aucun ancien nom, et les
deux retours SSO derivent juste sans reprise.

Un seul echec, sans rapport : sur une machine des quatorze, get_url a rendu
0 octet SANS ERREUR — le cache a servi un 200 au corps vide. Le defaut s est
lu deux cents lignes plus loin, dans un apt qui accusait la signature. La
garde posee hier ne mordait pas : retirer une ressource vide ne vaut qu au
passage suivant, quand le fichier existe deja.

Les cinq roles qui telechargent une cle la mesurent maintenant dans la meme
execution. P68 garde le motif. infra-mail-01 redeployee, make valider a
0 echec sur 14 machines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-11 07:57:24 -04:00
53e2f78148 vigie : la console de supervision porte enfin un nom de fonction
icinga.<tenant>.internal devient vigie.<tenant>.internal, sur les trois
locataires a la fois. Le mot avait ete ecarte pour Grafana precisement parce
qu il connote la guette du danger : c est le metier d Icinga.

Quatrieme recopie du meme FQDN a tomber : serveur_oauth2_proxy_redirect_url
etait posee a la main dans les group_vars de chaque instance. Elle derive
maintenant de serveur_oauth2_proxy_hostname. Le repli reste vide et non
fabrique : l assertion doit refuser un deploiement sans exposition, pas
inventer un nom que personne ne resout.

Deploye et VERIFIE sur Chezlepro : le SSO renvoie sur vigie, le certificat
servi porte observatoire et vigie et plus aucun ancien nom. Deployer nginx
pose le vhost mais laisse le SAN en arriere — c est client_pki qui reemet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 21:05:47 -04:00
d1ae41cfdf nom public : il vient du plan, plus de la devinette du role
Le renommage deploye, nginx servait observatoire, le certificat le portait,
les deux zones le publiaient — et Grafana fabriquait toujours son URL de
retour OIDC avec l ancien nom.

Quatre roles portaient en defaut une devinette du nom sous lequel ils sont
servis. Tant que le plan suit la meme convention, la devinette tombe juste et
rien ne revele qu il y a deux sources. Keycloak avait deja son remede, dans le
role — donc trois roles sans remede.

instancier derive desormais <groupe>_hostname de l exposition unique declaree
par le plan. Six services en heritent. P67 garde la derivation. Les quatre
instances sont regenerees, 67 preuves vertes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 20:38:52 -04:00
f5b5899284 wiki : temoin de publication apres le renommage
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 20:23:08 -04:00
2450c9ea65 observatoire : un seul nom pour la console, des deux cotes
grafana.chezlepro.internal et tableaux.genese.internal nommaient le meme
service de deux facons. Les deux deviennent observatoire.

Un nom de produit dans une URL se grave aussi dans les SAN du certificat et
dans les URI de redirection du SSO : remplacer Grafana obligerait alors a
renommer le service. Le nom dit desormais la fonction.

Le renommage a revele que les URI des clients Keycloak repetent a la main les
FQDN declares dans expose:. Rien ne les reliait. P66 garde ce lien, ecrite en
meme temps que le premier renommage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 20:21:13 -04:00
c733d2df1c la frontiere journalise a nouveau, et une garde veille cette fois
Doctrine posee par l exploitant : des lors que le site a son Loki, la
journalisation de la frontiere et des hyperviseurs doit y etre dirigee.

CE QUI ETAIT CASSE. La destination syslog d OPNsense pointait
10.17.20.11:3100 - une adresse de TENANT, sur le port de Loki, en UDP, ce
que Loki ne sait pas lire. La VM a ete rasee ; une cible morte a arrete
TOUTE la journalisation pendant dix jours. La destination a ete eteinte
pour reparer et jamais remplacee. Deux fautes en une : plus de journaux, et
une cible chez un locataire, ce que D-87 refuse dans l autre sens.

L intention etait juste - bonnes facilites, info et au-dessus. On l a
REPRISE telle quelle et change uniquement la destination : ces choix
avaient ete faits, ce n etait pas a nous de les redecider.

UN TRADUCTEUR, parce que Loki ne parle pas syslog : loki.source.syslog dans
l Alloy qui tourne DEJA sur le collecteur. Port 1514 et non 514, donc
ecoute sans privilege - un collecteur qui aurait besoin des droits du
systeme pour entendre un equipement serait un mauvais echange.

UNE REGLE DE RELABEL SANS GARDE N IGNORE PAS : ELLE EFFACE. Trois quarts d
heure sur un symptome absurde - la configuration deposee portait
host = "bifrost-1", visible dans le fichier, et le flux arrivait sans
etiquette. Sans regex, la regle correspond TOUJOURS, meme quand sa source n
existe pas, et pose host = "". Loki jette une etiquette vide, et celle de l
ecouteur disparaissait avec elle.

Et la verification finale a corrige une seconde erreur, la mienne :
label/host/values rendait une liste en CACHE. La requete directe rendait
bien un flux. Un index qui ne montre pas une chose ne prouve pas qu elle n
existe pas.

LA GARDE, qui manquait depuis l incident. journaux-frontiere demande a LOKI
ce qu il a RECU, pas a la frontiere ce qu elle croit avoir envoye : la
destination est le seul juge, et un emetteur qui parle a un trou noir se
porte tres bien. La fenetre est DERIVEE du debit observe - environ 1500
lignes par minute - pas choisie.

Trois etats eprouves sur une copie : frontiere muette rc=2, Loki
injoignable rc=3, debit normal rc=0. Le 3 compte autant que le 2 : « je ne
peux rien affirmer » n est pas « c est casse ».

Mesure : 46 681 lignes recues en 30 min, site a 68 services tous OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 19:38:19 -04:00
0778979b89 une source vide n est pas « tout le monde »
Les journaux de la fabric, et le defaut de classe que leur mise en place a
revele. 10 hotes dans Loki (7 VM du site + 3 hyperviseurs), site a 67
services tous OK contre 51 avant.

METRIQUES TIREES, JOURNAUX POUSSES. Un scrape part du collecteur et doit
REVENIR : il exige un chemin symetrique, que la route par defaut gelee
interdit. Un push part de la source et n attend qu un accuse.

Trois trous dans les generateurs. Le devis de la frontiere ne connaissait
fabric qu en DESTINATION - un flux entrant tombait dans « rien d autre n
entre » sans rien dire. La regle etait posee sur la patte de la destination
alors que D-61 raisonne en ARRIVEE, et opt7 manquait a la table des zones.

Et le plus grave : fabric etait un mot RECONNU mais NON RESOLU. Source
vide, donc regle sans saddr - tcp dport 3100 accept, ouvert a tous. Un mot
reconnu mais non resolu est pire qu un mot inconnu : celui-ci serait refuse
a la validation, celui-la produit une porte grande ouverte qui a l air d un
flux precis.

LA CLASSE ENTIERE EST REFERMEE. Le defaut n etait pas propre a fabric :
toute paire nommant un ensemble et ne resolvant rien ouvrait le port.
Trouve en lisant les fichiers generes, l API de supervision d un tenant
etait ouverte a tous parce que pair: serveur_backup ne resout rien - cet
ecosysteme depose chez le site. expositions et externe, eux, veulent bien
dire tout le monde : c est une intention. Ailleurs, pas de source, pas de
regle, et le generateur le DIT.

Trois regles se referment, chacune verifiee AVANT d appliquer. Deux etaient
sans effet ; la troisieme portait Grafana, qui ecoute bien sur 3000. Sa
declaration disait pair: edge - juste chez un tenant, faux au site qui n a
pas d edge. Ajout de admin : ce qui etait accidentel devient explicite.

Et la mesure a montre autre chose : Grafana etait DEJA injoignable depuis
le poste, la frontiere ne laissant pas passer le 3000. Le site a une
console deployee et sans chemin d acces. Ce n est pas corrige ici, mais
c est dit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 19:14:23 -04:00
5abf20102d le site surveille enfin sa fabric
La supervision du site voyait ses sept VM et rien d autre. Au depart, depuis
site-mon-01, les trois hyperviseurs, les neuf pattes de la frontiere et sa
PROPRE passerelle par defaut rendaient tous 100 pourcent de perte au ping.

  16 hotes UP sur 16       dont 9 pattes de frontiere en controle ACTIF
  11 cibles Prometheus     dont 3 hyperviseurs, job fabric separe

LE PARTAGE. Les hyperviseurs portent node_exporter - D-48 l autorise - et
exposent 1005 unites systemd avec leur etat, soit ce que la sonde sante
mesurait, plus la charge et le disque. Ils n entrent PAS dans le socle :
leur appliquer serveur_durci reecrirait le pare-feu, le SSH et les sysctl
de la machine qui tient tout le reste. La frontiere, elle, n accueille
aucun agent : controle actif, une entree par PATTE, parce qu une interface
eteinte coupe une zone pendant que les autres vont bien.

ON TIRE, ON NE POUSSE PAS. J avais propose du passif et il avait ete
valide ; la mesure a dit non. Un hyperviseur envoie vers un routeur qui ne
connait pas les reseaux du site, et sa route par defaut est GELEE (D-57).
Le porteur de sante y expirait en 20 s.

LE VRAI DEFAUT ETAIT UNE LISTE QUI N A PAS SUIVI. Le mecanisme de routage
existait deja sur vmbr0, avec un commentaire du 2026-08-26 tenant
exactement le raisonnement qu on venait de refaire. Sa liste s arretait a
10.0.34.0/24 quand le site en declare six : les zones sauvegarde et
supervision sont nees, les routes n ont pas suivi. Le symptome ne
ressemblait pas a une route manquante - il ressemblait a un pare-feu, puis
a un probleme de reseau chez l exploitant.

make routes-fabric-etat compare desormais TROIS choses : zones declarees,
routes declarees dans /etc/network/interfaces, routes vivantes dans le
noyau. Le cas le plus traitre est vivante mais non declaree : tout
fonctionne, la supervision est verte, et la panne attend la prochaine
maintenance. Eprouvee dans les deux sens.

Deux defauts trouves en construisant. bifrost-2 est un nom RESERVE, pas un
boitier - l underlay le disait en prose, illisible par le moteur ; il porte
desormais etat: reserve. Et un service passif n existe que pour un hote qui
peut POUSSER : l appartenance a un groupe sert deux choses qui ne
coincident pas toujours, a qui l on deploie et de qui l on attend un
rapport.

Les commutateurs restent dehors, choix de l exploitant, coherent avec D-48.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 18:50:43 -04:00
009325ee51 D-88 : le noeud du gabarit, point unique de la REPRODUCTION
Question de l exploitant : le modele vit sur vishnu, les clones sur asgard,
qu arriverait-il si vishnu tombait ? Mesuree, la reponse se coupe en deux.

LES DONNEES SURVIVENT. Le pool CephNVMe est en size=3 / min_size=2 avec des
OSD sur les trois hotes, et les images du gabarit y sont repliquees.
L image reste lisible avec un noeud en moins, et les quatorze VM d un
ecosysteme tournent ailleurs sans s apercevoir de rien.

LA REPRODUCTION, NON. La configuration du gabarit porte le nom du noeud
dans son chemin - /etc/pve/nodes/vishnu/qemu-server/9006.conf - et le
clonage appelle nodes/vishnu/... Noeud eteint, aucune VM nouvelle ne peut
naitre. Or la reproduction est ce que ce depot existe pour garantir.

La decision est d ASSUMER la dependance et de la rendre COURTE, pas de la
supprimer : depuis la migration du gabarit sur stockage partage ce matin,
la remise en route est un deplacement de fichier de configuration, sans
mouvement de donnees. Un benefice qu on n avait pas cherche : la migration
sur Ceph a raccourci une panne qu on n avait pas encore nommee.

Documentee en trois endroits, parce qu un seul ne suffit pas : D-88 pour
nommer la dependance, le runbook section 7 pour la manoeuvre et l ordre
des gestes, et le bloc gabarit du plan du site - c est la que l exploitant
lit noeud: vishnu.

Ce qui n est PAS fait et qui est dit : rien ne MESURE cette dependance.
gabarit_etat compare le declare au reel, il ne demande pas si le noeud du
gabarit heberge autre chose que le gabarit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 17:51:15 -04:00
a60b69f072 un fichier vide existe, et une sonde pour les correctifs
LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement
interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes
demandaient seulement s il etait la. infra-mail-01 a garde une cle
smallstep de 0 octet apres l epreuve hors ligne.

La premiere correction n a pas suffi. Ajouter le controle de taille faisait
bien s executer la tache - et le fichier faisait toujours 0 octet au
passage suivant. get_url sur une destination existante emet une requete
CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine
reste. Le play etait vert et ne reparait rien. Il faut effacer avant de
redemander.

Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles.

LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique
les correctifs au deploiement - choix defendable, le verrou dpkg a fait
decrocher une machine d une reconstruction entiere le matin meme. Mais rien
ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en
restant verte.

Elle mesure les paquets de securite en attente ET depuis quand. Elle ne
lance pas apt-get update - une sonde qui rafraichit l index toutes les
quinze minutes deviendrait la cause de la panne qu elle surveille. Et le
seuil de 72 h est un choix d exploitation, pas une derivation : le
mecanisme qui applique les correctifs est un geste humain.

P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont
des roles de declaration pure, sans une tache ; le travail est fait par les
roles que leur playbook applique. La preuve exigeait declaration et depot
dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une
sonde appartient au socle. Une garde qui force a contourner ce qu elle
protege est un defaut. Elle suit desormais le playbook du groupe.

Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 17:26:35 -04:00
1410fe41ec les cles de signature passent aussi par le cache
get_url IGNORE la configuration d apt : le mandataire pose dans
/etc/apt/apt.conf.d/ ne vaut que pour apt. Les six cles de signature
sortaient donc TOUJOURS en direct, malgre tout le travail sur le remap. Un
trou reste ouvert derriere une porte qu on croyait fermee.

Et ce n etait pas theorique. Depuis collab-01 :

  en direct     grafana 200      collabora TIMEOUT
  via le cache                   collabora 200

La route directe vers Collabora ne passe pas depuis cette zone. Trois cles
sur quatre avaient reussi PAR CHANCE, parce que leurs fournisseurs etaient
joignables. Le meme geste corrige les deux : plus rien ne sort, et la
machine qui n avait pas de route en trouve une.

POURQUOI SEULE UNE NAISSANCE POUVAIT LE MONTRER. Mes deploiements de
convergence rendaient failed=0 parce que les cles etaient DEJA sur disque
et que la tache etait sautee. Le defaut existait depuis le premier commit
du remap, invisible a tout deploiement sur une flotte existante. C est l
argument de la reconstruction depuis zero, applique a moi-meme.

Troisieme reconstruction : 32 minutes, un seul echec - celui-ci. Clonage
3 min 52, zero fatal dans le journal, 963 Mo servis par le cache contre
183 tires de l Internet, soit 81 pourcent servis localement.

Et la derive s est effacee toute seule : apt-cacher-ng tournait encore sur
forge-01 que plus aucun plan ne declarait. Je proposais de l arreter a la
main ; la reconstruction l a fait. Ce qui n est pas au plan n existe pas
apres une naissance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 15:41:56 -04:00
c6a7150c60 les depots tiers passent par le cache, et le tenant n a plus le sien
Deux mouvements d une seule doctrine : le site fournit tout ce dont un
tenant a besoin pour venir au monde.

1. LES DEPOTS TIERS. Grafana, Icinga, Smallstep et Collabora ne publient
qu en HTTPS ; apt-cacher-ng ne relaie pas un tunnel, et le socle pose donc
Acquire::https::Proxy DIRECT. Chaque machine sortait elle-meme sur
Internet. Le cache declare desormais un Remap par fournisseur, et chaque
role demande en {{ ..._depot_schema }}:// - http des qu un cache est
declare, https sinon. Le TLS n est rompu nulle part : il est TERMINE au
cache, qui est notre machine, et l integrite vient des signatures.

  avant : 4 fournisseurs en HTTPS direct, 14 machines sortant seules
  apres : 0 source en HTTPS direct, 0 erreur apt sur 14 machines

Trois lecons. Le remap appartient au cache qui SORT : pose sur un cache
chaine, il tente le HTTPS a travers son amont et rend 503. apt_repository
AJOUTE au lieu de remplacer, donc l ancienne ligne https sortait toujours.
Et la liste des fournisseurs ne se devine pas - j en avais trois, l audit
en a revele un quatrieme.

2. LE CACHE DU TENANT. Sa ligne portait son propre retrait depuis toujours
- service MUTUALISABLE, un ecosysteme au premier age peut pointer sur celui
de son hote. Retiree. Ce qu on perd, dit franchement : plus de trafic
inter-zone et plus de charge sur site-cache-01, contre un service de moins
a poser, superviser et reproduire.

3. UN ROLE QU ON RETIRE DOIT DEFAIRE CE QU IL A FAIT. Le retrait a montre
que rien ne nettoie derriere. Le fichier apt visait un cache eteint en
ecrasant le plancher qui fonctionnait - le defaut deja paye a quinze
machines. Et les sondes du cache restaient, le porteur poussant pour des
services qu Icinga ne definit plus (404). Le socle retire le premier,
client_sante derive les sondes attendues et retire les orphelines.

P65 refuse tout role visant un depot relaye en https ecrit en dur. Sa
limite est dite : elle empeche une regression sur ce qui est connu, elle ne
decouvre pas l inconnu.

Mesure : Icinga 87 OK sur 96, prouver 65 OK, lint 0 defaut.

Reste, et c est dit : apt-cacher-ng tourne toujours sur forge-01 que plus
aucun plan ne declare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 14:53:48 -04:00
9685e0bdbe reconstruction de confirmation : 0 echec, 34 minutes
Seconde reconstruction complete de Chezlepro dans la journee, pour
EPROUVER les quatre correctifs livres entre les deux. Rien de nouveau
n a ete construit : c est une mesure.

  gabarit sur CephNVMe          clonage 4 min 30 contre ~30 min
  placement declare             {asgard: 14} - les quatorze au bon endroit
  hosts_statiques conditionne   changed au 1er passage, skipping au 2e
  monitoring-plugins au role    ping4 14/14 OK sur une mon-01 nee neuve

Le troisieme est le plus instructif : les DEUX branches sont exercees dans
la meme execution. infra-pki-01 recoit le gabarit maitre de cloud-init au
premier passage, puis la tache est sautee au second, le durcissement
l ayant retire. Aucune preuve statique ne pouvait montrer cela - il fallait
une naissance.

Le facteur mesure est 6,6 et non 45 : un clone isole prend 8 s, mais quatre
clones se disputent le pool et le redimensionnement du disque s ajoute. On
retient la mesure en conditions reelles, celle qu on paie.

Des trois echecs du matin il ne reste rien. Le verrou dpkg ne s est pas
reproduit - c etait une course, et rien ne dit qu elle ne reviendra pas.
Et le cache apt N ETAIT PAS un defaut : les deux seuls fatal de cette
execution sont suivis de ...ignoring, ce sont les sondes de
client_artefacts qui laissent la machine servie par le plancher du SITE.
C est la filiation en train de fonctionner. Le matin, j avais compte ces
deux lignes comme des echecs sans lire la suivante.

Icinga : 89 OK, 9 UNKNOWN sur 98, aucun WARNING, aucun CRITICAL. Les neuf
sont tous des sauvegarde dont le minuteur nocturne n a pas encore visite
une flotte nee il y a vingt minutes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 14:09:02 -04:00
2cf68c2d30 hosts_statiques : une garde qui survit a ce qu elle gardait
Le role pose /etc/cloud/templates/hosts.debian.tmpl - le gabarit maitre
dont cloud-init regenere /etc/hosts au demarrage. Or D-85 fait retirer
cloud-init par serveur_durci, et le repertoire part avec lui.

L echec n avait aucun sens : ce gabarit ne sert qu a survivre a une
reecriture qui n a plus lieu. Plus de cloud-init, plus de reecriture, le
plancher tient tout seul - c est le resultat recherche par D-85.

CE QUE CA A COUTE. _amorcer-socle monte infra-pki-01 et infra-dns-01 en
entier d abord, durcissement compris. La tache echouait au second passage,
l hote sortait du play, et TOUT ce qui suivait n etait jamais pose - dont
icinga-ca.crt. Leur porteur de sante tournait ensuite normalement et chaque
rapport echouait sur curl: (77), en silence. Un echec bruyant au bon
endroit avait produit une panne muette ailleurs.

Et sauter la tache seule aurait deplace l echec d un cran : la garde qui
suit compare le nombre d entrees du plancher a celui du gabarit, donc 21
contre 0. Elle accuserait une divergence la ou il ne reste qu un fichier.
Une garde qui survit a ce qu elle gardait ne mesure plus rien : elle
invente. Les trois taches suivent desormais la meme condition.

Verifie sur les deux machines memes qui echouaient : failed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 13:29:25 -04:00
4ef3a1d553 D-86 mis a l epreuve : Chezlepro rasee et refaite depuis zero
Quatorze machines detruites, quatorze refaites. 1 h 08, 0 injoignable.

D-86 TIENT, et la mesure le dit : 30 resultats de controle recus a
12:20:41 alors que la reconstruction ne s est terminee qu a 12:35:29.
Trente services rapportaient leur etat pendant que Keycloak, Forgejo et
Nextcloud montaient encore.

Sa limite, dite franchement : D-86 affirme que le DNS peut rester en
couche 6 grace au plancher /etc/hosts. Or _amorcer-socle monte
infra-dns-01 EN ENTIER d abord. Le DNS etait debout, le plancher n a rien
eu a porter, et la partie la plus audacieuse de la decision reste non
testee.

LE PLACEMENT DES VM N ETAIT DECLARE NULLE PART. Les quatorze vivaient sur
asgard depuis toujours, mais SETOPS_NOEUD valait le vide : ce placement
n existait que dans l etat d execution de Proxmox. Sans cible, le clone
reste sur le noeud du gabarit - vishnu, qui a 14 Go libres et heberge
eregion et site-forge-01. Le defaut avait survecu a la reconstruction du
2026-09-02 parce que les VM existaient deja et que le clone les sautait.
Il faut un from-zero VRAI pour voir ce genre de chose.

LE CLONAGE ETAIT 45 FOIS TROP LENT, et pas a cause du reseau : source et
destination etaient sur le meme stockage. C est le LVM epais qui interdit
a Proxmox tout clone autre que complet. Gabarit deplace sur CephNVMe,
mesure 480 s -> 8 s. On garde les clones complets : le clone lie descend
a 1 s mais enchaine chaque VM a l image de base pour toujours.

Un champ stockage: entre au gabarit, branche en quatre points - declare,
expose, consomme par le Makefile, garde par gabarit_etat. Declarer sans
consommer, c est decorer.

ICINGA NE POUVAIT PAS FAIRE SES PROPRES CONTROLES : icinga2 n apporte
aucun greffon, et la supervision etant passive, le manque etait masque.
Quatorze ping4 muets d une seule cause. monitoring-plugins-basic entre au
role. Le site l avait deja - pose A LA MAIN, jamais declare : troisieme
occurrence du jour d un etat qui ne survivrait pas a une reconstruction.

Trois defauts laisses ouverts : le cache apt attendu pendant l amorcage,
la regression D-85 sur /etc/cloud - qui a fait decrocher deux hotes de la
tache deposant icinga-ca.crt, rendant leurs sondes muettes en silence - et
auditd sans regles dans le gabarit.

Mesure finale : Icinga 93 OK sur 98, site 51/51, Prometheus 15/15,
prouver 64 OK, lint 0 defaut. Les quatre non-OK restants sont des
sauvegarde dont le minuteur nocturne n a pas encore visite une flotte nee
il y a deux heures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 13:06:45 -04:00
84b5ecc71d frontiere posee, et cinq sondes qui disaient faux
Application de ce que l entree precedente avait prepare : 49 regles creees,
5 retirees. Cibles Prometheus 2/8 -> 8/8, hotes dans Loki 1/7 -> 7/7.

Puis correction de ce que l application a revele. Cinq defauts, tous de la
meme famille : un reglage qui ne suit pas l interrupteur dont il depend.

  - la sonde de Loki interrogeait en HTTPS un Loki servant en clair ;
  - la sonde de la forge visait http:// sur un port 443 chiffre, puis
    validait un certificat emis pour le FQDN en interrogeant 127.0.0.1 ;
  - la sonde du runner comparait six depots a UNE branche quand le plan en
    declare une par depot. Elle contredisait la declaration qu elle etait
    censee verifier : elle n accusait pas la machine, elle s accusait
    elle-meme.

Le tenant restait juste dans les cinq cas. C est le site, qui deploie ces
roles autrement, qui les a tous reveles d un coup.

La sonde des journaux, elle, avait deux defauts a la naissance. Elle criait
une perte deja reparee, faute de dire sur QUELLE fenetre - un ecart sans sa
fenetre n est pas une mesure, c est un nombre. Et elle alertait sur la
cicatrice plutot que sur la plaie : le compteur d Alloy est cumulatif, donc
six machines restaient orange pour toujours. Ce qui alerte est desormais la
CROISSANCE. En echange, un cas muet leve maintenant : zero envoi et zero
rejet n est pas la sante, c est un agent qui ne fait rien.

Enfin, le runner ne pouvait plus cloner le genome. Defaut PREEXISTANT,
revele par le redeploiement et non cause par lui - le diff des regles
generees le montre. generer_nftables(site=True) lisait le plan du TENANT :
derive se resolvait donc a 3000, le port que Forgejo ecoute derriere un
edge, alors que le plan du site dit 443 et explique pourquoi. La forge
ouvrait un port que personne n ecoute et laissait 443 ferme a toute la
flotte.

Mesure finale : Icinga 51/51, Prometheus 8/8, Loki 7/7, prouver 64 OK,
lint 0 defaut sur 82 fichiers. Controles negatifs sur des COPIES des
sondes : les quatre rendent CRITICAL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 10:27:12 -04:00
f2c580235d observabilite : le site cesse de ne rien voir de lui-meme
D-87 disait que l hebergeur n a pas le droit de voir les journaux de ses
locataires. La decision avait une face cachee : a force de refuser de voir
ceux des autres, le site s etait prive des siens. Ses sept machines
n expediaient nulle part.

Il prend donc sa propre pile : prometheus, loki et grafana sur site-mon-01,
sans aucun lien avec ceux d un tenant. L exemption qui bloquait portait sa
propre condition de levee, ecrite cinq jours plus tot dans le plan.

Grafana au site n a pas de SSO, et le role l ignorait : il reclamait l IdP
avant de regarder s il en voulait un. L interrupteur existait, il n etait
pas honore. Le role refuse desormais SSO eteint ET formulaire local eteint
- la combinaison deploie un Grafana en sante ou personne ne peut entrer -
et le reglage du formulaire sort du if du SSO, ou il disparaissait en
laissant le defaut amont decider en silence.

Deux manques se cachaient l un l autre dans le devis de la frontiere, et
Prometheus voyait 1 cible sur 7 :

  - le devis derivait les groupes d une machine du site de applications.yml
    seul, et ne voyait donc aucune integration universelle - alors que
    client_metrique ouvre un port d ecoute ;
  - une sortie vers un role du site visait !SETOPS_INTERNES, qui exclut
    precisement la machine nommee. Le devis autorisait a expedier les
    journaux du site a n importe quel Loki du monde, et a nul autre endroit
    qu a celui-la. Neuf flux dans ce cas.

Et deux declarations justes ne font qu une regle : appliquer_opnsense pose
tout en direction: in (D-61).

Les deux agents se supervisent enfin eux-memes. La sonde des journaux ne
demande pas si Alloy tourne, elle lit ce qu il a du JETER. Elle a fait ses
preuves le jour meme : Alloy actif, /-/ready a 200, et cinquante lignes
perdues sur six machines.

Mesure : metriques 7/7 vert, journaux 1/7 - les six autres attendent la
regle de frontiere, et le disent. prouver 64 OK, ansible-lint 0 defaut.

Reste a la main de l exploitant : make frontiere-appliquer CONFIRMER=true,
et le secret vault_grafana_admin a deposer dans underlay.vault.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 04:43:01 -04:00
5880b22d5a D-87 : ce que l hebergeur n a pas le droit de VOIR
Question posee : quels roles ne pas embarquer dans le site ? La table de
mutualisation existait et repondait presque — mais une de ses lignes
CONTREDISAIT ce qui tourne.

Elle disait « observabilite | oui | l hebergeur surveille ses locataires ».
Or chaque ecosysteme a son propre Icinga, et celui du site ne voit que ses
sept machines. L implementation avait raison, la doctrine avait tort.

Et cette case autorisait ce que la ligne du dessous interdit. LES JOURNAUX
CONTIENNENT DU CONTENU — un mot de passe dans un message d erreur, une
donnee metier dans une trace, qui a fait quoi et quand. Un hebergeur qui
ingere les journaux de son locataire en sait PLUS que s il detenait son
annuaire : l annuaire dit qui existe, les journaux disent ce qu ils font.

Decoupee en trois : disponibilite oui (une VM tombee est un fait de la
fabric), metriques oui avec reserve (elles disent quand et combien, ce qui
suffit a lire l activite d une organisation), journaux NON.

ET LA LIGNE PKI EST TRANCHEE : NON. Une AC intermediaire signee par l hote
lui donnerait le pouvoir d emettre des certificats valides pour les noms
du locataire — donc de se presenter comme n importe lequel de ses
services, devant les propres machines du locataire, qui les accepteraient
puisque c est ce que la chaine de confiance leur demande. Meme pouvoir que
l annuaire, sous une forme moins visible : aucune trace cote locataire.
Une PKI par ecosysteme, jamais derivee de l hote.

LE REVERS, MESURE ET ASSUME : le site n a ni client_journal ni
client_metrique, aucun loki ni prometheus. A refuser de voir ceux des
locataires, il s est prive des siens. Le remede n est pas d assouplir la
regle mais de lui donner sa propre pile.

Verifie par ailleurs : rien d intime n est au site aujourd hui. La
frontiere etait tenue en pratique avant d etre ecrite au net.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 04:05:28 -04:00
6e46ace4de sondes : les cinq qui manquaient le plus (autorite, base, annuaire, zones, edge)
CORRECTION DE COMPTE D ABORD. J avais annonce cinq groupes sans sonde. La
mesure en donne VINGT-SEPT : j avais compte ceux que j avais en tete, pas
ceux que le depot contient. Il en reste vingt-deux.

LES CINQ, PAR ORDRE DE DEGAT SILENCIEUX.

autorite (step_ca) — la plus urgente : nos certificats vivent 24 h, une AC
muette ne casse rien aujourd hui et casse TOUT demain, d un coup, sur les
21 machines. Elle surveille aussi l expiration de la RACINE, que personne
ne regarde parce qu elle vit des annees (relevee a 3642 jours).

base (postgresql) — une VRAIE requete, pas pg_isready : celui-ci dit que
le port repond, pas que la base sert. Plus le compte des connexions : a
saturation, chaque application tombe sans que la base ait l air morte.

annuaire (openldap) — elle COMPTE les entrees. Un annuaire vide repond
success a tout : le mensonge des sauvegardes vides, vert et sans contenu.

zones (powerdns) — un autoritatif sans zone repond NXDOMAIN a tout, ce qui
se lit comme « ce nom n existe pas ».

edge (nginx) — elle valide la configuration SUR DISQUE : nginx sert la
derniere valide, et une configuration cassee ne se voit qu au prochain
demarrage, souvent des mois plus tard.

Les cinq eprouvees vertes sur le sain puis rouges PAR PARAMETRE, sans
toucher a un service.

Etat : 20 sondes sur 19 roles, 23 services distincts, 70 instances, 67 au
vert. Les trois autres sont connues : deux sauvegardes sans donnee a
emporter, et Loki en delai de stabilisation apres redeploiement.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:47:03 -04:00
a5d9040a11 cache-apt scindee — et la scission a revele que moteur aurait alarme a tort
LA SCISSION. cache-apt fondait deux causes aux DELAIS differents : « ne
repond pas » arrete tout apt de l ecosysteme, on agit dans la minute ;
« volume a 90 % » est un billet pour demain. Les fondre obligeait soit a
reveiller quelqu un pour un disque, soit a traiter une panne comme un
billet. Deux sondes, chacune avec son etat, son historique, son
acquittement — et prouvees INDEPENDANTES :

  port ferme       -> cache-apt CRITIQUE, cache-apt-volume OK
  seuil impossible -> cache-apt OK, cache-apt-volume AVERTISSEMENT

CE QUE LA VERIFICATION A REVELE. Un resultat pousse et accepte (code 200)
n apparaissait pas en base. Hypothese testee et confirmee : IcingaDB n
ECRIT service_state QUE SUR CHANGEMENT D ETAT. Un OK identique repete ne
produit aucune ecriture ; un AVERTISSEMENT pousse ensuite est ecrit en 13 s.

Or la sonde moteur, ecrite quelques heures plus tot, lisait exactement
max(last_update) de service_state pour juger de la fraicheur. Elle
mesurait le CHANGEMENT. Sur un ecosysteme parfaitement STABLE — celui
qu on veut — plus rien ne change, last_update vieillit, et elle serait
passee en avertissement a 15 min puis en critique a 90. Une alarme qui se
declenche PARCE QUE tout va bien, avec un delai qui l aurait rendue
difficile a rattacher a sa cause.

La bonne source existait : icingadb_instance porte le battement du
synchroniseur, ecrit en continu. Releve a 1 s sur un systeme sain. Seuils
ramenes de 15/90 min a 1/5 min. Controle negatif rejoue.

Dix-huit services, tous verts sauf sauvegarde 7/9 (deja connu).

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:27:52 -04:00
3e8e052670 supervision : la regle qui decide COMBIEN de sondes
Question posee : « une sonde par role, est-ce parce qu elles agregent
plusieurs verifications ? » Oui — et « une par role » n etait pas mon
critere, c etait une coincidence : la plupart de ces roles n ont qu une
preoccupation. Mesure : les quatorze sondes agregent de 2 a 6 chemins d
echec chacune.

Le critere, applique en silence jusqu ici, est desormais ecrit : UNE SONDE
PAR CAUSE D ACTION — ni par role, ni par verification.

CE QUE COUTE UNE AGREGATION ABUSIVE, dans le modele d Icinga : un service
= un etat, un historique, un acquittement. Fondre deux causes qui
appellent des gestes differents, c est ne plus pouvoir acquitter l une
pendant que l autre alerte, et lire comme un service instable ce qui est
deux faits distincts qui alternent.

CE QUE COUTE L EXCES INVERSE : un voyant de plus est un voyant de moins
regarde.

Le test : les deux echecs appellent-ils le meme geste, de la meme
personne, dans le meme delai ? Si oui une sonde, et le message nomme
lequel a parle. Si non, deux.

Par ce critere, deux sondes existantes sont abusives : `certificat` (trois
gestes — minuteur bloque, AC changee, consommateur a recharger) et
`cache-apt` (deux delais — panne immediate contre billet pour demain).
Non scindees : c est une decision sur le nombre de voyants au mur.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:10:24 -04:00
6120e6f006 sondes : quatorze, une par role, derivees en objets Icinga
boites (dovecot) · cache-apt (artefacts) · certificat (client_pki, 14/14)
collaboration (nextcloud) · collecte (prometheus) · file-courriel (postfix)
forge (forgejo) · identite (keycloak) · ingestion (loki) · moteur (icinga)
resolution (resolveur) · runner (serveur_ops) · tableaux (grafana)
voute (ops_tenant) — toutes vertes, sans une ligne ecrite dans Icinga.

DEUX PRINCIPES QUE LA PREMIERE SONDE A IMPOSES.
Une sonde doit pouvoir etre mise en defaut PAR PARAMETRE : cible et seuils
sont des variables du role, on prouve le rouge avec un port ferme ou un
seuil impossible, sans rien casser. Et la sonde vit LA OU VIT LA VERITE :
« ce noeud est-il collecte ? » appartient a prometheus, pas au client —
une seule y voit les N noeuds, et surtout elle voit le cas SILENCIEUX.

ON DEMANDE AU SERVICE CE QU IL PENSE DE LUI-MEME quand il sait le dire
(healthz, /ready, status.php, decouverte OIDC). Quand il ne sait pas, on
va chercher la verite de terrain : « moteur » ne regarde ni le service ni
le port, il demande a la base depuis combien de temps elle n a pas ete
rafraichie — la lecon des sauvegardes appliquee a la supervision.

QUATRE FOIS J AI ECRIT LA SONDE AVANT DE MESURER, QUATRE FOIS ELLE A EU
TORT. La forge : port et chemin des depots inventes, elle ecoute en 3000
derriere l edge et n a legitimement aucun depot. Loki : « panne
persistante » conclue sur deux lectures a quelques secondes d intervalle
juste apres un redemarrage — deux mesures rapprochees ne distinguent pas
un etat d un instant. Keycloak : vise en 8443, il ecoute en 8080. Le
runner : git en root refuse un depot d un autre proprietaire. A chaque
fois le remede est le meme — lire la verite du role, ne pas la supposer.

Et le meme piege Jinja qu avec client_sante : ${#tableau[@]} contient {#.
Le remede etait deja au depot ; je l ai reecrit au lieu de le chercher.

RESTE : client_smtp, client_artefacts, client_journal, icingaweb2 et
ops_site. Ce sont des chemins de report, dont la panne se voit deja par le
silence des sondes qu ils portent.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute (P64 : 14 sondes).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:00:21 -04:00
1832530f29 sondes : les six premieres, et le patron qui les rend eprouvables
Ajout de meta/supervision.yml a six roles, chacun deposant sa propre sonde
selon le contrat des greffons Nagios.

DEUX PRINCIPES POSES AU DOCUMENT DE CONCEPTION, parce que la premiere
sonde les a imposes :

1. UNE SONDE DOIT POUVOIR ETRE MISE EN DEFAUT PAR PARAMETRE. Cible et
seuils sont des variables du role : on la prouve rouge avec un port ferme
ou un seuil impossible, sur une machine reelle, sans rien casser, et
aussi souvent qu on veut. Une sonde qu on ne peut prouver qu en cassant un
service ne sera prouvee qu une fois.

2. LA SONDE VIT LA OU VIT LA VERITE. « Ce noeud est-il collecte ? » est
une sonde de serveur_prometheus, pas de client_metrique : une seule y voit
les N noeuds, et surtout elle voit le cas SILENCIEUX — celui qui a cesse d
etre collecte ne peut pas s en plaindre.

LES SIX : cache-apt (artefacts, repond + place), resolution (resolveur,
zone interne ET Internet — deux chemins distincts), forge (forgejo, son
propre /api/healthz), collecte (prometheus, 15/15 cibles), tableaux
(grafana, base ok), ingestion (loki, PRET a ingerer, pas seulement en
ecoute).

TROIS FOIS J AI ECRIT LA SONDE AVANT DE MESURER, ET TROIS FOIS ELLE A EU
TORT. La forge : port 443 et chemin des depots INVENTES — elle ecoute en
3000 derriere l edge et n a legitimement aucun depot. Loki : j ai conclu
« panne persistante » sur deux lectures prises a quelques secondes d
intervalle, juste apres un redemarrage ; l anneau etait ACTIVE et la
reponse est passee a ready moins d une minute plus tard. Le delai de
stabilisation est desormais un AVERTISSEMENT nomme, pas une panne.

On demande au service ce qu il pense de lui-meme quand il sait le dire
(healthz, /ready, /api/health) plutot que d inventer un critere de l
exterieur.

make prouver : CONFORME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 02:48:55 -04:00
a8af541059 D-86 : la supervision se deploie juste apres la PKI, pas a la fin
On n allume pas la lumiere une fois la maison finie. L observabilite et le
moteur de supervision etaient deployes APRES presque tout ce qu ils
surveillent. Une reconstruction depuis zero est pourtant le moment ou l on
a le plus besoin de voir, et c etait le seul moment ou l on ne voyait rien.

  4. observabilite       postgresql, prometheus, loki, grafana, icinga
  5. agents_supervision  client_metrique, client_journal, client_sante

DEPLACER LES SERVEURS SANS LES AGENTS N AURAIT RIEN CHANGE : ce sont les
agents qui rapportent, et ils etaient en derniere couche. Les deux couches
vont donc ensemble. Des la couche 5, chaque hote expedie ses metriques,
ses journaux et l etat de ses unites — tout ce qui se deploie ensuite est
mesure pendant qu on le construit.

COUT : serveur_postgresql monte aussi (icinga l exige, il n exige rien).
C est le seul entrainement.

RESTE TARD A DESSEIN : icingaweb2 et oauth2_proxy reclament LDAP et
Keycloak. C est la CONSOLE, pas la mesure.

CE QUI REND CE DEPLACEMENT POSSIBLE : le DNS reste en couche 6, donc apres
la supervision, alors qu icinga joint sa base par un NOM. Ca tient grace au
plancher /etc/hosts pose des la couche 1 — 34 entrees, verifie. C est
exactement ce pour quoi il existe.

LIMITE : les notifications dependent de client_smtp, encore en derniere
couche. Pendant une reconstruction, l etat est mesure et consultable, mais
rien ne part par courriel avant la fin.

ET : l ordre est VALIDE, pas EPROUVE. P08 accepte, le graphe accepte,
site.yml regenere passe le syntax-check sur 782 lignes de plan. La seule
preuve reelle d un ordre de reconstruction, c est une reconstruction.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 23:39:19 -04:00
5b423cf954 correction : le PMTUD du site n etait pas casse, il etait non protege
J ai ecrit il y a une heure que la panne PMTUD « etait la, silencieuse,
sur les sept machines de l hebergeur ». La mesure ne le soutient pas :

  site-mon-01 -> autre zone du site : MTU de chemin 1500
  site-mon-01 -> Internet           : MTU de chemin 1500
  ops-01 (tenant, overlay EVPN)     : MTU 1450, chemin vers Internet 1450

Le site est a 1500 de bout en bout. Aucun lien du chemin ne plafonne plus
bas, donc aucun message « fragmentation necessaire » n avait lieu d etre
emis, donc rien n etait casse entre zones du site. La contrainte a 1450
vit chez les TENANTS, sur l overlay — pas chez l hebergeur.

Les six regles restent un vrai manque : elles couvrent le site parlant
vers l EXTERIEUR, ou un correspondant distant peut plafonner plus bas, et
elles seraient necessaires si une zone du site passait sur l overlay. Mais
c etait une PROTECTION ABSENTE, pas une panne active.

Decrire l un comme l autre est l exageration que ce depot ne doit pas
contenir : une correction qu on ne mesure pas se raconte toujours plus
grande qu elle n est. La correction est portee au CHANGELOG et dans le
commentaire de devis_opnsense.py, la ou on la relira.

Ce qui reste vrai et mesure : l Icinga du site tenait 6 de ses 7 machines
pour mortes, et 7/7 apres.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 23:23:08 -04:00
843d39e7aa frontiere : un type ICMP n est pas un port symbolique
LA CAUSE EXACTE. devis_opnsense traitait tout `port` non numerique comme
SYMBOLIQUE, a resoudre par le plan. Vrai pour `derive`, le port d un
service que seul le plan connait. FAUX pour l ICMP : echo-request et
frag-needed sont des litteraux. Ils tombaient dans la branche « le plan ne
resout pas » et aucune regle n etait emise. Une ligne de condition, et le
silence de toute une flotte.

CE QUE LA CORRECTION A REVELE EN PLUS. Huit regles a creer, zero a
retirer — et SIX sont du PMTUD entre zones du site, absent pour la meme
raison. Le depot dit de ces messages : bloques, la connexion s etablit,
les petites requetes passent et les grosses reponses restent suspendues —
la panne la plus couteuse a diagnostiquer. Elle etait la, silencieuse.

CE QUE LA REGLE AUTORISE, EXACTEMENT. appliquer_opnsense n envoie
destination_port que pour TCP et UDP : une regle ICMP ouvre le PROTOCOLE
entre deux pairs, pas le seul type. Le type reste porte par la regle d
HOTE, que resoudre_flux emet precisement. La frontiere dit qui peut parler
a qui, l hote dit ce qu il accepte d entendre. Emettre un icmptype a la
frontiere aurait demande de traduire frag-needed dans le vocabulaire de
pf, au risque de casser le PMTUD pour gagner de la precision sur une
barriere qui n est pas la derniere.

MESURE APRES APPLICATION :
  ping site-mon-01 -> les six autres zones : 6/6 repondent
  hotes UP : 1/7 -> 7/7 | ping4 : 1/7 -> 7/7 | certificat 7/7 | sante 7/7
  tenant inchange : 14/14

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 22:51:13 -04:00
a070c339ee icinga : l hote de supervision saturait par sa propre demonstration
« mon-01 tape dans l fond. » Il tapait : load 5,10 sur 4 coeurs, 8
processus check_disk a 70-99 % de CPU chacun, jusqu a 28 minutes de vie.

check_disk 2.4.0-3+deb13u1 ne rend jamais la main sur cet hote (etat R).
Icinga en relancait un a chaque intervalle pour le service `disk` de son
hote de DEMONSTRATION, et aucun ne mourait.

RETIRE : conf.d/hosts.conf, l hote NodeName livre par le paquet. Les apply
Service s y accrochaient — disk, http, swap, apt, load, procs, users — et
trois etaient rouges en permanence (swap sur une VM sans swap, http sur un
port ou rien n ecoute, apt pour un paquet). On retire l HOTE et non les
services : sans lui les apply ne s accrochent a rien, et on ne touche pas
a un fichier que le paquet remplacera. Ca garde ping4, qui vise nos hotes
et sert vraiment.

  avant : load 5,10 — 8 check_disk — 3 alarmes rouges permanentes
  apres : load 0,77 — 0 check_disk — certificat 14/14, sante 14/14, ping4 14/14

TROUVE EN VERIFIANT : l Icinga du SITE tenait 6 de ses 7 machines pour
MORTES (1/7 UP, contre 14/14 au tenant). hostalive est un ping, le site
est decoupe en zones, et l ICMP inter-zones n etait declare nulle part —
100 % de perte, mesure. Or Icinga SUPPRIME les notifications des services
d un hote DOWN : une supervision qui croit tout mort n alerte plus de
rien, tout en ayant l air de fonctionner.

Le flux est declare des DEUX cotes, et le registre a refuse la premiere
moitie seule — exactement sa raison d etre. CODES_ICMP apprend
echo-request. Regles d hote posees sur les 21 machines.

RESTE OUVERT : le generateur de la frontiere ne sait pas traduire un TYPE
ICMP pour un pair INTERNE — il le note et n emet rien. Le site reste a
1/7. Corriger devis_opnsense.py est le prochain geste.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 22:34:32 -04:00
988d35745e sondes : le contrat ETAIT celui de Nagios, sans le savoir
Question posee : « tu connais le paquet monitoring-plugins ? » Oui — et le
contrat que docs/supervision-conception.md decrivait quelques heures plus
tot EST le sien, mot pour mot. Je l avais reinvente sans le nommer, alors
que positionnement.md dit l inverse : adopter aux seuils, ne pas
reimplementer.

Le document nomme desormais l API des greffons Nagios, ajoute le code 3
(INCONNU) et la partie « | metriques » qui manquaient, et dit la
consequence : un greffon standard EST une sonde valide, sans colle. Le
paquet en fournit 54. On n ecrit du shell que lorsque la verite a mesurer
est propre a Set-OPS. Le porteur separe maintenant texte et
performance_data.

ESSAYER UN VRAI GREFFON A REVELE DEUX DEFAUTS DU PORTEUR.

Un envoi refuse faisait taire TOUTES les sondes suivantes : rapporter
sortait en exit 1. Une sonde deposee mais non declaree supprimait le
rapport des autres, sante comprise — le tableau ne devenait pas rouge, il
devenait vide, et le ttl le perimait des heures plus tard sans dire
pourquoi.

Aucun delai de garde sur les sondes. check_disk 2.4.0-3+deb13u1 sur mon-01
tourne sans fin (etat R) quels que soient ses arguments : un greffon
STANDARD, sur une machine saine, qui boucle. Sans garde il figeait le
rapport entier toutes les quinze minutes. Chaque sonde tourne desormais
sous timeout 20 ; au-dela on rapporte INCONNU en le disant.

La lecon n est pas que les greffons standards sont mauvais : c est qu
adopter un standard ne dispense pas de l eprouver, et que le porteur doit
survivre a une sonde qui se comporte mal.

CONSTATE AU PASSAGE, NON CORRIGE : la configuration d exemple d Icinga
crie en permanence sur mon-01 — swap sur une VM sans swap, http sur un
port ou rien n ecoute, apt pour un paquet. Trois alarmes qui ne peuvent
que rester rouges, dans le seul endroit qui doit rester lisible.

Etat : certificat 14/14, sante 14/14 au tenant ; 7/7 au site.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 22:14:30 -04:00
90228cb55e supervision : la sonde se declare dans le role, comme le flux
LE CONSTAT. 39 roles declarent leurs flux, 32 leur empreinte, 32 leur
authentification — tous derives. Et 19 groupes sur 19 declaraient une
surveillance en prose que RIEN n executait ; Icinga en surveillait deux.
La carte disait ce qui etait surveille, et personne ne surveillait.

LE MECANISME. Un role declare ses sondes dans meta/supervision.yml et
depose lui-meme son script dans /usr/local/lib/setops/sondes/. Le porteur
client_sante les fait toutes tourner et pousse un resultat passif par
sonde, sans savoir ce qu elles mesurent. serveur_icinga derive les objets
Service ET le filtre de permission d API des memes declarations. Ajouter
une sonde ne demande de toucher ni au porteur ni a Icinga.

PREMIERE SONDE : client_pki/certificat. Heures restantes sur le certificat
reellement pose, chaine verifiee, et empreinte SERVIE comparee au disque
quand un service le consomme. 14/14 au tenant, 7/7 au site.

QUATRE OBSTACLES, ET TROIS SONT LA MEME LECON.

La sonde a rendu 14/14 en CRITIQUE sur une PKI saine : openssl verify
-CAfile racine ne trouve pas l intermediaire qui signe nos certificats.
step certificate verify, lui, repond VALIDE.

Deployee au site, elle a rendu 5/7 : le seuil d avertissement (12 h)
etait AU-DESSUS du point de renouvellement (8 h, le tiers restant). Elle
criait avant que le mecanisme ne soit cense agir. Seuils ramenes a 6 h et
3 h. Un seuil se DERIVE du moment ou le mecanisme surveille agit.

Une alarme toujours allumee ne vaut pas mieux qu une alarme jamais
allumee : elle apprend a ne plus regarder. Une sonde se prouve DEUX FOIS,
verte sur le sain et rouge sur le casse.

Le filtre d API etait ecrit avant la lecture des declarations : les
services auraient existe et Icinga aurait refuse leurs resultats.

Et mon controle negatif a casse un service reel : substituer le certificat
d hote a fait propager un cert sans sa clef vers node_exporter. Un controle
negatif se fait sur une COPIE.

P64 tient les deux bouts : declaree sans etre deposee, ou deposee sans
etre declaree. Trois controles negatifs rejoues.

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 21:45:49 -04:00
69b84f4e2c client_sante : retrait d une garde qui ne pouvait pas se declencher
Le role portait une branche « aucun serveur_icinga : rien a poser » et un
when sur tout son bloc. Ni l une ni l autre ne pouvait s executer.

Eprouve sur le modele public, dans une copie jetable : instancier ne pose
une integration UNIVERSELLE que si son serveur existe dans l ecosysteme.
Sans Icinga au plan, le groupe client_sante est ABSENT de l inventaire
genere — comme client_metrique et client_journal le sont deja. Le role n
est donc jamais appele sans destinataire.

Une garde qui ne peut pas se declencher n est pas une garde : elle rassure
sans rien tenir, et elle coute le jour ou l on cherche pourquoi rien n a
alerte.

Ce qui la remplace se declenche vraiment, et les deux cotes sont eprouves :
hote vide -> FAILED, secret vide -> FAILED, et l echec dit lequel des deux
manque. Le bloc, prive de sa condition, est aplati : douze taches a plat.

Rejoue sur les deux flottes : zero changement sur zero hote.

make prouver : CONFORME, 63 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 21:06:08 -04:00
018d55ec6c supervision : le site rapporte aussi — et deux corrections pour que ca MARCHE
7 machines du site deployees, 0 echec au playbook — et CINQ rapports sur
sept en TimeoutError. Un playbook vert ne prouve pas qu une chose
fonctionne ; seul l essai de bout en bout l a dit.

1. LE PAIR DE LA FRONTIERE. Le role client declarait son egress 5665, la
politique de sortie etait accept, la regle d entree de l hote autorisait
la source — et les paquets mouraient ENTRE les deux. La frontiere filtre
l inter-zones du site et ne resout que les roles que les machines PORTENT
AU PLAN ; une integration universelle n y figure pas, elle est derivee.
pair: client_sante produisait donc une regle est-ouest correcte et AUCUNE
regle a la frontiere. Le pair devient serveur_debian — le vocabulaire du
depot pour « tout noeud », que le generateur traite deja comme tel, et
exact au sens strict. Six regles creees, zero retiree, une par patte de
zone.

2. LE RAPPORTEUR S ACCUSAIT LUI-MEME. Pendant l heure de blocage,
setops-sante.service a echoue ; une fois debloque, cinq machines ont
rapporte CRITIQUE en citant leur propre rapporteur, et systemd garde l
etat failed jusqu a un reset-failed. Sa propre unite est desormais exclue
du compte — non par complaisance : sa sante est deja mesuree, et mieux,
par la FRAICHEUR de ses envois. S il ne peut plus parler, le ttl perime le
service, ce qui se voit precisement quand il ne peut PAS ecrire.

ETAT FINAL : 7/7 au site, 14/14 au tenant, tous OK. Controle negatif
rejoue sur les deux flottes.

make prouver : CONFORME, 63 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 20:48:36 -04:00
6d23121dc2 supervision : systemctl --failed entre dans Icinga (role client_sante)
CE QU IL FERME. openipmi.service echouait a chaque demarrage sur les
quatorze machines depuis le 2026-09-02, et systemctl --failed rendait ZERO
partout : non parce qu elles allaient bien, mais parce qu aucune n avait
redemarre depuis. Il a fallu qu un humain redemarre une machine pour que
le defaut existe aux yeux de quelqu un. Un controle qui ne peut echouer
qu au demarrage ne mesure rien tant que rien ne demarre.

PASSIF, ET A DUREE DE VIE. Un controle actif ne voit pas la machine MUETTE.
Ici c est le noeud qui parle, et le ttl de son envoi fait la fraicheur :
sans nouvelle, Icinga perime le service tout seul. Le silence alerte autant
que l echec. Le minuteur declenche AU DEMARRAGE autant que toutes les 15
min : les echecs de cette famille naissent au boot.

CRITIQUE DES LA PREMIERE UNITE, jamais un seuil — une unite en echec est
soit un vrai probleme soit du bruit a retirer, et un seuil ferait vivre le
bruit. Les tolerances se nomment une par une, vide par defaut.

CONTROLE NEGATIF. Unite factice sur obs-01, etat relu dans IcingaDB :
CRITICAL, et le verdict NOMME l unite. Les treize autres OK. Apres
nettoyage : 14/14 OK.

UN CONFLIT EVITE. setops-sauvegardes.conf definissait les object Host ; un
second fichier de controle aurait redefini les memes, et Icinga refuse un
objet en double — la configuration entiere aurait ete rejetee, donc AUCUNE
supervision, en voulant en ajouter. Les hotes vivent maintenant dans
setops-hotes.conf, definis une fois.

TROIS OBSTACLES. ${#tableau[@]} contient {# que Jinja lit comme un debut de
commentaire (remede : comment_start_string en tete du gabarit). Ma premiere
sonde a traduit un 403 « Missing permission: objects/query/service » en
« 0 service » — encore un echec qui ecrasait permission ; l etat se lit
dans IcingaDB. Et un echec apt transitoire sur mon-01, local et disparu au
second essai : mesure avant conclusion.

NON FAIT : le SITE n a pas recu client_sante.

make prouver : CONFORME, 63 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 20:32:08 -04:00
f27ac9762e site : site-backup-01 a recu la PKI qui lui manquait
make site-appliquer GROUPE=client_pki : 7 hotes, 0 echec. site-backup-01 a
recu quatorze changements — depot apt Smallstep, step-cli, STEPPATH, mot de
passe du provisioner, etablissement de la confiance dans l AC, certificat
d hote emis, unite et minuteur de renouvellement actives.

Verifie sur les sept : chaine VALIDE (step certificate verify contre la
racine locale), racine presente, timer cert-renewer@<fqdn>.timer en
enabled/active avec une passe ce soir. site-pki-01 est la seule sans ancre
dans /usr/local/share/ca-certificates/, et c est juste : elle PORTE l
autorite.

TROIS DE MES SONDES ONT MENTI AVANT LA BONNE : un nom d unite devine, puis
la colonne « service active » lue a la place de la minuterie — un service
declenche par minuteur est normalement disabled, ce qui se lit comme une
panne quand on regarde la mauvaise ligne. La question etait serieuse :
sans minuteur, les certificats du site expiraient dans 24 h.

Et le changed=2 des six autres machines n etait pas une non-idempotence :
c etait le depot initial de step-cli. Rejoue sur site-forge-01 : changed=0.

make prouver : CONFORME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 19:55:31 -04:00
e9b22b0be0 site : cloud-init retire des sept machines de l hebergeur
make site-appliquer GROUPE=serveur_durci : 7 hotes, 0 echec, 0 injoignable.
Configuration reseau intacte sur les sept, ifquery rend l adresse attendue
sur chacune, zero unite cloud-init restante, zero unite en echec.

Le site HEBERGE, d ou la verification apres coup depuis les machines qui
ont le flux : la forge repond 200 et sert toujours ce depot au bon commit,
le cache APT repond 200, le DNS resout, step-ca rend status ok.

Le site ne portait pas le piege openipmi : il n applique pas
client_metrique.

TROUVE EN VERIFIANT, SANS RAPPORT : site-backup-01 est DANS le groupe
client_pki et n a ni /etc/step, ni minuterie de renouvellement, ni meme la
racine de l AC. Les six autres ont leur certificat et leur minuterie
quotidienne. L appartenance au groupe dit « couverte », la machine dit le
contraire. Non corrige — c est une decision d exploitation.

Corrige au passage deux affirmations fausses du CHANGELOG : le site n est
plus « arme, pas applique », et il ne faut pas passer par site-ops-01 pour
le deployer.

make prouver : CONFORME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 19:35:17 -04:00
f4bb40c4f2 metriques : le client installait un pilote IPMI qui echoue a chaque demarrage
LE REDEMARRAGE A TENU. obs-01 a redemarre avec son disque cloud-init
RETIRE de Proxmox — sans paquet et sans source de donnees — et elle est
revenue avec son adresse, sa passerelle et son resolveur. Le retrait de
cloud-init est desormais prouve par un demarrage reel.

ET IL A REVELE AUTRE CHOSE. Une unite en echec : openipmi.service. La
chaine est prometheus-node-exporter -> recommande collectors -> recommande
ipmitool -> recommande openipmi. Trois recommandations en cascade,
raisonnables sur du metal. Dans une VM il n y a pas de BMC et le script d
init echoue a chaque demarrage.

LE POINT N EST PAS LE PAQUET, C EST POURQUOI PERSONNE NE L AVAIT VU.
openipmi datait du 2026-09-02. systemctl --failed rendait pourtant ZERO
sur les quatorze machines — non parce qu elles allaient bien, mais parce
qu aucune n avait REDEMARRE depuis. Six jours et vingt heures pour obs-01.
Un controle qui ne peut echouer qu au demarrage ne mesure rien tant que
rien ne demarre.

Le degat n est pas cosmetique : une unite en echec permanent use le seul
signal qui devrait alerter. Le jour ou une vraie unite tombe, --failed
rend 2 au lieu de 1, et personne ne fait la difference.

CORRECTION A LA SOURCE ET CONDITIONNELLE. client_metrique retire ipmitool
et openipmi, mais SEULEMENT dans une VM (virtualization_role == guest) :
sur du metal le collecteur IPMI est legitime. Ils ne sont que RECOMMANDES,
donc les retirer n emporte pas les collectors, qui servent. Un handler
efface l etat failed que le retrait ne nettoie pas.

Applique aux quatorze : ipmi=0, collectors=1, node_exporter=active,
echecs=0. Second passage : zero changement sur zero hote.

RESTE OUVERT : rien dans le harnais ne regarde systemctl --failed sur la
flotte. Ce defaut a ete trouve parce qu un humain a redemarre une machine.

make prouver : CONFORME, 63 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 16:20:32 -04:00
36aba37200 wiki : les deux forges servent le meme wiki (origin rattrape)
CE QUE JE DISAIS ETAIT FAUX. « Il n y a rien sur origin » — le DEPOT y
est, et a jour, exactement notre HEAD. C est le WIKI qui etait vide. Je
l avais recopie d une session precedente sans le remesurer.

LA GARDE AVAIT RAISON DE REFUSER, ET TORT DE S ARRETER LA. wiki-publier
refuse un wiki vide parce que publier y inventerait un nom de branche.
Son message renvoyait a l interface Forgejo — injoignable depuis ce poste,
et ce n est pas une panne : la forge du site n accepte le 443 que des
machines qui declarent le flux.

Forgejo DECLARE pourtant la reponse : wiki_branch, dans son API. Interroge
depuis ops-01 — machine qui a le flux — il repond main. On ne devinait
pas : on ne demandait pas. WIKI_BRANCHE= ajoute a la recette, le refus
reste le defaut. Les deux cotes eprouves.

Resultat : 26 pages sur les deux forges, contenu identique au fichier pres.

LECON D INSTRUMENT. Trois sondes fausses avant la bonne : connect() direct
rend TimeoutError (politique, pas route manquante) ; un tunnel par la
frontiere ne repondait pas ; et git ls-remote fonctionnait tres bien, parce
que ~/.ssh/config passe par un ProxyJump que la sonde ignorait.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 13:50:06 -04:00
2d9dc866a9 cloud-init retire de la flotte : 14/14, 0 echec
Applique apres confirmation. Ordre suivi : obs-01 seul d abord, verifie,
puis les treize autres.

Releve sur les quatorze machines : paquet=0, unites=0, reseau=oui,
drapeau=oui, networking=enabled, echecs=0 — et sur chacune, ifquery rend
EXACTEMENT l adresse vive. C est le point : ifquery ne lit pas la memoire
du systeme, il RELIT le fichier que le retrait aurait pu emporter, donc il
repond ce que le prochain demarrage fera.

Second passage sur obs-01 : changed=1, et ce seul changement est la tache
nftables_baseline qui se declare toujours modifiee, anterieure a ce
chantier. Le retrait est idempotent.

make valider apres deploiement : 0 echec, 0 injoignable.

CE QUI N EST PAS PROUVE. Aucune machine n a ete REDEMARREE : la garde de
securite du poste a refuse le redemarrage, et on ne contourne pas une
garde. Le chemin de demarrage a donc ete prouve autrement — networking
(ifupdown) actif, systemd-networkd desactive, ifquery relit le fichier et
rend la bonne adresse, zero unite cloud-init dans multi-user.target, zero
unite en echec, nom d hote et cles SSH persistants. C est fort, ce n est
pas un redemarrage.

LE SITE. SITE-Chezlepro est une autre instance de CE depot : meme
serveur_durci, memes roles. Ses sept machines actives sont durcies depuis
le 2026-09-02 — leur prochain passage retirera cloud-init chez elles
aussi. Le site se deploie depuis son propre runner, pas depuis ce poste.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 11:03:45 -04:00
0b0d9ca708 wiki : publie (ce que le retrait de cloud-init ne ferme pas)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 10:13:15 -04:00
f89b097b26 D-85 : ce que le retrait de cloud-init NE ferme pas
La premiere redaction se lisait comme une emancipation. Elle n en est pas
une, et il valait mieux le dire que de laisser un lecteur presse en
conclure trop.

Retirer cloud-init n ote AUCUN pouvoir a l hebergeur. qemu-guest-agent est
au gabarit — il doit y etre (P56) — et l API Proxmox expose sur son dos,
sur toute VM vivante de la flotte, un pouvoir strictement plus grand que
le lecteur cloud-init. Releve le 2026-09-09 sur edge-mta-01, avec le jeton
du site : exec, exec-status, file-read, file-write, set-user-password,
shutdown.

Ce que D-85 ferme est donc precis et etroit : une reapplication
AUTOMATIQUE a chaque demarrage, depuis un support que le plan ne possede
pas et qu aucune preuve ne lit ; et le code de cloud-init lui-meme, un
interpreteur Python complet execute en root au boot avec ses ~29
dependances. La mainmise d un hyperviseur sur ses invites est une
propriete de la VIRTUALISATION, pas de cloud-init — elle appelle sa propre
decision, qui n est pas prise.

LE SEUIL EST NOMME. L alternative existe et sa piece est deja au gabarit :
poser adresse et cle par agent/file-write + agent/exec, sans reseau.
Aujourd hui ce serait reimplementer un standard, ce que positionnement.md
interdit, au moment le plus fragile et pour le pire mode de panne — une VM
injoignable. Le jour ou une premiere seconde ne pourra plus etre amorcee
par Proxmox (autre hyperviseur, metal nu, hebergeur sans API), ce chemin
devient LE chemin portable.

La nuance est portee partout ou l affirmation est faite : D-85, le README
et l entete du role, AGENTS.md, le wiki.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 10:11:52 -04:00
a6457a161b wiki : publie (cloud-init retire au durcissement)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-09 09:27:13 -04:00
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
765d97b5c9 wiki : publie (les six registres generes)
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_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 18:27:46 -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
c0ea0c8e71 wiki : publie (formulaires generes, ecritures preservantes)
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_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 17:56:02 -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
00a8dc5283 wiki : publie, avec la vue Nomenclature
Some checks are pending
verifier / verifier (push) Waiting to run
Temoin de publication mis a jour ; P60 repasse au vert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 17:11:56 -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
03c628d5e1 GUI : le formulaire des bases est GENERE depuis le schema
Etape 3, sur un seul registre — `bases_donnees`, le plus simple et le seul ou
observe et editable coincidaient deja. Les cinq autres gardent leurs formulaires
ecrits a la main : on ne bascule pas six vues d un coup.

CE QUI DISPARAIT DU JAVASCRIPT

Huit `<label>` en dur, trois constructions de `<option>`, et la regle qui
choisissait la source du consommateur selon la portee. Cette derniere ne vivait
que dans le JS ; elle est desormais DECLAREE au schema (`x-source-selon`), donc
lisible et gardee.

Le formulaire rend exactement les memes huit champs qu avant — verifie en
EXECUTANT le moteur sous node avec le schema et des donnees reelles, pas
seulement en passant `node --check`.

LA BOUCLE EST FERMEE DES DEUX COTES

Le chemin de SAUVEGARDE enumerait lui aussi les sept champs en dur. Un champ
ajoute au registre serait apparu au formulaire genere et aurait disparu
SILENCIEUSEMENT a l enregistrement — le pire des deux mondes. Il derive
maintenant du schema, valeurs par defaut comprises (`default`).

LA SEPARATION FORME / COHERENCE, MONTREE

  portee=groupe + consommateur APPLICATION   -> REFUSE par valider_bases
  portee=application + consommateur app      -> ACCEPTE
  secret absent                              -> REFUSE

Le schema a rempli la FORME (les defauts `groupe` et `principale` se sont
poses), le validateur a attrape l INCOHERENCE. Aucune de ces trois regles ne
s exprime en JSON Schema, et vouloir l y mettre creerait la seconde source de
verite que ce depot refuse.

CHAMPS_ECRITS_PAR_GUI COMMENCE A DISPARAITRE

Renomme CHAMPS_ECRITS_A_LA_MAIN, et `bases_donnees` en est SORTIE : sa
couverture se derive du schema. P19 lit desormais `champs_ecrits_par_gui()`, qui
reunit les deux. Le jour ou la table sera vide, elle gardera un mecanisme au
lieu d une liste.

UNE GARDE A CORRIGER AU PASSAGE

`declaration_derive()` verifiait que chaque champ declare apparait dans le
SOURCE du GUI. Pour un registre genere il n y apparait plus — c est le but.
Elle aurait crie sur precisement le progres qu elle devait constater. Les
registres generes en sont exemptes : c est P61 qui tient la promesse pour eux.

make prouver : CONFORME, 60 OK, 0 echec, 1 saute.
P07 (node --check), P19 (couverture), P61 (schema) : verts.

Reste : les cinq autres vues, et le trou de la nomenclature — que le passage au
generateur fermera par construction, puisque le schema decrit deja `categorie`
et `service`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 15:30:08 -04:00
0eaceb1048 schema du plan : la forme des registres devient derivee, et gardee
Etape 2 du chantier « l UI reflete fidelement la structure ». Le GUI porte
CHAMPS_ECRITS_PAR_GUI, une liste tenue A LA MAIN de ce qu il sait ecrire, que
P19 confronte au reel. C est une copie — gardee, donc honnete, mais une copie :
quelqu un doit penser a l allonger.

`make schema` produit docs/audit/schema-plan.json : six registres, 42 champs,
leurs types, leurs enumerations et ce qui est requis.

CE QUE LE SCHEMA EST, ET CE QU IL N EST PAS

  schema       -> la FORME      -> generera les champs du formulaire
  validateurs  -> la COHERENCE  -> refusent une saisie incoherente

Un JSON Schema ne sait pas dire qu un `consommateur` designe une application
inexistante, ni qu une integration universelle recopiee au plan est un defaut.
Vouloir l y mettre creerait la seconde source de verite que tout ce depot
refuse. Les valider_* restent l autorite.

LES ENUMERATIONS SONT IMPORTEES, JAMAIS RECOPIEES

ETATS_SERVEUR, PORTEES_BD et AUTORITES_DNS viennent des constantes que les
validateurs appliquent. Une enumeration recopiee diverge — c est la lecon des
neuf resolutions d instance que P41 garde depuis.

CE QUE L ETAPE 1 AVAIT TROUVE, ET QUE P61 A CONFIRME

Le recensement montrait `categorie` et `service` presents dans TOUS les plans et
absents de CHAMPS_ECRITS_PAR_GUI, dont la ligne `nomenclature` est vide : le GUI
ne sait pas les editer, l operateur doit ouvrir le YAML. P61 a refuse le premier
schema pour cette raison exacte. Les trois tables imbriquees de la nomenclature
sont donc DECRITES et non resumees en « object ».

31 champs observes dans l instance courante, 42 decrits par le schema. La
difference n est pas du bavardage : observer une instance n est pas un schema.
`noeud`, `stockage` et `coeurs` sont legitimes et simplement inutilises ici — un
schema derive de l observation les INTERDIRAIT.

P61, EPROUVEE DANS LES DEUX SENS

  fichier genere perime                -> REFUSE
  champ du plan absent du schema       -> REFUSE
  champ decrit mais inutilise au plan  -> COMPTE, pas refuse

Le troisieme point est delibere : refuser obligerait a retirer du schema un
champ valide des que plus personne ne s en sert. Meme mesure que les lacunes
nommees de P29.

UN DEFAUT DE MON INSTRUMENT, PAYE EN ROUTE

P61 comparait des noms a plat quand couverture_gui aplatit les tables
imbriquees : elle criait sur un schema correct. L instrument mesurait autre
chose que la cible. On aplatit desormais des deux cotes.

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

Prochaine etape : generer les formulaires depuis ce schema, et retirer
CHAMPS_ECRITS_PAR_GUI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 15:19:20 -04:00
6077b179af plan : l ecriture des registres devient atomique — tout, ou rien
Some checks are pending
verifier / verifier (push) Waiting to run
`path.open("w")` TRONQUE avant d ecrire : entre les deux, le fichier est vide.
Une exception dans yaml.safe_dump, un disque plein, un Ctrl-C, et
instance/plan/serveurs.yml reste mutile.

L asymetrie fait la gravite : hosts.yml se regenere d un
make instancier-appliquer, le PLAN ne se regenere de rien. C est la source
unique de verite. Git est le filet, mais encore faut-il savoir qu on est tombe.

TREIZE SITES, UNE SEULE FONCTION

Le defaut n etait pas dans le GUI seul : douze sites dans sept fichiers, dont
les miroirs CLI des MEMES registres. Corriger le GUI seul aurait recree la
divergence que P41 garde depuis les neuf resolutions d instance. La fonction
vit donc dans inventory_rules.py, que les sept importaient deja. Une source,
pas douze.

TROIS DETAILS QUI FONT LA DIFFERENCE ENTRE « CA MARCHE » ET « CA TIENT »

  temporaire dans le MEME dossier   os.replace n est atomique qu au sein d un
                                    meme systeme de fichiers ; un /tmp sur une
                                    autre partition casserait la garantie sans
                                    rien dire
  fsync AVANT le rename             sinon le renommage peut atteindre le disque
                                    avant le contenu : au retour d une coupure
                                    brutale, un fichier neuf et VIDE — le defaut
                                    qu on ferme, deplace d un cran
  report des droits                 mkstemp cree en 0600, le plan est en 0664 et
                                    doit rester lisible par le groupe sur les
                                    runners

LE TEST PORTE SON PROPRE CONTROLE NEGATIF

scripts/tests/test_ecriture_atomique.py rejoue D ABORD l ancienne forme et
verifie qu elle DETRUIT. Sans ce controle, « le fichier est intact » ne
prouverait rien — il pourrait l etre parce que rien n a ete ecrit du tout. Une
garantie qu on n a jamais vue echouer n est pas une garantie, c est une
habitude.

Branche sur P02, dont le titre annoncait « inventory_host » alors qu il lance
maintenant trois tests. Corrige au passage.

LA VOUTE DU GUI : VERIFIEE, PAS DE DEFAUT

Le soupcon etait qu executer_flux pose ANSIBLE_VAULT_PASSWORD_FILE (un seul mot
de passe) alors que creer une VM ouvre DEUX voutes depuis « une voute, une cle ».

Eprouve contre deux voutes JETABLES a mots de passe distincts — jamais les
vraies. Les deux variables se CUMULENT : Ansible essaie tous les secrets, et un
PASSWORD_FILE errone n empeche rien. Confirme en sondant l environnement qu une
recette make recoit reellement : le mot de passe saisi ET les cinq cles
calculees par voutes.py.

Ce qui sauve ce chemin n est donc pas le mot de passe saisi, c est
l IDENTITY_LIST que make pose par-dessus. Chacun couvre ce que l autre ne
couvre pas — le PASSWORD_FILE sert le runner qui n a que sa cle, l IDENTITY_LIST
le poste qui les a toutes. Ecrit au-dessus du code, pour que personne ne
« simplifie » en retirant l un des deux.

make prouver : CONFORME, 59 OK, 0 echec, 1 saute.
make instancier : DIFF VIDE, quatre registres relus, droits 664 preserves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 14:57:57 -04:00
6da1032ceb positionnement : le gel tient, c est la carte des seuils qui etait fausse
La question « et si on retirait le gel du perimetre ? » a mis les cinq seuils a
l epreuve. Deux ne tenaient pas — et les garder etait plus dangereux que le gel
lui-meme : une carte des seuils fausse ne fait pas perdre du temps, elle fait
FRANCHIR UN SEUIL QUI NE L EST PAS.

RBAC — couvert, et par un mecanisme plus fort

Trois classes d acteurs aux pouvoirs disjoints existent depuis les runners : le
poste de l exploitant, le runner de SITE (materialiser, n entre jamais chez un
tenant) et les runners de TENANT (configurer). La separation est CRYPTOGRAPHIQUE
— une voute, une cle, 2026-08-28 — pas applicative : c est la presence des
fichiers qui borne le pouvoir, jamais une table de permissions qu une faille de
l application contournerait. Adopter AWX pour ce besoin serait REGRESSER.

Le tableau avait ete ecrit avant que les runners existent.

IPAM — sans objet par construction

Un IPAM sert a ALLOUER. Ici rien ne s alloue : tout derive du seed. Et cinq
preuves tiennent deja ce qu il verifierait — P20 (aucun adressage stocke), P21
(collisions d index), P23 (chevauchement d underlay), P28 (pools), P33 (ports).
L adopter remplacerait une propriete PAR CONSTRUCTION par un controle a
posteriori.

LE SEUIL QUI MANQUAIT : L EMANCIPATION

Le GUI ecoute sur 127.0.0.1 avec un jeton de session — un modele
mono-utilisateur, juste tant que l exploitant est une personne a son poste. La
trajectoire de filiation-emancipation.md mene a plusieurs HUMAINS, aux portees
disjointes, sur des machines qui ne sont pas les notres.

Ce seuil n appelle pas AWX : les runners portent deja la separation des
pouvoirs. Il appelle une decision sur la facon dont le GUI s ouvre a quelqu un d
autre, et elle n est pas prise. Un seuil qu on ne nomme pas est un seuil qu on
franchit sans le voir.

CE QUE LE GEL N INTERDIT PAS

Il porte sur les FONCTIONS de type NetBox/AWX, jamais sur les VUES. Montrer a l
ecran ce que le moteur sait deja — l ecart des dix devis, l etat du diff entre
Sauvegarder et Appliquer, le perimetre sur lequel un check vert a porte, les
temoins du genome et lequel a decroche — ne franchit aucun seuil : rien de tout
cela n existe dans NetBox ou AWX, parce que rien de tout cela n existe hors de
ce modele.

Le gel n est pas leve. D-84 le consigne, AGENTS.md suit.

make prouver : CONFORME, 59 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 14:02:08 -04:00
cbdc6c523d wiki-publier : un wiki vide se clone tres bien, et le garde-fou ne le voyait pas
Some checks are pending
verifier / verifier (push) Waiting to run
En rattrapant origin, sa forge s est revelee porter un wiki VIDE — jamais
publie. Avant de viser la forge, la manoeuvre a ete eprouvee contre un depot
bare local, comme la premiere fois.

LE DEFAUT

Le garde-fou ne testait que l ECHEC du clone, alors que son message parle du cas
vide : « le wiki doit exister (creer une 1re page dans Forgejo) ». Or un depot
vide se clone parfaitement. La recette allait donc au bout et creait une branche
`master` — parce que init.defaultBranch vaut master sur ce poste — alors que le
wiki deja en service vit sur `main`.

Le nom de branche etait DEVINE, et il divergeait d une forge a l autre. Un wiki
publie sur la mauvaise branche est un wiki que Forgejo peut ne pas afficher :
la publication reussit, la page reste introuvable, et rien ne l explique.

LE CORRECTIF

Refuser un wiki sans commit, plutot que de choisir a la place de Forgejo. Creer
une premiere page dans son interface initialise le wiki avec LA branche qu il
attend — il n y a plus rien a deviner.

EPROUVE DANS LES DEUX SENS

  depot bare vide      -> REFUSE, avec le geste a faire
  depot bare non vide  -> publie
  forge eregion reelle -> « deja a jour », temoin depose

Le troisieme essai compte autant que le premier : remplacer un defaut par un
blocage aurait ete un autre defaut.

CE QUE CA LAISSE OUVERT

Le wiki d origin reste vide : l initialiser demande une action humaine dans
l interface de la forge, que cette cible refuse desormais de contourner.

Et le temoin ne nomme qu UNE forge. P60 prouve que le depot n a pas bouge depuis
la derniere publication — pas que toutes les forges sont a jour. Tant qu il n y
en a qu une de publiee, la distinction ne coute rien ; a deux, il faudra que le
temoin porte une liste.

make prouver : CONFORME, 59 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 12:56:21 -04:00
e2935edd3d wiki : publie, et le temoin le prouve — P60 passe au vert
Some checks are pending
verifier / verifier (push) Waiting to run
La forge servait le wiki du 2026-08-10 : deux unites jamais publiees, vingt et
une differentes. Toute la revision de documentation n existait pas pour qui lit
la forge plutot que le depot.

Publie. Verifie en reclonant : 26 pages sur 26, aucune differente, aucune en
trop, 8 figures. La forge porte 4da68c7, source c9d31e9.

DEUX DEFAUTS DANS MON PROPRE AJOUT, PAYES A L EXECUTION

1. `@#` au milieu d un bloc shell continue. Le prefixe @ appartient a make, pas
   au shell : bash a cherche une commande nommee « @# ». La publication avait
   REUSSI, et le temoin n a pas ete ecrit — erreur 127 apres coup.

2. Plus grave, et invisible au premier essai : le temoin n etait ecrit que dans
   la branche « il y a des changements ». Or « deja a jour » est PRECISEMENT le
   cas ou il doit dire que la forge est au niveau du depot. Sans lui, P60
   restait rouge apres une publication reussie — une preuve qui refuse un etat
   sain, donc une preuve qu on apprend a ignorer.

   C est la seconde execution qui l a montre : elle est passee par cette branche
   justement parce que la premiere avait publie. Le defaut se corrigeait en se
   revelant.

Les deux tiennent dans la meme lecon : une garde qu on n a pas vue dire OUI ne
vaut pas mieux qu une garde qu on n a pas vue dire non.

make prouver : CONFORME, 59 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 12:49:40 -04:00
c9d31e9a60 preuves : trois gardes pour ce que ma lecture ne tiendra pas
Une revision de documentation vieillit comme le reste. Ce qui tient, c est ce
qu une machine verifie — et trois lacunes etaient nommees sans etre gardees.

P58  HABILITATIONS. autorisation.md posait la regle (un service nomme un
     GROUPE, jamais une personne, D-66) et meta/acces.yml la portait ; rien ne
     la verifiait. P29 gardait les POSITIONS d authentification, personne ne
     gardait les DROITS.

     Le controle qui porte la preuve est un croisement : une entree
     porte_par: role-realm affirme que l habilitation voyage par un role de
     realm projete depuis un groupe LDAP. P58 le confronte a serveur_keycloak.
     Sans ca, un service annonce une habilitation que rien ne transporte, et l
     ecran reste vide sans que personne sache pourquoi.

     CE QU ELLE N EXIGE PAS, et c est le point le plus important : que les
     groupes nommes existent dans l annuaire. Ce serait contredire le regime du
     paragraphe 2 — le depot AMORCE un acces et se retire, les appartenances
     appartiennent a une personne. dev et personnel n existent dans aucun code,
     et ce n est pas un defaut.

P59  ENUMERATIONS ANNONCEES. Les deux ecarts trouves a la main pendant la
     tournee — cinq portes annoncees devant une table de six, huit lignes
     renvoyees vers une fiche qui en compte dix — etaient d une forme que P57
     ne voit pas.

     Ma premiere version a signale CINQ ecarts, et les cinq etaient du bruit :
     dans « reprise dans les deux devis : », le nombre qualifie autre chose que
     la liste. Cent pour cent de faux positifs — la preuve qui crie sur un cas
     sain et qu on apprend a ignorer. Resserree aux deux formes ou le nombre ne
     peut compter rien d autre. Etroite et vraie plutot que large et devineuse.

P60  WIKI PUBLIE. Le wiki est publie DEPUIS le depot ; rien ne mesurait l ecart,
     et il s est creuse de VINGT-SEPT JOURS en silence. Deux unites jamais
     publiees, vingt et une differentes : pour qui lit la forge plutot que le
     depot, toute la revision n existait pas.

     Le harnais est STATIQUE, zero appel reseau — cloner la forge romprait la
     seule propriete qui fasse qu une preuve vaille hors de ce poste. La mesure
     passe donc par un TEMOIN que make wiki-publier depose. Amorce avec la
     valeur MESUREE : le wiki d eregion porte b6167f2, dont le message dit
     source: ac85278.

     Ce qu elle ne prouve pas : un temoin dit ce qui est PARTI, jamais ce qui
     est ARRIVE.

LES TROIS SONT EPROUVEES DANS LES DEUX SENS

Douze essais negatifs, douze refus : groupe non projete, acces.yml disparu,
personne au lieu d un groupe, mecanisme invente, raison manquante, compte
revenu a cinq, septieme porte ajoutee sans toucher au compte, renvoi croise
fausse, temoin absent, temoin d un autre depot. Une garantie qu on n a jamais vu
dire non n est pas une garantie, c est une habitude.

ETAT : NON CONFORME, 58 OK, 1 echec, 1 saute.

P60 est rouge, et c est le comportement voulu : le registre a le droit de
perdre. Le retard qu elle signale est reel et anterieur a elle. Une commande le
ferme, et elle vient ensuite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 12:47:18 -04:00
36b6926e21 patient 0 : il n existe plus, et le depot le disait encore au present
Ses machines sont detruites. Le depot en parlait comme d un ecosysteme vivant,
et l une de ces phrases datait de la veille : positionnement.md comptait ses
5 VM dans la flotte, chiffre que j avais moi-meme ecrit en revisant.

CE QUI EST CORRIGE

positionnement.md         51 VM sur quatre plans vivants, et non 56 sur cinq
sortir-les-cles-du-poste  le genome est replique DEUX fois, non trois
wiki/Glossaire.md         SQLite : ce qu il PORTAIT
filiation-emancipation    la famille de pairs a perdu un pair
decisions-architecture    D-83 consigne le retrait, avec son cout
carte-set-ops.md          80 decisions en vigueur

LE COUT, PARCE QU IL N ETAIT NOMME NULLE PART

D-82 lui avait laisse deux raisons d etre : la mise en oeuvre de reference du
modele origine, et un TEMOIN de plus du genome. Le retrait solde la premiere et
abaisse la seconde de trois copies vivantes a deux.

Or c est le raisonnement de son propre README qui portait tout : on n echappe
pas a la boucle par la ruse, mais par le NOMBRE. Le nombre a baisse. Et le point
unique de defaillance que patient 0 existait pour eliminer — eregion, hors
flotte, que Set-OPS ne deploie ni ne sauvegarde ni ne prouve — est toujours a la
racine. Il n a jamais ete elimine, seulement promu ; il n y a maintenant plus de
miroir independant pour l absorber.

CE QUI RESTE SUR LE TERRAIN, ET QUI N EST PAS DE LA PROSE

Son plan est encore sur disque en federe: true. La federation lui reserve donc
toujours l index 29, la zone SDN t29, ses VNets, les VLAN 1291-1296 et les
sous-reseaux 10.29.16-21.0/24. Quatre machines du site — backup, cache, dns,
forge — acceptent encore SSH, apt, DNS et HTTPS depuis 10.29.0.0/16 : un
perimetre vide. Le runner du site clone encore ops-patient0.

C est un geste, pas une intention, et il touche le reseau : il n est pas fait
ici. La sequence est listee dans D-83.

make prouver --verifier : CONFORME, 56 OK, 0 echec, 1 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-07 00:13:21 -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
00cee67f02 depot hors site : trois pieges payes en une manoeuvre
Some checks are pending
verifier / verifier (push) Waiting to run
1. ansible -e cle=valeur DECOUPE SUR LES BLANCS — c est ainsi qu on passe
   plusieurs variables. Un chemin avec une espace y perd tout ce qui suit le
   premier blanc, et l erreur parle d un repertoire introuvable au nom tronque.
   Passage en JSON. Le depot avait deja appris ca pour le clonage de VM ; la
   lecon ne s etait pas propagee. Quatrieme fois.

2. /srv/restic est en 0711 — traversable, NON listable, pour qu un locataire
   n apprenne pas qui d autre depose ici. La consequence tombe sur rsync :
   opendir Permission denied, un message qui accuse un droit sans dire lequel.
   --rsync-path=sudo rsync fait lire la SOURCE en root.

3. rsync_opts ne protege pas ses elements : le shell coupait
   --rsync-path=sudo rsync en deux. Guillemets internes.

Resultat mesure : COPIE FIDELE, 233 fichiers, 95,7 Mo, empreintes identiques
une a une — comparees entre la source et la copie, pas supposees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 16:33:26 -04:00
9a823a5ea2 depot hors site : sortir du site ce que le site garde pour tout le monde
Some checks are pending
verifier / verifier (push) Waiting to run
Le depot heberge la racine de l AC, la forge du genome, et l etat de CHAQUE
locataire — 109 Mo sur une seule machine, dans un seul batiment. C est le
probleme du filet range dans la flotte qu il protege, un etage plus haut.

On emporte AUSSI les depots des locataires : si le site brule, ils perdent
leurs sauvegardes avec lui, et eux ne peuvent rien y faire. Ils ont depose chez
l hebergeur, c est a l hebergeur de tenir cette promesse.

La copie est OPAQUE — chiffree cote client, illisible par qui la porte. C est
ce qui permet de la deposer chez un pair sans lui demander autre chose que de
la disponibilite.

L empreinte est prise A LA SOURCE avant la copie, puis recalculee sur la copie
et comparee une a une. Sans ca on rentre chez soi avec un repertoire.

Un pipe sans pipefail masque l echec de tout ce qui n est pas le dernier
maillon : ici sed, qui reussit toujours. Une empreinte vide des deux cotes
aurait passe la comparaison.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 14:20:19 -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
3eff217bd5 cles : un chmod refuse ne doit pas annuler une verification reussie
Some checks are pending
verifier / verifier (push) Waiting to run
Une cle USB est le plus souvent en FAT32 ou exFAT — pas de permissions Unix.
Le chmod de la derniere ligne y echoue, et le script serait mort APRES avoir
ecrit et verifie l archive : une trace d erreur sur un travail termine, au pire
moment pour semer un doute.

On le dit plutot que de le taire : sur un support sans droits, l archive est
lisible par qui branche la cle. C est le chiffrement qui protege, pas le support.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-05 11:16:45 -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
cccb4e5b43 supervision : declarer a qui elle parle
Some checks are pending
verifier / verifier (push) Waiting to run
Icinga livre son exemple avec root@localhost — une adresse que personne ne lit.
Une supervision qui voit tout et n en parle a personne a le meme effet qu aucune
supervision, en plus couteux : elle rassure.

serveur_icinga_destinataire cree un utilisateur dans le groupe auquel les
notifications sont deja assignees (Icinga refuse deux objets de meme nom, on ne
peut pas redefinir l exemple ; on en ajoute un autre, l exemple reste muet).

Vide, aucun destinataire n est pose ET LE DEPLOIEMENT LE DIT — plutot que de
laisser croire qu une alerte partira.

Eprouve : message expedie par le relais du site, status=sent (250 2.0.0 Ok),
livre directement a mx.chezlepro.ca sans intermediaire ni dependance envers un
locataire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-09-02 17:48:19 -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
a5c9a88f00 resolveur du site : le troisieme service prete pendant la jeunesse d un tenant
Apres les paquets (serveur_cache_site) et le genome (serveur_forge_site), les NOMS.
Meme patron : un installateur, un marqueur.

CE QUE CA CORRIGE, mesure sur infra-pki-01, premiere machine a porter l autorite
de Chezlepro :

    DNS               BLOQUE      un tenant resout chez lui, sa requete ne sort pas
    443 sortant       OK          par adresse
    apt via le cache  OK          le mandataire resout a sa place

apt s en sortait ; TOUT LE RESTE ETAIT AVEUGLE AUX NOMS. Versionner la cle de
signature Smallstep a fait passer une tache et l echec s est deplace d un cran : le
depot lui-meme devait etre joint par son nom.

POURQUOI PAS L UNBOUND DE LA FRONTIERE : il tourne sur le boitier sans etre un
service gere, et le plan du site l avait deja ecrit une fois — un processus n est
pas un service. site-dns-01 est deploye par le moteur et verifie par le harnais.

OU VIT LA DERIVATION : dans site_inventaire.py, qui connait le plan du site ET le
registre des tenants. Le role tourne sur une machine qui n a aucune raison de lire
la carte de l hebergeur — la derivation appartient a qui detient les deux sources.

PIEGE DE FILTRE, ATTRAPE EN LE BRANCHANT : _supernets_voisins() EXCLUT l instance
montee (personne n est son propre voisin). Le site sert TOUS ceux qu il heberge — y
compris celui qu on deploie. Reutilise tel quel, il privait de resolution le seul
tenant en cours de montage. On reutilise donc la DECOUVERTE partagee sans son
filtre : deux recensements divergent, deux filtres non.

Le marqueur verifie deux choses : que le resolveur ECOUTE, et que sa liste
d autorisation ADMET reellement les tenants. Sans la seconde, la panne chez le
locataire ressemblerait a un DNS mort alors que c est une ACL.

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:37:33 -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
6f4833e65c nftables : armer le pare-feu coupait la main qui l arme
Une session SSH ouverte AVANT le demarrage de nftables n a aucune entree
conntrack. Des que le ruleset s installe, ses paquets arrivent en invalid et
tombent sur la regle ct state invalid drop : la session meurt. Les connexions
NEUVES passent sans probleme. Le pare-feu est correct ; c est la continuite qui ne
l est pas.

CE N EST PAS UN VERROUILLAGE, ET C EST CE QUI LE REND DANGEREUX. Rien n echoue :
Ansible attend une reponse qui ne viendra jamais, sans delai. Un deploiement est
reste suspendu CINQUANTE-HUIT MINUTES sur six machines pendant que les quinze
repondaient parfaitement a qui se connectait a neuf.

async + poll 0 : on lance et on ne guette pas une reponse qui ne peut pas revenir.
reset_connection jette la session morte, wait_for_connection en ouvre une neuve,
suivie par conntrack des sa poignee.

Le delai est une ATTENTE, pas un verdict : si l hote ne revient pas, c est un vrai
verrouillage — la garde nftables_admin_ssh a manque sa cible — et il faut le dire
fort plutot que de laisser un playbook pendre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-30 10:00:18 -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
5c59f19530 voutes : une cle absente n est pas une faute — elle est souvent la bonne reponse
Sur le runner d un tenant, la cle du SITE DOIT manquer : il porte la carte de la
fabric et ne doit jamais pouvoir l ouvrir. Ma premiere version sortait en erreur
des qu une cle manquait — elle presentait une SEPARATION REUSSIE comme un defaut,
et a fait echouer une chaine parfaitement saine.

Seule l absence de la cle de l INSTANCE MONTEE empeche la machine de travailler.
C est elle, et elle seule, qui decide du code de sortie ; le reste s affiche.

Constate sur ops-01 le jour de son armement :

    instance    OPS-Chezlepro     cle presente
    hebergeur   SITE-Chezlepro    sans cle

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 18:41:21 -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
58ce3dfea4 forge du site : le marqueur manquant — le genome n etait ouvert a personne
MEME PATRON QUE serveur_artefacts + serveur_cache_site : un installateur, un
marqueur. Le site rend DEUX services a ses locataires pendant leur jeunesse, les
PAQUETS et le GENOME. Le premier etait declare, le second ne l etait pas — alors
que D-81 fait de cette forge l autorite dont tout ecosysteme se reproduit.

Mesure sur ops-01, premiere machine de la reconstruction de Chezlepro :

    cache du SITE 10.0.33.21:3142 : OK
    forge du SITE 10.0.33.11:443  : BLOQUE

Le plan du tenant declarait pourtant lire son genome a cette adresse. UNE
DEPENDANCE DECLAREE CHEZ LE CONSOMMATEUR, SANS FLUX CHEZ LE FOURNISSEUR — et rien
ne le signalait : la garde de matrice de resoudre_flux ne verifie que les paires
role->role, jamais un pair symbolique comme voisins_site.

POURQUOI UN ROLE A PART : ajouter cet ingress a serveur_forgejo aurait ouvert LA
FORGE DE CHAQUE TENANT a ses voisins. La responsabilite appartient a une machine
precise, pas au logiciel qu elle fait tourner.

Il verifie que la forge ECOUTE vraiment : sans ca il attribuerait l autorite du
genome a un port muet, et l ecosysteme venu s y reproduire attendrait sans savoir
pourquoi — ce qui est exactement arrive, quinze minutes durant.

TROIS GARDES ONT TRAVAILLE : le catalogue a refuse un role qu il ne nomme pas, la
carte a corrige ses deux chiffres, et P49 a exige la regeneration du registre.
P33 a impose partage: true — le marqueur emprunte l ecoute de serveur_forgejo.

Frontiere : 4 objets crees, 0 retire. Verifie depuis ops-01 : 443 OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 16:15:42 -04:00
aca63011ea roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.

Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :

    ERROR: Could not find a version that satisfies the requirement ansible-core
    Echec temporaire dans la resolution du nom

PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.

--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.

LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -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
5e4d711b97 socle : la source d'artefacts d'amorcage — nourrir la premiere machine
LA PREMIERE MACHINE D'UN ECOSYSTEME N'A NI RESOLVEUR NI CACHE, ET C'EST ELLE QUI
DOIT LES CONSTRUIRE. Mesure sur ops-01, premiere machine de la reconstruction de
Chezlepro : le socle restait bloque sur son premier apt update.

    sortie Internet TCP 443    OK
    DNS UDP 53 -> 9.9.9.9      MUET

Le DNS sortant est ferme PAR CONCEPTION — un tenant resout chez lui. Or apt vise
deb.debian.org par son NOM, et le cache de l'ecosysteme n'existe pas encore.

UN MANDATAIRE HTTP RESOUT CE QUE LE CLIENT NE PEUT PAS RESOUDRE : apt lui envoie
l'URL absolue et c'est lui qui traduit. La machine n'a besoin d'aucun DNS, seulement
d'une ADRESSE. Symetrique exacte de , meme place dans le socle.

AUCUN FLUX A OUVRIR :  accepte deja le 3142 depuis
, qui resout les SUPERNETS des tenants — donc toute machine, pas
seulement leur cache. Verifie depuis ops-01 : HTTP 200 en 0,158 s.

C'EST DE LA FILIATION, PAS UNE RUSTINE. L'emprunt est declare (l'intrant nomme
l'amont) et il s'efface :  ecrit 00-setops-artefacts, qui trie
APRES, donc gagne des que l'ecosysteme a sa source. Le plancher reste dessous,
comme /etc/hosts reste sous le DNS. Le retirer le jour venu est une emancipation —
un geste, pas un effet.

DIAGNOSTIC CORRIGE EN CHEMIN : j'avais lu "BLOQUE" vers le resolveur, le cache et
la PKI du tenant comme un pare-feu. C'etait une ABSENCE — ops-01 est la seule VM de
Chezlepro qui existe. Un port muet ressemble a un refus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:05:11 -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
6fe62f74e1 creer-vm : prouver la materialisation sans entrer chez le tenant
`creer-vm` confirmait son succes en attendant une reponse SSH. Le runner du SITE
materialise le terrain de TOUS les tenants, mais la frontiere lui refuse d'entrer
chez eux — c'est le sens meme de leur isolation. La premiere VM qu'il a creee a
donc ete declaree en echec apres 600 secondes alors qu'elle tournait, avec
l'adresse exacte que le plan lui destinait :

    Attente de SSH sur ops-01 ............ ECHEC: injoignable apres 600s.

LA TENTATION ETAIT D'OUVRIR LE SSH du runner vers tous les tenants. Ca aurait
repare la mesure en detruisant ce qu'elle protege : une machine capable d'entrer
chez chaque locataire est precisement ce que cette architecture refuse d'avoir.

L'agent invite repond sans rien ouvrir — l'API des hyperviseurs est deja le flux
par lequel la VM vient d'etre creee, donc qui peut la creer peut la voir naitre —
et il PROUVE DAVANTAGE. « Quelque chose ecoute sur le port 22 » ne dit ni quel
systeme a demarre, ni si cloud-init a pose la bonne adresse. Sur ops-01 :

    10.17.19.41 portee sur asgard — Debian GNU/Linux 13 (trixie) 6.12.101

Ses echecs distinguent deux causes tres differentes : « la machine demarre mais
rapporte une AUTRE adresse — cloud-init, ou le pont sur lequel elle est posee »,
et « introuvable sur la fabric ».

ATTENDRE LA DISPONIBILITE A CHANGE DE MAIN. Guetter cloud-init et la liberation de
dpkg appartient a qui va CONFIGURER : `deployer` le fait desormais, la ou il se
contentait d'un `ping` unique. Il y gagne ce qu'il n'avait pas — attendre le verrou
APT, faute de quoi la premiere couche echouait dessus. Contrepartie assumee : un
nom d'hote errone patiente au lieu d'echouer vite ; echouer vite interdirait de
chainer creation et deploiement, le geste central d'une reconstruction.

P52 garde le couplage ferme. Deux controles negatifs verifies.

make prouver : CONFORME, 52 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:48:49 -04:00
5d0f82792a portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.

1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
   runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
   11 a retire les modules Proxmox de cette collection.
       ERROR! couldn't resolve module/action 'community.general.proxmox_pool'

2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
   ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.

3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
   `make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
   dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
   mainteneur, ou les collections viennent du paquet systeme, toujours dans le
   chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
   DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
   desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.

4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
   dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
   `proxmoxer` SUR LE CONTROLEUR.
       La bibliotheque Python proxmoxer est absente

   Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
   QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
   sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
   ce qui prouve que la ligne est au bon endroit.

P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.

RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.

Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.

make prouver : CONFORME, 51 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
3fa6e4c3e1 collections : epingler les versions — le runner echouait la ou le poste reussissait
Premiere materialisation de VM depuis le runner du SITE :

    ERROR! couldn't resolve module/action 'community.general.proxmox_pool'

Meme depot, meme playbook, meme plan que chez le mainteneur. La difference tenait
a une seule chose que le moteur ne disait pas : la VERSION de ses collections.

Le poste porte `community.general` 10.3.0. Le runner, monte un jour plus tard, a
recu la 13.3.0 — et la version 11 a RETIRE les modules Proxmox de cette collection
(ils vivent desormais dans `community.proxmox`). `requirements.yml` nommait ses
collections sans dire lesquelles : chaque machine installait donc ce qui etait
courant le jour de son montage. Une dependance non epinglee n'est pas une
dependance, c'est un pari sur l'etat d'Internet a la date du deploiement.

TROIS CORRECTIONS.

`requirements.yml` epingle les trois collections aux versions eprouvees.

`serveur_ops` installe avec `--force`. Sans lui, ansible-galaxy laisse en place une
version SUPERIEURE a celle demandee : il ne retrograde pas. Un poste peut etre en
avance, pas seulement en retard, et le depot doit faire autorite dans les deux sens.

P51 garde les deux faiblesses de ce fichier — celle du 2026-08-24, une collection
utilisee sans etre declaree (`ansible.posix`), et celle d'aujourd'hui, declaree sans
version. La preuve ne lit que les fichiers de TACHES : un `defaults/main.yml` porte
`net.ipv4.ip_forward`, qu'un motif trop large prend pour un module.

MIGRATION CONNUE, PAS FAITE : passer les quatre modules Proxmox a
`community.proxmox` permettra de suivre `community.general` au-dela de la 11.

make prouver : CONFORME, 51 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:58:23 -04:00
3b65384b0f frontiere : le devis sait desormais refuser SANS consigner
L'outil ne savait qu'AUTORISER — `"action": "pass"` etait en dur dans l'emetteur.
Une regle de silence ne pouvait donc pas naitre du depot, et j'en avais pose deux
a la main sur le boitier : exactement ce que ce projet refuse.

CE QUI L'A MOTIVE. Le journal de la frontiere ecrivait 982 000 entrees par jour,
dont 82 % un balayage Internet contre le port VNC et le reste du bavardage de
decouverte du reseau local. Sa fenetre utile etait tombee a QUARANTE-QUATRE
SECONDES. J'y ai cherche la trace d'un flux du site vers les hyperviseurs, je n'ai
rien trouve, et j'en ai conclu a tort qu'aucune regle ne bloquait. Un journal noye
ment aussi surement qu'un journal mort. Mesure apres declaration : ~20 700/jour.

Une regle de silence ne change AUCUN comportement : ce qu'elle vise etait deja
refuse par le defaut. Elle ne supprime qu'une trace que personne ne lira.

TROIS PIECES.

`cle_regle` accepte une action sans changer d'un octet la cle des regles `pass`
deja posees. L'ajout naif d'un champ les aurait toutes detruites pour les recreer
a l'identique, sur la frontiere, en production. Le plan l'a confirme : 7 a creer,
0 a retirer, 121 inchangees.

`_corps_regle` lit l'action, la consignation et la SEQUENCE depuis le devis. La
sequence est ce qui rend un `block` sur : OPNsense evalue en `quick`, donc un
blocage large emis avant les `pass` fermerait courrier, web et acces distant.

`devis_opnsense` lit `opnsense_silences` et en fabrique regles et alias, motif
compris — une regle `block` muette sans raison ecrite est indiscernable d'un oubli.

P50 garde ces deux dangers. Controles negatifs verifies : un silence en sequence 1
echoue, un silence sans motif echoue.

Carte : 28 pieces d'audit (P48 l'avait vu juste).

make prouver : CONFORME, 50 OK, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:10:39 -04:00
14fa129731 audit : rapport de preuve du jour (49 OK, apres correction du retour du site)
Rejoue apres les deux corrections portees par SITE-Chezlepro (8c28641) : la
patte 192.168.11.17 de la frontiere sur `grappe-controle`, et
`proxmox_api_host` passe d'un nom d'hyperviseur a une adresse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 15:41:08 -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
8195d6b814 poste : l'outillage sur le chemin de celui qui l'exploite
Ansible vit dans un venv isole — c'est voulu, il est epingle. Mais rien ne le
mettait sur le PATH du compte d'exploitation : `make instancier` y echouait sur
« [Errno 2] No such file or directory: 'ansible-inventory' », un message qui
accuse un fichier manquant alors que le fichier est la, deux dossiers plus loin.

Mesure du 2026-08-26, en EPROUVANT la sequence depuis le runner du site. Un
exploitant y serait tombe exactement de la meme facon — et c'est precisement ce
qu'un outil « utilisable sans IA » ne doit pas faire.

Un fragment `profile.d` plutot qu'un `.bashrc` : il vaut pour toute session du
compte, `sudo -iu setops` compris, et se retire d'un seul geste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:42:24 -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
9c173f52ba forge du genome : mutualisee par site, avec une confiance limitee a une url
Une forge sert a deux choses que le meme logiciel confondait : porter le GENOME
-- meme contenu pour tous les tenants d'un site -- et heberger le TRAVAIL de ses
gens, qui est un service du tenant. La premiere se mutualise, la seconde non.

Lire le genome chez un voisin suppose de faire confiance a SON autorite. On ne
pose PAS cette racine dans le magasin systeme : `git config
http.<url>.sslCAInfo` limite la confiance a cette seule forge. Une porte, pas un
trousseau.

L'adresse est une IP : `forge.genese.internal` ne resout pas depuis un autre
tenant, et le certificat de l'edge porte l'IP dans ses SAN.

Effet de bord recherche : le genome devient disponible AVANT que la forge du
tenant soit debout. Un ecosysteme neuf n'attend plus sa propre forge pour se
remplir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 18:55:44 -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
c391d1ed30 frontiere : rendre les flux inter-tenants, sans encore les declarer
Le mot `voisins_site` etait accepte par la validation mais AUCUN generateur ne
le rendait : zero regle au devis, et rien ne le signalait. Deux causes, toutes
deux fermees.

flux_frontiere() ne retenait que les flux `externe`. Or deux tenants de la meme
fabric vivent sur des VLAN distincts, routes par la frontiere : leur trafic la
traverse, donc elle doit le porter.

Et le rendu manquait : une regle inter-tenant s'attache au meme lien de transit
que le reste -- les tenants s'y distinguent par leur ALIAS SOURCE, pas par une
interface. Ma mise en garde precedente reposait sur un modele faux.

LA REGLE APPARTIENT AU SITE, pas a l'un des deux tenants : c'est son runner qui
prepare le terrain, aucun ecosysteme n'ouvre de porte chez un autre.

Les declarations de chainage restent RETIREES : le devis obtenu ouvrait plus que
voulu -- maillage complet entre tous les caches, et un egress 3142 vers
l'Internet au lieu du seul voisin. Deux raffinements a faire avant de declarer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:32:35 -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
12d938cdb0 artefacts : le cache existe dans chaque ecosysteme, plus seulement chez patient 0
Un seul cache existait -- celui de patient 0. Les trois autres instances et les
six modeles allaient chercher leurs paquets chez Debian, machine par machine.

Place sur la forge quand il y en a une (« la forge est la source », du code ET
des binaires), sinon sur l'hote des services d'infrastructure.

Voir docs/filiation-emancipation.md : le cache est MUTUALISABLE. Un ecosysteme
au premier age peut aussi bien pointer sur celui de son hote plutot que d'en
heberger un.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:02:54 -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
d1db332ed3 frontiere : lire ses reglages a la racine du depot de site
Some checks are pending
verifier / verifier (push) Waiting to run
`opnsense.yml` decrivait le monde physique depuis les group_vars d'un tenant. Le devis et
le GUI le cherchent desormais d'abord a la racine du depot de site, comme underlay.yml et
proxmox-hebergeur.yml, par la meme derivation depuis le symlink.

CE QUE LE MAUVAIS RANGEMENT A COUTE : l'adresse d'API de la frontiere y etait restee a
10.0.0.1 apres migration vers 10.17.0.1. `make frontiere-appliquer` restait suspendu sur
une adresse morte, sans aucun message — trouve par l'exploitant en lancant la commande
dans son terminal, apres que j'aie moi-meme conclu deux fois a tort.

DEUX LECONS, ecrites plutot que corrigees en silence :
- un objet range chez celui qui n'en est pas responsable derive sans que personne le voie ;
- mes commandes s'executent dans ma session : l'exploitant ne voit pas leur sortie. Une
  commande lente ressemble alors a un blocage, et un blocage a une commande lente. Pour
  toute ecriture longue sur du materiel, c'est a lui de la lancer.

Les anciens emplacements restent lus : un site pas encore migre continue de fonctionner.
make verifier 41/41.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 00:47:32 -04:00
caee04385b voutes : le monde physique a la sienne, les tenants n'en portent plus les cles
Some checks are pending
verifier / verifier (push) Waiting to run
Demande de l'exploitant : « l'underlay et son tenant doivent avoir chacun sa voute ».

CE QUI ETAIT FAUX. Le jeton d'API du cluster et la cle d'API de la frontiere vivaient dans
la voute de CHAQUE tenant. Patient 0 a du les recopier pour exister. Consequence : on ne
pouvait plus revoquer l'acces d'un locataire sans le revoquer pour tous — la faute des
neuf copies, appliquee aux secrets.

CE QUI EST POSE :
- `underlay.vault.yml`, chez l'hebergeur, a cote d'underlay.yml. Quatre secrets deplaces
  (jeton Proxmox, cle et secret d'API OPNsense), retires des deux voutes de tenants.
- `proxmox_api.voute()` lit l'underlay APRES le tenant, donc l'hebergeur fait foi ; un site
  non encore migre continue de fonctionner sur son ancienne voute.
- `appliquer_opnsense._voute()` n'a plus sa propre lecture : elle appelle celle du cluster.
  La reecrire aurait fait une dixieme copie le jour ou l'on refermait les neuf autres.
- Le playbook de clonage charge la voute de l'underlay APRES celle du tenant, par la meme
  derivation que proxmox-hebergeur.yml : le symlink designe deja l'hebergeur.
- `voute.py` sait que ces secrets ne sont plus attendus chez un tenant (P18).

EPROUVE SUR LE REEL, apres retrait des cles chez les deux tenants : l'API du cluster
repond, le devis de placement est conforme pour les deux ecosystemes, le devis de
frontiere se genere (86 objets), le SDN est convergent.

UNE PRECAUTION APPRISE EN CHEMIN : le premier essai a ecrit la voute EN CLAIR avant de la
chiffrer, et le chiffrement a echoue — il a fallu detruire le fichier. La sequence est
desormais l'inverse : chiffrer dans un dossier de travail, ne deposer que le resultat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 17:13:48 -04:00
9f36f08db0 underlay : la frontiere entre les deux mondes, et l'instrument qui la mesure
Some checks are pending
verifier / verifier (push) Waiting to run
Demande de l'exploitant : « il faut vraiment faire une distinction entre l'underlay et les
tenants. Definis bien la frontiere entre les deux mondes. L'underlay et son tenant doivent
avoir chacun sa voute. »

LA DOCTRINE (docs/frontiere-physique-virtuel.md) : qui possede quoi, ou ca vit, qui
l'administre. Et la regle qui la rend operante — UN TENANT NE DETIENT JAMAIS UN SECRET DU
MONDE PHYSIQUE. Aujourd'hui chaque tenant porte le jeton d'API du cluster ; patient 0 a du
le recopier pour exister. C'est la faute des neuf copies, appliquee aux secrets : une
valeur qui vit a N endroits diverge, et on ne peut plus en revoquer une sans les autres.

L'INSTRUMENT (`make underlay-plan`) confronte le fichier au reel : l'API du cluster pour
les adresses REELLEMENT portees, une sonde TCP pour ce qui repond. N'ecrit rien.

CE QU'IL A TROUVE, des le premier passage :
  management 10.0.0.0/24, stockage 10.0.1.0/24, ceph 10.0.2-3.0/24  -> PERSONNE
  transit 10.0.4.0/24, vxlan 10.0.5.0/24                            -> occupes (3 noeuds)
  portes par les noeuds et declares NULLE PART : 192.168.11.x (la vraie gestion),
  10.11.5-7.x, 192.168.50.x, 10.1.110.254
La frontiere avait deja migre vers 10.17.0.1 ; les hyperviseurs, non. Le fichier decrivait
le monde d'avant — et c'est pour cela que la regle d'admin de patient 0 atterrissait sur
`wan`, ou elle n'aurait jamais laisse passer personne.

DEUX PRECAUTIONS ECRITES DANS L'INSTRUMENT, apprises en l'ecrivant :
- l'AUTORITE DEPEND DU ROLE. L'API de Proxmox connait ses hyperviseurs, et eux seuls.
  Declarer un commutateur « porte par personne » parce que le cluster l'ignore, c'est
  accuser le monde de ce que l'instrument ne voit pas.
- « pas joignable d'ici » n'est pas « absent ». Les reseaux de CHEMIN (transit, VXLAN,
  stockage) ne sont jamais joignables de l'exterieur, par construction (D-78). Le premier
  jet les declarait morts.

Le devis a donc corrige DEUX FOIS sa propre facon de mesurer avant de rendre un verdict.

RESTE, dans l'ordre : ecrire dans underlay.yml ce qui EST ; separer les voutes ; et alors
seulement appliquer la frontiere.

make verifier 41/41.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:31:35 -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
5bb503be45 doc : enseigner la classe de defaut, pas seulement la corriger
Some checks are pending
verifier / verifier (push) Waiting to run
L'exploitant, apres le refactor : « je n'y comprends rien ». C'est la mesure qui compte —
la regle fondatrice du depot est qu'un humain pilote sans IA, et une correction qu'il ne
peut pas expliquer ne lui appartient pas.

- L'unite « La preuve » gagne une section : le defaut le plus dangereux n'est pas
  l'erreur, c'est la COPIE. Neuf copies ne vieillissent pas ensemble, et la divergence ne
  se voit jamais de l'interieur d'une copie. Avec le cas vecu — un devis qui repondait
  CONFORME sur le mauvais ecosysteme parce que les deux avaient les memes valeurs.
- Le glossaire gagne « source unique » et « resolution d'instance » ; P39 les exige.
- Le rapprochement qui rend la chose evidente : c'est la meme lecon que
  proxmox-hebergeur.yml, ou les listes du cluster recopiees chez chaque tenant avaient
  deja diverge. Une source, pas N copies — pour les donnees comme pour le code.

Plan de recette regenere. make verifier 41/41.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:31:52 -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
742bcbf4b0 genome : les quatre depots sans lesquels un ecosysteme ne renait pas
Un ecosysteme Set-OPS ne se reproduit pas depuis ses machines, mais depuis QUATRE depots :
le moteur, le plan du tenant, le depot de l'hebergeur (sa fabric) et les modeles. Perdre
les VM coute du temps ; perdre ces quatre-la coute l'ecosysteme. Rien ne les nommait.

`scripts/genome.py` les DERIVE au lieu de les declarer : le moteur est ce depot,
l'instance vient de SETOPS_INSTANCE, l'hebergeur se lit du symlink underlay.yml, et les
modeles se reconnaissent a leur FORME — des plans en sous-dossiers, aucun a la racine.

Deux criteres appris d'un faux positif : sans le second, le detecteur designait le lab,
qui porte un lien `OPS-Technolibre -> ../OPS-Technolibre` que le motif traversait. Un
lien vers un frere n'est pas un contenu.

Trois cibles : `make genome` (constater), `genome-inscrire` (ecrire la parente),
`genome-verifier` (tient-elle encore ?).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:46:42 -04:00
701 changed files with 76237 additions and 2158 deletions

3
.gitignore vendored
View file

@ -9,6 +9,9 @@ facts_cache/
**/vault.yml
# Underlay = fabric physique de l'operateur (cluster-global) ; gabarit public seul.
/underlay.yml
# Contexte actif de CETTE machine (`site:<depot>` ou `locataire:<depot>`) : propre a chaque
# poste et a chaque runner, jamais versionne. Voir scripts/contexte.py.
/contexte
*.secret
*.pem
*.key

135
AGENTS.md
View file

@ -20,7 +20,7 @@ Piliers de l’écosystème :
- confiance — autorité de certification interne (ACME) ;
- nommage et adressage — DNS interne et nomenclature dérivable ;
- données — bases relationnelles et cache, avec registre des connexions ;
- communication — relais courriel interne ;
- communication — service de courriel souverain (boîtes LDAP, SMTP, IMAP, antispam, DKIM) ; le relais des notifications système en est distinct (`client_smtp`) ;
- observabilité et supervision — métriques, journaux, tableaux de bord, supervision active ;
- applicatif — services internes (forge, etc.) et couche web.
@ -29,7 +29,7 @@ Propriétés visées, avec leurs nuances honnêtes :
- **Souverain / auto-suffisant** : c’est l’objectif. Aucune dépendance à un service externe pour la confiance, l’identité, le nom ou la communication. Cela justifie le choix de construire plutôt qu’assembler des SaaS.
- **Déclaratif et convergent** : l’état voulu est décrit dans des registres machine-lisibles (`instance/plan/nomenclature.yml`, `docs/dependances-groupes.yml`, `instance/plan/bases-donnees.yml`) et appliqué par les groupes Ansible. Ces registres sont la **source unique de vérité**.
- **Pas (encore) auto-réparé** : la convergence est pilotée par l’opérateur (GUI / CLI / `make`), pas une boucle fermée d’auto-remédiation.
- **Définition avant déploiement** : une grande partie est planifiée et validée (`--syntax-check`, `ansible-lint`) mais pas encore exécutée contre des VM réelles. Ne jamais présenter un rôle non déployé comme « en production ».
- **Éprouvé sur VM réelles — mais la nuance tient toujours.** Cette ligne a dit, jusqu’au 2026-09-06, que « une grande partie est planifiée et validée mais pas encore exécutée contre des VM réelles ». C’est faux depuis longtemps : la flotte a été **rasée et remontée depuis zéro** le 2026-08-13, puis **deux fois le 2026-09-02** (15/15 puis 14/14 hôtes, 0 échec, `make valider` à 0 échec sur 13 hôtes). Ce qui reste vrai, et qu’il faut garder : **un `--syntax-check` vert ne prouve rien de l’exécution**, et le tableau de maturité de `docs/catalogue-services.md` — pas ce fichier — dit ce qui est éprouvé et ce qui ne l’est pas. Ne jamais présenter comme « en production » un rôle que ce tableau ne donne pas pour tel.
Conséquence pour le travail : préserver la discipline qui tient l’ensemble — registres comme source unique, dépendances explicites, validation de chaque pièce, secrets hors dépôt. C’est ce qui empêche l’écosystème de devenir un objet ingérable. Le template Debian 13 reste la fondation (le moule des VM), pas la finalité.
@ -40,7 +40,9 @@ Conséquence pour le travail : préserver la discipline qui tient l’ensemble
`Set-OPS` se pilote par un **plan**, pas par l’édition directe de l’inventaire.
L’inventaire Ansible est **généré** depuis le plan.
**RÈGLE D’OR : `instance/inventories/production/hosts.yml` est un artefact GÉNÉRÉ. Ne jamais l’éditer à la main.** On édite le *plan*, puis on régénère.
**RÈGLE D’OR : `instance/inventories/<inventaire>/hosts.yml` est un artefact GÉNÉRÉ. Ne jamais l’éditer à la main.** On édite le *plan*, puis on régénère.
`<inventaire>` est une **place, pas un nom** : le moteur le résout (`principal`, sinon `production` — `scripts/inventory_rules.py`, `ORDRE_INVENTAIRE`). La flotte dit `principal`, le modèle public dit `production`. Ne coder ni l’un ni l’autre en dur dans un document.
- L’**application** est l’entité pivot ; le **groupe** Ansible n’est qu’une capacité (le rôle appliqué), plus une cible de liaison.
- Le plan vit dans des registres machine-lisibles : `instance/plan/serveurs.yml` (les VM), `instance/plan/applications.yml` (les services et leurs liens `requiert`/`utilise`/`expose`), `instance/plan/bases-donnees.yml`, `instance/plan/domaines.yml`, `instance/plan/nomenclature.yml`.
@ -51,7 +53,11 @@ L’inventaire Ansible est **généré** depuis le plan.
Référence complète : **`docs/plan-et-generation.md`**.
**Plan de contrôle gelé en périmètre** : le GUI, le générateur, l'IPAM et la modélisation sont volontairement *maison* et **souverains**, mais leur périmètre est gelé. Ne pas y ajouter de fonctionnalités de type NetBox/AWX (RBAC, historique d'audit, détection de conflits IPAM, API riche) : le besoin réel d'une de ces fonctions est le **signal d'adopter l'outil mûr correspondant** (NetBox pour la source de vérité, AWX pour l'exécution), pas de le réimplémenter. Décision et seuils : **`docs/positionnement.md`**.
**Plan de contrôle gelé en périmètre** : le GUI, le générateur et la modélisation sont volontairement *maison* et **souverains**, mais leur périmètre est gelé. Ne pas y ajouter de fonctionnalités de type NetBox/AWX — **historique d'audit applicatif, API riche, source de vérité partagée entre organisations** : le besoin réel d'une de ces fonctions est le **signal d'adopter l'outil mûr correspondant** (NetBox pour la source de vérité, AWX pour l'exécution), pas de le réimplémenter.
**Avant d'invoquer un seuil, vérifier qu'il n'est pas déjà couvert autrement (D-84).** Deux exemples cités ici jusqu'au 2026-09-08 ne tenaient plus : le **RBAC** est assuré par la séparation *cryptographique* des voûtes et des runners — l'adopter d'AWX serait régresser ; la **détection de conflits IPAM** est sans objet, rien ne s'alloue et cinq preuves (P20, P21, P23, P28, P33) tiennent déjà ce qu'un IPAM vérifierait. Une carte des seuils fausse ne fait pas perdre du temps : elle fait **franchir un seuil qui ne l'est pas**.
**Le gel porte sur les FONCTIONS, jamais sur les VUES.** Montrer à l'écran ce que le moteur sait déjà — l'écart d'un devis, l'état du diff, le périmètre sur lequel un ✅ a porté — ne franchit aucun seuil. Décision et seuils : **`docs/positionnement.md`**.
---
@ -160,9 +166,11 @@ Avant de proposer un changement comme terminé, vérifier au minimum la syntaxe
Exemple pour le template Debian 13 Proxmox :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
ansible-playbook -i "$SETOPS_INVENTAIRE" playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
```
`SETOPS_INVENTAIRE` est **exporté par le `Makefile`**, qui résout le nom de l’inventaire au lieu de le coder en dur (cf. la règle d’or ci-dessus) — la variable est donc déjà là dans toute recette `make`. Pour couvrir tous les playbooks d’un coup : `make syntaxe`. Depuis un shell nu, la variable n’existe pas : passer le chemin réel de l’inventaire de l’instance montée.
Pour un autre playbook, remplacer le chemin par le playbook concerné.
Ne jamais déclarer un playbook prêt si `--syntax-check` échoue.
@ -180,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
## Écrire, puis relire (D-68)
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
C'est aussi ce que font les 35 preuves de `make prouver` : elles lisent le dépôt, sans le
C'est aussi ce que font les 94 preuves de `make prouver` : elles lisent le dépôt, sans le
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
dépôt annonce.**
@ -207,6 +215,9 @@ make identite-plan make certificats-plan make expositions-plan
make postgresql-plan make courriel-plan
```
Le même patron existe **sous** les services, pour le monde physique — `make frontiere-plan`,
`proxmox-fw-plan`, `sdn-plan`, `underlay-plan`, `placement-plan`.
Ils ne modifient rien et sortent en code 1 s'il y a un écart. Après un changement qui
touche l'identité, les certificats, une exposition, la base ou le courriel, **lancer le
devis correspondant** : une tâche verte ne prouve pas que le service rend son service.
@ -276,36 +287,32 @@ Lorsqu’une commande shell est nécessaire, elle doit être encadrée avec les
## Structure générale du dépôt
Le dépôt peut contenir progressivement :
Ce que la racine porte réellement (mesuré le 2026-09-06) :
```text
inventories/
playbooks/
roles/
templates/
files/
scripts/
docs/
roles/ les rôles Ansible
playbooks/ les playbooks, classés par domaine
scripts/ le moteur de plan, la GUI, les devis, les preuves
filter_plugins/ les filtres Jinja du dépôt
docs/ wiki/ la documentation
exemples/ les modèles d'instance prêts à copier
instance/ SYMLINK vers le dépôt de l'instance active — jamais un vrai dossier ici
```
Les playbooks peuvent être classés par domaine :
**Il n'y a ni `inventories/`, ni `templates/`, ni `files/` à la racine**, et il ne doit pas y en avoir : les inventaires appartiennent à l'instance (derrière le symlink), les gabarits et fichiers appartiennent à leur rôle.
Les playbooks sont classés par domaine :
```text
playbooks/
├── groupes/
├── maintenance/
├── monitoring/
├── networking/
├── proxmox/
├── modeles_vm/
├── web/
├── database/
├── identity/
├── backup/
└── applications/
├── groupes/ un playbook par groupe opérationnel — la conformité passe par là
├── maintenance/ les devis et les manœuvres ponctuelles
├── modeles_vm/ la fabrication du gabarit doré
├── proxmox/ le clonage et le cycle de vie des VM
└── applications/ backup/ database/ monitoring/ web/ — vides, un README d'espace réservé
```
À ce jour, seuls `groupes/`, `maintenance/`, `modeles_vm/` et `proxmox/` sont réellement peuplés. Les autres domaines de cette liste sont **prospectifs** : ils n'apparaissent que lorsqu'un besoin réel les justifie (cf. la règle « ne pas créer de structure inutile » ci-dessous).
Seuls les quatre premiers sont peuplés. Les cinq autres ne contiennent qu'un README : ce sont des **espaces réservés**, et leur existence contredit à demi la règle « ne pas créer de structure inutile » ci-dessous — les laisser vides est un choix assumé, en créer d'autres ne l'est pas.
Les rôles peuvent être ajoutés progressivement selon les besoins.
@ -338,11 +345,12 @@ Ne pas multiplier les cibles `make` secondaires si elles ne correspondent pas à
Les cibles d'exploitation des VM doivent privilégier les groupes :
```text
make deployer HOTE=web-01
make deployer HOTE=web-frontal-01
make deployer-groupe GROUPE=serveur_debian
make hote-planifier HOTE=obs-01 VMID=94101 GROUPES="serveur_debian serveur_durci serveur_prometheus"
```
**`make hote-planifier`, `hote-ajouter` et `hote-groupes` sont DÉPRÉCIÉES** — elles refusent et sortent en 2. Elles éditaient l'inventaire à la main, ce que la RÈGLE D'OR interdit. Pour ajouter un hôte : le déclarer dans `instance/plan/serveurs.yml` (ou la vue **Serveurs** du GUI), puis `make instancier-appliquer`. Le VMID n'est plus saisi : il est **dérivé** (neuf chiffres, miroir de l'IP — `117602101`, et non le format à cinq chiffres d'avant).
Éviter les cibles parallèles qui réappliquent les mêmes rôles par couche, par exemple `make socle`, `make durcissement`, `make converger` ou `make deployer-vm`.
Toute cible `make` qui lance une action destructive ou risquée doit exiger une confirmation explicite.
@ -437,15 +445,20 @@ intégration cliente : raccorde les VM au service central
Exemples :
```text
PowerDNS serveur → clients DNS / resolver / enregistrements
step-ca serveur → confiance CA / ACME client
LDAP serveur → SSSD / NSS / PAM client
Keycloak serveur → intégrations OIDC applicatives
Prometheus → node_exporter sur les VM
Icinga2 → agent ou checks distants
PowerDNS serveur → hosts_statiques (plancher) + client_resolveur (opt-in)
step-ca serveur → client_pki (certificat + renouvellement + rechargement du service)
LDAP serveur → resoudre_annuaire, et les applications qui s'y lient (Postfix, Dovecot, Keycloak)
Keycloak serveur → intégrations OIDC applicatives, ou serveur_oauth2_proxy si l'app n'a pas d'OIDC
Prometheus → client_metrique (node_exporter) sur les VM
Icinga2 → SANS AGENT : contrôles actifs depuis le cœur, résultats passifs poussés par l'API
Grafana → datasources et dashboards côté plateforme
```
Deux précisions qui ont manqué ici longtemps, et qui changent la conception d'un nouveau service :
- **Pas de login LDAP au niveau du système.** `SSSD` / `NSS` / `PAM` ne sont pas la couche cliente de l'annuaire : le rôle `client_ldap` a été **retiré (2026-07-04), hors conception**. L'annuaire sert les *applications*, pas l'ouverture de session Unix.
- **Pas d'agent de supervision.** Icinga ne pose rien sur les hôtes. Un nœud qui doit rapporter le fait **lui-même**, en poussant son résultat à l'API — c'est ainsi que `client_backup` rapporte l'état de son propre dépôt. Ne pas prévoir de rôle « agent ».
Quand un nouveau service central est ajouté, prévoir aussi le ou les rôles clients nécessaires pour intégrer les VM existantes et futures.
Les dépendances entre groupes doivent être déclarées dans :
@ -475,11 +488,13 @@ Les variables de rôle doivent utiliser un préfixe correspondant au rôle.
Exemples :
```yaml
ssh_durcissement_port: 22
nftables_socle_enabled: false
fail2ban_ssh_enabled: true
ssh_hardening_port: 22 # rôle ssh_hardening
nftables_baseline_enabled: false # rôle nftables_baseline
fail2ban_ssh_enabled: true # rôle fail2ban_ssh
```
Le préfixe est **le nom du rôle, tel qu'il est**, pas sa traduction : ces trois lignes sont copiées des `defaults/main.yml` réels. (Elles disaient `ssh_durcissement_port` et `nftables_socle_enabled` jusqu'au 2026-09-06 — deux préfixes qui ne correspondaient à aucun rôle, dans l'exemple censé illustrer la règle.)
Séparer clairement :
```text
@ -532,8 +547,8 @@ Il peut contenir :
- compte technique `ansible` ;
- sudo NOPASSWD pour `ansible` lorsque requis ;
- `qemu-guest-agent` ;
- `cloud-init` ;
- `cloud-guest-utils` ;
- `cloud-init` — **au gabarit seulement** : retiré par `serveur_durci` une fois la VM née (D-85) ;
- `cloud-guest-utils` (`growpart`) — conservé : ni service, ni source de données ;
- chrony ;
- outils de diagnostic de base ;
- durcissement raisonnable ;
@ -596,7 +611,7 @@ Ne pas activer un pare-feu générique dans un template sans confirmation explic
Pour le template Debian 13, `nftables` peut être installé et préparé, mais rester désactivé par défaut.
L’activation du pare-feu doit être faite sur un clone ou sur un serveur final, avec des règles adaptées à son rôle.
L’activation du pare-feu doit être faite sur un clone ou sur un serveur final. **Ses règles ne s’écrivent pas à la main** : elles sont *dérivées* du registre des flux — chaque rôle déclare ce qu’il reçoit dans son `meta/flux.yml`, et `scripts/resoudre_flux.py` en produit le jeu de règles. Le même registre alimente le pare-feu de l’hyperviseur et la frontière OPNsense, ce qui leur interdit de se contredire. Voir `docs/flux-conception.md` et `docs/registre-flux.md`.
---
@ -621,6 +636,23 @@ Proxmox + cloud-init : identité initiale de la VM
Set-OPS + Ansible : configuration réelle du serveur
```
**Et cloud-init ne survit pas à cette première seconde** (D-85, 2026-09-09). Il se réveille
à *chaque* démarrage et relit le lecteur attaché par l'hyperviseur — lequel peut redéfinir
comptes, clés SSH, mots de passe et réseau. Sur une machine que le plan possède, c'est un
**second maître**, que le plan ne décrit pas. Le groupe `serveur_durci` le retire donc
(`cloud_init_retrait`), et le socle ne l'installe plus : le garder aux deux endroits
produisait un va-et-vient à chaque déploiement. **Le gabarit, lui, le garde** — sans lui un
clone n'a ni adresse ni nom. **P63** garde les trois moitiés.
**Ce que ce retrait ne ferme pas.** Il n'ôte **aucun pouvoir à l'hébergeur** :
`qemu-guest-agent` est au gabarit (il doit y être — P56), et l'API Proxmox expose sur son
dos `exec`, `file-write`, `set-user-password`, `shutdown` — strictement plus que le lecteur
cloud-init. Ce qui est fermé est étroit et réel : une réapplication *automatique, à chaque
démarrage*, depuis un support que le plan ne possède pas, et un interpréteur Python
complet exécuté en root au boot. La mainmise de l'hyperviseur sur ses invités est une
propriété de la virtualisation, pas de cloud-init — elle appelle sa propre décision, non
prise.
---
## Nettoyage avant template
@ -630,7 +662,7 @@ Le nettoyage final avant conversion en template doit être protégé par une con
Exemple :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
ansible-playbook -i "$SETOPS_INVENTAIRE" playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
```
Le nettoyage peut inclure :
@ -650,21 +682,22 @@ Ne jamais lancer ce nettoyage sur un serveur de production sans confirmation exp
Chaque modification significative doit être inscrite dans `CHANGELOG.md`.
Format recommandé :
**Le format a changé, et ce fichier disait encore l'ancien.** Les entrées d'avant le
2026-08-03 suivaient trois sections fixes (`### Ajouté` / `### Modifié` / `### Corrigé`).
Depuis, chaque entrée est un **récit** : ce qui était cassé, pourquoi personne ne le voyait,
ce que ça coûte de le réapprendre. Suivre la pratique en vigueur :
```markdown
## YYYY-MM-DD
## AAAA-MM-JJ — un titre qui dit CE QUI A ÉTÉ APPRIS, pas ce qui a été touché
### Ajouté
- ...
**N preuves.** Une ou deux phrases : l'état du harnais, et l'enjeu.
### Modifié
- ...
### Corrigé
- ...
### Un sous-titre par piège payé
Ce que le dépôt croyait, ce que la machine faisait, et la mesure qui a tranché.
```
Deux entrées le même jour se distinguent par un rang : `## 2026-09-02 (6) — …`.
Les corrections de rôles, handlers, playbooks et templates doivent être consignées.
---

14227
CHANGELOG.md

File diff suppressed because it is too large Load diff

992
Makefile

File diff suppressed because it is too large Load diff

View file

@ -22,8 +22,10 @@ git clone <url-de-Set-OPS> Set-OPS && cd Set-OPS
```
## 2. Choisir un modèle et créer ton instance
Le dépôt public fournit **un modèle générique : `socle`** — le socle souverain minimal
(DNS interne, AC/PKI, edge TLS, relais courriel) sur lequel on ajoute des modules. Les
Le dépôt public fournit **un modèle générique : `socle`** — quatre VM, le minimum
souverain : PKI (`step-ca`), DNS interne (PowerDNS), edge TLS (nginx) et **magasin
courriel** (Dovecot). C'est un magasin de boîtes, pas un relais : le MTA (`serveur_postfix`)
s'ajoute ensuite, comme les autres modules. Les
modèles **assemblés** par offre (`identite`, `observabilite`, `forge`, `collaboration`,
`presence-web`, `integral`) sont un actif à part, dans le dépôt privé `Set-OPS-modeles`
(cf. [`exemples/modeles/README.md`](exemples/modeles/README.md)).
@ -35,6 +37,11 @@ ln -s ../mon-instance instance # le moteur la trouve via ce lien
*(Alternative au symlink : `export SETOPS_INSTANCE=../mon-instance`.)*
## 3. Renseigner ton instance (« tes couleurs »)
> Les chemins ci-dessous disent `production/` parce que **c'est le nom que porte le modèle
> que tu viens de copier**. Ce n'est pas un nom imposé : le moteur cherche `principal`
> d'abord, `production` ensuite. Si tu lis ailleurs `<inventaire>`, c'est cette place-là.
- `instance/inventories/production/group_vars/all/10-intrants.yml` → **`domaine_interne`** (ex. `monorg.internal`) ;
- `instance/plan/nomenclature.yml` → ton **supernet** (ex. `10.20.0.0/16`) ;
- `instance/plan/domaines.yml` → ton **domaine public** ;
@ -47,14 +54,26 @@ Tu peux aussi le faire dans le GUI plus tard (`make inventaire-ui`).
make config # renseigne API host/user/port, nœud, stockage, VMID du template...
```
Chaque paramètre demandé est expliqué dans [`docs/config-proxmox.md`](docs/config-proxmox.md).
Tous tes secrets (token API + `vault_*`) vont dans **une voûte unique** :
Tous tes secrets (token API + `vault_*`) vont dans **une voûte unique par instance** :
`instance/inventories/production/group_vars/all/vault.yml`, à partir du
gabarit [`exemples/vault.exemple.yml`](exemples/vault.exemple.yml), chiffrée avec
`ansible-vault`. Exporte ton mot de passe Vault, ex. :
`ansible-vault`.
**Une voûte, une clé** (depuis le 2026-08-28). Le mot de passe de ta voûte se dépose dans un
fichier dont le nom est **dérivé du dossier de ton instance**, en minuscules :
```bash
export ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass
# instance dans ../mon-instance -> clé dans ~/.config/setops-vault-mon-instance
install -m 600 /dev/null ~/.config/setops-vault-mon-instance
$EDITOR ~/.config/setops-vault-mon-instance # ta phrase de passe, une seule ligne
python3 scripts/voutes.py etat # vérifie que le moteur la trouve
```
Il n'y a **rien à exporter** : le `Makefile` construit `ANSIBLE_VAULT_IDENTITY_LIST` en
appelant `scripts/voutes.py`. Un seul `ANSIBLE_VAULT_PASSWORD_FILE` ne suffirait plus de
toute façon — créer une VM ouvre **deux** voûtes dans la même exécution : la tienne, et
celle de l'hébergeur qui détient le jeton Proxmox.
## 5. Construire le golden template Debian 13 (UNE seule fois)
Une grappe vierge n'a aucun template. Crée une VM **Debian 13 vanille**, rends-la
joignable par Ansible, puis :
@ -63,8 +82,10 @@ make preparer-modele # socle + durcissement + cloud-init + qemu-guest-age
make verifier-modele
make nettoyer-modele CONFIRMER=true
```
Convertis ensuite la VM en **template Proxmox** nommé `modele-debian13` (le nom de
clone source par défaut). Détails et procédure : `docs/vm-lifecycle.md` et
Convertis ensuite la VM en **template Proxmox**. Son nom doit être celui que `make config`
a enregistré comme *nom logique du modèle* — **`modeleSetOPS`** par défaut
(`proxmox_clone_source_nom`). Un nom qui ne correspond pas se solde par un clonage qui ne
trouve pas sa source. Détails et procédure : `docs/vm-lifecycle.md` et
`docs/procedure-template-debian13-proxmox.md`.
## 6. Générer ton inventaire depuis le plan
@ -102,8 +123,8 @@ Les déploiements de groupe ne ciblent **que** les hôtes actifs.
make inventaire-verifier # registres + inventaire + garde-fous
make verifier # + ansible-lint + --syntax-check
```
> Ces cibles chargent l'inventaire complet : exporte d'abord ton mot de passe Vault
> (étape 4, `ANSIBLE_VAULT_PASSWORD_FILE`), sinon Ansible s'arrête sur
> Ces cibles chargent l'inventaire complet : pose d'abord la clé de ta voûte
> (étape 4), sinon Ansible s'arrête sur
> « Attempting to decrypt but no vault secrets found ».
---

View file

@ -6,7 +6,7 @@ Le dépôt est le **moteur** (générique, partageable). Chaque déploiement ré
## Par où entrer — selon ce que tu viens faire
On n'arrive pas avec un *sujet*, on arrive avec une **situation**. Il y en a cinq :
On n'arrive pas avec un *sujet*, on arrive avec une **situation**. Il y en a six :
| Ta situation | Ta porte |
|---|---|
@ -36,18 +36,18 @@ Piliers de l'écosystème :
- **confiance** — autorité de certification interne (ACME) ;
- **nommage et adressage** — DNS interne et nomenclature dérivable ;
- **données** — bases relationnelles et cache, avec registre des connexions ;
- **communication** — relais courriel interne ;
- **communication** — service de courriel souverain (boîtes LDAP, SMTP, IMAP, antispam, DKIM), et le relais des notifications système ;
- **observabilité et supervision** — métriques, journaux, tableaux de bord, supervision active ;
- **applicatif** — services internes (forge, etc.) et couche web.
L'état voulu est **déclaratif et convergent** (appliqué par les groupes Ansible), **souverain** par conception, mais **piloté par l'opérateur** (pas d'auto-remédiation : la boucle n'est pas fermée). Une grande partie est aujourd'hui *définie et validée* avant d'être déployée sur des VM réelles ; le template Debian 13 reste la fondation, pas la finalité.
L'état voulu est **déclaratif et convergent** (appliqué par les groupes Ansible), **souverain** par conception, mais **piloté par l'opérateur** (pas d'auto-remédiation : la boucle n'est pas fermée). Ce n'est pas resté sur le papier : la flotte a été **rasée et remontée depuis zéro** le 2026-08-13, puis deux fois le 2026-09-02, sans échec. Le degré de maturité service par service est dans `docs/catalogue-services.md`. Le template Debian 13 reste la fondation, pas la finalité.
Cadre et règles d'autorité : voir `AGENTS.md` (section « Mission et identité »).
## Le plan : on édite, l'inventaire se génère
`Set-OPS` se pilote par un **plan**, pas par l'édition directe de l'inventaire.
`instance/inventories/production/hosts.yml` est **généré** depuis le plan — **ne pas l'éditer à la main**.
`instance/inventories/<inventaire>/hosts.yml` est **généré** depuis le plan — **ne pas l'éditer à la main**.
```
éditer le PLAN → make instancier (revoir le diff) → make instancier-appliquer → make deployer
@ -80,16 +80,18 @@ playbooks/modeles_vm/debian13_proxmox_nettoyer.yml
## Principe
Le template contient seulement le socle commun.
Le template contient seulement le socle commun. Les services spécialisés sont installés
ensuite sur les clones, par les playbooks de groupes : edge nginx, PostgreSQL et Redis,
identité (OpenLDAP, Keycloak), courriel (Postfix, Dovecot, rspamd), observabilité
(Prometheus, Loki, Grafana), supervision (Icinga), forge (Forgejo), collaboration
(Nextcloud, Collabora), plateforme web. La liste qui fait foi est
[`docs/catalogue-services.md`](docs/catalogue-services.md).
Les services spécialisés seront installés ensuite sur les clones :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker/Podman ;
- monitoring complet ;
- applications métier.
> **Ni MariaDB, ni Docker, ni Podman.** Cette section les a listés jusqu'au 2026-09-06,
> par recopie de la liste de ce que le *template* ne doit pas contenir. Le dépôt n'a pas de
> rôle MariaDB, et **plus aucun rôle n'a besoin de Docker** depuis la réécriture native de
> `serveur_collabora` — qui en était la dernière exception. Le conteneur n'est pas un
> détail d'implémentation ici : c'est un choix fondateur (voir `roles/serveur_collabora/README.md`).
## Exploitation courante
@ -135,7 +137,7 @@ Planifier une VM passe désormais par le **plan**, pas par l'édition de l'inven
```bash
make instancier # génère + diff sémantique (que va-t-il changer ?)
make instancier-appliquer # régénère instance/inventories/production/hosts.yml
make instancier-appliquer # régénère instance/inventories/<inventaire>/hosts.yml
```
Les anciennes commandes `make hote-planifier` / `hote-ajouter` / `hote-groupes` éditaient l'inventaire **directement** ; elles sont **supplantées** par le plan (l'inventaire est généré, ne pas l'éditer à la main).

View file

@ -46,15 +46,19 @@ après avoir vérifié l'accès SSH par clé et les privilèges sudo du compte `
## Vérification
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_verifier.yml
make verifier-modele
```
## Nettoyage final avant conversion
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
make nettoyer-modele CONFIRMER=true
```
*(Ces deux blocs appelaient `ansible-playbook -i instance/inventories/lab/hosts.yml …`
jusqu'au 2026-09-06. Cet inventaire n'existe pas : le nom est résolu par le `Makefile`,
c'est précisément pourquoi les cibles `make` supplantent les commandes brutes.)*
Ensuite :
```bash

View file

@ -2,7 +2,28 @@
> **Pour qui :** l'**agent IA** qui reprend le dépôt — et le mainteneur qui relit ce qu'on lui dit.
## État du dépôt
> ## ⚠️ Document HISTORIQUE — relu le 2026-09-06
>
> Cette note était une **passation datée de juillet 2026**, écrite quand le chantier en
> cours était le gabarit Debian 13. Elle a continué d'être listée comme lecture de
> gouvernance longtemps après avoir cessé d'être vraie, et elle envoyait l'agent réparer un
> incident réglé, dans un rôle qui n'existe pas (`roles/ssh_durcissement/` — le rôle
> s'appelle `ssh_hardening`).
>
> **Ce qui tient toujours** : les fichiers de gouvernance à lire (ci-dessous), la règle de
> conduite finale (« lire l'existant, corriger petit, valider »), et la discipline des
> handlers — qui est désormais **prouvée** par **P04** et le harnais, plus seulement
> recommandée.
>
> **Ce qui ne tient plus** : « le chantier en cours est le template Debian 13 »,
> l'« incident récent » et son correctif. Les deux `handlers/main.yml` existent
> (`ssh_baseline`, `ssh_hardening`) depuis longtemps.
>
> **Où aller à la place** : `AGENTS.md` (autorité), `docs/carte-set-ops.md` (par où entrer
> pour modifier le moteur), `docs/catalogue-services.md` (ce qui est éprouvé et ce qui ne
> l'est pas).
## État du dépôt *(juillet 2026)*
`Set-OPS` est le moteur Ansible global d’exploitation d’écosystèmes numériques souverains.
@ -67,7 +88,12 @@ Il ne doit pas contenir :
---
## Incident récent à corriger
## Incident de juillet 2026 — RÉGLÉ, conservé pour la leçon
> Les deux `handlers/main.yml` existent. `roles/ssh_durcissement/` cité plus bas **n'a
> jamais existé sous ce nom** : le rôle s'appelle `ssh_hardening`. Ce qui reste utile ici,
> c'est la *classe* d'erreur — un `notify` sans handler local — et le fait qu'elle est
> maintenant tenue par une preuve plutôt que par la vigilance.
Le playbook :

View file

@ -0,0 +1,117 @@
# L'accès d'administration — un tunnel nominatif
> **Pour qui :** celui qui administre le site et ses écosystèmes, et celui qui se demande
> par où un humain entre, avec quelle clé, et ce qu'il peut atteindre une fois entré.
>
> Décidé le 2026-09-17, en réponse à une question : *le runner ne devrait-il pas être le
> rebond SSH des admins ?*
## Pourquoi pas le runner
Le runner du site détient **la clé de la voûte du site** et les clés SSH qui configurent
toutes les machines. En faire la porte des humains réunirait deux pouvoirs que tout le reste
du dépôt sépare :
- une session humaine compromise (un agent SSH transféré de trop) deviendrait le **plan de
contrôle** — pas seulement un rebond ;
- il **ne doit jamais entrer chez un locataire** (charte des responsabilités) ; en faire le
passage obligé des admins créerait ce chemin en fait ;
- il est **reconstructible par le code**, et c'est sa vertu. Un point d'entrée doit survivre à
la reconstruction de ce qu'il sert ;
- « qui est entré » et « qu'est-ce qui a été déployé » cesseraient de se raconter séparément.
## Ce qui a été construit
Un **second** tunnel WireGuard sur la frontière — l'instance `admins`, port 51821 — à côté du
tunnel site-à-site vers le site pair (instance `chezlePro`, port 51820), jamais mêlé à lui.
| | |
|---|---|
| déclaré | `SITE-<nom>/plan/10-intrants.yml`, clé `acces_admin_vpn` |
| réconcilié | `scripts/vpn_admin.py` (`make vpn-admin-plan`, `make vpn-admin-appliquer`) |
| réseau | `10.37.29.0/24`, la frontière en `.1` |
| un pair | **une personne ET un appareil** — révoquer l'appareil perdu ne coupe pas les autres |
| clés | seule la clé **publique** est au plan ; la privée ne quitte jamais l'appareil |
**Le réseau du tunnel est un réseau d'administration, et tout en dérive** : les pare-feux des
machines du site (`resoudre_flux`), le contrat vers les locataires (`site_intrants`, donc les
pare-feux de leurs machines) et les règles de la frontière (`devis_opnsense`). Rien à recopier
— une liste recopiée prend toujours du retard sur celle qu'elle suit.
## Ajouter un appareil
```bash
python3 scripts/vpn_admin.py pair-nouveau --nom prenom-appareil # tire la paire de clés
# coller le bloc `pairs:` rendu dans SITE-<nom>/plan/10-intrants.yml
make vpn-admin-plan # lire ce qui changerait
CONFIRMER=true make vpn-admin-appliquer # poser le pair
python3 scripts/vpn_admin.py config --nom prenom-appareil # la config de l'appareil
make flux && make deployer-groupe GROUPE=serveur_durci # les pare-feux des machines
```
La clé privée ne s'affiche **qu'une fois**, au moment où elle est tirée : elle appartient à
l'appareil. Perdue, on en tire une autre ; l'ancienne se révoque par `etat: absent`.
## Retirer un accès
`etat: absent` sur le pair, puis `CONFIRMER=true make vpn-admin-appliquer`. Le script retire
aussi tout pair **attaché à notre instance que le plan ne déclare plus** : un accès qui
survivrait à la décision de le retirer est exactement ce qu'on ne veut pas.
## Deux trous que seul l'usage a montrés (2026-09-17, une heure après la pose)
- **Le SSH des machines du site.** Le socle déclare son 22 en `pair: [flotte, externe]`, jamais
`admin` : la source dérivée est le site lui-même. L'exploitant y arrivait **par rebond sur la
frontière** — la connexion partait alors du boîtier, que pf laisse sortir sans règle. Par le
tunnel, il arrive comme une source extérieure, et plus rien ne l'autorisait. Les locataires,
eux, marchaient : leur règle SSH naît de `nftables_admin_ssh`, qui contient déjà le tunnel.
- **La frontière elle-même.** Aucun flux ne la désignait comme destination d'administration :
monter le tunnel faisait perdre sa console et son API — donc le moyen même de réparer la
règle manquante. *La panne se referme sur celui qui la répare* : il a fallu remettre
l'adresse d'avant pour poser le correctif.
Les deux règles sont désormais émises par `devis_opnsense` dès que `acces_admin_vpn` est
déclaré (`ports_frontiere`, par défaut 22 et 443). Une règle **par port** : OPNsense refuse
« 22,443 » dans un champ de port, et aucun flux jusque-là n'en portait deux.
## Un tunnel par locataire (2026-09-17)
Chaque écosystème a le sien, et **c'est lui qui décide qui entre chez lui**. Le site porte la
route, jamais la liste des gens — même partage que les zones DNS publiques.
| | |
|---|---|
| déclaré par | le **locataire**, dans `plan/acces.yml` (registre `acces_admin_vpn`) |
| réseau | `10.<index>.29.0/24` — **dérivé** de son index, hors de ses six zones (.16 à .21) |
| port | `52000 + index` — dérivé, donc sans collision : P21 garde déjà l'unicité des index |
| instance | `wg<index>` — un rang (2, 3, 4…) renommerait les interfaces le jour où un écosystème part |
| ce qu'il atteint | **son supernet, et rien d'autre** : SSH et consoles web |
**L'isolement est gardé, pas promis.** `devis_opnsense.verifier` refuse toute règle dont la
source est le tunnel d'un locataire et la destination autre chose que **son** supernet —
sinon, un écosystème obtiendrait un accès chez un voisin en ajoutant un pair dans son propre
plan. La garde est exécutée par P24, avant toute écriture. Mise en défaut vérifiée.
**Le pare-feu de ses machines suit tout seul** : `resoudre_flux` ajoute le réseau du tunnel aux
sources d'administration **dérivées de son plan**, et seulement s'il déclare au moins un pair
actif — déclarer un réseau que personne n'emprunte ouvrirait le SSH à un tunnel qui n'existe
pas.
```bash
python3 scripts/vpn_admin.py pair-nouveau --nom prenom-appareil --locataire OPS-<X>
# coller le bloc rendu dans OPS-<X>/plan/acces.yml
make vpn-admin-plan && CONFIRMER=true make vpn-admin-appliquer
python3 scripts/vpn_admin.py config --nom prenom-appareil --locataire OPS-<X>
```
## Ce que le tunnel donne, et ce qu'il ne donne pas
`AllowedIPs` est **dérivé** de la carte : zones du site, fabric, lien de transit et supernets
des locataires. Ce que la frontière laisse ensuite passer reste décidé par les flux déclarés —
SSH partout, et les consoles d'administration (Grafana, Icinga Web, la forge, le runner).
**Le seul port ouvert sur l'Internet par cette voie est le 51821/udp**, vers l'adresse publique
de la frontière. Tout le reste voyage dans le tunnel.
**Ce qui n'est pas fait** : l'enregistrement des sessions (un rebond dédié le permettrait, pas
un tunnel), et la double authentification — la possession de l'appareil fait foi.

View file

@ -3,7 +3,7 @@
> **Pour qui :** le **mainteneur** — le survol du modèle. À lire avant `plan-et-generation.md`.
Set-OPS définit et construit l'écosystème numérique souverain. Il se
pilote par un **plan** : l'inventaire Ansible (`instance/inventories/production/hosts.yml`)
pilote par un **plan** : l'inventaire Ansible (`instance/inventories/<inventaire>/hosts.yml`)
est **généré** depuis le plan, pas édité à la main.
- **Par où commencer + catalogue des mécanismes transverses : `docs/carte-set-ops.md`.**
@ -36,7 +36,7 @@ Les valeurs communes aux rôles doivent rester dans les defaults des rôles quan
Les groupes opérationnels de l'inventaire doivent correspondre à un playbook homonyme :
```text
instance/inventories/production/hosts.yml
instance/inventories/<inventaire>/hosts.yml
serveur_debian
playbooks/groupes/serveur_debian.yml
@ -60,9 +60,11 @@ Le cycle de vie des VM est documenté dans :
docs/vm-lifecycle.md
```
## Intégrations futures
## Intégrations transversales
Les intégrations transversales des VM sont documentées dans :
Elles ne sont plus « futures » : `client_pki`, `client_backup`, `client_metrique`,
`client_journal`, `client_smtp`, `client_resolveur` et `client_artefacts` sont déployés sur
la flotte. Elles sont documentées dans :
```text
docs/integrations-vm.md

View file

@ -26,18 +26,37 @@ pré-commit). Une preuve **SAUTÉE** (⚪) n'est pas un échec.
### Prérequis Vault
La preuve `P15` (inventaire Ansible complet, `ansible-inventory --list`) déchiffre le
`group_vars` de l'instance. Sans `ANSIBLE_VAULT_PASSWORD_FILE`, elle est **automatiquement
sautée** (⚪) avec la mention du prérequis — le reste du harnais reste vert, car les
validateurs Python lisent le plan et l'inventaire directement, sans secret. Pour l'inclure :
La preuve **`P16`** (inventaire Ansible complet, `ansible-inventory --list`) déchiffre le
`group_vars` de l'instance. Sans la clé de la voûte, elle est **automatiquement sautée** (⚪)
avec la mention du prérequis — le reste du harnais reste vert, car les validateurs Python
lisent le plan et l'inventaire directement, sans secret.
Pour l'inclure, il suffit que la clé de l'instance soit en place ; il n'y a **rien à
exporter** (une voûte, une clé — `scripts/voutes.py` la trouve par convention de nommage) :
```bash
export ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass
python3 scripts/voutes.py etat # la clé de cette instance est-elle là ?
make prouver
```
*(Ce paragraphe désignait `P15` et un `ANSIBLE_VAULT_PASSWORD_FILE` unique jusqu'au
2026-09-06 : ni l'un ni l'autre n'était juste.)*
## Ce que couvre `make prouver`
> **Ce tableau est un EXTRAIT, pas l'inventaire.** Il s'arrête à `P23` et le dépôt porte
> **57 preuves** (`P01`–`P57`, sans trou). La liste complète et à jour est produite par le
> harnais lui-même, jamais recopiée :
>
> ```bash
> make prouver # écrit docs/audit/preuve-<date>.md : chaque preuve, son verdict
> grep -oE '"id": "P[0-9]+", "titre": "[^"]+"' scripts/prouver.py # la source
> ```
>
> On garde l'extrait parce qu'il **explique** les premières preuves, celles qui fondent le
> reste. On ne le complète pas : un tableau de 57 lignes recopié à la main aurait dérivé
> avant d'être fini — c'est exactement ce qui est arrivé à celui-ci.
| # | Preuve | Ce qu'elle établit |
|---|---|---|
| P01 | Lint (`ansible-lint`) | 0 violation, profil `production`. |

View file

@ -10,7 +10,7 @@ Ce plan est le **pendant manuel** de `make prouver` : là où le harnais prouve
le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (cf.
`protocole-operateur-independant.md`).
**85 gestes** sur **21 unités** · **19** en « casse-répare »
**90 gestes** sur **22 unités** · **20** en « casse-répare »
· **5** doublés d'un garde-fou machine (colonne *Preuve auto*).
> **Honnêteté de couverture.** La colonne *Preuve auto* n'est remplie que lorsqu'une
@ -27,7 +27,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | Observe le claim | Dans Keycloak (console admin) → Clients → grafana → *Client scopes* → *Evaluate* pour testmail : le jeton contient "roles": ["grafana-editor"]. C'est ② en vrai. | 👁 observe | — |
| 4 | Casse & répare | Retire grafana-editor de testmail (ou renomme-le en grafana-viewer), reconnecte : Explore disparaît. Remets-le : il revient. Tu *sens* que c'est le rôle, pas l'identité, qui ouvre la porte. | 🔨 casse-répare | — |
*Source : [Autorisation & RBAC](Autorisation-et-RBAC) · § À toi de jouer.*
*Source : [Autorisation & RBAC](../../wiki/Autorisation-et-RBAC.md) · § À toi de jouer.*
## Bases de données
@ -38,7 +38,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | Suis une liaison | Ouvre plan/bases-donnees.yml : l'entrée keycloak (serveur, base, propriétaire, secret) est *exactement* ce que le rôle serveur_keycloak va lire pour se connecter. | 👁 observe | — |
| 4 | Casse & répare | Change le mot de passe d'un compte dans PostgreSQL (garde l'ancien !) sans mettre à jour la voûte : l'app ne se connecte plus. Restaure : ça repart. Tu *sens* que la connexion = *identité + secret cohérents des deux côtés*. | 🔨 casse-répare | — |
*Source : [Bases de données](Bases-de-données) · § À toi de jouer.*
*Source : [Bases de données](../../wiki/Bases-de-donn%C3%A9es.md) · § À toi de jouer.*
## Cache
@ -49,17 +49,17 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | Vois la borne | : redis-cli -a … CONFIG GET maxmemory et … maxmemory-policy (LRU). | 👁 observe | — |
| 4 | Casse & répare | Interroge sans mot de passe : redis-cli GET essai:1 → NOAUTH (refusé). Puis vide le cache (FLUSHALL) : rien ne casse dans l'écosystème — la vraie donnée est en base. Tu *sens* qu'un cache est jetable. | 🔨 casse-répare | — |
*Source : [Cache](Cache) · § À toi de jouer.*
*Source : [Cache](../../wiki/Cache.md) · § À toi de jouer.*
## Courriel (SMTP / IMAP)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Envoie via la soumission `:587` | (client authentifié) : « commande » 235 Authentication successful puis 250 queued = ① en action. | 👁 observe | — |
| 1 | Envoie via la soumission `:587` | (client authentifié) : « commande » *(MDP_ESSAI se saisit à la main — read -rs MDP_ESSAI. Un mot de passe écrit ici partirait dans l'historique du shell et dans le dépôt public : ce document en portait un en clair jusqu'au 2026-09-06.)*… | 👁 observe | — |
| 2 | Lis la boîte | (le MDA) : doveadm search -u testmail mailbox INBOX all \| wc -l sur infra-mail-01. | 👁 observe | — |
| 3 | Casse & répare | Coupe l'annuaire (arrête OpenLDAP), renvoie un courriel : Postfix **rejette le destinataire** (il ne peut plus valider en LDAP). Rallume OpenLDAP : ça repart. Tu *sens* la dépendance requise MTA → annuaire. | 🔨 casse-répare | — |
*Source : [Courriel (SMTP / IMAP)](Courriel) · § À toi de jouer.*
*Source : [Courriel (SMTP / IMAP)](../../wiki/Courriel.md) · § À toi de jouer.*
## DNS & résolution de noms
@ -68,20 +68,32 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 1 | Le plancher, sans DNS | Sur un nœud : « commande » | 👁 observe | — |
| 2 | Interroge l'autoritatif | Demande à PowerDNS directement : « commande » | 👁 observe | — |
| 3 | Vois les couches | Compare getent hosts (plancher) et dig (DNS) : deux chemins, même IP. | 👁 observe | — |
| 4 | Casse & répare | Sur un nœud sans client_unbound, vide /etc/hosts de ses entrées chezlepro (garde une sauvegarde !) et coupe l'accès au DNS : la résolution interne échoue. Restaure /etc/hosts : ça remarche sans DNS. Tu viens de *sentir* pourquoi le planc… | 🔨 casse-répare | — |
| 4 | Casse & répare | Vide /etc/hosts de ses entrées chezlepro (garde une sauvegarde !) et pointe /etc/resolv.conf ailleurs : la résolution interne échoue. Restaure /etc/hosts seul : ça remarche sans DNS. Tu viens de *sentir* pourquoi le plancher est le filet… | 🔨 casse-répare | — |
| 5 | Le piège du récursif | Demande un nom qui n'existe pas sous internal., puis redemande un nom qui existe. Si le résolveur répond NXDOMAIN aux deux, tu viens de reproduire la panne de deux jours du 2026-09-02 : la racine étant signée, elle *prouve* que internal.… | 👁 observe | — |
*Source : [DNS & résolution de noms](DNS-et-résolution) · § À toi de jouer.*
*Source : [DNS & résolution de noms](../../wiki/DNS-et-r%C3%A9solution.md) · § À toi de jouer.*
## Filiation, signatures et témoins
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | — | make genome — quatre dépôts ? Lequel n'a pas de *remote* ? Celui-là n'existe qu'ici. | 👁 observe | — |
| 2 | — | git verify-tag v2026.08.21 — que se passe-t-il si tu retires ta ligne de .git-allowed-signers ? (remets-la ensuite) | 👁 observe | — |
| 3 | — | make genome-inscrire, puis ouvre parente.yml. Dans un an, qu'est-ce que ce fichier te dira que ta mémoire ne dira plus ? | 👁 observe | — |
| 4 | — | Demande-toi où sont les témoins aujourd'hui. Combien de copies vivantes du moteur existent, sur combien de machines distinctes ? C'est la vraie mesure de la résistance de la lignée — pas la longueur des clés. Voir aussi : Multi-instance… | 👁 observe | — |
*Source : [Filiation, signatures et témoins](../../wiki/Filiation-signatures-et-t%C3%A9moins.md) · § À toi de jouer.*
## Identité & SSO
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Vis le SSO | Ouvre https://grafana.lab.chezlepro.internal → « *Se connecter avec Chezlepro* » → testmail. Puis ouvre Forgejo, puis Icinga : tu n'es reconnecté nulle part. | 👁 observe | — |
| 1 | Vis le SSO | Ouvre https://observatoire.chezlepro.internal → « *Se connecter avec Chezlepro* » → testmail. Puis ouvre Forgejo, puis Icinga : tu n'es reconnecté nulle part. | 👁 observe | — |
| 2 | Observe le flux | Rouvre Grafana en navigation privée, ouvre les outils dév (F12 → Réseau) : repère la redirection vers Keycloak, puis le retour avec un code=. C'est ① en action. | 👁 observe | — |
| 3 | Interroge l'annuaire (la source de vérité) | Sur un nœud avec ldap-utils : « commande » Tu vois l'entrée que Keycloak fédère — il ne l'a pas recopiée. | 👁 observe | — |
| 4 | Casse & répare (la fédération) | Dans la console admin Keycloak → *User Federation* → désactive le fournisseur LDAP. Reconnecte-toi : échec (l'IdP ne voit plus l'annuaire). Réactive : ça remarche. Tu viens de *sentir* la dépendance requise entre l'IdP et l'annuaire. | 🔨 casse-répare | — |
*Source : [Identité & SSO](Identité-et-SSO) · § À toi de jouer.*
*Source : [Identité & SSO](../../wiki/Identit%C3%A9-et-SSO.md) · § À toi de jouer.*
## Infra as Code & idempotence
@ -92,7 +104,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | La réversibilité | git diff / git checkout sur le plan : l'état est du code, donc annulable. | 👁 observe | — |
| 4 | Casse & répare | Modifie à la main un fichier géré par un rôle (ex. un .conf), puis redéploie : Ansible rétablit l'état voulu (le code gagne sur la dérive manuelle). Tu *sens* que la source de vérité, c'est le code. | 🔨 casse-répare | — |
*Source : [Infra as Code & idempotence](Infra-as-Code-et-idempotence) · § À toi de jouer.*
*Source : [Infra as Code & idempotence](../../wiki/Infra-as-Code-et-idempotence.md) · § À toi de jouer.*
## La preuve — prouver, pas affirmer
@ -104,7 +116,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 4 | Lis le registre | Ouvre docs/audit/affirmations.md : trouve une affirmation ⚪ (*non prouvable localement*) — vois comment elle est assumée comme intention, jamais présentée comme prouvée. | 👁 observe | — |
| 5 | Comprends la valeur | Demande-toi : *quelle promesse est-ce que je fais sans preuve ?* C'est exactement ce que ce registre force à regarder en face. | 👁 observe | — |
*Source : [La preuve — prouver, pas affirmer](La-preuve) · § À toi de jouer.*
*Source : [La preuve — prouver, pas affirmer](../../wiki/La-preuve.md) · § À toi de jouer.*
## Le GUI (console d'exploitation)
@ -117,7 +129,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 5 | Sens le garde-fou | Essaie de sauvegarder un plan incohérent (ex. une base dont le consommateur n'existe pas) : la console refuse avec une raison. L'invalide ne passe pas. | 👁 observe | — |
| 6 | Casse & répare | Édite hosts.yml à la main, reviens dans la GUI, « Appliquer le plan » : ta modification est écrasée par le plan. La source de vérité, c'est le plan — pas l'inventaire. | 🔨 casse-répare | — |
*Source : [Le GUI (console d'exploitation)](Le-GUI-console-d-exploitation) · § À toi de jouer.*
*Source : [Le GUI (console d'exploitation)](../../wiki/Le-GUI-console-d-exploitation.md) · § À toi de jouer.*
## Le plan & l'adressage dérivé
@ -129,7 +141,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 4 | Casse & répare | Édite hosts.yml à la main (change une IP). Relance make instancier : il signale l'écart. Ré-applique : le plan écrase ta modification. Tu *sens* que hosts.yml n'est pas la vérité — le plan l'est. | 🔨 casse-répare | — |
| 5 | Éprouve le garde-fou | Ajoute une ligne supernet: 10.99.0.0/16 dans une nomenclature, puis make prouver : P20 échoue (« adressage stocké »). Retire-la : vert. La règle se *prouve*. | 👁 observe | **P20** |
*Source : [Le plan & l'adressage dérivé](Le-plan-et-l-adressage-dérivé) · § À toi de jouer.*
*Source : [Le plan & l'adressage dérivé](../../wiki/Le-plan-et-l-adressage-d%C3%A9riv%C3%A9.md) · § À toi de jouer.*
## Le réseau des tenants — du câble au VRF
@ -138,9 +150,9 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 1 | Lis ton réseau physique | : make underlay. Repère les VLAN sous 1000 (l'underlay) et les MTU. Pourquoi le transport doit-il être à 1500 quand l'overlay est à 1450 ? | 👁 observe | — |
| 2 | Regarde sans écrire | : make sdn-plan. Si le cluster dit déjà ce que le plan dit, la sortie tient en une ligne. | 👁 observe | — |
| 3 | Trouve la sortie d'un tenant | : dans make devis-sdn, repère la strophe FRR et l'adresse du prochain saut. À quel équipement appartient-elle ? | 👁 observe | — |
| 4 | Change `index` dans un modèle | (jamais en production) et régénère : combien de valeurs ont bougé ? C'est la mesure exacte de ce que la dérivation t'épargne. Pour aller plus loin : docs/sdn-evpn.md (référence technique), Le plan & l'adressage dérivé, Multi-instance & f… | 👁 observe | — |
| 4 | Change `index` dans un modèle | (jamais en production) et régénère : combien de valeurs ont bougé ? C'est la mesure exacte de ce que la dérivation t'épargne. Pour aller plus loin : docs/sdn-evpn.md dans le dépôt (référence technique), Le plan & l'adressage dérivé, Mult… | 👁 observe | — |
*Source : [Le réseau des tenants — du câble au VRF](Le-réseau-des-tenants) · § À toi de jouer.*
*Source : [Le réseau des tenants — du câble au VRF](../../wiki/Le-r%C3%A9seau-des-tenants.md) · § À toi de jouer.*
## Liaisons (bindings)
@ -148,10 +160,10 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
|---|---|---|---|---|
| 1 | Lis une liaison | Ouvre instance/plan/applications.yml : une app avec expose: (liaison *app→domaine*), et serveurs.yml : un nœud avec integrations: (liaison *nœud→service*). | 👁 observe | — |
| 2 | Vois-la se résoudre | Après make instancier, regarde l'inventaire généré : la cible est devenue une valeur concrète (FQDN, groupe) — le moteur a câblé. | 👁 observe | — |
| 3 | Requise vs optionnelle | Compare : retirer client_journal d'un nœud → aucun problème (optionnelle). Déclarer une base sans serveur → make instancier/la validation échoue (requise). Tu *sens* la différence de modalité. | 👁 observe | — |
| 3 | Requise, optionnelle, universelle | Compare les trois : retirer client_backup d'un nœud sans état → aucun problème (optionnelle). Déclarer une base sans serveur → make instancier échoue (requise). Essayer de recopier client_metrique dans serveurs.yml → le plan refuse (univ… | 👁 observe | — |
| 4 | Casse & répare | Casse une liaison requise (ex. réfère une base à un serveur inexistant), relance l'instanciation : échec clair *avant* tout déploiement. Corrige : ça passe. Le moteur attrape le câblage manquant à ta place. | 🔨 casse-répare | — |
*Source : [Liaisons (bindings)](Liaisons-bindings) · § À toi de jouer.*
*Source : [Liaisons (bindings)](../../wiki/Liaisons-bindings.md) · § À toi de jouer.*
## Multi-instance & fédération
@ -164,7 +176,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 5 | Casse & répare | Donne à deux instances fédérées le même index (édite une nomenclature), make instances : la bannière de collision s'allume ; make prouver : P21 échoue. Corrige l'index : tout redevient vert. | 🔨 casse-répare | **P21** |
| 6 | (Avancé) Promeus un produit | Une instance qui *tourne et se prouve* peut devenir un modèle vendable : make model-creer MODE=instance SOURCE=OPS-… NOM=… — elle est généralisée (identité → exemple.*, secrets retirés) et validée. | 👁 observe | — |
*Source : [Multi-instance & fédération](Multi-instance-et-fédération) · § À toi de jouer.*
*Source : [Multi-instance & fédération](../../wiki/Multi-instance-et-f%C3%A9d%C3%A9ration.md) · § À toi de jouer.*
## Métriques & journaux
@ -172,10 +184,10 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
|---|---|---|---|---|
| 1 | Interroge les métriques | (sur obs-01) — combien de nœuds scrapés, tous UP ? « commande » | 👁 observe | — |
| 2 | Vois les journaux | : curl -s http://localhost:3100/loki/api/v1/label/host/values → les nœuds qui expédient leurs logs. | 👁 observe | — |
| 3 | Ouvre Grafana | (https://grafana.lab.chezlepro.internal) — métriques *et* logs au même endroit. | 👁 observe | — |
| 3 | Ouvre Grafana | (https://observatoire.chezlepro.internal) — métriques *et* logs au même endroit. | 👁 observe | — |
| 4 | Casse & répare | Arrête prometheus-node-exporter sur un nœud : dans Prometheus, sa cible passe up=0 (DOWN). Redémarre : elle repasse UP. Tu *sens* que c'est l'agent qui nourrit le serveur (modèle pull). | 🔨 casse-répare | — |
*Source : [Métriques & journaux](Métriques-et-journaux) · § À toi de jouer.*
*Source : [Métriques & journaux](../../wiki/M%C3%A9triques-et-journaux.md) · § À toi de jouer.*
## PKI & confiance
@ -186,38 +198,38 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | Vois-le servir en vrai | Le LDAPS d'OpenLDAP utilise ce certificat : « commande » 0 (ok) = confiance vérifiée. | 👁 observe | — |
| 4 | Casse & répare | Retire la racine du magasin système, refais un curl HTTPS interne : avertissement de certificat (plus de confiance). Réinstalle la racine : ça remarche. Tu viens de *sentir* pourquoi « faire confiance à la racine » est la clé de voûte. | 🔨 casse-répare | — |
*Source : [PKI & confiance](PKI-et-confiance) · § À toi de jouer.*
*Source : [PKI & confiance](../../wiki/PKI-et-confiance.md) · § À toi de jouer.*
## Reverse-proxy & TLS
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Route par nom | Deux noms, un seul edge (192.168.15.21) : « commande » Change grafana en forge : même IP, backend différent. C'est le routage par SNI. | 👁 observe | — |
| 2 | Vois la terminaison TLS | Le certificat présenté est celui de l'edge (avec les SAN des exposés) : openssl s_client -connect 192.168.15.21:443 -servername grafana.lab… \| openssl x509 -noout -text \| grep -A1 'Subject Alternative'. | 👁 observe | — |
| 1 | Route par nom | Deux noms, un seul edge (10.17.16.11) : « commande » Change grafana en forge : même IP, backend différent. C'est le routage par SNI. | 👁 observe | — |
| 2 | Vois la terminaison TLS | Le certificat présenté est celui de l'edge (avec les SAN des exposés) : openssl s_client -connect 10.17.16.11:443 -servername observatoire.chezlepro.internal \| openssl x509 -noout -text \| grep -A1 'Subject Alternative'. | 👁 observe | — |
| 3 | Casse & répare | Arrête le backend (ex. systemctl stop grafana-server sur obs-01) et rouvre Grafana : l'edge répond 502 Bad Gateway (le proxy est là, le service non). Redémarre : ça remarche. Tu distingues le proxy de ce qu'il sert. | 🔨 casse-répare | — |
*Source : [Reverse-proxy & TLS](Reverse-proxy-et-TLS) · § À toi de jouer.*
*Source : [Reverse-proxy & TLS](../../wiki/Reverse-proxy-et-TLS.md) · § À toi de jouer.*
## Sauvegardes (3-2-1)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Lance une sauvegarde | Sur un nœud avec client_backup : « commande » | 👁 observe | — |
| 2 | Liste les instantanés | (le dépôt vit hors-nœud) : « commande » | 👁 observe | — |
| 2 | Liste les instantanés | (le dépôt vit hors-nœud, et hors de l'écosystème) : « commande » *Le même dépôt est interrogé chaque nuit par setops-verifier-mon-depot.sh, qui rapporte à Icinga : c'est le nœud, seul détenteur de la clé, qui juge.* | 👁 observe | — |
| 3 | Restaure — le vrai test | Restaure dans un dossier temporaire et compare : « commande » *(sur infra-pki-01 ; ailleurs, compare le dump correspondant.)* | 👁 observe | — |
| 4 | Casse & répare | Supprime un fichier de donnée (une copie de test !), restaure-le depuis l'instantané, vérifie qu'il est identique. Tu viens de *sentir* que la valeur d'une sauvegarde est la restauration, pas la sauvegarde. | 🔨 casse-répare | — |
*Source : [Sauvegardes (3-2-1)](Sauvegardes) · § À toi de jouer.*
*Source : [Sauvegardes (3-2-1)](../../wiki/Sauvegardes.md) · § À toi de jouer.*
## Supervision & impact
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Ouvre Icinga Web 2 | (https://icinga.lab.chezlepro.internal, via le SSO) : la liste des hôtes et services supervisés, avec leur état (vert/jaune/rouge). | 👁 observe | — |
| 1 | Ouvre Icinga Web 2 | (https://vigie.chezlepro.internal, via le SSO) : la liste des hôtes et services supervisés, avec leur état (vert/jaune/rouge). | 👁 observe | — |
| 2 | Vois l'impact | Menu *Business Processes* → « Supervision Chezlepro » : un processus qui agrège des checks (load, procs, ping…) en un état roulé. C'est l'impact, pas une case. | 👁 observe | — |
| 3 | Casse & répare | Provoque l'échec d'un check (ex. arrête un service surveillé) : l'état passe CRITICAL, et le processus BPM qui en dépend rougit (l'impact remonte). Répare : tout reverdit. Tu *sens* la différence entre *mesurer* et *superviser/alerter*. | 🔨 casse-répare | — |
*Source : [Supervision & impact](Supervision-et-impact) · § À toi de jouer.*
*Source : [Supervision & impact](../../wiki/Supervision-et-impact.md) · § À toi de jouer.*
## Sécurité & durcissement
@ -227,7 +239,7 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 2 | Moindre privilège | : PermitRootLogin est à no, l'accès se fait par le compte ansible + clé SSH. Vérifie : sshd -T \| grep -E 'permitrootlogin\|passwordauthentication'. | 👁 observe | — |
| 3 | Casse & répare (avec prudence, en lab) | Assouplis un réglage sysctl, observe, puis remets-le. Tu *sens* que chaque ligne de durcissement ferme une porte précise. | 🔨 casse-répare | — |
*Source : [Sécurité & durcissement](Sécurité-et-durcissement) · § À toi de jouer.*
*Source : [Sécurité & durcissement](../../wiki/S%C3%A9curit%C3%A9-et-durcissement.md) · § À toi de jouer.*
## Virtualisation & clonage
@ -238,14 +250,14 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
| 3 | Sépare les deux couches | cloud-init a posé *l'identité* ; tout le reste (paquets, services, durcissement) vient d'Ansible. Le template, lui, ne contient aucune donnée de clone. | 👁 observe | — |
| 4 | Casse & répare (mentalement + lab) | Supprime un nœud non critique et reclone-le depuis le template, puis redéploie : il revient à l'identique. Tu *sens* que la machine est reconstructible. | 🔨 casse-répare | — |
*Source : [Virtualisation & clonage](Virtualisation-et-clonage) · § À toi de jouer.*
*Source : [Virtualisation & clonage](../../wiki/Virtualisation-et-clonage.md) · § À toi de jouer.*
## Vérifier le déployé — quand la preuve statique ne suffit plus
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | — | Lance les cinq devis sur ta flotte. Note le temps que ça prend : quelques minutes pour ce qui demandait une journée d'enquête à la main. | 👁 observe | — |
| 1 | — | Lance les dix devis sur ta flotte — les cinq de service, puis les cinq d'infrastructure. Note le temps que ça prend : quelques minutes pour ce qui demandait une journée d'enquête à la main. | 👁 observe | — |
| 2 | Casse quelque chose exprès | — arrête un service publié, change un port — et relance le devis concerné. S'il ne dit rien, c'est *lui* qu'il faut réparer, pas le service. | 👁 observe | — |
| 3 | — | Cherche, dans ton propre outillage, une vérification qui n'a jamais échoué. Demande-toi si c'est parce que tout va bien, ou parce qu'elle ne regarde rien. \| Terme \| Ce que tu retiens \| \| \| \| \| preuve statique \| lit le code ; rapide, univ… | 👁 observe | — |
*Source : [Vérifier le déployé — quand la preuve statique ne suffit plus](Vérifier-le-déployé) · § À toi de jouer.*
*Source : [Vérifier le déployé — quand la preuve statique ne suffit plus](../../wiki/V%C3%A9rifier-le-d%C3%A9ploy%C3%A9.md) · § À toi de jouer.*

View file

@ -0,0 +1,78 @@
# Preuve de conformite — Set-OPS — 2026-08-23
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (42 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 33 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 31 rôles, 82 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 24 secret(s) exige(s), tous presents. Voute reelle : 25 cle(s), aucun manque. |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 7 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 52 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 48 groupe(s), 77 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 33 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 25 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 14, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 47 scripts expliques et atteignables, 98 cibles make documentees, 57 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 34 exigence(s) de role, toutes satisfaites (118 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 41 document(s) declarent leur lecteur (21 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 32 role(s) serveur/client tous nommes, 33 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 43 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-23._

View file

@ -0,0 +1,78 @@
# Preuve de conformite — Set-OPS — 2026-08-24
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (42 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 36 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 34 rôles, 87 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 35 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. Voute reelle : 25 cle(s), aucun manque. |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 7 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 68 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,10.29.19.41/32,192.168.254.2/32,192.168.255.2/32. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 80 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 28 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 17, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 49 scripts expliques et atteignables, 102 cibles make documentees, 60 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (125 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (22 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 35 role(s) serveur/client tous nommes, 36 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 45 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-24._

View file

@ -0,0 +1,83 @@
# Preuve de conformite — Set-OPS — 2026-08-25
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/production/hosts.yml`
- **Verdict** : ✅ CONFORME (46 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 36 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 34 rôles, 92 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_oauth2_proxy |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 18 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 14 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 79 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 28 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 17, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 49 scripts expliques et atteignables, 102 cibles make documentees, 60 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (16 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (23 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 0 application(s) exigeant une base l'ont toutes (0 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 2 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 35 role(s) serveur/client tous nommes, 36 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 45 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-25._

View file

@ -0,0 +1,85 @@
# Preuve de conformite — Set-OPS — 2026-08-26
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/production/hosts.yml`
- **Verdict** : ✅ CONFORME (48 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 37 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 35 rôles, 92 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_oauth2_proxy |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 18 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 14 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 79 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 50 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (16 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (24 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 0 application(s) exigeant une base l'ont toutes (0 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 2 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 36 role(s) serveur/client tous nommes, 37 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 46 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (110 lignes). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-26._

View file

@ -0,0 +1,88 @@
# Preuve de conformite — Set-OPS — 2026-08-27
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/production/hosts.yml`
- **Verdict** : ✅ CONFORME (52 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 37 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 35 rôles, 92 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_oauth2_proxy |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 5 hotes, 14 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 18 secret(s) exige(s), tous presents. Voute reelle : 21 cle(s), aucun manque. |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 14 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 79 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 51 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (16 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (25 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 0 application(s) exigeant une base l'ont toutes (0 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 2 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 36 role(s) serveur/client tous nommes, 37 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 47 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (110 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 121 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-27._

View file

@ -0,0 +1,90 @@
# Preuve de conformite — Set-OPS — 2026-08-28
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (54 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 38 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 36 rôles, 95 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 30 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 19, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 53 scripts expliques et atteignables, 106 cibles make documentees, 62 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (26 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 37 role(s) serveur/client tous nommes, 38 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 49 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 60 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (113 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 125 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-28._

View file

@ -0,0 +1,91 @@
# Preuve de conformite — Set-OPS — 2026-08-30
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (55 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 39 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 37 rôles, 97 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 31 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 20, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 53 scripts expliques et atteignables, 106 cibles make documentees, 63 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (27 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 38 role(s) serveur/client tous nommes, 39 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 49 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 66 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (115 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 131 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-30._

View file

@ -0,0 +1,91 @@
# Preuve de conformite — Set-OPS — 2026-08-31
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (55 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 39 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 37 rôles, 97 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 11 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 31 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 20, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 53 scripts expliques et atteignables, 106 cibles make documentees, 63 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (28 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 38 role(s) serveur/client tous nommes, 39 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 49 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 66 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (115 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 131 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-08-31._

View file

@ -0,0 +1,92 @@
# Preuve de conformite — Set-OPS — 2026-09-01
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (56 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 98 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 12 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 54 scripts expliques et atteignables, 108 cibles make documentees, 64 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (29 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 50 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 6 machine(s) du plan retrouvees, 80 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (116 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 145 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-01._

View file

@ -0,0 +1,92 @@
# Preuve de conformite — Set-OPS — 2026-09-02
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ❌ NON CONFORME (55 OK · 1 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 98 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 51 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 54 scripts expliques et atteignables, 108 cibles make documentees, 64 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (30 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ❌ ECHEC | 1 hote(s) detiennent de l'etat sans sauvegarde : site : site-mon-01 detient ['serveur_postgresql'] — ajouter `client_backup` a leurs `integrations` dans `plan/s |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 50 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 101 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (116 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 166 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-02._

View file

@ -0,0 +1,93 @@
# Preuve de conformite — Set-OPS — 2026-09-03
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ❌ NON CONFORME (55 OK · 1 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 35 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 55 scripts expliques et atteignables, 109 cibles make documentees, 65 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (31 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 51 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 104 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ❌ ECHEC | La carte d'orientation ne dit plus vrai :
- « pieces d'audit » : la carte annonce 33, le depot en compte 34 |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-03._

View file

@ -0,0 +1,95 @@
# Preuve de conformite — Set-OPS — 2026-09-05
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ❌ NON CONFORME (56 OK · 1 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 35 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 57 scripts expliques et atteignables, 114 cibles make documentees, 65 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 43 document(s) declarent leur lecteur (32 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 53 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 104 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ❌ ECHEC | Des comptes ecrits en prose ne disent plus vrai :
- docs/autorisation.md:13 annonce 29 roles, le depot en compte 65
- wiki/Vérifier-le-déployé.md:30 annonce |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-05._

View file

@ -0,0 +1,93 @@
# Preuve de conformite — Set-OPS — 2026-09-06
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (56 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 57 scripts expliques et atteignables, 114 cibles make documentees, 65 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 43 document(s) declarent leur lecteur (33 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 53 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 104 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 87 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (57 preuves, 65 roles, 40 groupes). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-06._

View file

@ -0,0 +1,98 @@
# Preuve de conformite — Set-OPS — 2026-09-08
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (61 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 40 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 58 scripts expliques et atteignables, 115 cibles make documentees, 65 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 43 document(s) declarent leur lecteur (34 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 54 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 104 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (62 preuves, 65 roles, 40 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `2887b57` (publie le 2026-09-08). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-08._

View file

@ -0,0 +1,100 @@
# Preuve de conformite — Set-OPS — 2026-09-09
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (64 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 103 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 36 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 82 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 58 scripts expliques et atteignables, 115 cibles make documentees, 67 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 39 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (35 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 40 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 54 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 118 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (121 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 183 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (64 preuves, 67 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `2d9dc86` (publie le 2026-09-09). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 1 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_pki/certificat. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-09._

View file

@ -0,0 +1,103 @@
# Preuve de conformite — Set-OPS — 2026-09-10
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (66 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 107 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 87 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 59 scripts expliques et atteignables, 116 cibles make documentees, 67 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (137 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (36 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 40 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 55 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 155 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (125 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 217 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (67 preuves, 67 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `d1ae41c` (publie le 2026-09-10). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 24 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-10._

View file

@ -0,0 +1,104 @@
# Preuve de conformite — Set-OPS — 2026-09-11
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (67 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 107 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 87 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 60 scripts expliques et atteignables, 117 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (138 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (37 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 56 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 151 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (125 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 219 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (68 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `989398f` (publie le 2026-09-11). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 26 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-11._

View file

@ -0,0 +1,106 @@
# Preuve de conformite — Set-OPS — 2026-09-12
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (69 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 109 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 87 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 4 pool(s) Proxmox, 41 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 62 scripts expliques et atteignables, 119 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (139 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (38 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 58 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (127 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 239 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (70 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `904ece3` (publie le 2026-09-12). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 26 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-12._

View file

@ -0,0 +1,111 @@
# Preuve de conformite — Set-OPS — 2026-09-13
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (74 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 109 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 29 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 51 groupe(s), 90 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 65 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 45 document(s) declarent leur lecteur (39 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 61 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (127 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 240 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 14 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (75 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `904ece3` (publie le 2026-09-12). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 26 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 6 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-13._

View file

@ -0,0 +1,114 @@
# Preuve de conformite — Set-OPS — 2026-09-14
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (77 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 111 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 30 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 52 groupe(s), 96 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 43 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 67 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 36 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 36 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (40 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 63 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 8 machine(s) du plan retrouvees, 178 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 92 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (129 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 268 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (78 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `392da4d` (publie le 2026-09-14). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 40 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 3 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (5 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 11 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 6 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 155 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-14._

View file

@ -0,0 +1,115 @@
# Preuve de conformite — Set-OPS — 2026-09-15
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (78 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 111 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 31 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 52 groupe(s), 96 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 43 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 67 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 36 exigence(s) de role, toutes satisfaites (144 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 36 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (41 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 63 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 8 machine(s) du plan retrouvees, 182 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 92 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (129 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 272 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (79 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `392da4d` (publie le 2026-09-14). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 40 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 13 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 6 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 155 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 6 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0, SITE-Chezlepro) : pattes, edges, certificats, rechargemen |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-15._

View file

@ -0,0 +1,118 @@
# Preuve de conformite — Set-OPS — 2026-09-16
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (82 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 119 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 13 hotes, 33 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 33 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 56 groupe(s), 104 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 70 scripts expliques et atteignables, 131 cibles make documentees, 69 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (147 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 42 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 48 document(s) declarent leur lecteur (42 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 66 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 192 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 97 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (137 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 282 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (82 preuves, 69 roles, 42 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `392da4d` (publie le 2026-09-14). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 52 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:8, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 41 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 13 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 9 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 163 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 6 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0, SITE-Chezlepro) : pattes, edges, certificats, rechargemen |
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des symlinks, source d'inventaire `instance`, et 14 route(s) POST exigent toutes un pouvoir. |
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-16._

View file

@ -0,0 +1,118 @@
# Preuve de conformite — Set-OPS — 2026-09-17
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (82 OK · 0 echec · 0 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 120 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 13 hotes, 33 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 33 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 56 groupe(s), 104 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 72 scripts expliques et atteignables, 133 cibles make documentees, 69 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (147 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 42 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 49 document(s) declarent leur lecteur (43 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 68 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 200 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 101 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (138 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 308 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (82 preuves, 69 roles, 42 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `392da4d` (publie le 2026-09-14). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 55 champ(s) sur 7 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:8, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 41 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 13 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 9 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 165 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 6 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0, SITE-Chezlepro) : pattes, edges, certificats, rechargemen |
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des symlinks, source d'inventaire `instance`, et 14 route(s) POST exigent toutes un pouvoir. |
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-17._

View file

@ -0,0 +1,120 @@
# Preuve de conformite — Set-OPS — 2026-09-30
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ❌ NON CONFORME (81 OK · 1 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 121 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 32 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) et 7 zone(s) de site : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 40 groupe(s), 108 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 77 scripts expliques et atteignables, 146 cibles make documentees, 69 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 44 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 49 document(s) declarent leur lecteur (44 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 7 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (8 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 97 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 73 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 202 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 101 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (139 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 309 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (83 preuves, 69 roles, 42 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `3fe7e3c` (publie le 2026-09-29). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 56 champ(s) sur 7 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:9, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 47 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, client_s |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (7 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 14 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 10 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 185 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ❌ ECHEC | 1 repli(s) silencieux — une derivation vide rend un succes :
- flux : OPS-Chezlepro-lab — perimees par rapport au plan : ops-01.nft |
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des voutes portees, source d'inventaire `instance/inventories/principal/hosts.yml`, et 15 route(s) POST exigent toutes un pouvoir. |
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
| P83 | Assistants : le registre des runbooks ne prend pas de retard sur le Makefile | — | ✅ OK | 17 runbooks, 139 etapes, 145 cibles documentees : chacune portee par un assistant ou exemptee avec son motif. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-09-30._

View file

@ -0,0 +1,119 @@
# Preuve de conformite — Set-OPS — 2026-10-01
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (82 OK · 0 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 121 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 32 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) et 7 zone(s) de site : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 40 groupe(s), 108 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 77 scripts expliques et atteignables, 146 cibles make documentees, 69 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 44 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 49 document(s) declarent leur lecteur (45 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 7 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (8 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 97 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 73 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 202 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 101 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (139 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 309 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere : WAN muet, interfa |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (83 preuves, 69 roles, 42 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `3fe7e3c` (publie le 2026-09-29). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 56 champ(s) sur 7 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:9, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 47 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, client_s |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (7 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 14 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 10 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 185 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 5 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, SITE-Chezlepro) : pattes, edges, certificats, rechargements, jumeaux d' |
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des voutes portees, source d'inventaire `instance/inventories/principal/hosts.yml`, et 15 route(s) POST exigent toutes un pouvoir. |
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
| P83 | Assistants : le registre des runbooks ne prend pas de retard sur le Makefile | — | ✅ OK | 17 runbooks, 139 etapes, 145 cibles documentees : chacune portee par un assistant ou exemptee avec son motif. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-10-01._

View file

@ -0,0 +1,120 @@
# Preuve de conformite — Set-OPS — 2026-10-03
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
> [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ❌ NON CONFORME (81 OK · 1 echec · 1 saute)
## Preuves
| # | Preuve | Affirmations | Statut | Detail |
|---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 121 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 32 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) et 7 zone(s) de site : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. |
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 14 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 40 groupe(s), 108 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 77 scripts expliques et atteignables, 146 cibles make documentees, 69 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 44 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 49 document(s) declarent leur lecteur (46 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 7 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (8 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 97 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 73 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 202 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ❌ ECHEC | La carte d'orientation ne dit plus vrai :
- « pieces d'audit » : la carte annonce 50, le depot en compte 51 |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (139 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 309 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere : WAN muet, interfa |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (83 preuves, 69 roles, 42 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `3fe7e3c` (publie le 2026-09-29). |
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 56 champ(s) sur 7 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:9, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 47 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, client_s |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (7 exposition(s)). |
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 14 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 10 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 185 gabarits de role : tous se rendent. |
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 5 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, SITE-Chezlepro) : pattes, edges, certificats, rechargements, jumeaux d' |
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des voutes portees, source d'inventaire `instance/inventories/principal/hosts.yml`, et 15 route(s) POST exigent toutes un pouvoir. |
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
| P83 | Assistants : le registre des runbooks ne prend pas de retard sur le Makefile | — | ✅ OK | 17 runbooks, 139 etapes, 145 cibles documentees : chacune portee par un assistant ou exemptee avec son motif. |
## Couverture des affirmations ✅ du registre
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
elles restent hors du harnais recurrent (rien d'executable a rejouer).
## Declarations d'intention (⚪ invérifiables localement — assumees)
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
comme declarations d'intention, non comme preuves :
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
contre une flotte vivante.
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
_Rapport genere le 2026-10-03._

View file

@ -94,7 +94,7 @@ Binaires, vérifiables, définis **avant** le début. On ne les renégocie pas e
| # | Critère | Vérification |
|---|---|---|
| **R1** | Instance créée depuis `socle`, plan renseigné à ses valeurs | `make inventaire-verifier` → rc=0 |
| **R2** | Golden template Debian 13 construit et converti en template Proxmox | `make verifier-modele` OK ; template `modele-debian13` visible dans Proxmox |
| **R2** | Golden template Debian 13 construit et converti en template Proxmox | `make verifier-modele` OK ; template visible dans Proxmox **sous le nom que `make config` a enregistré** (`proxmox_clone_source_nom`, `modeleSetOPS` par défaut) |
| **R3** | Au moins une VM créée depuis le plan | `make creer-vm HOTE=…` OK ; VM jointe par Ansible (`ansible -m ping`) |
| **R4** | Cet hôte déployé selon ses groupes | `make deployer HOTE=…` → rc=0 |
| **R5** | Validation globale du dépôt passée par l'opérateur | `make verifier` → rc=0 |

546
docs/audit/schema-plan.json Normal file
View file

@ -0,0 +1,546 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Registres du plan Set-OPS",
"description": "GENERE par scripts/schema_plan.py (make schema). Ne pas editer a la main. Decrit la FORME des registres ; la COHERENCE reste aux validateurs de inventory_rules.py.",
"registres": {
"serveurs": {
"title": "Serveurs (VM)",
"x-fichier": "plan/serveurs.yml",
"x-racine": "serveurs",
"entite": {
"type": "object",
"properties": {
"fonction": {
"type": "string",
"title": "Fonction",
"description": "Determine VMID, IP, VLAN et passerelle. Doit exister dans la nomenclature.",
"x-source-valeurs": "nomenclature.fonctions"
},
"etat": {
"type": "string",
"title": "État",
"description": "`planifie` = decrit mais pas deploye ; `actif` = joignable par Ansible.",
"enum": [
"actif",
"planifie"
]
},
"integrations": {
"type": "array",
"title": "Intégrations facultatives",
"description": "Seulement les facultatives. Les universelles viennent du role et sont refusees ici.",
"items": {
"type": "string"
},
"x-editeur": "matrice"
},
"noeud": {
"type": "string",
"title": "Nœud Proxmox",
"description": "Surcharge le defaut de `make config`.",
"x-defaut-intrant": "proxmox_clone_noeud",
"x-source-valeurs": "intrants.proxmox_noeuds"
},
"stockage": {
"type": "string",
"title": "Stockage",
"description": "Surcharge le defaut de `make config`.",
"x-defaut-intrant": "proxmox_clone_stockage",
"x-source-valeurs": "intrants.proxmox_stockages"
},
"disque": {
"type": "string",
"title": "Disque",
"description": "Ex. `32G`. Vide = derive des empreintes des roles.",
"x-defaut-derive": "disque_taille"
},
"memoire": {
"type": "integer",
"title": "Mémoire (Mo)",
"description": "Vide = derive des empreintes des roles.",
"x-defaut-derive": "memoire"
},
"coeurs": {
"type": "integer",
"title": "Cœurs",
"description": "Vide = derive des empreintes des roles.",
"x-defaut-derive": "coeurs"
}
},
"additionalProperties": false,
"required": [
"fonction"
]
},
"x-clef": {
"title": "Nom d'hôte",
"description": "Le nom court de la VM. Le rang final derive l'adresse."
}
},
"applications": {
"title": "Applications",
"x-fichier": "plan/applications.yml",
"x-racine": "applications",
"entite": {
"type": "object",
"properties": {
"groupe": {
"type": "string",
"title": "Groupe (rôle)",
"description": "La capacite appliquee. Doit avoir un playbook homonyme.",
"x-source-valeurs": "groupes_operationnels"
},
"hote": {
"type": "string",
"title": "Hôte",
"description": "Une VM declaree au plan. Un hote inconnu est un « hote fantome » (P06).",
"x-source-valeurs": "serveurs"
},
"port": {
"type": "integer",
"title": "Port"
},
"expose": {
"type": "array",
"title": "Exposition publique",
"description": "FQDN publies par l'edge. Derivent le vhost, les SAN et le plancher /etc/hosts.",
"items": {
"type": "string"
}
},
"requiert": {
"type": "array",
"title": "Requiert",
"description": "Dependance applicative. Indicative : l'ordre de deploiement vient des couches.",
"items": {
"type": "string"
}
},
"liens": {
"type": "array",
"title": "Liens (bindings)",
"description": "Roles acceptes par le role porteur (meta/liens.yml).",
"items": {
"type": "object",
"properties": {
"vers": {
"type": "string",
"title": "Vers",
"description": "L'application liee.",
"x-source-valeurs": "applications"
},
"role": {
"type": "string",
"title": "Rôle",
"description": "Le role du lien, accepte par meta/liens.yml."
}
},
"additionalProperties": false,
"required": [
"role",
"vers"
]
},
"x-editeur": "liens"
},
"websocket": {
"type": "boolean",
"title": "WebSocket",
"description": "L'edge doit relayer la mise a niveau de connexion."
}
},
"additionalProperties": false,
"required": [
"groupe",
"hote"
]
},
"x-clef": {
"title": "Identifiant",
"description": "Nom de l'application dans le plan."
}
},
"bases_donnees": {
"title": "Bases de donnees",
"x-fichier": "plan/bases-donnees.yml",
"x-racine": "bases_donnees",
"entite": {
"type": "object",
"properties": {
"serveur": {
"type": "string",
"title": "Serveur de BD",
"x-source-valeurs": "serveurs_bd"
},
"base": {
"type": "string",
"title": "Base"
},
"proprietaire": {
"type": "string",
"title": "Propriétaire"
},
"secret": {
"type": "string",
"title": "Secret (Vault)",
"description": "NOM d'une variable Vault, jamais une valeur. Le secret ne quitte pas le role."
},
"consommateur": {
"type": "string",
"title": "Consommateur",
"description": "Qui utilise cette base. La liste depend de la portee.",
"x-source-selon": {
"champ": "portee",
"cas": {
"hote": "serveurs",
"application": "applications"
},
"defaut": "groupes_operationnels"
}
},
"portee": {
"type": "string",
"title": "Portée",
"description": "Comment le consommateur est designe : par application, par groupe ou par hote.",
"enum": [
"application",
"groupe",
"hote"
],
"default": "groupe"
},
"usage": {
"type": "string",
"title": "Usage",
"default": "principale"
}
},
"additionalProperties": false,
"required": [
"base",
"consommateur",
"proprietaire",
"secret",
"serveur"
]
},
"x-clef": {
"title": "Identifiant",
"description": "Nom de l'entree au registre, ex. `bd_forgejo`."
}
},
"serveurs_bd": {
"title": "Serveurs de bases de donnees",
"x-fichier": "plan/bases-donnees.yml",
"x-racine": "serveurs_bd",
"entite": {
"type": "object",
"properties": {
"type": {
"type": "string",
"title": "Moteur"
},
"hote": {
"type": "string",
"title": "Hôte",
"x-source-valeurs": "serveurs"
},
"port": {
"type": "integer",
"title": "Port"
},
"groupe": {
"type": "string",
"title": "Groupe",
"x-source-valeurs": "groupes_operationnels"
}
},
"additionalProperties": false,
"required": [
"hote",
"type"
]
},
"x-clef": {
"title": "Nom",
"description": "Nom du serveur de bases, ex. `pg-principal`."
}
},
"domaines_publics": {
"title": "Domaines publics",
"x-fichier": "plan/domaines.yml",
"x-racine": "domaines_publics",
"entite": {
"type": "object",
"properties": {
"autorite": {
"type": "string",
"title": "Autorité DNS",
"enum": [
"auto-heberge",
"delegue",
"primaire-cache"
]
},
"edge": {
"type": "string",
"title": "Edge",
"description": "Le GROUPE Ansible qui sert cette zone (ex. serveur_nginx).",
"x-source-valeurs": "groupes_edge"
},
"resolution_interne": {
"type": "string",
"title": "Resolution interne",
"description": "`service` : le plancher et la zone menent au service lui-meme, l'edge ne sert qu'aux personnes.",
"enum": [
"edge",
"service"
]
},
"secondaires": {
"type": "array",
"title": "Secondaires",
"description": "Serveurs DNS secondaires de la zone.",
"items": {
"type": "string"
}
},
"dnssec": {
"type": "boolean",
"title": "DNSSEC"
},
"exposition": {
"type": "array",
"title": "Expositions declarees",
"description": "FQDN publies pour cette zone, vers un groupe cible.",
"items": {
"type": "object",
"properties": {
"nom": {
"type": "string",
"title": "Nom",
"description": "Sous-domaine, ou `@` pour la zone elle-meme."
},
"cible": {
"type": "string",
"title": "Cible",
"description": "Le groupe interne qui sert ce nom.",
"x-source-valeurs": "groupes_operationnels"
},
"type": {
"type": "string",
"title": "Type",
"default": "web"
}
},
"additionalProperties": false,
"required": [
"cible",
"nom"
]
}
},
"enregistrements": {
"type": "array",
"title": "Enregistrements publics",
"description": "MX, SPF, DMARC, DKIM, CAA et noms historiques de la zone.",
"items": {
"type": "object",
"properties": {
"nom": {
"type": "string",
"title": "Nom",
"description": "Relatif a la zone : `@`, `mx`, `_dmarc`."
},
"type": {
"type": "string",
"title": "Type",
"enum": [
"A",
"AAAA",
"CAA",
"CNAME",
"MX",
"TXT"
]
},
"valeur": {
"type": "string",
"title": "Valeur",
"description": "Adresse publique, nom d'hote complet ou texte (sans guillemets)."
},
"priorite": {
"type": "integer",
"title": "Priorite",
"description": "Requise pour un MX."
},
"etiquette": {
"type": "string",
"title": "Etiquette CAA",
"enum": [
"iodef",
"issue",
"issuewild"
]
},
"ttl": {
"type": "integer",
"title": "TTL",
"description": "Secondes (60 a 604800) ; defaut de la zone sinon."
}
},
"additionalProperties": false,
"required": [
"nom",
"type",
"valeur"
]
}
}
},
"additionalProperties": false,
"required": [
"autorite",
"edge"
]
},
"x-clef": {
"title": "Domaine",
"description": "Le nom public, ex. `chezlepro.ca`."
}
},
"acces_admin_vpn": {
"title": "Acces d'administration (WireGuard)",
"x-fichier": "plan/acces.yml",
"x-racine": "acces_admin_vpn",
"entite": {
"type": "object",
"properties": {
"cle_publique": {
"type": "string",
"title": "Cle publique",
"description": "Cle WireGuard PUBLIQUE de l'appareil (44 caracteres). La privee ne quitte jamais l'appareil."
},
"adresse": {
"type": "string",
"title": "Adresse dans le tunnel",
"description": "Un /32 du reseau derive `10.<index>.29.0/24`."
},
"etat": {
"type": "string",
"title": "Etat",
"description": "`absent` revoque l'acces au prochain passage.",
"enum": [
"present",
"absent"
],
"default": "present"
}
},
"additionalProperties": false,
"required": [
"adresse",
"cle_publique"
]
},
"x-clef": {
"title": "Pair",
"description": "`personne-appareil`, ex. `daniel-portable` — un pair par appareil, pour revoquer l'un sans l'autre."
}
},
"nomenclature": {
"title": "Nomenclature",
"x-fichier": "plan/nomenclature.yml",
"x-racine": null,
"entite": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"title": "Index (seed)",
"description": "LE seul champ d'adressage. Tout en derive ; P20 refuse d'en stocker un autre.",
"minimum": 0,
"maximum": 255
},
"cidr_hote": {
"type": "integer",
"title": "CIDR d'hôte",
"description": "Masque des sous-reseaux de zone. 24 = 254 hotes par zone."
},
"categories": {
"type": "object",
"title": "Catégories (zones)",
"description": "Une zone de securite par cle. Le numero derive le 3e octet et le VLAN.",
"additionalProperties": {
"type": "object",
"properties": {
"libelle": {
"type": "string",
"title": "Libellé",
"description": "Nom lisible de la zone (Frontiere, Identite...)."
}
},
"additionalProperties": false,
"required": [
"libelle"
]
}
},
"fonctions": {
"type": "object",
"title": "Fonctions",
"description": "categorie + service par fonction. VMID et IP en derivent.",
"additionalProperties": {
"type": "object",
"properties": {
"categorie": {
"type": "integer",
"title": "Catégorie",
"description": "La zone. Fixe le 3e octet (15 + categorie) et le VLAN.",
"x-source-valeurs": "nomenclature.categories"
},
"service": {
"type": "integer",
"title": "Service",
"description": "Fixe le bloc d'adresses de l'hote dans la zone."
}
},
"additionalProperties": false,
"required": [
"categorie",
"service"
]
}
},
"reservations": {
"type": "object",
"title": "Réservations",
"description": "Adresses soustraites a la derivation dans la zone.",
"properties": {
"passerelle": {
"type": "integer",
"title": "Passerelle",
"description": "Dernier octet de la passerelle. P23 refuse un SVI qui s'en ecarte."
},
"reserve_min": {
"type": "integer",
"title": "Réserve (min)",
"description": "Premier octet soustrait a la derivation."
},
"reserve_max": {
"type": "integer",
"title": "Réserve (max)",
"description": "Dernier octet soustrait a la derivation."
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"required": [
"index"
]
}
}
}
}

View file

@ -0,0 +1,5 @@
---
# Ecrit par `make wiki-publier`, lu par la preuve P60. Ne pas editer a la main.
remote: ssh://git@eregion.chezlepro.ca:2222/Alliance-Boreale/Set-OPS-Public.wiki.git
source: e9215a2
date: 2026-10-07

View file

@ -98,13 +98,23 @@ Une directive qu'aucune garde ne vérifie finit par ne plus être vraie. Chaque
| `socle-identite` | **est** la chaîne d'identité, ne peut pas se déléguer à elle-même | keycloak, openldap |
| `ldap-direct` | protocole non-OIDC lié à LDAP | dovecot, postfix |
| `interne-sans-auth` | interface joignable **dans** le tenant sans authentification, non exposée | prometheus, loki |
| `sans-auth-humaine` | aucun point d'authentification humaine | 12 |
| `sans-auth-humaine` | aucun point d'authentification humaine | 23 |
La preuve refuse **l'oubli et le mensonge** : un rôle sans déclaration, une portée
inventée, un `web-sso` sans accès de secours ou sans posture de formulaire, un `web-sso
natif` sans réglage `<rôle>_connexion_locale` **défini dans `defaults`**, et une
déclaration que le code contredit.
**Et depuis le 2026-09-07, sa sœur `P58` garde les habilitations** — ce que P29 ne fait
pas : elle tient les *positions* (qui s'authentifie comment), pas les *droits* (qui obtient
quoi). Voir [`autorisation.md`](autorisation.md) §5.
**Depuis le 2026-09-06, elle refuse aussi que ce tableau mente.** Les chiffres et les listes
de la troisième colonne sont confrontés aux déclarations réelles. Ils avaient cessé d'être
vrais sans que rien ne le signale : la ligne `sans-auth-humaine` annonçait 12 rôles, il y en
avait 21 — la preuve lisait les déclarations depuis le début, mais ne regardait pas ce que
le document en disait.
> **Les indices doivent être nommés.** Une première version cherchait les mots « ldap » et
> « oidc » dans le rôle. Le mot *LDAP*, présent dans un commentaire de `serveur_grafana`,
> suffisait alors à valider une déclaration `ldap-direct` mensongère. La preuve exige

View file

@ -16,6 +16,12 @@ Toute personne qui s'authentifie obtient donc le défaut du service — tout, ou
aucun humain n'a de compte : `ou=people` est vide aussi. L'écosystème est déployé et
personne ne peut y entrer autrement que par les comptes de secours en voûte.
> **Révisé le 2026-09-05 — le constat ci-dessus est daté, et il a bougé.** `ou=groups`
> n'est plus un conteneur que personne ne lit : `amorcage_acces`, `serveur_keycloak` et
> `serveur_icingaweb2` s'en servent. La mesure du 2026-08-07 est conservée telle quelle
> parce qu'elle explique *pourquoi* ce document existe — mais elle ne décrit plus l'état
> du dépôt. Le §2 et la suite, eux, restent la doctrine en vigueur.
## 2. Deux régimes, et la frontière entre eux
C'est la décision principale, et elle **contredit délibérément la doctrine du dépôt**.
@ -170,10 +176,22 @@ ansible-vault view instance/inventories/principal/group_vars/all/vault.yml \
opération sauf le changement lui-même (§3). C'est le premier geste de la reprise, et il
n'est pas optionnel.
> Le mot de passe de la voûte est lu depuis `ANSIBLE_VAULT_PASSWORD_FILE`
> (`~/.config/setops-vault-pass` par défaut). **Sans ce fichier, rien de ce qui suit n'est
> possible** — c'est la clé de voûte au sens propre, et la première chose à sauvegarder
> hors de la machine.
> **La clé de la voûte — une voûte, une clé (depuis le 2026-08-28).** Ce paragraphe a
> longtemps désigné un `ANSIBLE_VAULT_PASSWORD_FILE` unique, `~/.config/setops-vault-pass`.
> Ce n'est plus le mécanisme : un seul mot de passe ouvrait alors *toutes* les voûtes de la
> flotte, celle de l'hébergeur comprise — compromettre le plus petit locataire, c'était
> obtenir les secrets de tous. Chaque dépôt a désormais **sa** clé, nommée d'après lui :
> `~/.config/setops-vault-<dépôt-en-minuscules>`. Le `Makefile` les rassemble tout seul dans
> `ANSIBLE_VAULT_IDENTITY_LIST` (`scripts/voutes.py`), et Ansible les essaie toutes — il n'y
> a **rien à exporter**. Pour voir ce que cette machine peut ouvrir :
>
> ```
> python3 scripts/voutes.py etat
> ```
>
> **Sans la clé de ton écosystème, rien de ce qui suit n'est possible** — c'est la clé de
> voûte au sens propre, et la première chose à sortir de la machine
> (`make cles-exporter`, cf. [`sortir-les-cles-du-poste.md`](sortir-les-cles-du-poste.md)).
### 6.2 Deux consoles Keycloak, et la racine mène à la mauvaise
@ -375,5 +393,34 @@ quel dossier Nextcloud : ça se règle dans le service, à partir du groupe qu'i
Set-OPS accorde l'entrée et le niveau ; il ne réimplémente pas le modèle de chaque
application.
**Rien n'est construit.** Ce document fixe la direction ; le rôle d'amorçage, les
`meta/acces.yml` et la preuve restent à écrire.
**Ce qui est construit, et ce qui ne l'est pas — mesuré le 2026-09-06.** Ce document s'est
terminé jusqu'à cette date sur « **Rien n'est construit** [...] le rôle d'amorçage, les
`meta/acces.yml` et la preuve restent à écrire ». C'était devenu faux au point de contredire
le §3 du même document, qui rapporte des mesures **datées du 2026-08-11** prises sur le rôle
en fonctionnement. L'état réel :
| | État |
|---|---|
| Le rôle d'amorçage | **`roles/amorcage_acces/`** — écrit, déployé, et c'est lui qui crée l'unique compte `sysadmin` du §3 |
| Les `meta/acces.yml` | **écrits pour les cinq services `web-sso`** : `serveur_forgejo`, `serveur_grafana`, `serveur_icingaweb2`, `serveur_keycloak`, `serveur_nextcloud` |
| La preuve | **écrite le 2026-09-07 — c'est P58** |
Le §5 de [`authentification.md`](authentification.md) formule la règle : *une directive
qu'aucune garde ne vérifie finit par ne plus être vraie*. `meta/acces.yml` a été dans ce cas
jusqu'au 2026-09-07 : rien ne vérifiait qu'un service `web-sso` en porte un. **P29** gardait
les *positions* d'authentification ; **P58** garde désormais les *habilitations*.
Ce qu'elle exige :
| | |
|---|---|
| tout rôle `web-sso` porte un `meta/acces.yml` | **sauf** `formulaire_local: aucun` — une passerelle authentifie devant, elle n'accorde rien. L'exemption est **dérivée** de la déclaration, jamais un nom en dur |
| chaque entrée nomme `groupe`, `accorde`, `porte_par`, `raison` | `porte_par` existe pour rendre visible, dans la déclaration même, **ce qui n'est pas câblé** |
| `groupe` est un groupe, pas un compte | ni `@`, ni `uid=` — c'est D-66, tenu par une machine |
| une habilitation `porte_par: role-realm` est **réellement projetée** par `serveur_keycloak` | c'est le croisement qui porte la preuve : sans lui, un service peut annoncer une habilitation que rien ne transporte, et l'écran reste vide sans que personne sache pourquoi |
> **Ce qu'elle n'exige PAS, et c'est le point le plus important.** Elle ne demande pas que
> les groupes nommés existent dans l'annuaire. Ce serait contredire le §2 : le dépôt
> **amorce** un accès et se retire ; les appartenances appartiennent à une personne.
> `amorcage_acces` ne crée qu'un groupe — `dev` et `personnel` sont créés par l'exploitant,
> et leur absence du code n'est pas un défaut, c'est le régime.

View file

@ -2,8 +2,14 @@
> **Pour qui :** le **mainteneur** qui ajoute une relation service → service.
> Note de conception, 2026-07-02. Décision d'architecture à valider avant implémentation.
> Direction retenue : **liens déclarés côté application (le consommateur déclare ses besoins)**.
> Note de conception, 2026-07-02. Direction retenue : **liens déclarés côté application (le
> consommateur déclare ses besoins)**.
>
> **Statut, revu le 2026-09-06 : ce n'est plus « à valider avant implémentation ».** Le
> mécanisme est construit et déployé — le §9 en donne le phasage, et les §1 et §2 ci-dessous
> décrivent l'**état de départ de juillet**, conservé parce qu'il explique *pourquoi* le
> modèle est ce qu'il est. Ils ne décrivent pas le dépôt d'aujourd'hui : voir l'encadré au
> §2. Ce qui reste ouvert est nommé au §7 (le graphe des liens) et au §10.
## 1. Problème
@ -19,9 +25,9 @@ Conséquence : **la topologie n'est pas déclarative**. Déplacer Dovecot sur un
éditer des variables à la main. **Cela échoue à l'épreuve de la portabilité multi-tenant** —
pourtant au cœur de la mission.
## 2. Ce qui existe déjà (partiel)
## 2. Ce qui existait déjà, en juillet 2026 (état de départ)
Le concept est à moitié né, mais éclaté et non câblé :
Le concept était à moitié né, éclaté et non câblé :
- **Bases** (`plan/bases-donnees.yml`) : `consommateur` + `portee` (groupe|hote|application) +
`secret` (Vault) + `usage` + `proprietaire`. Un vrai binding base → consommateur, mais vide et
@ -31,6 +37,12 @@ Le concept est à moitié né, mais éclaté et non câblé :
- **Applications** (`plan/applications.yml`) : seulement `groupe` + `hote`. Aucun lien app → app.
- **Rôles** : portent déjà une méta auto-descriptive (`meta/empreinte.yml`).
> **Aucune de ces quatre lignes n'est encore vraie (mesuré le 2026-09-06).** Le registre des
> bases porte **4 bases** et un serveur, tous consommés ; **6 applications** déclarent un
> `expose` ; `postfix` déclare **3 liens** app→app. La section est gardée au passé parce
> qu'elle est le *problème* que le reste du document résout — la lire au présent donnerait
> l'impression que rien n'a bougé.
## 3. Modèle proposé
**Un binding est une arête typée et dirigée**, d'un **consommateur** (une application) vers une
@ -132,7 +144,7 @@ Pour chaque application portant des `liens`, pour chaque lien `{vers, role}` :
applications:
forgejo:
groupe: serveur_forgejo
hote: git-01
hote: forge-01
liens:
- vers: git.chezlepro.ca # domaine public (domaines.yml)
role: exposition
@ -149,9 +161,12 @@ le lien app ↔ domaine relie enfin app + domaine + edge, aujourd'hui séparés.
> app ». Mais le binding app→base **existe déjà et fonctionne**, par un mécanisme *différent* de
> celui des liens app→app. Il **ne faut pas le dupliquer** dans `instancier`.
Réalité : quatre rôles (`serveur_postgresql`, `serveur_forgejo`, `serveur_keycloak`,
`serveur_icinga`) résolvent leur base **au déploiement, dans le rôle**, depuis le registre
`plan/bases-donnees.yml` :
Réalité : les rôles **consommateurs** résolvent leur base **au déploiement, dans le rôle**,
depuis le registre `plan/bases-donnees.yml`. Ils sont **cinq** aujourd'hui — `serveur_forgejo`,
`serveur_icinga`, `serveur_icingaweb2`, `serveur_keycloak`, `serveur_nextcloud` — et passent
tous par `roles/resoudre_base` (cf. le paragraphe qui suit). *`serveur_postgresql` figurait
dans cette liste jusqu'au 2026-09-06 : il n'y a pas sa place. Il lit bien le registre, mais
pour **créer** les bases et leurs comptes — il est le serveur, pas un consommateur.* :
```yaml
- include_vars: bases-donnees.yml # charge le registre
@ -175,9 +190,12 @@ annuaire, milter…) résolus par instancier. Les liens **app→base** restent *
dans le rôle. Deux directions, **assumées**, chacune selon la nature de la cible et la sensibilité
du secret. La §3.1 est donc raffinée, pas contredite.
Reste comme valeur réelle (non bloquant) : **factoriser** le bloc de résolution copié-collé dans
les 4 rôles en un include partagé (ex. `roles/_resoudre_base/`). À faire **avec l'épreuve de
Keycloak** (qui utilise ce mécanisme), pour valider le DRY en le déployant. Cf. §9.
~~Reste comme valeur réelle (non bloquant) : **factoriser** le bloc de résolution copié-collé
dans les 4 rôles en un include partagé.~~ **Fait.** Le rôle utilitaire
[`roles/resoudre_base/`](../roles/resoudre_base/README.md) porte la résolution, et **cinq**
rôles consommateurs l'incluent (`serveur_forgejo`, `serveur_icinga`, `serveur_icingaweb2`,
`serveur_keycloak`, `serveur_nextcloud`). Le DRY a bien été validé **en le déployant**, comme
prévu — l'épreuve de Keycloak. Cf. §9.
## 6. Règles de validation
@ -200,27 +218,39 @@ Keycloak** (qui utilise ce mécanisme), pour valider le DRY en le déployant. Cf
d'inventaire), très parlante pour la **démo de portabilité** (déplacer un nœud, voir les
arêtes suivre).
## 8. Preuve de migration (les 3 liens mail)
## 8. Preuve de migration (les liens mail) — faite, mais pas comme prévu
Premier cas concret, à faire en régression (les mêmes variables doivent être générées) :
Premier cas concret. **Deux des trois lignes sont passées par les `liens`, la troisième
non** — et l'écart est instructif, il fixe la frontière entre les deux mécanismes.
| Avant (codé en dur) | Après (déclaratif) |
|---|---|
| `serveur_postfix_mailstore_hote` (group_var) | `postfix.liens: [mailstore → dovecot]` |
| `serveur_postfix_rspamd_milter` (group_var) | `postfix.liens: [milter → rspamd]` |
| `id-ldap-01` (defaults) | `postfix.liens: [annuaire → openldap]`, `dovecot.liens: [annuaire → openldap]` |
| Avant (codé en dur) | Après | Par quel mécanisme |
|---|---|---|
| `serveur_postfix_mailstore_hote` (group_var) | `postfix.liens: [mailstore → dovecot]` | **lien** ✅ (2026-07-03) |
| `serveur_postfix_rspamd_milter` (group_var) | `postfix.liens: [milter → rspamd]` | **lien** ✅ (2026-07-03) |
| `id-ldap-01` (defaults) | connexion LDAP dérivée du `domaine_interne` | **rôle utilitaire** `resoudre_annuaire` |
**Pourquoi l'annuaire n'est pas un lien.** Ce document a annoncé jusqu'au 2026-09-06
`postfix.liens: [annuaire → openldap]` et l'équivalent pour Dovecot. Ni l'un ni l'autre
n'existe : `roles/serveur_postfix/meta/liens.yml` n'accepte que `mailstore` et `milter`.
L'annuaire est traité comme les bases (§5) — par un **rôle utilitaire** que quatre
consommateurs incluent (`serveur_dovecot`, `serveur_postfix`, `serveur_keycloak`,
`serveur_icingaweb2`, plus `amorcage_acces`), parce qu'il n'y a **qu'un** annuaire par
écosystème et que sa connexion se dérive entièrement du `domaine_interne` : il n'y a pas de
choix de topologie à déclarer, donc pas d'arête à porter dans le plan. Un lien exprime un
choix ; ici il n'y en a pas.
## 9. Phasage
- **Phase 0** : cette note + décision. ✅ (direction : liens côté app pour app→app)
- **Phase 1** : résolveur de liens dans `instancier.py` + `meta/liens.yml` des rôles mail ;
migrer les liens mail ; régression DIFF VIDE. ✅ (2026-07-03 : `mailstore` + `milter` migrés)
- **Phase 2** : bases. ✅ **déjà en place** — binding **côté base** (registre + résolution en
rôle, 4 rôles), cf. §5. Ne PAS dupliquer dans instancier. **Reste (reporté à la session
Keycloak)** : factoriser le bloc de résolution copié-collé en un include partagé (DRY),
validé en déployant Keycloak.
- **Phase 3** : exposition / domaines (vhosts nginx dérivés des liens ; machinerie `expose` /
`expositions_des_applications` déjà présente).
- **Phase 2** : bases. ✅ **complète** — binding **côté base** (registre + résolution en
rôle), cf. §5. Ne PAS dupliquer dans instancier. La factorisation qui restait est faite :
`roles/resoudre_base/`, inclus par cinq rôles consommateurs.
- **Phase 3** : exposition / domaines. ✅ — **6 applications** déclarent un `expose`
(keycloak, forgejo, grafana, oauth2_proxy, nextcloud, collabora), et `serveur_nginx` en
dérive vhost, SAN et enregistrement A (`expositions_des_applications`). `make expositions-plan`
vérifie ensuite que chacune répond réellement.
- **Phase 4** : vue GUI des liens + graphe. 🟡 **éditeur fait** (2026-07-22, cf. §7) ;
**graphe des liens reste à faire**.

View file

@ -10,29 +10,46 @@ Point d'entrée vers le corpus documentaire, et **catalogue des mécanismes tran
ceux qui vivent dans le code et qu'on *re-découvre* sinon. Créée le 2026-07-03 après un audit
du dépôt, **revue le 2026-07-29**. But : ne plus re-déterrer ce qui existe.
Le dépôt est **déjà bien documenté** (34 docs + 15 pièces d'audit + 23 unités de wiki, et un
README pour chacun des 54 rôles). Le manque n'était pas la doc du *modèle*,
mais (a) un index « par où commencer » et (b) une carte des *mécanismes* (dispersés dans le
code + les README de rôles). Cette page comble ces deux trous.
Le dépôt est **déjà bien documenté**. Le manque n'était pas la doc du *modèle*, mais (a) un
index « par où commencer » et (b) une carte des *mécanismes* (dispersés dans le code + les
README de rôles). Cette page comble ces deux trous.
### Le dépôt en chiffres
> Ces valeurs sont **mesurées**, pas recopiées : **P48** les recompte et refuse tout écart.
> Elles étaient toutes fausses le 2026-08-26 — de 6 rôles, de 12 pièces d'audit, de 8
> décisions. Aucune ne faisait travailler personne ; mais une carte dont les faits
> vérifiables sont faux cesse d'être consultée, et c'est alors ses **pointeurs** qu'on perd.
| Ce qu'on compte | Combien | Comment on le mesure |
|---|---|---|
| rôles | 69 | `roles/*/` |
| README de rôles | 69 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
| documents | 46 | `docs/*.md` |
| pièces d'audit | 51 | `docs/audit/*` |
| unités de wiki | 27 | `wiki/*.md` |
| décisions en vigueur | 85 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
## 1. À lire d'abord (dans l'ordre)
| Sujet | Documents |
|---|---|
| **Autorité / gouvernance** | `AGENTS.md` (source d'autorité), `CLAUDE.md`, `docs/MISE-A-JOUR-CODEX-CLAUDE.md` |
| **Le modèle (plan)** | `docs/architecture-set-ops.md` (survol) → `docs/plan-et-generation.md` (à fond) → `docs/meta-classe.md` (concept) |
| **Le modèle (plan)** | `docs/architecture-set-ops.md` (survol) → `docs/plan-et-generation.md` (à fond) → `docs/meta-classe.md` (concept) → `docs/conception-contextes.md` (site et locataire : deux classes, un contrat) |
| **Services, maturité, dette** | `docs/catalogue-services.md` (**la carte de maturité + la cruft y sont déjà**) |
| **Exploitation / VM** | `docs/vm-lifecycle.md`, `docs/procedure-template-debian13-proxmox.md`, `docs/config-proxmox.md`, `docs/nomenclature-vm.md`, `docs/multi-instances.md` |
| **Conceptions de domaine** | `docs/identite-sso.md`, `docs/courriel-conception.md`, `docs/bindings-conception.md`, `docs/dns-interne.md`, `docs/dimensionnement-ressources.md`, `docs/integrations-vm.md` |
| **Supervision & métriques** | `docs/supervision-conception.md` (les verdicts, vers Icinga) → `docs/metriques-conception.md` (les séries, vers Prometheus et Grafana) — deux questions, deux fichiers `meta/`, un même patron : le rôle déclare, le moteur dérive |
| **Réseau / pare-feu** | `docs/flux-conception.md` (le modèle) → `docs/registre-flux.md` (**généré**, matrice d'audit) → `docs/frontiere-opnsense.md` (la bordure nord/sud) ; underlay : `underlay.yml.example` + `make underlay` |
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`) **et les cinq devis d'infrastructure** (`frontiere-plan`, `proxmox-fw-plan`, `sdn-plan`, `underlay-plan`, `placement-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md` — **statique** : lit le dépôt, aucun appel réseau ; la conformité du déployé est l'affaire des devis de service (ligne au-dessus), `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **70 décisions en vigueur** (D-01 → D-73, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
| **Décisions d'architecture** | `docs/decisions-architecture.md` — les décisions en vigueur (comptées ci-dessus), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **En service** depuis le 2026-08-03 (`routage_tenants: sdn` dans l'`underlay.yml` du site) : les trunks ne portent plus que deux VLAN d'underlay au lieu de quinze, les passerelles `.1` sont anycast sur chaque hyperviseur. Écart mesuré par `make sdn-plan` |
| **Migration de tenant** | `docs/migration-tenant.md` — recette en **neuf étapes (0 à 8)**, machine à états, gardes ; le receveur se construit **avant** tout gel |
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
| **Pédagogie (le wiki)** | `wiki/` — 21 unités (+ `_Sidebar`) publiées par `make wiki-publier` ; entrer par `wiki/Home.md` |
| **Pédagogie (le wiki)** | `wiki/` — les unités (comptées ci-dessus) publiées par `make wiki-publier` ; entrer par `wiki/Home.md` |
| **Vision / positionnement** | `docs/ecosysteme-chezlepro.md`, `docs/positionnement.md`, `docs/pouvoirs-set-ops.md` |
## 2. Les mécanismes transverses (et OÙ ils vivent)
@ -42,25 +59,29 @@ Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanis
| Mécanisme | Ce que c'est | Où, dans le code | Doc |
|---|---|---|---|
| Plan → inventaire | `plan/*.yml` → `hosts.yml` généré | `scripts/instancier.py`, `scripts/inventory_rules.py` | `plan-et-generation.md` |
| **Schéma des registres** | la **forme** des six registres — champs, types, énumérations, requis — **dérivée** des validateurs, jamais écrite à la main. Sert à générer les formulaires plutôt qu'à les écrire. La **cohérence** reste aux `valider_*` : un schéma ne sait pas dire qu'un `consommateur` désigne une application inexistante | `scripts/schema_plan.py` (`make schema`) → `docs/audit/schema-plan.json` ; garde **P61** | `plan-et-generation.md` |
| Nomenclature dérivée | VMID / IP / VLAN / FQDN dérivés | `inventory_rules.deriver_nomenclature` + `plan/nomenclature.yml` | `nomenclature-vm.md` |
| Dimensionnement | RAM/CPU/disque sommés par logiciel | `roles/*/meta/empreinte.yml` → `deriver_ressources` | `dimensionnement-ressources.md` |
| **Bindings app→app** | lien **côté app** (`liens`) résolu en host_vars | `plan/applications.yml` `liens:` + `roles/*/meta/liens.yml` + `instancier.resoudre_liens` | `bindings-conception.md` |
| **Bindings app→base** | lien **côté base** (`consommateur`/`portee`) résolu **dans le rôle** | `plan/bases-donnees.yml` + rôle utilitaire `resoudre_base` (`lookup('vars', secret)`, `no_log`) inclus par le consommateur | `bindings-conception.md` §5 |
| Résolution d'annuaire | connexion LDAP (uri/base DN/bind) **dérivée**, jamais recopiée | rôle utilitaire `resoudre_annuaire` (inclus par dovecot/postfix/keycloak/icingaweb2) | `identite-sso.md` |
| Résolution d'annuaire | connexion LDAP (uri/base DN/bind) **dérivée**, jamais recopiée | rôle utilitaire `resoudre_annuaire` (inclus par dovecot/postfix/keycloak/icingaweb2 et `amorcage_acces`) | `identite-sso.md` |
| Plancher de résolution | `/etc/hosts` généré depuis l'inventaire + alias d'`expose` → l'écosystème se résout **DNS éteint** | rôle `hosts_statiques` (appliqué dans la couche socle) | `dns-interne.md` |
| Pont de certificat | cert step_ca → service, resync au renouvellement | script `*-cert-sync` + unité `.path`, dans chaque rôle serveur ; cert déposé par `client_pki` | — |
| Ordonnancement socle-first | socle/durci avant les `client_*` | `serveur_debian`/`serveur_durci` d'abord (posent `/etc/hosts` via `hosts_statiques`) | — |
| Sûreté check-mode | dry-run fiable | `when: not ansible_check_mode` sur les tâches de service + handlers | — |
| Voûte au déploiement | secret jamais en clair | `ANSIBLE_VAULT_PASSWORD_FILE` / `~/.config/setops-vault-pass` ; déréférencé par `lookup('vars', <nom>)` | — |
| Voûte au déploiement | secret jamais en clair ; **une voûte, une clé** depuis le 2026-08-28 | `ANSIBLE_VAULT_IDENTITY_LIST` construit par `scripts/voutes.py` (clé nommée `~/.config/setops-vault-<dépôt>`) ; déréférencé par `lookup('vars', <nom>)` | `autorisation.md` §6.1 |
| Multi-instance | un dépôt par écosystème ; l'active = symlink `instance/`, les autres **découvertes par convention** (dossiers frères, aucun registre) | active : symlink `instance/` ; découverte : `scripts/instances.py` / `devis_reseau.py` (glob `../*/plan/nomenclature.yml` avec `index`) ; garde-fou collision : preuve **P21** | `multi-instances.md` |
| Exposition → edge | app expose un FQDN public servi par un edge | `plan/domaines.yml` + `expose` (applications) | `bindings-conception.md` §4 |
| Exploitation de l'hébergeur | ses **opérations** (supervision de la fabric, sauvegarde des configs, DNS d'underlay) n'appartiennent à aucun tenant et restent **hors overlay** | décidé, **non construit** : aucun équipement d'hébergeur n'est encore dans un inventaire | `hebergeur-exploitation.md` |
| Exploitation de l'hébergeur | ses **opérations** n'appartiennent à aucun tenant et restent **hors overlay** | **à moitié construit** : les *VM* du site ont leur inventaire (`scripts/site_inventaire.py`), leur socle, leur durcissement, leurs sauvegardes et leur supervision (`site-mon-01`, 2026-09-02). Les *équipements* : hyperviseurs et frontière **supervisés** depuis le 2026-09-17 — températures, ventilateurs, SMART, usure NVMe, un catalogue (`scripts/materiel.py`) lu par le tableau Grafana « Matériel » et le service Icinga `materiel` (`supervision-conception.md`). Les commutateurs restent sans supervision ; aucun équipement n'a de sauvegarde de configuration | `hebergeur-exploitation.md` |
| Authentification | web → Keycloak ; LDAP source unique ; secours par `sudo`, formulaire local non annoncé | `<rôle>_connexion_locale: false` (grafana, forgejo, nextcloud) ; garde de version Forgejo ≥ 10 | `authentification.md` |
| Accès & habilitations | Set-OPS **amorce** un accès sysadmin puis se retire ; les appartenances aux groupes ne sont **jamais réconciliées** — c'est une personne qui gouverne | **construit et éprouvé** (2026-08-08) : rôle `amorcage_acces` (idempotence par existence, D-67), groupes projetés en rôles par `serveur_keycloak`, `meta/acces.yml` dans les 5 rôles web ; chaîne LDAP → Keycloak → groupe → service exercée de bout en bout sur Icinga Web 2 | `autorisation.md` (§6 = runbook de reprise) |
| SDN EVPN | ajouter un tenant implique **1 zone + 6 VNets + 6 sous-réseaux**, tous dérivés du seed | `scripts/devis_sdn.py` (`make devis-sdn`) ; nommage dérivé du tenant (`CHEZ17`, `chez174`), ≤ 8 caractères ; garde **P30** | `sdn-evpn.md` §2 |
| **DNS public** | le locataire **écrit** ses zones `autorite: primaire-cache`, le site les **sert** en secondaire, un site pair les réplique. Transfert ouvert **à la clé TSIG seule** (`allow-axfr-ips` et TSIG sont alternatifs dans PowerDNS) ; instance `pdns@public` à part pour que le site ne puisse jamais interroger la zone `.internal` | `roles/serveur_dns_public/`, `roles/serveur_powerdns/tasks/zones-publiques.yml` ; relations dérivées dans `scripts/site_inventaire.py` ; mots de flux `dns_public_site` / `primaires_dns_locataires` ; garde **P82** | `dns-interne.md` §Zones publiques |
| **Remise au client** | remettre un écosystème se fait en **deux temps** : l'*identité* le jour de la livraison (sa clé de voûte, sa voûte, sa racine d'AC), la *machine* à l'échéance (sa clé SSH entre, la nôtre sort, la voûte est re-clétée). Le registre déclare enfin le **responsable désigné** (D-18) | `scripts/remise.py` (`make remise-paquet`, `remise-inscrire`, `remise-recleer`) ; registre `remise.yml` chez le locataire ; garde **P80** | `remise-au-client.md` |
| SDN EVPN | ajouter un tenant implique **1 zone + 6 VNets + 6 sous-réseaux**, tous dérivés du seed | `scripts/devis_sdn.py` (`make devis-sdn`) ; nommage dérivé du seed (`t17`, `t17serv`), ≤ 8 caractères ; garde **P30** | `sdn-evpn.md` §2 |
| Pools Proxmox | un pool par tenant : les noms courts de VM sont **volontairement identiques** d'un tenant à l'autre (même fonction, même nom), et seule la console Proxmox en souffrait | `scripts/devis_proxmox_pools.py` (`make devis-proxmox-pools`) ; nom dérivé de l'`index` ; garde de collision = preuve **P28** | `decisions-architecture.md` D-37 |
| Routage | **aucun commutateur ne route** : la frontière est le seul équipement L3 ; les switches commutent | `passerelle` dit qui porte la passerelle, le SVI se dérive du rôle du porteur | `decisions-architecture.md` D-49/50 |
| **Devis de service** | LIT le système en marche et le compare à ce que le plan dérive ; n'écrit rien (D-23/D-24 portés du réseau aux services). Le playbook **relève**, Python **compare** | `playbooks/maintenance/devis-*.yml` + `scripts/devis_*.py` ; cible `make <sujet>-plan` | `devis-services.md` |
| Accès d'administration | un **tunnel WireGuard nominatif** (instance `admins`) : un pair par personne et par appareil, le réseau du tunnel devient un réseau d'administration dont les pare-feux d'hôte, le contrat vers les locataires et les règles de bordure **dérivent**. Le runner n'est pas le rebond, et le document dit pourquoi | `scripts/vpn_admin.py` (`make vpn-admin-plan`) ; déclaré dans `acces_admin_vpn` (plan du site) | `acces-administration.md` |
| Frontière nord/sud | les flux `pair: externe` — **sautés** par le pare-feu d'hôte — sont la politique de bordure | `scripts/devis_opnsense.py` (`make devis-opnsense`) ; garde d'accès admin = preuve **P24** | `frontiere-opnsense.md` |
> ⚠️ **Deux directions de binding, assumées** : `app→app` côté app (instancier),
@ -75,7 +96,7 @@ Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanis
- ✅ **soldé** — README de rôles : **tous les rôles en ont un** (les 12 manquants écrits le
2026-07-29 : `serveur_debian`, `hosts_statiques`, `resoudre_base`, `resoudre_annuaire`,
`serveur_dovecot`, `serveur_postfix`, `serveur_rspamd`, `client_backup`, `serveur_backup`,
`client_unbound`, `serveur_oauth2_proxy`, `serveur_icingaweb2`).
`client_resolveur`, `serveur_oauth2_proxy`, `serveur_icingaweb2`).
- ✅ **soldé** — `expose` **est** consommé au déploiement : `plan/applications.yml` →
filtre `expositions_des_applications` → vhosts nginx dérivés
(`roles/serveur_nginx/tasks/main.yml`, template `expositions.conf.j2`, drapeau

View file

@ -10,10 +10,10 @@ groupe opérationnel -> playbooks/groupes/<groupe>.yml (P04 le prouve)
```
Le **rôle porte le nom du groupe** : `serveur_keycloak`, `client_pki`. Il n'y a pas de nom
court séparé. Seule exception, `serveur_durci` : un groupe dont le playbook **compose**
onze rôles de durcissement (`hardening_packages`, `sysctl_hardening`, `apparmor`,
`auditd`, `fail2ban_ssh`, `ssh_hardening`, `nftables_baseline`…) plutôt qu'un rôle
homonyme.
court séparé. Seule exception, `serveur_durci` : un groupe dont le playbook **compose dix
rôles** de durcissement, et dans cet ordre — `hardening_packages`, `sysctl_hardening`,
`core_dumps`, `unattended_upgrades`, `apparmor`, `auditd`, `fail2ban_ssh`, `journald`,
`ssh_hardening`, `nftables_baseline` — plutôt qu'un rôle homonyme.
La nomenclature des VM et des VMID est documentée dans `docs/nomenclature-vm.md`.
@ -23,21 +23,28 @@ Un service central peut partager un hôte avec d'autres services de la même fon
## État d'implémentation des rôles
> **Mise à jour (2026-08-18), vérifiée rôle par rôle contre `roles/`.** Les 29 groupes
> `serveur_*` / `client_*` de ce catalogue ont **tous** leur rôle et leur playbook. Aucune
> capacité annoncée ici n'est un point d'ancrage vide.
> **Mise à jour (2026-09-06), vérifiée groupe par groupe contre `roles/` et
> `playbooks/groupes/`.** Les **40 groupes** `serveur_*` / `client_*` du tableau ci-dessous
> ont **tous** leur rôle et leur playbook — 40 fichiers dans `playbooks/groupes/`, 40 noms
> distincts cités ici. Aucune capacité annoncée n'est un point d'ancrage vide.
>
> *(Le chiffre lu ici jusqu'au 2026-09-06 était 29, mesuré le 2026-08-18 : le catalogue a
> grandi de onze groupes — site, runners, résolveur, artefacts — sans que la phrase suive.
> C'est ce genre d'écart que la preuve **P57** garde désormais.)*
**Éprouvés sur VM réelles.** Le 2026-08-13, la flotte a été **reconstruite depuis zéro**
— 43 groupes, 0 échec, 37 minutes — puis remontée d'un seul trait. Ce n'est donc plus
« du code validé » : chaque rôle a repris une machine nue et l'a menée à l'état voulu.
**Éprouvés sur VM réelles.** Le 2026-08-13, la flotte a été **reconstruite depuis zéro** —
43 groupes, 0 échec, 37 minutes. L'épreuve a été **rejouée deux fois le 2026-09-02**, sur
un dépôt qui avait beaucoup bougé depuis : 15/15 hôtes puis 14/14, 0 échec, `make valider`
à 0 échec sur 13 hôtes. Ce n'est donc plus « du code validé » : chaque rôle a repris une
machine nue et l'a menée à l'état voulu — et il l'a refait après coup.
Ce que la reconstruction couvre, par capacité :
| Capacité | Rôles | Ce qui est éprouvé |
|---|---|---|
| Socle et durcissement | `serveur_debian`, `serveur_durci` | clone du gabarit doré → machine conforme |
| Socle et durcissement | `serveur_debian`, `serveur_durci` (dix rôles composés) | clone du gabarit doré → machine conforme |
| Confiance | `serveur_step_ca`, `client_pki` | mTLS avec SAN dérivés du plan, renouvellement |
| Noms | `serveur_powerdns`, `client_unbound` | autoritaire interne + résolveur local |
| Noms | `serveur_powerdns`, `client_resolveur` | autoritaire interne + résolveur local |
| Identité | `serveur_openldap`, `serveur_keycloak` | LDAPS, SSO OIDC, **fédération LDAP automatisée** (`tasks/federation-ldap.yml`), exposé par le plan (`auth.<domaine>`) |
| Passerelle SSO | `serveur_oauth2_proxy` | SSO devant une app sans OIDC natif (éprouvé sur Icinga Web 2) |
| Données | `serveur_postgresql`, `serveur_redis` | bases et comptes dérivés du registre, TLS `verify-full` |
@ -46,13 +53,23 @@ Ce que la reconstruction couvre, par capacité :
| Observabilité | `serveur_prometheus`, `serveur_loki`, `serveur_grafana` | métriques, journaux, tableaux sous SSO |
| Supervision | `serveur_icinga`, `serveur_icingaweb2` | Icinga 2 + IcingaDB + Web 2 + BPM, au SSO |
| Forge | `serveur_forgejo` | Git + PostgreSQL + SSO OIDC |
| Collaboration | `serveur_nextcloud`, `serveur_collabora` | déployés par la reconstruction, base et client OIDC dérivés du plan — **usage** (dépôt de fichier, édition partagée) non consigné comme preuve |
| Collaboration | `serveur_nextcloud`, `serveur_collabora` (**natif**, plus de conteneur) | déployés par la reconstruction, base et client OIDC dérivés du plan — **usage** (dépôt de fichier, édition partagée) non consigné comme preuve |
| Plateforme webapp | `serveur_web_frontal`, `serveur_web_dorsal` | sites statiques et webapps natives (venv + systemd + nginx), **zéro conteneur** |
| Sauvegardes | `serveur_backup`, `client_backup` | restic hors-nœud, **restauration éprouvée** (2026-08-12 : la donnée revient) |
| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte |
| Exploitation | `serveur_ops` | le poste depuis lequel l'ecosysteme se reconstruit : Ansible epingle, genome clone depuis **sa propre forge**, cle SSH propre — **sans** la voute ni son mot de passe |
| Source d'artefacts | `serveur_artefacts`, `client_artefacts` | **deux faces sur un seul service.** Cache apt (apt-cacher-ng) : les paquets viennent de chez soi, pas de six serveurs etrangers — **mode hors ligne** pour prouver ce que le cache detient vraiment. Et **depot des binaires directs** (`LocalDirs`) : Forgejo, Keycloak, Nextcloud, oauth2-proxy ne vivent dans aucun depot apt et etaient tires d'Internet par CHAQUE runner — 570 Mo mesures le 2026-09-12. Meme port, meme regle de pare-feu, garde `P70` |
| Runner de tenant | `serveur_ops_tenant` | le pouvoir de **configurer** : la voute de l'ecosysteme, deposee CHIFFREE sur son propre runner. Sans elle un runner calcule son inventaire et ne peut rien en faire — chaque role qui demande un secret echoue sur son assertion. N'atteint ni la fabric ni la frontiere |
| Runner de site | `serveur_ops_site` | le pouvoir de **materialiser** : creer et detruire des VM sur la fabric. Detient la voute du SITE, chiffree, et n'entre JAMAIS chez un tenant — reserve a l'ecosysteme de l'hebergeur |
| Resolution | `serveur_resolveur`, `client_resolveur` | UN resolveur recursif par tenant (Unbound), qui recurse depuis la racine et delegue la zone souveraine a PowerDNS. Remplace les N demons locaux d'avant le 2026-08-24 |
| Cache du site | `serveur_cache_site` | designe LE cache que les ecosystemes voisins prennent comme amont : Debian telecharge une fois pour toute la fabric, et le cache ne voit que des requetes agregees. Reserve a l'ecosysteme de l'hebergeur |
| Forge du genome du site | `serveur_forge_site` | designe LA forge dont les ecosystemes de ce site se reproduisent (D-81), et l'ouvre a eux. Marqueur : `serveur_forgejo` installe. |
| Resolveur du site | `serveur_resolveur_site` | designe LE resolveur que les ecosystemes de ce site interrogent tant qu'ils n'ont pas le leur. Marqueur : `serveur_resolveur` installe. |
| Depot de sauvegarde du site | `serveur_backup_site` | designe LE depot ou les ecosystemes de ce site posent leur etat tant qu'ils n'ont pas le leur. Marqueur : `serveur_backup` installe. |
| DNS public du site | `serveur_dns_public` | **secondaire** public de toutes les zones `autorite: primaire-cache` des locataires du site : le locataire ecrit, le site sert. Transfert signe TSIG, aucune adresse de confiance, aucune zone `.internal`. Phase 1 : non expose a Internet |
| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp`, `client_sante` | collecte, relais et rapport de santé sur toute la flotte |
*Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail`
(→ Postfix), `client_dns` (→ plancher `/etc/hosts` + `client_unbound`), `client_ldap`
(→ Postfix), `client_dns` (→ plancher `/etc/hosts` + `client_resolveur`), `client_ldap`
(login LDAP au niveau OS, hors design).
> **Ce que « éprouvé » ne dit pas.** La reconstruction prouve que le moteur mène une
@ -136,9 +153,14 @@ table décrit une répartition éprouvée, pas un minimum requis.
| `mon-01` | Icinga 2, Icinga Web 2, oauth2-proxy |
| `forge-01` | Forgejo |
| `collab-01` | Nextcloud, Collabora |
| `backup-01` | dépôt restic |
| `web-frontal-01` | site statique |
| `web-dorsal-01` | webapp native |
| `ops-01` | runner de l'écosystème (`serveur_ops`, `serveur_ops_tenant`) |
> `ops-01` manquait de cette table jusqu'au 2026-09-06, alors que le compte annoncé
> ci-dessus le comptait : quatorze hôtes, treize lignes. C'est le nœud depuis lequel
> l'écosystème se reconstruit **sans le poste de l'exploitant** — la pièce la moins visible
> et la plus structurante de la reconstruction autonome.
Le courriel occupe **deux** hôtes, et ce n'est pas un détail de taille : `edge-mta-01`
porte ce qui parle à l'extérieur (Postfix, rspamd), `infra-mail-01` ce qui détient les
@ -151,7 +173,9 @@ boîtes (Dovecot). La coupure suit l'exposition, pas le logiciel.
| Confiance PKI / ACME | `client_pki` | **tout hôte** (universelle) |
| Métriques Prometheus | `client_metrique` | **tout hôte** (universelle) |
| Journaux vers Loki | `client_journal` | **tout hôte** (universelle) |
| Résolution locale (Unbound) | `client_unbound` | **tout hôte** (universelle) |
| Résolution locale (Unbound) | `client_resolveur` | **tout hôte** (universelle) |
| Source d'artefacts (cache apt) | `client_artefacts` | **tout hôte** (universelle) |
| Santé du nœud (unités en échec) | `client_sante` | **tout hôte** (universelle) |
| Relais SMTP | `client_smtp` | déclaré par hôte, dans le plan |
| Sauvegarde restic | `client_backup` | déclaré par hôte — **obligatoire pour tout détenteur d'état** (P36) |
@ -163,14 +187,21 @@ dans le plan.
> **`client_supervision` n'existe pas.** Ce catalogue l'a longtemps annoncé ; il n'a
> jamais eu ni rôle ni playbook, et rien ne l'attend. La supervision s'exerce **sans agent
> sur les hôtes** : contrôles actifs depuis le cœur (`hostalive`) et résultats **passifs
> poussés par l'API** par celui qui détient la vérité de terrain — ainsi l'état des
> sauvegardes est-il rapporté par `backup-01`, seul à pouvoir lire ses dépôts. Le nom est
> retiré plutôt que réservé : une case vide dans un catalogue se lit comme une promesse.
> poussés par l'API** par celui qui détient la vérité de terrain. Le nom est retiré plutôt
> que réservé : une case vide dans un catalogue se lit comme une promesse.
> **Qui rapporte l'état des sauvegardes a changé le 2026-09-02.** Tant que le dépôt vivait
> dans l'écosystème, il était le seul à voir ce qui était réellement arrivé, et il
> rapportait pour tout le monde. Depuis que les écosystèmes déposent chez leur **hébergeur**
> — qui héberge des octets chiffrés côté client et ne peut pas les juger — **chaque nœud
> vérifie son propre dépôt distant** et le rapporte lui-même. La vérification suit la clé,
> pas le stockage. `serveur_icinga` se branche sur les deux modèles ; `backup-01` a été
> retiré du plan de Chezlepro, sa VM détruite.
## Ordre de déploiement — le raisonnement
> **L'ordre exécutable n'est pas ici.** Il vit dans `docs/couches-deploiement.yml` et
> `docs/dependances-groupes.yml`, que P08 prouve cohérents (30 groupes classés, aucun
> `docs/dependances-groupes.yml`, que P08 prouve cohérents (42 groupes classés, aucun
> cycle, aucune arête en arrière) et que `make reconstruire` suit. Ce qui suit en est le
> **raisonnement**, utile pour comprendre pourquoi cet ordre-là — et pour placer un
> service nouveau. Les phases sont franchies : les « intégrations à prévoir » ci-dessous
@ -178,10 +209,24 @@ dans le plan.
L'ordre ci-dessous privilégie les dépendances structurantes avant les applications.
> **Ce raisonnement a été révisé le 2026-09-09 sur un point** : l'observabilité et la
> supervision ne viennent plus en phases 3 et 4, mais **juste après la PKI** — donc avant
> presque tout ce qu'elles surveillent. *On n'allume pas la lumière une fois la maison
> finie.* Une reconstruction depuis zéro est précisément le moment où l'on a le plus
> besoin de voir. Les phases ci-dessous gardent leur numérotation, qui dit une **parenté
> logique** ; l'ordre exécutable, lui, est dans `docs/couches-deploiement.yml` (D-86).
>
> Ce qui reste tard, et à dessein : la **vigie** (`icingaweb2`, `oauth2_proxy`), qui
> réclame LDAP et Keycloak. L'interface humaine peut attendre ; la mesure, non.
### Phase 1 - Fondations transversales
1. `serveur_powerdns`
- Service central : DNS interne autoritaire et/ou résolution interne selon le design retenu.
- Service central : DNS interne **autoritaire** de la zone souveraine. La *résolution*
est une couche distincte (`serveur_resolveur` / `client_resolveur`, Unbound), et le
**plancher `/etc/hosts`** posé par `hosts_statiques` précède les deux — c'est lui qui
permet à l'écosystème de se résoudre DNS éteint. Trois couches, pas un choix de design
(`docs/dns-interne.md`).
- Raison : les autres intégrations auront besoin de noms stables plutôt que d'adresses IP.
2. `serveur_step_ca`
@ -268,7 +313,7 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
- Tout service exposé en HTTP(S) doit prévoir son intégration avec `serveur_nginx`.
- Tout service avec authentification humaine doit prévoir son intégration avec `serveur_keycloak`, sauf justification contraire.
- Tout service générant des alertes ou notifications doit prévoir `client_smtp`.
- Toute VM de service rejoint `client_pki`, `client_metrique`, `client_journal` et `client_unbound` — **par dérivation, sans rien écrire** (intégrations universelles, P26). La supervision, elle, ne pose rien sur l'hôte.
- Toute VM de service rejoint `client_pki`, `client_metrique`, `client_journal`, `client_resolveur` et `client_artefacts` — **par dérivation, sans rien écrire** (les cinq intégrations marquées `universelle: true`, P26). La supervision, elle, ne pose rien sur l'hôte.
- Tout hôte qui **détient de l'état** doit porter `client_backup` (P36 le refuse sinon).
- Tout service utilisant un certificat interne doit dépendre de `client_pki`.
- Tout rôle serveur doit documenter ses ports, secrets, sauvegardes, dépendances et groupes clients associés.

View file

@ -0,0 +1,277 @@
# Contextes : un tronc commun, deux classes (SITE et LOCATAIRE)
> **Pour qui :** le **mainteneur** — comment le moteur sait s'il sert un site ou un locataire, et ce que les deux s'apprennent l'un à l'autre.
> **Statut : arrêtée avec l'exploitant le 2026-10-04.** Rien n'est encore construit ; les
> décisions sont au §7, le chemin au §6.
## 1. Le problème, mesuré
Le moteur ne sait pas dans quel contexte il tourne : **chaque script le devine**. Relevé du
2026-10-04 : **33 scripts** font leur propre déduction, à partir de cinq indices différents.
| Indice | Ce qu'on en déduit | Scripts |
|---|---|---|
| lien `instance/` ou `SETOPS_INSTANCE` | « un locataire est monté » | 23 |
| lien `underlay.yml` ou `SETOPS_UNDERLAY` | « un site est monté » ; son plan est à côté | 8 |
| `SETOPS_INVENTAIRE` | quel inventaire de locataire lire | 8 |
| `../*/plan/nomenclature.yml` | « la fédération », les locataires frères | 11 |
| `../SITE-*/underlay.yml` | « les sites » | 2 |
Les indices ne concordent pas toujours, et chaque désaccord a déjà produit un défaut silencieux :
- **2026-08-14** : `frontiere-plan` voulait poser sur la frontière de Technolibre les règles de
Chezlepro. « La fédération » valait « les locataires de ce site », jusqu'au second site.
- **2026-09-16** : la console du runner du site affichait zéro machine, sans erreur. Elle
cherchait un inventaire de locataire là où il n'y en a pas.
- **2026-10-04** : `make ci`, sur le poste, mélangeait le modèle public et les écosystèmes
réels. P74 lisait `SETOPS_UNDERLAY` (le modèle) ; P82 lisait le lien `underlay.yml` et les
dossiers frères (le site réel).
Les rôles Ansible, eux, ne posent pas ce problème : ils sont **déjà** le tronc commun. Un même
`serveur_postgresql` sert au site et chez un locataire.
## 2. Le modèle
```
Ecosysteme (tronc commun)
/ \
Site Locataire
\ /
`-- contrat --' (associations : un site A des locataires,
un locataire A un site)
```
### 2.1 Le tronc commun : `Ecosysteme`
Ce que tout écosystème possède, quel que soit son contexte :
- un **nom** et un **dépôt** (`SITE-Chezlepro`, `OPS-Technolibre`) ;
- une **voûte** et sa clé (`~/.config/setops-vault-<dépôt>`) ;
- un **plan** (`<dépôt>/plan/`) et un **index**, dont dérive son adressage (le site
aussi depuis le 2026-09-20) ;
- des **machines**, déployées par les **mêmes rôles** : socle, durcissement, PKI, journaux,
métriques, supervision, sauvegarde de son propre état ;
- une **filiation** : le moteur et le commit dont il descend ;
- les **preuves communes** : lint, rendu des gabarits, adressage dérivé, etc.
Méthodes abstraites, que chaque classe **surcharge** : `inventaire()`, `machines()`,
`preuves()`, `verbes()`, `console()`.
### 2.2 `Site(Ecosysteme)`
- **Déclaration** : `underlay.yml` (le matériel, les réseaux `site` et `fabric`) et `plan/`
(`10-intrants.yml`, serveurs, applications, domaines, bases).
- **Inventaire** : dynamique (`site_inventaire.py`). Le site ne dérive rien d'un plan de services ;
sa déclaration est sa forme finale.
- **Ce qu'il porte en propre** : le matériel (hyperviseurs, commutateurs, frontière),
la matérialisation des VM (Proxmox), le SDN, le pare-feu Proxmox, la frontière OPNsense,
le DNS public, le dépôt des sauvegardes des locataires, le cache et les artefacts, la forge
du génome.
- **Relation** : `locataires()`, la liste de `underlay.tenants` résolue en objets
`Locataire`. Le site n'en lit que la **face réseau** (§2.4).
### 2.3 `Locataire(Ecosysteme)`
- **Déclaration** : `plan/` (nomenclature, serveurs, applications, bases, domaines).
- **Inventaire** : généré (`instancier.py` → `hosts.yml`). C'est la **méta-classe** de
[`meta-classe.md`](meta-classe.md) : une définition qui engendre toute la flotte.
- **Ce qu'il porte en propre** : la configuration de ses services, la remise au client.
- **Relation** : `site()`, l'hébergeur que nomme `parente.yml`, résolu en objet `Site`. Le
locataire n'en lit que les **intrants exposés** (§2.4).
### 2.4 Ce que le site et le locataire s'apprennent l'un à l'autre
Les deux entités **s'informent mutuellement**. Relevé du 2026-10-04 : qui décide de chaque
information, où elle vit, et comment elle parvient à l'autre.
**Ce que chacun a sous la main.** Le runner du site porte le moteur, son dépôt **et ceux de
ses locataires** (sans leurs voûtes). Le runner d'un locataire ne porte que le moteur et
**son propre** dépôt. Le poste porte tout.
#### Le site informe le locataire, par trois canaux
**Canal 1 : des copies écrites à la main** dans le dépôt du locataire.
| Information | Décidée par | Tenue chez le site dans | Copiée chez le locataire dans | Contrôle |
|---|---|---|---|---|
| son **index** | le site | `underlay.yml` → `tenants` | `plan/nomenclature.yml` (`index`) | `underlay valider` |
| son **adresse publique** | le site | `opnsense.yml` → `opnsense_ips_publiques` | `10-intrants.yml` (`ip_publique`) | — |
| les **10 intrants de service** : résolveur, cache, binaires, forge du génome, cible de sauvegarde, DNS public, plan d'administration, passerelle | le site (dérivés de son plan, par `site_intrants.py`) | son plan | `10-intrants.yml`, et `serveur_ops.yml` pour la forge | `site_intrants.py --verifier`, seulement là où les deux dépôts sont présents (le poste) |
| sa **racine de confiance** | le site | `ac-racine-site.crt` | le même fichier, copié | — |
| *hors contrat* : un dépôt de la forge du site désigné par son adresse | — | — | `serveur_web_dorsal.yml` (Chezlepro) | **aucun** |
**Canal 2 : une lecture directe, au moment de générer l'inventaire.** `instancier.py` ouvre
l'`underlay.yml` et le plan du site pour écrire le `hosts.yml` du locataire. Mesuré sur
Technolibre, inventaire généré avec puis sans le site monté : **quatre variables changent**.
| Variable du locataire | Avec le site monté | Sans le site |
|---|---|---|
| `chrony_serveurs` | `10.0.4.1` (la frontière) | absente |
| `proxmox_pont` | `t23appl` (le VNet SDN) | absente |
| `proxmox_etiquette_vlan` | aucune (le SDN étiquette) | `1236` |
| `serveur_resolveur_zones_deleguees` | `genese.internal` → `10.37.34.11` | absente |
**Canal 3 : le réseau.** Le runner du locataire **tire** son génome de la forge du site ; il est
né de l'**insémination** par le runner du site.
#### Le locataire informe le site : le site lit et recalcule
| Information | Décidée par | Tenue chez le locataire dans | Parvient au site par |
|---|---|---|---|
| ses **zones** et son adressage | dérivés de l'index | `plan/nomenclature.yml` | le site **lit le fichier** |
| les **VM à matérialiser** | le locataire | `plan/serveurs.yml` → `hosts.yml` | le site **lit les fichiers** (placement, clonage, pools, SDN) |
| ses **flux** | ses rôles et son plan | `meta/flux.yml` des rôles (moteur), croisés avec son `hosts.yml` | le site **recalcule** lui-même, avec **sa** version du moteur et la totalité de l'inventaire du locataire → frontière, NAT, pare-feu Proxmox |
| ses **domaines publics** | le locataire | `plan/domaines.yml` (+ `applications.yml`, `serveurs.yml`) | le site **lit les fichiers** → DNS public secondaire |
| sa **clé de sauvegarde** | le locataire | `inventories/*/group_vars/serveur_backup.yml` | le site **lit le fichier** → compte Unix sur le dépôt |
| ses **accès d'administration** | le locataire | `plan/acces.yml`, `nftables_admin_ssh` | le site **lit les fichiers** → pairs WireGuard, règles d'administration |
Le SDN, lui, ne prend aucun flux : il ne filtre pas. Il ne reçoit que l'index, dont il dérive
la zone, les 6 VNets et les 6 sous-réseaux.
#### À l'exécution, entre machines
Ces échanges-là passent par le réseau, pas par les dépôts. Ils sont déjà déclarés en flux :
le locataire **dépose** ses sauvegardes chez le site (SFTP), **tire** ses paquets, ses binaires
et son génome, **entre** par le tunnel d'administration du site ; le site **réplique** les
zones publiques du locataire (AXFR signé TSIG).
#### Ce que le relevé montre
1. **Le site fouille l'intérieur du locataire.** Six fichiers de son plan et de son inventaire,
`group_vars` compris. Rien ne dit ce que le locataire **accepte** de montrer. Renommer un
champ chez le locataire casse le site sans bruit.
2. **Les flux sont calculés deux fois**, par le locataire pour ses `nftables` et par le site pour
la frontière et Proxmox, chacun avec **sa** version du moteur. Ils concordent tant que les deux
runners tiennent le même commit (c'était le cas le 2026-10-04), mais rien ne l'impose.
3. **Le locataire vit de copies** : douze valeurs et un certificat, recopiés à la main, plus une
valeur hors contrat. La garde qui compare ne tourne que sur le poste ; le runner du locataire
ne peut pas savoir que sa copie a vieilli.
4. **L'inventaire d'un locataire dépend du site monté au moment de le générer.** Généré sur le
runner du locataire, qui n'a pas le dépôt du site, il perdrait son serveur de temps, son SDN
et sa délégation DNS. Ça ne s'est jamais vu, parce que l'inventaire est toujours généré sur le
poste puis versionné.
5. **Deux décisions du site** (l'index, l'adresse publique) vivent en double.
#### Proposition : deux fiches, une dans chaque sens
Chacun **publie** ce qu'il donne à l'autre, dans une fiche **générée** par le moteur, jamais
écrite à la main. Chacun ne lit que la fiche que l'autre lui destine. Plus aucune lecture
croisée, plus aucun recalcul.
- **La fiche du site pour un locataire.** Tout ce que le site lui **attribue** (index, adresse
publique) et lui **offre** : les 10 intrants, sa racine de confiance, et ce que l'instancier
allait lire en douce (serveur de temps, délégation DNS, mode SDN et nom des VNets). Une fiche
**par locataire** : aucun ne voit le plan du site ni ses voisins. L'instancier ne lit plus que
cette fiche, et l'inventaire devient **identique où qu'on le génère**.
- **La face réseau du locataire.** Ce qu'il **demande** au site : VM à matérialiser, zones,
domaines publics, clé de sauvegarde publique, accès d'administration, et **ses flux déjà
résolus** (adresses, ports, protocoles), ceux avec l'extérieur pour la frontière et ceux de
chaque VM pour Proxmox. Le locataire génère déjà ses flux résolus (`flux-genere/*.nft`,
`*.connectivite.json`) : la face réseau en est la partie destinée au site. Le site ne
recalcule plus rien : il applique ce que le locataire publie, après l'avoir confronté à sa
propre politique.
- **Chaque fiche porte l'empreinte de sa source**, et une preuve de chaque côté vérifie que la
fiche reçue correspond à ce que l'autre a publié.
**Où en est l'étape 2 (2026-10-04).** La fiche du site (P84), les faits de la face réseau
(P85) et les flux de chaque machine (P86) existent, et disent exactement ce que les lectures
croisées produisent. La première mesure des flux a trouvé une information que le locataire
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
les publie désormais (`sources_declarees`). La frontière, en trois temps : les identités
(P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites.
**L'étape 2 est terminée** (2026-10-05). Étape 3 : l'instancier lit la fiche déposée par le site, et
l'inventaire d'un locataire se génère sans son site, à l'octet près (P91). Le locataire publie sa face
réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox, la frontière et la
découverte des locataires du site la lisent. La matérialisation aussi : la face publie les
paramètres de clonage (P93) ; `locataire-creer`, `locataire-raser` et `placement-plan TENANT=`
nomment leur locataire au lieu de le monter, et visent les mêmes machines, à l'argument près de
la ligne `ansible-playbook` (P94, `test_appels_locataire.py`) ; `reconstruire-locataire` les
emploie. Reste : la preuve par reconstruction.
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
#### Ce que les fiches donnent : la portabilité
Un locataire qui change de site, pour un déménagement, un plan de reprise ou une émancipation,
n'a plus qu'à **recevoir la fiche de son nouveau site**. Son dépôt ne contient plus rien
d'interne à l'ancien : ni copie d'adresse, ni inventaire généré avec l'ancien site monté. En
face, le nouveau site n'a qu'à lire sa **face réseau**. Aujourd'hui, la même bascule demande de
corriger des copies dans plusieurs fichiers, puis de régénérer l'inventaire avec le nouveau site
monté sur le poste.
## 3. Le poste : un sélecteur
Aujourd'hui, le poste monte les deux contextes **en même temps** (`instance/` + `underlay.yml`),
et `ConsolePoste` hérite de `ConsoleLocataire`. Désormais :
- **Le poste choisit un contexte actif** : un site **ou** un locataire. La console ouvre celui-là,
et seulement celui-là.
- **Une opération qui traverse les deux** nomme ses objets au lieu de les deviner.
`reconstruire-locataire` en est l'exemple : le site matérialise, puis le locataire monte.
L'orchestration devient `site.materialiser(locataire)` puis `locataire.monter()`.
- **Les runners ne changent pas** : celui du site n'a qu'un `Site`, celui d'un locataire qu'un
`Locataire`. Leur contexte est désormais **dit**, plus déduit.
## 4. Qui surcharge quoi
| Méthode | Tronc commun | Site | Locataire |
|---|---|---|---|
| `inventaire()` | abstraite | script dynamique (`underlay.yml`) | `hosts.yml` généré du plan |
| `machines()` | abstraite | VM du site + équipements | VM du plan |
| `adressage()` | dérivé de l'index | zones `site` dérivées, liens `fabric` écrits | 6 zones dérivées |
| `sauvegardes()` | son propre état, vérifié par restauration | + héberge les dépôts des locataires | dépose chez son site |
| `supervision()` | sondes déclarées par les rôles | + matériel, fabric, frontière | — |
| `raser()` / `reconstruire()` | — | `site_raser.py` | `raser.py`, `reconstruire_locataire.py` |
| `preuves()` | preuves communes | + preuves de site (P23, P74, P82…) | + preuves de locataire |
| `verbes()` | `verifier`, `publier`… | `site-*`, `frontiere-*`, `proxmox-*` | `appliquer`, `instancier`, `remise-*` |
| `console()` | — | console SITE | console LOCATAIRE |
## 5. Ce qui ne change pas
- Les **rôles Ansible**, déjà communs.
- Les **formats de plan** : pas dans ce chantier.
- La **doctrine** d'`AGENTS.md`.
## 6. Le chemin, chaque pas prouvé avant le suivant
1. **`scripts/contexte.py`** : `Ecosysteme`, `Site`, `Locataire`, `contexte_actif()`,
`Site.charger(nom)`, `Locataire.charger(nom)`. Tests unitaires. Rien ne l'utilise encore.
2. **Les deux fiches**, générées à côté de l'existant sans rien remplacer :
`site.fiche_pour(locataire)` et `locataire.face_reseau()`. Une preuve vérifie que chaque
fiche dit **exactement** ce que les lectures croisées d'aujourd'hui produisent.
3. **Les consommateurs basculent sur les fiches**, un par un : l'instancier sur la fiche du site
(l'inventaire généré doit rester identique, octet pour octet) ; la frontière, Proxmox, le DNS
public et les comptes de sauvegarde sur la face réseau (chaque devis doit rester inchangé).
4. **`prouver.py`** : chaque preuve déclare son contexte (commun, site, locataire) et reçoit son
écosystème du module.
5. **Les autres scripts**, un par un, vérifiés par `make verifier` et par un devis inchangé.
6. **Une preuve « aucune devinette, aucune lecture croisée »** : les indices du §1, et toute
ouverture d'un fichier de l'autre contexte, interdits hors de `contexte.py`.
7. **Les verbes du Makefile** rangés par contexte.
8. **Les consoles** : le sélecteur et deux consoles (chantier suivant).
9. **OPS-Modele** : un locataire modèle, et sans doute un site modèle, vérifiés chacun dans son
contexte.
Après les étapes 3 et 5, une reconstruction prouve que la flotte n'a pas bougé.
## 7. Décisions et questions ouvertes
### Tranché par l'exploitant
- **Deux classes, `Site` et `Locataire`, qui héritent d'un tronc commun** (2026-10-04).
- **Le poste est un sélecteur** : un contexte actif à la fois (2026-10-04).
- **On commence par le moteur**, les consoles viennent ensuite (2026-10-04).
- **Le site dépose sa fiche dans le dépôt du locataire** (2026-10-04), comme il y amorce déjà
son runner. Le runner du locataire n'a besoin d'aucun accès au dépôt du site, et ne voit
ni le plan du site ni ses voisins.
- **Le contexte actif se nomme dans un fichier `contexte` explicite** (2026-10-04), une seule
valeur : `site:SITE-Chezlepro` ou `locataire:OPS-Technolibre`. Un sélecteur qui monte deux
liens à la fois contredirait sa propre règle. Les liens `instance/` et `underlay.yml` restent
le temps de la bascule, lus par le seul `contexte.py`.
- **Un modèle SITE public, `SITE-Modele`**, à côté d'`OPS-Modele` (2026-10-04). Sans site, la
CI ne peut exercer ni les preuves de site (P74, P81, P82) ni la fiche que le site dépose chez
le locataire.
- **Le site a aussi son `parente.yml`** (2026-10-04) : la filiation est dans le tronc commun.

View file

@ -13,7 +13,7 @@ la connexion au cluster Proxmox et les valeurs de clonage par défaut. Il pose
Le chemin se **dérive** du symlink qui désigne déjà l'hébergeur — rien de nouveau
n'est déclaré. Sans underlay monté, tout retombe dans le fichier du tenant et
`make config` fonctionne comme avant.
- Secrets → **voûte unique** `instance/inventories/production/group_vars/all/vault.yml`
- Secrets → **voûte unique** `instance/inventories/<inventaire>/group_vars/all/vault.yml`
(chiffrée par `ansible-vault`), qui contient **tous** les secrets de l'instance
(token Proxmox + `vault_*`). Voir [§4](#4-secrets-de-linstance-).
@ -42,7 +42,7 @@ sans tout retaper.
| Invite | Variable | Défaut | Sens / quoi saisir |
| --- | --- | --- | --- |
| VMID du modèle Debian 13 | `proxmox_clone_vmid_modele` | `9000` | VMID de la VM-modèle existante à cloner pour chaque nouvelle VM. |
| Nom logique du modèle | `proxmox_clone_source_nom` | `modele-debian13` | Nom de référence du template (lisibilité ; doit correspondre au modèle). |
| Nom logique du modèle | `proxmox_clone_source_nom` | `modeleSetOPS` | Nom de référence du template. **Il doit correspondre au nom réel du template Proxmox** : sinon le clonage ne trouve pas sa source. *(Ce tableau a annoncé `modele-debian13` jusqu'au 2026-09-06 — un défaut qui n'a jamais été celui du code.)* |
> Le golden template est l'**actif central** : il est cloné pour chaque VM, jamais
> jeté ni reconstruit à la légère.
@ -68,10 +68,17 @@ Ces valeurs s'appliquent à toute VM clonée, **sauf** si l'hôte les surcharge
## 4. Secrets de l'instance 🔒 *(voûte unique)*
Tous les secrets de l'instance vivent dans **une seule voûte chiffrée par
environnement** : `instance/inventories/<env>/group_vars/all/vault.yml`. Un seul
fichier, un seul mot de passe — fini les voûtes éparpillées. Gabarit committé :
[`exemples/vault.exemple.yml`](../exemples/vault.exemple.yml) (token Proxmox +
17 clés `vault_*` pour PKI, LDAP/SSO, bases, forge, observabilité).
instance** : `instance/inventories/<inventaire>/group_vars/all/vault.yml`. Un seul fichier
par écosystème — fini les voûtes éparpillées.
> **Une voûte, une clé (2026-08-28).** « Un seul mot de passe » a été vrai, et c'était le
> défaut : le même ouvrait *toutes* les voûtes de la flotte, celle de l'hébergeur comprise.
> Chaque dépôt a maintenant **sa** clé — `~/.config/setops-vault-<dépôt-en-minuscules>` —
> et le `Makefile` les rassemble dans `ANSIBLE_VAULT_IDENTITY_LIST` via
> `scripts/voutes.py`. Créer une VM ouvre d'ailleurs **deux** voûtes dans la même
> exécution : celle du tenant, et celle de l'hébergeur qui détient le jeton Proxmox. Gabarit committé :
[`exemples/vault.exemple.yml`](../exemples/vault.exemple.yml) (les deux clés du token
Proxmox + **15 clés `vault_*`** pour PKI, LDAP/SSO, bases, forge, observabilité).
L'assistant demande « Configurer la voûte de secrets maintenant ». Si `oui` :
@ -96,10 +103,16 @@ L'assistant demande « Configurer la voûte de secrets maintenant ». Si `oui` :
| **Saisir** | un **tiers** — le secret existe déjà ailleurs et ne s'invente pas (clé d'API OPNsense, jeton Proxmox) | `python3 scripts/voute.py saisir <clés>` |
```bash
ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass \
python3 scripts/voute.py saisir vault_opnsense_api_key vault_opnsense_api_secret
python3 scripts/voute.py saisir vault_opnsense_api_key vault_opnsense_api_secret
```
Il n'y a **rien à exporter** : `voute.py` trouve la clé de la voûte par la convention de
nommage (`scripts/voutes.py etat` la montre). Il n'y a pas non plus de cible `make` pour ce
geste — c'est délibéré : saisir un secret est une manœuvre rare et attentive.
*(`voute.py` au singulier manipule **le contenu** d'une voûte ; `voutes.py` au pluriel dit
**où sont les clés**. Les deux existent, et ce n'est pas une faute de frappe.)*
Saisie **sans écho**, double confirmation, rien sur la ligne de commande — donc ni
dans l'historique du shell, ni dans la liste des processus. Rien n'est écrit en clair
sur disque : la voûte est déchiffrée en mémoire, complétée, reparsée et re-déchiffrée

View file

@ -33,40 +33,94 @@ couches:
groupes:
- client_pki
- nom: observabilite
raison: >-
VOIR AVANT DE CONSTRUIRE. La mesure vient juste après la PKI, donc avant tout ce
qu'elle devra surveiller — et non après, comme si l'on n'allumait la lumière qu'une
fois la maison finie. Une reconstruction depuis zéro est précisément le moment où
l'on a le plus besoin de voir ce qui se passe : chaque rôle déployé ensuite l'est
sous l'œil de la supervision, et une unité qui casse se voit à la minute plutôt qu'à
la fin. `obs` ne dépend de rien ; `serveur_icinga` n'exige que PostgreSQL, qui
n'exige rien lui-même. Ce qui reste plus tard, c'est la CONSOLE (`icingaweb2`,
`oauth2_proxy`), qui demande LDAP et Keycloak — l'interface humaine peut attendre,
la mesure non.
groupes:
- serveur_postgresql
- serveur_prometheus
- serveur_loki
- serveur_grafana
- serveur_icinga
- nom: agents_supervision
raison: >-
Les agents qui FONT voir, poses juste apres leurs serveurs. Deplacer la supervision
en amont sans eux n'aurait rien change : ce sont eux qui rapportent. Des cette
couche, chaque hote expedie ses metriques, ses journaux, et l'etat de ses unites
systemd — donc tout ce qui se deploie apres est mesure pendant qu'on le construit.
groupes:
- client_metrique
- client_journal
- client_sante
- nom: services
raison: "Les services d'infrastructure dont dépendent les applications : bases, annuaire, DNS, cache, métriques, journaux, edge, courriel."
groupes:
- serveur_postgresql
- serveur_openldap
- serveur_powerdns
# Le resolveur du tenant vient APRES son autoritatif : il le prend en stub-zone,
# et sa validation exige que la zone souveraine reponde deja.
- serveur_resolveur
- serveur_redis
- serveur_prometheus
- serveur_loki
- serveur_nginx
- serveur_rspamd
- serveur_dovecot
- serveur_postfix
- serveur_backup
# La source d'artefacts vient AVANT ceux qui installent des paquets — c'est tout
# son objet. Placee plus tard, elle serait remplie apres avoir servi.
- serveur_artefacts
# LA RACINE DE LA CHAINE DE CACHES, et elle appartient au SITE, pas au tenant :
# VM du tenant -> cache du tenant -> cache du SITE -> Debian
# Classee ici pour que son ordre soit dit, mais elle ne se deploie pas dans le meme
# mouvement : elle vit dans l'underlay de l'hebergeur et se joint par
# `scripts/site_inventaire.py`. Un tenant ne la deploie jamais — il la CONSOMME.
- serveur_cache_site
# La forge du genome, meme nature : elle vit dans l'underlay de
# l'hebergeur et un tenant la CONSOMME sans jamais la deployer.
- serveur_forge_site
# Le resolveur du site, meme nature : prete aux locataires pendant leur jeunesse.
- serveur_resolveur_site
# Le depot de sauvegarde du site, meme nature : il recoit l'etat des locataires.
- serveur_backup_site
# Le DNS public du site vient APRES les services : il tire les zones que les
# autoritatifs des locataires ecrivent, et n'a rien a servir avant eux.
- serveur_dns_public
- nom: apps
raison: "Les applications métier, qui consomment les services (base, SSO, courriel, edge)."
groupes:
- serveur_keycloak
- serveur_oauth2_proxy
- serveur_forgejo
- serveur_icinga
- serveur_icingaweb2
- serveur_grafana
- serveur_collabora
- serveur_nextcloud
- serveur_web_frontal
- serveur_web_dorsal
# Le poste d'exploitation vient APRES la forge : il clone le genome depuis
# elle. Le placer plus tot le laisserait sans source.
- serveur_ops
# Le runner de TENANT vient APRES le poste : il suppose les depots clones et le lien
# `instance` pose. Il n'ajoute qu'un pouvoir -- celui de CONFIGURER cet ecosysteme,
# par sa voute deposee chiffree.
- serveur_ops_tenant
# Le runner de SITE vient APRES le runner de tenant : il suppose les depots
# clones. Il n'ajoute qu'un pouvoir -- celui de materialiser sur la fabric.
- serveur_ops_site
- nom: agents
raison: "Les intégrations clientes qui expédient vers les services centraux (métriques, journaux, courriel, sauvegardes, résolution locale). Déployées en dernier, quand leurs cibles sont debout."
groupes:
- client_metrique
- client_journal
- client_smtp
- client_backup
- client_unbound
- client_resolveur
- client_artefacts

View file

@ -2,8 +2,15 @@
> **Pour qui :** le **mainteneur** du service de courriel.
> **Statut : CONCEPTION (cadrage).** Aucun rôle n'est encore écrit. Ce document fixe
> les décisions, les prérequis et la topologie avant toute implémentation.
> **Statut, revu le 2026-09-06 — l'Étape A est LIVRÉE, l'Étape B reste du cadrage.**
> Ce document annonçait « aucun rôle n'est encore écrit » : les trois rôles
> (`serveur_postfix`, `serveur_dovecot`, `serveur_rspamd`) existent, sont déployés, et le
> flux interne est **prouvé de bout en bout** — SMTP → validation LDAP → LMTP chiffré →
> boîte → **lecture IMAP**, avec antispam et signature DKIM. Ce qui n'est **pas** livré,
> c'est l'**Étape B** (§11) : la face publique — Let's Encrypt, reprise du MX `.53`,
> enregistrements chez Namespro, tests de délivrabilité. Lire ce document ainsi : les
> décisions du §1 sont **arrêtées et appliquées** ; la feuille de route du §11 est
> **ouverte**.
## 1. Décisions arrêtées
@ -194,13 +201,16 @@ _dmarc TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@chezlepro.
But : prouver **toute la pile en interne**, en **code de prod**, dans le bac à sable.
Aucune dépendance au public.
1. **Pilier identité** : `serveur_openldap` (TLS **step_ca**) — ✅ **déployé et prouvé** en bac à sable.
2. **`serveur_postfix` + `serveur_dovecot` + `serveur_rspamd`** sur `mail-01` : TLS **step_ca**,
annuaire/auth **LDAP**, DKIM interne, nftables mail.
1. **Pilier identité** : `serveur_openldap` (TLS **step_ca**) — ✅ **déployé et prouvé**.
2. **`serveur_postfix` + `serveur_rspamd`** sur `edge-mta-01`, **`serveur_dovecot`** sur
`infra-mail-01` : TLS **step_ca**, annuaire/auth **LDAP**, DKIM, nftables mail — ✅ **déployés**.
3. **Prouver** : réception → boîte → accès **IMAP** → envoi **intra-écosystème**, le tout en
TLS interne, auth LDAP.
TLS interne, auth LDAP — ✅ **prouvé de bout en bout**, et rejoué à la demande par
`make courriel-plan`, file d'attente comprise.
*(Étape actuelle : identité OK ; on démarre `serveur_postfix`.)*
*(Ce paragraphe disait « Étape actuelle : identité OK ; on démarre `serveur_postfix` » et
plaçait toute la pile sur un `mail-01` unique. La topologie retenue est celle du §3 révisé —
le MTA en périphérie, les boîtes à l'intérieur — et l'Étape A est close.)*
### Étape B — Fonctionnement EXTERNE (transition prod, plus tard)
@ -216,4 +226,5 @@ But : brancher sur le monde **en reprenant l'existant** (voir §2).
---
*Ce document est un cadrage vivant : il évolue à mesure que les décisions ouvertes se
tranchent. Il ne décrit pas encore de code livré.*
tranchent. **L'Étape A qu'il décrit est livrée et prouvée** ; la séquence ci-dessus, qui est
l'Étape B, ne l'est pas.*

View file

@ -55,6 +55,14 @@ sont les seules vérifiables.
| # | Décision | Pourquoi | Détail | Garde |
|---|---|---|---|---|
| **D-82** | **Patient 0 n'est le parent de personne.** Il est la **mise en œuvre de référence** du modèle `origine` — le plus petit écosystème complet — et un pair de la famille du génome, pas sa racine | trois faits l'ont retiré un par un : D-81 a donné l'autorité du génome à la forge du SITE (son dernier lecteur corrigé le 2026-08-26, Technolibre le 08-31) ; le dénominateur commun vit dans les modèles depuis le 08-24 ; et **l'ancêtre était locataire de son enfant** — index 29 sur la fabric de `SITE-Chezlepro`, qui descend de lui. `eregion` (`forge.alliance-boreale.ca`) n'est PAS sur le chemin du génome : le poste porte les commits en bundle au runner du site (`make genome-pousser`), qui pousse sur sa forge. `eregion` est une forge héritée, porte publique des contributions, qui ne fera jamais partie de Set-OPS — la redondance vient d'une forge par site, chacune inséminée du génome *(corrigé le 2026-09-28 : cette ligne l'avait dite « SPOF promu », sans mesurer le chemin)* | `OPS-Patient0/README.md`, `docs/filiation-emancipation.md` | — |
| **D-83** | **Patient 0 a été retiré** — ses machines n'existent plus (constaté le 2026-09-06) | D-82 lui avait laissé une raison d'être : la mise en œuvre de référence du modèle `origine`, et un **témoin** de plus du génome. Le retrait solde les deux : l'une vit dans le modèle `origine`, l'autre revient aux forges de site. **Son plan a été effacé le 2026-09-27** : l'index 29 est libéré, et le site n'ouvre plus rien à `10.29.0.0/16` | `SITE-Chezlepro/underlay.yml` (`tenants`), `SITE-Chezlepro/flux-genere/` | **P21** (index), **P23** |
| **D-84** | **Le plan de contrôle reste gelé — c'est la CARTE DES SEUILS qui était fausse** | La question « et si on retirait le gel ? » a mis à l'épreuve les cinq seuils de `positionnement.md`, et deux ne tenaient pas. **RBAC** : couvert depuis que trois classes d'acteurs aux pouvoirs disjoints existent — poste, runner de site, runners de tenant — séparés **cryptographiquement** (une voûte, une clé, 2026-08-28) et non par une table de permissions qu'une faille applicative contournerait ; adopter AWX pour ce besoin serait **régresser**. **IPAM** : sans objet par construction — rien ne s'alloue, tout dérive du seed, et P20/P21/P23/P28/P33 tiennent déjà ce qu'un IPAM vérifierait *a posteriori*. Les deux lignes sont retirées du tableau : les garder aurait fait adopter un outil pour un besoin déjà rempli. **Et un seuil manquait** — l'**émancipation** : le GUI est mono-utilisateur (`127.0.0.1` + jeton), or la trajectoire mène à plusieurs humains aux portées disjointes, sur des machines qui ne sont pas les nôtres. Ce seuil n'appelle pas AWX, il appelle une décision non prise. *Un seuil qu'on ne nomme pas est un seuil qu'on franchit sans le voir.* Corollaire consigné : le gel porte sur les **fonctions**, jamais sur les **vues** — montrer à l'écran ce que le moteur sait déjà ne franchit aucun seuil | `positionnement.md` §3, §4, §5 | — |
| **D-85** | **cloud-init naît avec la VM et ne lui survit pas** | cloud-init n'est pas un logiciel d'installation : c'est une **source de vérité externe**, qui se réveille à *chaque* démarrage et relit le lecteur attaché par l'hyperviseur — lequel peut redéfinir comptes, clés SSH autorisées, mots de passe et réseau. Sur une machine que le plan possède, c'est un **second maître** : le plan ne le décrit pas, `make valider` ne le mesure pas, et il parle en premier. Sa tâche est pourtant finie à la première seconde — c'est parce qu'il a **réussi** à poser l'adresse et les clés qu'Ansible a pu entrer. **Trois moitiés, qui se défont séparément** : le **gabarit** le garde (sans lui un clone ne naît pas — P56) ; le **socle** ne l'installe plus (le garder produisait un va-et-vient à chaque déploiement : le socle installe, le durcissement retire, deux `changed` par passage) ; le **durcissement** le retire (`cloud_init_retrait`, en dernier). **Ce qui rend le retrait sûr est mesuré, pas supposé** (2026-09-09, `obs-01`) : `/etc/network/interfaces.d/50-cloud-init` n'appartient à aucun paquet — `dpkg -S` ne le trouve pas — et le `postrm` ne le nomme jamais, même en `purge`. L'adresse survit. Le rôle le **vérifie** malgré tout, avant et après : une VM qui perd ce fichier ne se plaint pas, elle repart sans adresse et plus personne ne peut entrer pour le constater. **⚠ CE QUE CETTE DÉCISION NE FERME PAS — et il faut le dire, sinon elle se lit comme une émancipation qu'elle n'est pas.** Retirer cloud-init **n'ôte aucun pouvoir à l'hébergeur**. `qemu-guest-agent` est au gabarit (P56 : il doit y être — c'est par lui que `creer-vm` confirme la matérialisation sans entrer chez le tenant), et l'API Proxmox expose sur son dos, sur toute VM vivante de la flotte, un pouvoir **strictement plus grand** que le lecteur cloud-init : `exec`, `file-write`, `file-read`, `set-user-password`, `shutdown` (relevé le 2026-09-09 sur `edge-mta-01`, jeton d'API du site). Ce que D-85 ferme est donc **précis et étroit** : (a) une réapplication **automatique, à chaque démarrage**, depuis un support que le plan ne possède pas et qu'aucune preuve ne lit ; (b) le code de cloud-init lui-même — un interpréteur Python complet, exécuté en root au démarrage, et ses ~29 dépendances. Elle ne ferme **pas** la mainmise de l'hyperviseur sur ses invités : celle-là est une propriété de la virtualisation, pas de cloud-init, et elle appelle sa propre décision — non prise. **Le seuil où le remplacer deviendrait juste** : le jour où une première seconde ne peut plus être amorcée par Proxmox (autre hyperviseur, métal nu, hébergeur sans API), le chemin par l'agent invite cesse d'être une réimplémentation d'un standard — que `positionnement.md` interdit — et devient **le chemin portable**. Tant que ce seuil n'est pas atteint, écrire soi-même l'amorçage serait échanger un standard éprouvé contre du code maison au moment le plus fragile, dont le mode de panne est le pire : une VM injoignable | `roles/cloud_init_retrait/`, `serveur_durci.yml`, `positionnement.md` | **P63** |
| **D-86** | **La supervision se déploie juste après la PKI, pas à la fin** | L'observabilité (`prometheus`, `loki`, `grafana`) et le **moteur** de supervision (`icinga`) passent en couche 4, immédiatement après `client_pki` ; les agents qui les nourrissent (`client_metrique`, `client_journal`, `client_sante`) en couche 5. **Le raisonnement** : ce qui se déploie ensuite l'est *sous l'œil* de la supervision — une unité qui casse se voit à la minute, pas à la fin. Une **reconstruction depuis zéro** est précisément le moment où l'on a le plus besoin de voir, et c'était le seul moment où l'on ne voyait rien. **Déplacer les serveurs sans les agents n'aurait rien changé** : ce sont les agents qui rapportent, et ils étaient en dernière couche. **Ce que ça a coûté en dépendances** : `serveur_postgresql` monte aussi (il n'exige rien lui-même, et `icinga` l'exige). **Ce qui reste tard, à dessein** : `icingaweb2` et `oauth2_proxy` réclament LDAP et Keycloak — c'est la CONSOLE, pas la mesure. L'interface humaine peut attendre. **Limite dite franchement** : les *notifications* dépendent de `client_smtp`, encore en dernière couche — pendant une reconstruction, l'état est mesuré et consultable, mais rien ne part par courriel avant la fin. **CE QUI REND CE DÉPLACEMENT POSSIBLE**, et qui n'est pas un détail : le DNS (`powerdns`, `resolveur`) reste en couche 6, donc *après* la supervision. Or `icinga` joint sa base par un **nom** (`data-sql-01.chezlepro.internal`), et `client_sante` pousse vers un **nom**. Ça tient parce que le **plancher `/etc/hosts`**, posé dès la couche 1 par `hosts_statiques`, porte déjà les 34 entrées de l'écosystème — vérifié. C'est exactement ce pour quoi il existe : *« il ne s'installe pas, il rend installable »*. Sans lui, cette décision serait impossible. **P08** valide l'ordre : aucune arête en arrière | `docs/couches-deploiement.yml`, `catalogue-services.md` §Ordre | **P08** |
| **D-87** | **Ce que l'hébergeur n'a pas le droit de VOIR** — l'observabilité découpée, la PKI tranchée | La question *« quels rôles ne dois-je pas embarquer dans le site ? »* a mis à l'épreuve la table de mutualisation de `filiation-emancipation.md`, et **une ligne contredisait ce qui tourne**. Elle disait « observabilité \| oui \| l'hébergeur surveille ses locataires » — or chaque écosystème a son propre Icinga, et celui du site ne voit que ses sept machines (mesuré). Surtout, elle autorisait en une case ce que la ligne du dessous interdit : **les journaux contiennent du contenu** — un mot de passe dans un message d'erreur, une donnée métier dans une trace. Un hébergeur qui ingère les journaux de son locataire en sait **plus** que s'il détenait son annuaire ; l'annuaire dit qui existe, les journaux disent ce qu'ils font. **Découpée en trois** : *disponibilité* oui (une VM tombée est un fait de la fabric), *métriques* oui avec réserve (elles disent quand et combien, ce qui suffit à lire l'activité d'une organisation), *journaux* **non**. **Et la ligne PKI, « à trancher », est tranchée : non.** Une AC intermédiaire signée par l'hôte lui donnerait le pouvoir d'émettre des certificats valides pour les noms du locataire, donc de se présenter comme n'importe lequel de ses services — devant les propres machines du locataire, qui les accepteraient, puisque c'est ce que la chaîne de confiance leur demande. Même pouvoir que l'annuaire, sous une forme **moins visible** : aucune trace côté locataire. Une PKI par écosystème, jamais dérivée de l'hôte. **Le revers, mesuré et assumé** : le site n'a NI `client_journal` NI `client_metrique`, aucun `loki` ni `prometheus` — à refuser de voir ceux des locataires, il s'est privé des siens. Le remède n'est pas d'assouplir la règle, c'est de lui donner sa propre pile | `filiation-emancipation.md` §mutualisable | — |
| **D-88** | **Le nœud qui porte le gabarit est un point unique de défaillance — pour la REPRODUCTION, pas pour l'exploitation** | Question posée par l'exploitant le 2026-09-10 : *« le modèle vit sur vishnu, les clones sont sur asgard — qu'arriverait-il si vishnu tombait ? »*. **Mesuré, la réponse se coupe en deux.** Les **données** survivent : le pool `CephNVMe` est en `size=3 / min_size=2`, avec des OSD sur les trois hôtes, et `base-9006-disk-0/1` y sont répliquées — l'image reste lisible avec un nœud en moins, et les VM d'un écosystème tournent ailleurs sans s'apercevoir de rien. Mais la **configuration** du gabarit porte le nom du nœud dans son chemin (`/etc/pve/nodes/vishnu/qemu-server/9006.conf`), et le clonage appelle `nodes/vishnu/qemu/9006/clone` : nœud éteint, API muette, **aucune VM nouvelle ne peut naître**. Or la reproduction est ce que ce dépôt existe pour garantir. **La décision est d'ASSUMER cette dépendance et de la rendre courte**, pas de la supprimer : depuis que le disque du gabarit vit sur un stockage partagé (2026-09-10), la remise en route est un **déplacement de fichier de configuration** — quelques minutes, aucun mouvement de données — suivi de la déclaration `gabarit.noeud`. Sur stockage local il aurait fallu recopier 16 Go ou refabriquer. **Ce qui n'est PAS fait, et qui est dit** : rien ne *mesure* cette dépendance. `gabarit_etat` compare le déclaré au réel, il ne demande pas si le nœud du gabarit héberge autre chose que le gabarit. *Une dépendance qu'on documente sans la mesurer reste une dépendance qu'on découvrira au mauvais moment.* | `runbooks-exploitation.md` §7, `SITE-Chezlepro/plan/10-intrants.yml` §gabarit | — |
| **D-81** | **La forge du SITE fait autorité pour le génome.** Toute autre copie — y compris celle d'où le moteur a été poussé jusqu'ici — est un **miroir**. Le poste de l'exploitant ne route pas jusqu'à elle : c'est le **runner du site** qui publie, par `make genome-pousser` | un écosystème se reproduit depuis la forge de son site : c'est de là qu'il clone son moteur, ses plans, ses modèles. Si l'autorité est ailleurs, cette forge devient un cache qu'on croit à jour — et le 2026-08-26 elle était **quatre commits en arrière** sans que rien ne le signale, dont le correctif qui désarme le pare-feu Proxmox. **Un écosystème qui se reproduit depuis une forge en retard reproduit ses défauts.** Le poste n'a de patte que sur l'administration, et on ne perce pas de chemin pour lui : le runner existe pour ce travail | `playbooks/maintenance/genome_pousser.yml`, `scripts/genome_colis.py`, `Makefile` §genome-pousser | — |
| **D-13** | Un **hébergeur** sert plusieurs **tenants** et a son tenant par défaut | Chezlepro est les deux à la fois, ce qui masquait la distinction | `frontiere-opnsense.md` §2 | — |
| **D-14** | `underlay.yml` appartient à l'**hébergeur**, monté par symlink | ce sont ses commutateurs, ses câbles ; le moteur est générique, un tenant n'en possède pas | `sdn-evpn.md`, `underlay.yml.example` | — |
| **D-15** | Ce symlink **ne suit pas** `make instance-utiliser` | basculer le tenant actif ne change pas la fabric | `frontiere-opnsense.md` §2 | — |
@ -69,9 +77,9 @@ sont les seules vérifiables.
| **D-48** | Les **hyperviseurs** sont gérables par Ansible ; « hors flotte » ne vaut que pour les **commutateurs** et la **frontière** | ce sont des Debian joignables en SSH ; c'est la seule façon d'y poser un exportateur de métriques | `hebergeur-exploitation.md` §5 | — |
| **D-55** | Le dépôt réseau porte une **interface normalisée** vers les tenants de l'Alliance, et abstrait le matériel en les encapsulant dans des zones EVPN | un tenant qui ne nomme aucun équipement se déplace d'un hébergeur à l'autre sans rien changer ; le VRF borne ce qu'il a le droit de connaître | `hebergeur-exploitation.md` §7 | — |
| **D-56** | Le **VNet d'une VM est dérivé** (`index` + zone), jamais déclaré ; l'étiquette VLAN est **vide** en SDN | déclaré, il faisait naître les VM sur `vmbr1` avec un tag — l'ancien monde, à rebrancher une par une | `instancier.py` | P02, P03 |
| **D-53** | Le **réseau et l'underlay** de l'hébergeur méritent leur **propre dépôt**, séparé de son tenant | `underlay.yml` et le cluster décrivent une infrastructure ; le dépôt de tenant décrit une organisation. Les mêler oblige à trancher qui possède quoi à chaque commit | `hebergeur-exploitation.md` §7 | — |
| **D-54** | `10.0.0.0/24` est réservé à l'**IPAM, la gestion des équipements et l'OOB** — accès sysadmin | aucune VM, aucun trafic tenant ; c'est la raison d'être des VLAN 11 et 40 | `underlay.yml` | — |
| **D-57** | L'interface **sysadmin** d'un hyperviseur (`vmbr0`) n'a **pas de route par défaut** ; celle-ci vit sur `vlan40`, vers la frontière | on n'atteint l'administration que depuis son propre domaine de diffusion — un accès distant doit être ouvert explicitement, il ne peut pas exister par accident. Et le trafic tenant ne touche plus la carte d'administration | `underlay.yml` | — |
| **D-53** | Le **réseau et l'underlay** de l'hébergeur ont leur **propre dépôt**, séparé de son tenant — **appliqué** : `SITE-Chezlepro` | `underlay.yml` et le cluster décrivent une infrastructure ; le dépôt de tenant décrit une organisation. Les mêler obligeait à trancher qui possède quoi à chaque commit. Le dépôt porte aussi, depuis, le **plan des VM du site** : l'hébergeur n'est pas qu'un porteur de fabric, c'est un exploitant | `hebergeur-exploitation.md` §8 | — |
| **D-54** | Le **plan d'administration** est réservé à l'**IPAM, la gestion des équipements et l'OOB** — accès sysadmin | aucune VM, aucun trafic tenant ; c'est la raison d'être des VLAN de transport et de transit, qui l'en sortent. **Adresses révisées le 2026-09-06** : la décision citait `10.0.0.0/24` et « les VLAN 11 et 40 ». D-77 a déplacé ce plan en `10.<index>.0.0/24` (`10.17.0.0/24` chez l'hébergeur de référence, **sans VLAN** — segment physique, aucun pont ne le touche), et D-78 a fait passer le transport VXLAN au **VLAN 50**. Le principe est intact ; seules les adresses ont bougé | `underlay.yml` | P23 |
| **D-57** | ~~La route par défaut d'un hyperviseur vit sur `vlan40`, vers la frontière~~ → **NON APPLIQUÉE, et gelée** | L'intention tient : le trafic tenant ne doit pas toucher la carte d'administration. Mais la bascule elle-même **n'a pas été faite et ne doit pas être proposée** : la route par défaut des hyperviseurs reste sur `vmbr0`, vers le routeur du site (`192.168.11.254`), et c'est un état **gelé**. Conséquence assumée, écrite noir sur blanc dans l'`underlay.yml` : ce que ce plan envoie dehors **ne passe pas par la frontière**. Déplacer la route par défaut d'un hyperviseur en service, c'est risquer de perdre l'hyperviseur *et* le chemin pour le réparer | `underlay.yml` | — |
| **D-58** | Un hôte déclare **par quelle interface** (`via`) chaque réseau lui arrive ; le devis en dérive un **port par interface** et son **type** | un hyperviseur a plusieurs pattes ; les grouper remettait la gestion sur le trunk du transport | `devis_reseau.py` | P23 |
| **D-59** | Un VLAN qui ne porte que des **adresses d'hôte** n'a **pas besoin de pont** | un pont sert à brancher des invités ; vide, il coûte une table MAC et un saut de plus sur le lien qui porte tout le trafic tenant | `underlay.yml` | — |
| **D-18** | Chaque tenant a un **responsable désigné** | sans lui, « qui peut décider de déménager cette organisation ? » se pose au pire moment | `migration-tenant.md` §3 | — |

View file

@ -12,6 +12,12 @@ groupes:
raison: "Les exporters clients doivent etre collectes par Prometheus."
surveillance: "Verifier targets Prometheus, scrape duration et erreurs de collecte."
client_sante:
requiert_groupes_actifs:
- serveur_icinga
raison: "Le rapport de sante depose un resultat passif sur l'API d'Icinga."
surveillance: "Verifier que chaque noeud rapporte : un service `sante` EXPIRE vaut un echec."
client_journal:
requiert_groupes_actifs:
- serveur_loki
@ -68,10 +74,83 @@ groupes:
requiert_groupes_actifs:
- serveur_postgresql
- serveur_nginx
# UNE EXIGENCE PEUT ETRE CONDITIONNELLE (2026-08-22). Depuis que le role sait tenir sa
# base dans un fichier, exiger un serveur PostgreSQL est faux pour qui a choisi SQLite
# — et bloquait le deploiement d'un ecosysteme parfaitement coherent.
sauf_si:
serveur_postgresql: { variable: serveur_forgejo_bd, vaut: sqlite }
# UTILISE SI PRESENT : Forgejo envoie des notifications quand un MTA existe, et s'en
# passe sinon. Ce n'etait pas une EXIGENCE — le confondre avec une exigence obligeait
# une forge a deployer une pile courriel pour exister.
utilise_si_present:
- serveur_postfix
raison: "Forgejo depend d'une base, d'une publication HTTP(S) et d'un relais courriel (MTA Postfix)."
raison: "Forgejo depend d'une base (serveur ou fichier) et d'une publication HTTP(S) ; le courriel est un agrement."
surveillance: "Verifier HTTP(S), base, files Git et envoi courriel."
serveur_ops:
requiert_groupes_actifs:
- serveur_forgejo
# LE POSTE LIT LE GENOME SUR LA FORGE DE SON PROPRE ECOSYSTEME — c'est ce qui le rend
# autonome : il ne redemande rien a son parent. Sans forge, il n'a aucune source.
#
# Une forge EXTERNE reste possible (un ecosysteme peut lire le genome ailleurs) : il
# suffit de surcharger `serveur_ops_forge_url`. L'exigence tombe alors, comme pour
# toute exigence conditionnelle du registre.
sauf_si:
serveur_forgejo: { variable: serveur_ops_forge_externe, vaut: true }
# UTILISE SI PRESENT : sans confiance PKI, `git clone` refuse le certificat de la
# forge — et il a raison de refuser. Ce n'est pas une exigence du groupe : une forge
# a certificat public se cloner sans client_pki.
utilise_si_present:
- serveur_step_ca
raison: "Le poste d'exploitation clone le genome depuis la forge de l'ecosysteme ; sans elle, il n'a pas de source."
surveillance: "Verifier que les depots clones suivent leur amont et qu'ansible repond dans le venv."
client_artefacts:
requiert_groupes_actifs:
- serveur_artefacts
# L'INTEGRATION SUIT L'EXISTENCE DU SERVICE. Un ecosysteme sans source d'artefacts
# prend ses paquets a l'amont : c'est un choix valide, pas une panne. Le role se
# desactive alors seul (`client_artefacts_actif` derive de l'inventaire) et RETIRE la
# direction posee auparavant -- sans quoi les hotes resteraient braques sur une
# machine disparue.
sauf_si:
serveur_artefacts: { variable: client_artefacts_actif, vaut: false }
raison: "Un hote ne peut prendre ses paquets chez lui que si l'ecosysteme heberge une source."
surveillance: "Verifier que le cache repond sur 3142 et que les hotes le designent bien."
serveur_artefacts:
requiert_groupes_actifs: []
raison: "Un cache apt ne depend d'aucun service de l'ecosysteme : il ne fait que relayer et retenir."
surveillance: "Verifier l'ecoute sur 3142, le taux de service depuis le journal, et l'espace du cache."
serveur_ops_tenant:
requiert_groupes_actifs:
- serveur_ops
# LE RUNNER DE TENANT EST ADDITIF, comme celui du site : il suppose le poste
# d'exploitation en place, dont il reutilise la racine, l'utilisateur, les depots
# clones et le lien `instance`. Seul, il ne ferait que deposer un secret sur une
# machine qui n'a pas le plan qu'il ouvre.
raison: "Le runner de tenant n'ajoute qu'un pouvoir a un poste d'exploitation existant : sans lui, rien a quoi l'attacher."
surveillance: "Verifier que la voute de l'ecosysteme est presente ET CHIFFREE sous son dossier d'inventaire."
serveur_ops_site:
requiert_groupes_actifs:
- serveur_ops
# LE RUNNER DE SITE EST ADDITIF : il suppose le poste d'exploitation en place, dont il
# reutilise la racine, l'utilisateur et les depots clones. Seul, il n'aurait ni carte
# de la fabric ni moteur pour agir.
raison: "Le runner de site n'ajoute qu'un pouvoir a un poste d'exploitation existant : sans lui, rien a quoi l'attacher."
surveillance: "Verifier que la voute du site est presente ET CHIFFREE, et que la carte de la fabric est lisible."
serveur_resolveur:
requiert_groupes_actifs:
- serveur_powerdns
# Le resolveur prend la zone souveraine en STUB-ZONE : sans autoritatif, il ne saurait
# resoudre aucun nom de l'ecosysteme, et sa propre validation echouerait.
raison: "Le resolveur du tenant delegue la zone souveraine a l'autoritatif ; sans lui, il ne sait rien de l'ecosysteme."
surveillance: "Verifier qu'il repond pour la zone interne ET pour un nom de l'Internet."
serveur_nextcloud:
requiert_groupes_actifs:
- serveur_postgresql

View file

@ -16,9 +16,21 @@ make mtu-mesurer # l'invité porte-t-il le MTU de sa zone SDN ?
make versions-mesurer # de combien nos épinglages ont-ils vieilli ?
```
**Le patron a été porté sous les services, au monde physique** — mêmes pièces (un playbook
qui relève, un script qui compare), même refus d'écrire. Ce document ne traite que la
moitié haute ; ces cinq-là existent aussi :
```
make frontiere-plan # les règles de la frontière OPNsense contre leur devis
make proxmox-fw-plan # le pare-feu est-ouest de l'hyperviseur contre le registre des flux
make sdn-plan # la zone EVPN, ses VNets, et la sortie des VRF
make underlay-plan # l'underlay déclaré contre ce que le cluster porte vraiment
make placement-plan # chaque VM est-elle là où le plan la met
```
## Le trou qu'il comble
`scripts/prouver.py` porte 35 preuves. Elles sont toutes **statiques** : elles lisent le
`scripts/prouver.py` porte 94 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
propriétaire, que rien n'est codé en dur.
@ -215,7 +227,7 @@ Une API est souvent préférable, mais pour une raison précise : elle rend la r
l'attendu dans le réel ; si rien ne change, c'est conforme*. Une interface qui n'accepte
que des écritures ne peut pas le soutenir.
**Les cinq devis sont cette relecture**, faite après coup et par une autre main que celle
**Ces devis sont cette relecture**, faite après coup et par une autre main que celle
qui a écrit. C'est ce qui les distingue d'un déploiement : `make deployer` réconcilie, les
devis constatent.
@ -242,5 +254,12 @@ La forme correcte, reprise dans les deux devis :
Ils ne corrigent pas — c'est `make deployer` qui réconcilie. Ils répondent à l'autre
question, et sortent en code 1 s'il y a un écart.
Ils couvrent l'identité et les certificats. Le courriel, la base de données et les
expositions web attendent le même traitement ; le patron est là pour être repris.
**Ce paragraphe disait, en dernière ligne du document, que le courriel, la base de données
et les expositions web « attendent le même traitement ».** Ils l'ont reçu — ce document
décrit leurs trois devis quelques écrans plus haut, et le patron a même été porté sous les
services, au monde physique (frontière, pare-feu de l'hyperviseur, SDN, underlay, placement).
Ce qui reste vraiment hors de leur portée, et qu'aucun ne mesure : la **tenue sous charge**
et le **comportement dans la durée**. Un devis dit que le service rend son service à
l'instant où on le lui demande, pas qu'il le rendra encore à mille utilisateurs, ni dans six
mois.

View file

@ -28,7 +28,7 @@ que recalculer la cible ; aucune dérive silencieuse.
| Donnée | Emplacement | Remarque |
| --- | --- | --- |
| Empreinte d'un logiciel | `roles/<groupe>/meta/empreinte.yml` | Propriété du logiciel, voyage avec le rôle. Fichier *pur données* (parsable sans Jinja). |
| Groupes sans rôle dédié | `EMPREINTES_SANS_ROLE` dans `scripts/inventory_rules.py` | p. ex. `serveur_web_frontal`, `serveur_web_dorsal`. |
| Groupes sans rôle dédié | `EMPREINTES_SANS_ROLE` dans `scripts/inventory_rules.py` | **repli aujourd'hui vide d'effet** : tous les groupes de service ont leur rôle. Voir l'encadré plus bas. |
| Repli ultime | `EMPREINTE_DEFAUT` | groupe inconnu : empreinte minimale. |
| Socle SE | `SOCLE_SE` dans `inventory_rules.py` | coût de base Debian durci. |
| Override par hôte | `serveurs.yml` (`coeurs`/`memoire`/`disque`) | déjà supporté par le générateur (`PLACEMENT`). |
@ -65,24 +65,35 @@ la somme. Le socle (`serveur_debian`/`serveur_durci`) et les groupes d'état son
5. `playbooks/proxmox/cloner_vm_debian.yml` — passe `cores`/`memory` à `proxmox_kvm`
(avec `omit` si absent : aucune régression, on garde alors les specs du template).
## Empreintes actuelles
## Les empreintes : ne pas les recopier, les mesurer
| Rôle | cœurs | RAM (Mo) | disque (Go) |
| --- | --- | --- | --- |
| serveur_step_ca | 1 | 256 | 2 |
| serveur_powerdns | 1 | 512 | 2 |
| serveur_nginx | 1 | 512 | 3 |
| serveur_openldap | 1 | 512 | 3 |
| serveur_keycloak | 2 | 1536 | 5 |
| serveur_postgresql | 2 | 2048 | 20 |
| serveur_redis | 1 | 512 | 2 |
| serveur_forgejo | 1 | 1024 | 20 |
| serveur_prometheus | 1 | 1024 | 20 |
| serveur_loki | 1 | 1024 | 20 |
| serveur_grafana | 1 | 512 | 2 |
| serveur_icinga | 2 | 1024 | 10 |
| serveur_web_frontal (repli) | 1 | 512 | 5 |
| serveur_web_dorsal (repli) | 1 | 1024 | 10 |
Ce document a porté jusqu'au 2026-09-06 un tableau de quatorze empreintes recopiées à la
main. Il y en a **32** aujourd'hui, et l'une des quatorze avait cessé d'être vraie. Recopier
une valeur qui vit ailleurs, c'est s'engager à la suivre — cette page ne s'y engage plus :
```bash
# l'empreinte de chaque logiciel, telle qu'elle est déclarée
python3 - <<'EOF'
import yaml, pathlib
for f in sorted(pathlib.Path('roles').glob('*/meta/empreinte.yml')):
e = (yaml.safe_load(f.read_text()) or {}).get('setops_empreinte') or {}
print(f"{f.parts[1]:26} {e.get('coeurs')} coeur(s) {e.get('memoire_mo')} Mo {e.get('disque_go')} Go")
EOF
# ce que ça donne pour un hôte donné, une fois sommé et arrondi
make hote-afficher HOTE=obs-01
```
Ce qui mérite d'être écrit ici, c'est ce qui **façonne** le calcul et ne se lit pas dans un
rôle — le socle, les marges, les paliers, les bornes. Ils sont au §« Règles d'agrégation »
ci-dessus, et vivent en tête de `scripts/inventory_rules.py`.
> **Un repli devenu inutile.** `EMPREINTES_SANS_ROLE` couvrait `serveur_web_frontal` et
> `serveur_web_dorsal` du temps où ces groupes n'avaient pas de rôle. Ils en ont un depuis,
> avec leur propre `meta/empreinte.yml` — et comme la précédence est *rôle > repli > défaut*,
> ces deux entrées ne sont plus jamais lues. Elles disent d'ailleurs autre chose que les
> rôles (5 Go contre 10 pour le frontal) : c'est sans effet, mais c'est le genre d'écart
> qu'on croit lire comme une vérité.
## Ajuster

View file

@ -8,18 +8,42 @@ Le service DNS interne est la premiere capacite de plateforme.
> `serveur_debian`) genere `/etc/hosts` sur **chaque** VM depuis l'inventaire : tout
> l'ecosysteme se resout par nom **meme serveur DNS eteint** (et au bootstrap, avant
> que PowerDNS ne soit la). PowerDNS devient une **commodite** (zone, externe,
> dynamique), plus un point de defaillance. Le resolveur local `client_unbound` est
> **optionnel** (opt-in, avec bascule validee) : sans lui, le plancher `/etc/hosts` suffit.
> dynamique), plus un point de defaillance.
## Trois couches, et une seule est optionnelle
> **Ce paragraphe decrivait le modele d'avant le 2026-08-24** — un Unbound *sur chaque VM*,
> en opt-in. Ce n'est plus le cas : `client_resolveur` **n'installe plus rien**, et son
> integration est **universelle**, pas elective.
```text
1. hosts_statiques le PLANCHER : /etc/hosts genere sur chaque VM depuis l'inventaire
-> l'ecosysteme se resout DNS eteint. Jamais optionnel.
2. serveur_powerdns l'AUTORITATIF de la zone souveraine (un par ecosysteme)
3. serveur_resolveur le RECURSIF : UN seul Unbound pour tout le tenant, qui recurse
depuis la racine et delegue la zone souveraine a PowerDNS
client_resolveur l'integration : ecrit /etc/resolv.conf pour designer ce resolveur
```
**`client_resolveur` n'installe plus de demon** (2026-08-24). Il en posait un par VM — N
demons identiques de ~21 Mo pour quelques centaines de requetes. Il ne fait plus qu'une
chose : **ecrire `/etc/resolv.conf`**. Son integration est marquee `universelle: true`, et
elle n'a **aucune exemption, pas meme l'hote qui porte le resolveur** : il se sert
lui-meme. L'ancien nom (`client_unbound`) mentait des lors qu'il n'installait plus Unbound.
**L'integration suit l'existence du service, elle ne se declare pas.** Aucun
`serveur_resolveur` au plan rend `client_resolveur_actif` faux et le role ne touche a rien :
l'ecosysteme garde la resolution d'amorcage de cloud-init. C'est un choix valide, pas une
panne.
## Groupes
```text
serveur_powerdns -> service DNS central PowerDNS Authoritative
client_unbound -> resolveur local optionnel (opt-in, bascule validee)
serveur_powerdns -> autoritatif interne (PowerDNS Authoritative)
serveur_resolveur -> LE recursif du tenant (Unbound), un seul
client_resolveur -> integration universelle : designe ce resolveur dans /etc/resolv.conf
```
`client_unbound` (résolveur local optionnel) peut viser PowerDNS en stub-zone + récursion.
## Zone initiale
La zone initiale est :
@ -31,7 +55,7 @@ exemple.internal
Elle est definie dans :
```text
instance/inventories/production/group_vars/serveur_powerdns.yml
instance/inventories/<inventaire>/group_vars/serveur_powerdns.yml
```
## Enregistrement automatique
@ -67,22 +91,42 @@ Raison :
Quand `serveur_postgresql` sera stable, il sera possible de migrer vers un backend SQL si le besoin operationnel le justifie.
## Résolveur local (optionnel)
## La bascule de `/etc/resolv.conf` est protegee
Le role `client_unbound` (opt-in) installe un résolveur récursif local qui, en stub-zone,
délègue les noms internes à PowerDNS et récurse le reste.
La bascule du resolver local est **protegee** (le role valide qu'Unbound répond AVANT de
basculer `/etc/resolv.conf`) :
Le role `client_resolveur` ne bascule `/etc/resolv.conf` qu'apres avoir verifie que le
resolveur repond **deja** — pour l'interne *et* pour l'Internet :
```yaml
client_unbound_apply: true
client_unbound_confirm: true
client_resolveur_apply: true
client_resolveur_confirm: true
```
Sans ces deux variables, le role prépare Unbound mais ne modifie pas `/etc/resolv.conf`.
Cette protection est volontaire : une mauvaise configuration DNS peut couper la resolution de noms.
Cette protection est volontaire : une mauvaise configuration DNS peut couper la resolution
de noms. C'est la dependance la plus dangereuse du lot — basculer un hote sur un resolveur
qui n'est pas encore pret le rend **muet**, et le runner qui devrait reparer tombe avec les
autres. Le playbook de groupe applique donc l'hote qui *porte* le resolveur avant ceux qui
s'y adressent, et la preuve **P44** refuse tout ecart entre cette declaration et lui.
## Le piege qui a coute deux jours : la racine signee nie notre TLD
`internal.` n'est **pas delegue dans la racine**, qui est signee : elle rend donc une preuve
NXDOMAIN *validee* pour ce TLD. Or `harden-below-nxdomain` — **actif par defaut** dans
Unbound — tient ce « non » pour prouve et repond NXDOMAIN pour **tout** nom sous
`internal.` depuis son cache, **sans jamais interroger la `stub-zone`** declaree plus bas.
La delegation etait correcte. L'autoritatif repondait juste. Pas une requete ne lui
parvenait.
Ce qui declenche l'empoisonnement : n'importe quelle question sur un nom inexistant sous
`internal.` — y compris la zone d'**un autre ecosysteme**, que ce resolveur ne sert pas et
va donc chercher a la racine. Sur un resolveur partage, ca arrive en permanence.
Et la panne parait **intermittente** : au redemarrage le cache est vide, tout fonctionne, on
conclut que c'est regle. Le remede mesure (2026-09-02) est `harden-below-nxdomain: no` dans
`roles/serveur_resolveur/templates/setops.conf.j2` — **pas** `aggressive-nsec`, qui traite
un autre symptome.
## Surveillance a prevoir
@ -95,3 +139,103 @@ Les premiers checks utiles :
- enregistrements des hotes actifs presents ;
- serial de zone attendu ;
- latence de resolution.
## Zones publiques
> Ajouté le 2026-09-16. Le mode `primaire-cache` était **validé par le schéma et consommé par
> rien** depuis sa création — *une autorité qu'on s'attribue sans l'exercer est une panne
> différée*.
`plan/domaines.yml` porte un champ `autorite` par domaine. Deux valeurs sont exercées :
| `autorite` | Ce que ça veut dire | Qui sert la zone |
|---|---|---|
| `auto-heberge` | zone **interne** (`.internal`), servie à l'écosystème seul | l'instance principale de `serveur_powerdns`, sur la boucle locale |
| `primaire-cache` | zone **publique** : le locataire l'écrit, le site la sert | `pdns@public` chez le locataire (primaire caché), `serveur_dns_public` au site (secondaire public) |
`delegue` est accepté par le validateur et **n'est consommé par aucun rôle**. Il ne faut pas
le déclarer en croyant qu'il fait quelque chose.
**Le locataire écrit, le site sert, un site pair réplique.** Le primaire reste chez le
locataire : quand il part, il emporte sa zone. Le détail — et ce que l'épreuve de PowerDNS a
appris — est dans `roles/serveur_dns_public/README.md`.
Trois règles, chacune payée ou mesurée :
- **TSIG seul ouvre le transfert.** `allow-axfr-ips` et TSIG sont *alternatifs* : une adresse
listée obtient la zone sans signature. L'instance publique n'autorise que la boucle locale.
- **Une instance à part pour le public.** Faire écouter l'instance principale sur l'adresse
de l'hôte aurait permis au serveur public du site d'interroger la zone `.internal`.
- **Le serial suit le contenu.** Un serial figé ferait garder au secondaire l'ancienne zone
pour toujours.
**Rien n'est exposé à Internet en phase 1.** P82 refuse d'exposer le serveur public tant
que toutes ses zones ne sont pas signées DNSSEC.
### Ce qu'une zone publique porte
> Ajouté le 2026-09-16, phase 2. Une zone qui ne portait que ses expositions web aurait coupé
> le courriel de production à la minute où le registraire l'aurait suivie.
Chaque zone `primaire-cache` déclare ses **enregistrements** au plan (`enregistrements:` dans
`plan/domaines.yml`) : A, AAAA, CNAME, MX, TXT, CAA. `valider_domaines` refuse une adresse
privée, une cible incomplète, un CNAME à l'apex ou à côté d'un autre type, un MX sans
priorité, un TXT avec guillemets ou hors ASCII, un nom écrit en absolu (`mx.chezlepro.ca`
sous `chezlepro.ca`), et tout enregistrement dans une zone que Set-OPS n'écrit pas.
Le **SOA et les NS** ne se déclarent pas : ils désignent le serveur de noms du site, sous le
nom que **le site** déclare (`dns_public_nom`, contrat du site, reçu par chaque locataire).
Seule la zone qui contient ce nom porte son A. Le premier choix, `ns1.<zone>`, aurait déplacé
en silence un `ns1.chezlepro.ca` qui existe en production vers une autre adresse.
**Avant de basculer : `make dns-bascule-devis`.** Il compare le plan au DNS en service, nom
par nom et type par type, sur les noms déclarés et une liste de sondes usuelles. Il rend ce
qui serait **perdu**, **changé**, **ajouté**, ou **abandonné en connaissance de cause** (les
marques `heritage=external-dns`), et les préalables : le serveur de noms doit déjà résoudre
publiquement, et il en faut **deux** (exigence du registre `.ca`). Une mesure qui échoue rend
« mesure impossible », jamais « absent ». Sa limite est écrite à chaque rapport : sans
transfert de zone, un export chez le fournisseur actuel reste la seule preuve d'exhaustivité.
Au 2026-09-16 : `chezlepro.ca` reproduit la production (rien ne serait perdu) ;
`technolibre.ca` est **préparée, pas basculée** — la décision revient au responsable désigné
de TechnoLibre, et elle attend que `dns1.chezlepro.ca` soit publié.
### Signature DNSSEC : le locataire signe, avec la clé de sa voûte
> Ajouté le 2026-09-16, phase 2. Éprouvé sur les machines réelles avant d'être codé.
`dnssec: true` sur une zone `primaire-cache` la fait signer **par le primaire du locataire**,
avec **une** clé CSK ECDSA P-256 tenue dans **sa** voûte (`vault_dnssec_<zone>`). Le site ne
détient aucune clé : il reçoit la zone déjà signée et la sert telle quelle (PowerDNS pose
`PRESIGNED` au transfert).
| Geste | Commande | Écrit ? |
|---|---|---|
| Créer la clé d'une zone | `python3 scripts/dnssec.py generer <zone>` | la voûte du locataire (copie de sûreté chiffrée, relue avant et après) |
| Lire le DS à remettre au registraire | `make dnssec-ds` | rien — calculé depuis la voûte, sans la machine |
| Voûte, plan et registre concordent-ils ? | `make dnssec-verifier` | rien |
Pourquoi la clé ne naît pas sur la machine : une reconstruction depuis zéro signerait avec
une **autre** clé, le DS du registraire ne correspondrait plus, et le domaine deviendrait
**BOGUS** pour tout résolveur validant, sans qu'aucune machine soit en panne.
Trois règles, chacune mesurée :
- **Changer la clé ou retirer la signature pendant qu'un DS est publié est refusé** par l'outil
de la machine (`setops-dnssec-zone`) comme par `dnssec.py`. Une mesure du DS qui échoue vaut
refus.
- **Le serial servi est l'heure (`SOA-EDIT EPOCH`).** PowerDNS renouvelle ses signatures chaque
semaine (inception au début de la semaine précédente, expiration deux semaines après le
début de la semaine courante). `INCEPTION-EPOCH`, essayé d'abord, ne dépassait notre serial
qu'au moment où les signatures du site expiraient. Avec l'heure, le site retire la zone à
chaque rafraîchissement ; les signatures servies gardent 7 à 14 jours.
- **L'état DNSSEC fait partie du corps de la zone.** Signer ne change aucun enregistrement :
sans la ligne `; DNSSEC : …`, le serial n'aurait pas bougé et le site aurait gardé la version
non signée.
La sonde `zones-publiques` du site alerte sur une zone signée servie **nue**, avertit sous
6 jours de signature restante, et alerte sous 3.
**Ordre de la mise en service** : basculer les serveurs de noms (zone signée, sans DS : le
domaine reste « non sécurisé », pas cassé), vérifier, **puis** remettre le DS au registraire.
Jamais l'inverse.

View file

@ -35,13 +35,17 @@ cohérent.
| **Confiance** | Autorité de certification interne (PKI/ACME) : les serveurs se reconnaissent par certificats émis localement, sans acheter de confiance à l'extérieur. |
| **Nommage** | DNS interne : des noms de machines stables et dérivables, plutôt que des adresses IP fragiles. |
| **Données** | Bases relationnelles et cache, avec un registre des connexions applicatives. |
| **Communication** | Relais courriel interne pour les alertes, notifications et réinitialisations. |
| **Communication** | Service de courriel souverain : boîtes adossées à l'annuaire, réception SMTP, lecture IMAP, antispam et signature DKIM. Le relais des alertes système en est distinct. |
| **Observabilité** | Métriques, journaux centralisés, tableaux de bord et supervision active. |
| **Applicatif** | Services internes (forge logicielle, collaboration documentaire) derrière une couche web sécurisée. |
Tous ces services sont **libres** (OpenLDAP, Keycloak, step-ca, PowerDNS, PostgreSQL,
Redis, NGINX, Prometheus, Loki, Grafana, Icinga, Forgejo, Sendmail…). Aucun verrou
propriétaire, aucune licence captive.
Tous ces services sont **libres** : OpenLDAP, Keycloak, step-ca, PowerDNS, Unbound,
PostgreSQL, Redis, NGINX, Postfix, Dovecot, rspamd, Prometheus, Loki, Grafana, Icinga,
Forgejo, Nextcloud, Collabora, restic. Aucun verrou propriétaire, aucune licence captive,
**et aucun conteneur** — tout est installé nativement, en paquets et unités systemd.
*(Cette liste nommait Sendmail jusqu'au 2026-09-06 : ce rôle a été retiré le 2026-07-04,
supersédé par Postfix.)*
---
@ -60,12 +64,18 @@ plan :
reconstruit à l'identique depuis le plan et le code. L'infrastructure n'est pas un
objet fragile patiemment bricolé — c'est un artefact reproductible.
> **Honnêteté sur le statut.** Une grande partie de l'écosystème est aujourd'hui
> *définie, codée et validée* (vérification de syntaxe, analyse statique) mais n'a pas
> encore été éprouvée sur des machines de production réelles. Le socle Debian durci et
> son modèle sont la fondation établie ; les services applicatifs sont prêts en tant
> que code et se déploient progressivement. Nous ne présentons jamais un service non
> déployé comme « en production ».
> **Honnêteté sur le statut — mise à jour le 2026-09-06.** Ce paragraphe disait, bien après
> que ce fut faux, que l'écosystème n'avait « pas encore été éprouvé sur des machines
> réelles ». Il l'a été : la flotte entière a été **rasée et remontée depuis zéro** le
> 2026-08-13, puis **deux fois le 2026-09-02** — 15/15 puis 14/14 machines, **zéro échec**,
> et la validation complète à zéro échec sur treize hôtes. La reconstruction n'est donc pas
> une promesse de conception : c'est une manœuvre exécutée, chronométrée et rejouée.
>
> Ce qui reste honnête à dire : la reconstruction prouve qu'un plan mène des machines nues à
> l'état voulu. Elle ne dit rien de la tenue d'un service **sous charge**, ni de sa mise à
> jour dans la durée — deux questions distinctes, et non traitées ici. Le degré de maturité,
> service par service, est tenu à jour dans `docs/catalogue-services.md`, et nous ne
> présentons jamais comme « en production » un service que ce tableau ne donne pas pour tel.
---
@ -100,7 +110,8 @@ Une trentaine de paramètres noyau resserrent le comportement du système et du
### 4. Pare-feu prêt, activé au bon moment
- **nftables** est installé et préparé sur le socle, mais **volontairement désactivé dans le modèle**. Il est activé sur les serveurs finaux avec des règles adaptées à leur rôle (politique par défaut « tout refuser » en entrée et en transit). Principe : on n'active jamais un pare-feu générique sans connaître la fonction réelle de la machine, pour ne pas couper l'accès par accident.
- **nftables** est **volontairement désactivé dans le modèle** — on n'active jamais un pare-feu générique sans connaître la fonction réelle de la machine — et **armé sur chaque serveur de la flotte**, en politique « tout refuser » par défaut.
- **Ses règles ne sont pas écrites à la main.** Chaque rôle déclare ce qu'il écoute et ce à quoi il se connecte ; le jeu de règles en est **dérivé**. Le même registre alimente le pare-feu de l'hyperviseur et la frontière du site : trois couches de filtrage qui ne *peuvent pas* se contredire, parce qu'elles descendent d'une seule décision. C'est aussi ce qui produit la **matrice d'accès réseau** exigée par un audit de sécurité — source, destination, port, chiffrement et *raison*, pour chaque flux autorisé.
### 5. Confinement et intégrité
@ -134,6 +145,8 @@ Au-delà des réglages machine, l'architecture elle-même est une mesure de séc
- **Certificats internes** : les services se font confiance via la PKI interne, sans exposer de secrets à des autorités externes.
- **Dépendances déclarées** : un service ne se déploie pas si ses prérequis (DNS, PKI…) ne sont pas réellement actifs — ce qui évite les états incohérents.
- **Source unique de vérité** : l'état voulu vit dans des registres lisibles, versionnés par Git, qui sert de filet en cas d'erreur.
- **Les sauvegardes sortent du site.** L'état non régénérable (clés de l'autorité, annuaire, bases, boîtes, forge) est sauvegardé **chiffré côté client** vers un dépôt hors-machine, puis emporté **hors du bâtiment**. Chaque nœud vérifie lui-même son propre dépôt distant : celui qui héberge les octets ne peut pas les lire, donc ne peut pas les juger.
- **Ce qu'on affirme, on le prouve.** Un harnais de **57 vérifications** rejoue à la demande ce que le dépôt promet et produit une pièce justificative datée ; dix « devis » interrogent en plus le système *déployé* pour confirmer qu'il ressemble à ce qui est déclaré.
---

View file

@ -0,0 +1,269 @@
# Filiation, mutualisation, émancipation
> **Pour qui :** l'**architecte** de la lignée et l'**hébergeur** qui abrite des
> écosystèmes tiers. Décrit les trois âges d'un écosystème et ce qu'ils impliquent au plan.
## Le constat
Un écosystème ne naît pas autoportant. Il lui faut une forge pour lire son génome, une
source d'artefacts pour ses paquets, un dépôt pour ses sauvegardes — et rien de tout cela
n'existe la première seconde. Il emprunte donc à son hôte.
Jusqu'ici, le moteur a rencontré ce besoin **trois fois sans le nommer** :
```
client_artefacts_actif dérivé : « une source existe-t-elle chez moi ? »
serveur_ops_forge_externe « je lis mon génome ailleurs »
client_backup_cible « je sauvegarde chez le site » — mutualisé, en production
```
Trois astuces, une seule notion. Ce document la déclare.
## Les trois âges
```
FILIATION l'enfant emprunte à son parent ce qu'il ne porte pas encore
MUTUALISATION un état DURABLE et CHOISI — on emprunte parce qu'on le veut
ÉMANCIPATION l'écosystème porte tout lui-même
```
**L'émancipation n'est pas une obligation.** Un petit organisme peut rester mutualisé
toute sa vie ; ce qui compte est qu'il *puisse* s'émanciper, et que la sortie ne soit ni
un piège ni une reconstruction. C'est la différence entre un locataire et un captif.
## Ce que ça veut dire pour les modèles
Un modèle sans forge n'est pas un modèle amputé : c'est un écosystème **au premier âge**,
dont le runner lit le génome chez son hôte. L'ajout d'une forge n'est pas une correction,
c'est une **émancipation**.
```
premier âge serveur_ops_forge_externe: true + l'adresse de la forge de l'hôte
émancipé serveur_ops_forge_externe: false + sa propre serveur_forgejo au plan
```
## Ce que ça veut dire pour l'hébergeur
L'hébergeur ne vend plus seulement des machines : il vend **l'abri pendant la jeunesse**.
Chaque service mutualisé est une ligne de facture, et chaque émancipation est une décision
du client — jamais une rupture technique.
Pour une clientèle d'OBNL et de coopératives, c'est le bon geste : on entre à bas coût, on
grandit vers l'autonomie, et **on n'est jamais captif**, puisque le génome est déjà chez
soi dès le premier jour.
## Ce qui est mutualisable, et ce qui ne l'est pas
> **Le critère, à deux tranchants.** Un rôle appartient au SITE s'il sert la **relation**
> avec les locataires, ou les machines du site elles-mêmes. Il appartient au TENANT s'il
> sert des **utilisateurs** ou des **applications**. À l'usage, ça se décide sur deux
> questions plus dures :
>
> 1. **Ce que le site ne doit pas pouvoir VOIR** — sinon l'hébergement n'est plus
> souverain, quelles que soient les intentions.
> 2. **Ce que le tenant doit pouvoir EMPORTER** — sinon l'émancipation est un mot.
| service | mutualisable | remarque |
|---|---|---|
| forge (génome) | oui | `serveur_ops_forge_externe` |
| source d'artefacts | oui | `client_artefacts` se désactive seul si aucune n'existe |
| sauvegarde | oui | `client_backup_cible` pointe déjà hors de l'écosystème ; le site héberge du **chiffré côté client** et ne peut rien en juger |
| **disponibilité** (est-ce debout ?) | oui | c'est sa fabric : il doit savoir qu'une VM est tombée |
| **métriques** (charge, disque) | oui, avec réserve | révèlent des **rythmes d'activité**, pas du contenu |
| **journaux** | **non** | voir ci-dessous — plus intime que l'annuaire |
| relais courriel | oui | l'enveloppe transite ; le contenu appartient au locataire |
| DNS | partiellement | un sous-domaine délégué, pas la zone entière |
| **PKI** | **non** (tranché le 2026-09-10) | voir ci-dessous |
| **identité** (LDAP/SSO) | le plus intime | mutualiser l'annuaire, c'est confier ses gens |
| **la voûte** | **jamais** | les secrets ne se mutualisent pas, à aucun âge |
### Pourquoi « observabilité » a été découpée en trois
La ligne disait : *« observabilité | oui | l'hébergeur surveille ses locataires »*. Elle
était trop grossière, et elle **contredisait ce qui tourne** : chaque écosystème a son
propre Icinga, et celui du site ne voit que ses sept machines (mesuré le 2026-09-10).
Surtout, elle autorisait en une case ce que la ligne du dessous interdit. **Les journaux
contiennent du contenu** — un mot de passe dans un message d'erreur, une donnée métier dans
une trace, qui a fait quoi et quand. Un hébergeur qui ingère les journaux de son locataire
en sait **plus** que s'il détenait son annuaire : l'annuaire dit qui existe, les journaux
disent ce qu'ils font.
La disponibilité, elle, est légitime : une VM tombée est un fait de la **fabric**, que
l'hébergeur porte. Les métriques sont entre les deux — elles ne disent pas *quoi*, mais
elles disent *quand* et *combien*, ce qui suffit à lire l'activité d'une organisation.
### Pourquoi la ligne PKI est tranchée : **non**
Elle disait « à trancher », en notant qu'*une AC intermédiaire signée par l'hôte est
possible*. Elle l'est techniquement — et c'est précisément ce qu'il ne faut pas faire.
Une intermédiaire signée par l'hôte lui donne le pouvoir d'**émettre des certificats
valides pour les noms du locataire**. Il peut alors se présenter comme n'importe lequel de
ses services, y compris devant les propres machines du locataire, qui les accepteront sans
sourciller — c'est exactement ce que la chaîne de confiance leur demande de faire.
C'est le même pouvoir que l'annuaire, sous une forme **moins visible** : rien n'apparaît
dans la configuration du locataire, aucune trace côté locataire, et la vérification passe.
**Une PKI par écosystème, jamais dérivée de l'hôte.** C'est déjà ce qui tourne ; la ligne
ne fait que cesser de laisser la porte entrouverte.
### Le revers : le site n'a pas d'observabilité du tout
Mesure du 2026-09-10 : le site ne porte **ni `client_journal` ni `client_metrique`**, et
aucun `loki` ni `prometheus`. Ses sept machines n'expédient rien nulle part — l'hébergeur
ne peut pas lire ses propres journaux.
C'est la conséquence directe de la frontière : à refuser de voir ceux des locataires, il
s'est privé des siens. Le remède n'est pas d'assouplir la règle, c'est de lui donner **sa
propre pile** — un `loki` et un `prometheus` du site, pour les machines du site, sans
aucun lien avec ceux d'un tenant.
## L'insémination — le premier lien, et le seul que le SITE ait le droit d'avoir
Entre tenants, le zéro-confiance est le défaut : la frontière refuse tout ce qui n'est pas
déclaré, et c'est cette règle qui rend l'hébergement mutualisé défendable. **La parenté est
la seule exception, et elle est asymétrique** : un écosystème neuf ne peut pas s'amorcer
lui-même. Quelqu'un doit poser sa première machine et lui donner de quoi continuer.
Ce geste s'appelle l'**insémination** :
```
le SITE matérialise VNets, VM, routes, flux — le terrain
le SITE amorce ops-01 la première machine, et elle seule
le TENANT achève ses autres machines, ses rôles, ses intégrations
```
**Le partage d'intelligence qui le rend possible.** Les deux runners portent le même
moteur ; ils n'en consomment pas la même face :
| | ce qu'il lit du moteur | ce qu'il en fait |
|---|---|---|
| runner du **SITE** | `roles/*/meta/flux.yml` — la face **réseau** des rôles | SDN, pare-feu Proxmox, frontière OPNsense |
| runner du **TENANT** | `tasks/`, `templates/`, `meta/integration.yml` — la face **machine** | configure ses services |
C'est ce partage qui permet au SITE de poser les bonnes règles pour un tenant **sans jamais
lire sa configuration ni détenir sa voûte**. Il sait de quoi les rôles ont besoin sur le
fil ; il ignore ce qu'ils font sur la machine.
**Ce que le lien doit être, pour ne pas devenir un pouvoir permanent :**
- **étroit** — vers le seul `ops-01` du tenant, jamais vers ses autres machines ;
- **déclaré** — dans `meta/flux.yml`, comme tout le reste, pour que la frontière et
`nftables` le connaissent au lieu de le subir ;
- **borné par un acte**, et cet acte appartient à un humain (voir ci-dessous).
> **Ce qui existe déjà sans être déclaré, au 2026-08-28.** `make creer-vm` exige
> `_instance-requise` : pour matérialiser les VM d'un tenant, le runner du SITE doit
> basculer son symlink `instance` sur le dépôt de ce tenant — alors que son plan pose
> `serveur_ops_instance: ""` et que la doctrine dit qu'un SITE n'a pas de plan monté par ce
> lien. Le couplage est donc **déjà là**, et rien ne borne sur quels tenants il porte ni
> jusqu'à quand. Le déclarer, c'est le rendre limitable ; le taire ne l'a jamais empêché.
## Ce que l'émancipation exige — et de qui
Basculer une déclaration ne suffit pas. Mais l'erreur symétrique est plus tentante encore :
faire de l'émancipation un **automatisme**, que la machine déclencherait dès qu'elle
constate que le tenant se reproduit sans son parent.
**L'émancipation exige toujours la gouverne d'un humain.** Le fruit de l'émancipation est
livré à quelqu'un — un client qui reprend ses clés, sa forge, son écosystème. Sans
quelqu'un pour le recevoir, il n'y a pas de livraison : seulement un lien qui tombe. *Une
émancipation qui se déclencherait toute seule ne serait pas une émancipation, ce serait une
expulsion.*
C'est déjà la forme des gestes lourds de ce dépôt : `raser` exige `CONFIRMER=true` et le
nom de l'instance ; le mot de passe de la voûte se tape à l'exécution — « le seul objet que
la reproduction exige d'un humain ». **L'émancipation est le deuxième objet de cette
liste.**
D'où la répartition, qui ne se confond pas :
| | qui l'exécute |
|---|---|
| **le lien de filiation** — déclaré, étroit, visible au plan | le moteur |
| **le constat d'aptitude** — « ce tenant se reproduit sans son parent » | la machine — elle **instruit**, elle n'agit pas |
| **l'acte d'émancipation** — retirer le lien, remettre les clés | **l'humain**, garde `CONFIRMER=true` |
| **la preuve que le lien est coupé** | la machine, **après** l'acte |
La quatrième ligne est celle qu'on oublie, et c'est elle qui décide si les trois autres ont
servi. Sans elle, on croirait s'être émancipé en restant dépendant sans le savoir —
exactement le défaut que ce dépôt traque partout ailleurs : un vert sur un périmètre vide.
**Une émancipation non prouvée est une émancipation non faite.**
### L'instrument de la quatrième ligne — `make emancipation-prouver`
Il existe depuis le 2026-09-01, et il **coupe** au lieu de sonder.
Vérifier que le service local répond ne prouve rien : *tant que l'amont répond, une
fonction qui marche ne dit pas d'où vient l'octet.* L'instrument bloque donc l'amont —
table `nftables` dédiée, retirée par un bloc `always` quoi qu'il arrive — puis refait
marcher la chose.
**Le même essai rend les deux verdicts, et c'est ce qui le rend honnête :**
```
coupé, la fonction marche -> ÉMANCIPÉ, et c'est prouvé
coupé, la fonction casse -> PAS ÉMANCIPÉ, et la dépendance est prouvée RÉELLE
```
Le second n'est pas un échec de l'outil : c'est son **contrôle négatif**, rendu par la
même commande. Une preuve d'émancipation incapable de montrer la dépendance qu'elle mesure
ne prouverait rien le jour où elle passerait au vert.
Un témoin précède la coupure — *la fonction marchait-elle seulement avant ?* Sans lui, une
panne préexistante se lirait comme une dépendance.
```
make emancipation-prouver SERVICE=artefacts HOTE=forge-01 CONFIRMER=true
```
*Mesuré le jour de sa naissance : `obs-01` ne résout plus rien dès que le résolveur du
site est coupé — dépendance réelle ; `forge-01` installe des paquets le cache du site
coupé, listes vidées — émancipation prouvée sur ce service.*
## Forme attendue d'une déclaration
Tout service mutualisable doit se déclarer de la même façon, pour qu'un exploitant lise
l'état de son écosystème d'un coup d'œil plutôt qu'en fouillant chaque rôle :
```yaml
<service>_externe: true # je l'emprunte
<service>_amont: "https://…" # à qui
```
`false` — ou l'absence du couple — signifie *chez moi*. Le rôle **refuse** de démarrer si
le drapeau est levé sans que l'amont soit nommé : emprunter sans dire à qui, c'est ne rien
déclarer du tout.
## Qui est le parent ? — tranché le 2026-08-31 (D-82)
**Personne. La forge du SITE fait autorité pour le génome** (D-81) ; toute autre copie est
un miroir. Le dépôt l'avait suivi avant de le déclarer : `serveur_forge_site`, puis
`serveur_cache_site`, puis `serveur_resolveur_site` — trois services prêtés par le site à
ses locataires, un seul patron.
**Par où le génome arrive.** Le poste porte les commits en bundle au runner du site
(`make genome-pousser`), qui les pousse sur sa forge. Aucune autre forge n'est sur ce
chemin. `eregion` (`forge.alliance-boreale.ca`) est une forge **héritée** : la porte
publique des contributions, qui ne fera jamais partie de Set-OPS. La redondance ne vient
pas d'elle mais du nombre de sites — une forge par site, chacune inséminée du génome.
## État — revu le 2026-09-06
Seule la forge porte cette déclaration complète (`serveur_ops`). Les autres services
mutualisés le sont par des mécanismes antérieurs, cohérents mais non uniformes. Les
aligner sur la forme ci-dessus reste à faire.
Des trois manques que cette section listait au 2026-08-28, **deux sont comblés** :
| Manque du 2026-08-28 | Aujourd'hui |
|---|---|
| l'**instrument de preuve** de la dernière ligne — « le lien est coupé » — n'existe pas | **`make emancipation-prouver`**, depuis le 2026-09-01 (§ ci-dessus). Il *coupe* au lieu de sonder, et rend les deux verdicts. |
| la séparation des voûtes est **organisationnelle, pas cryptographique** — un seul mot de passe ouvre celle du site et celles des tenants | **Séparé le 2026-08-28 même** : une voûte, **une clé** (`~/.config/setops-vault-<dépôt>`, `scripts/voutes.py`). L'ancien mot de passe unique n'ouvre plus rien. La séparation précédait nécessairement la distribution des clés aux runners : sinon, poser « la » clé sur le plus petit locataire lui donnait les secrets de l'hébergeur. |
| le **lien d'insémination** n'est pas déclaré | **Toujours vrai.** Il existe, par le symlink `instance` du runner du SITE, sans borne ni visibilité. C'est le manque qui reste. |
*(Le paragraphe sur les voûtes se contredisait avec `scripts/voutes.py` — daté du même jour —
et celui sur l'instrument avec la section qui le décrit, deux écrans plus haut. Un document
qui se contredit lui-même à cette distance n'est plus lu comme une référence.)*

View file

@ -38,9 +38,26 @@ flux:
| `externe` | hors flotte (frontière publique — géré à l'OPNsense, pas dans le nœud) |
| `localhost` | boucle locale — aucune règle inter-nœud (nftables autorise `lo`) |
| `expositions` | dérivé des `expose:` des applications (cas de l'edge → backends) |
| `admin` | les **réseaux** d'administration (intrant `nftables_admin_ssh`) — l'exploitant n'est ni la flotte, ni l'Internet |
| `voisins_site` | les **autres tenants fédérés** de la même fabric (leurs supernets) — le voisinage, quatrième chemin d'arrivée |
| `fabric` | le **matériel de l'hébergeur** : hyperviseurs, frontière, commutateurs |
| `runner_site` | le **runner du site**, seul autorisé à matérialiser des VM |
| `derive` | résolu ailleurs que par le nœud (hors périmètre de sa règle) |
La résolution `pair → IP` réutilise le **plan** (registre IP/FQDN/zones) déjà en place.
> **Quatre de ces mots sont nés d'un piège, et pas d'un besoin de vocabulaire.**
> `voisins_site` (2026-08-24) : sans lui, le chaînage des caches d'artefacts n'aurait pu se
> déclarer qu'en `ingress` + `externe` — ce qui aurait **publié le cache à l'Internet
> entier**. `fabric` (2026-08-25) : le runner de site déclarait son API Proxmox en
> `externe`, or « externe » se rend par « tout sauf les espaces privés » et les hyperviseurs
> *sont* en RFC 1918 — la règle avait l'air d'ouvrir le flux et l'excluait. Un flux qui a
> l'air ouvert et qui ne l'est pas est pire qu'un flux fermé : il ne se cherche pas.
>
> Les cinq derniers ne rendent **pas des hôtes** : `admin`, `voisins_site`, `fabric` et
> `runner_site` rendent des CIDR ou des sources extérieures à l'écosystème, `derive` ne rend
> rien. Ils sont donc traités à part dans `scripts/resoudre_flux.py`.
## Génération
Un **résolveur** (miroir de `instancier`) agrège, par serveur, les `flux.yml` de tous ses rôles
(services + intégrations), résout les `pair`, et produit :
@ -50,14 +67,26 @@ Un **résolveur** (miroir de `instancier`) agrège, par serveur, les `flux.yml`
## Activation prudente
Activer nftables = **action destructive** (peut couper l'accès) → confirmation explicite +
déploiement graduel (garder l'accès SSH/Ansible, tester par nœud). nftables reste *préparé mais
non activé* tant que le registre n'est pas complet et validé.
déploiement graduel (garder l'accès SSH/Ansible, tester par nœud).
## Séquence
> **Le pare-feu est armé sur la flotte depuis.** Ce paragraphe disait « nftables reste
> *préparé mais non activé* tant que le registre n'est pas complet et validé ». Le registre
> **est** complet — **P49** vérifie qu'il reproduit exactement ce que les `meta/flux.yml`
> déclarent — et `nftables_baseline_enabled` vaut `true` pour `hotes_actifs` dans le plan.
> Le rôle, lui, garde `false` par défaut : le pare-feu n'est pas une propriété du rôle,
> c'est une décision de l'instance. Le gabarit doré reste à `false`, et c'est voulu.
## Séquence — franchie
1. ✅ figer le schéma (ce doc) + **piloter** sur postgresql / client_metrique / nginx ;
2. le **résolveur** (agrégation → règles + registre) ;
3. **remplir** tous les rôles (large transcription du travail zéro-confiance déjà fait) ;
4. **générer** + registre d'audit ; puis **activer** nftables nœud par nœud.
2. ✅ le **résolveur** (`scripts/resoudre_flux.py`) — agrégation → règles + registre ;
3. ✅ **remplir** tous les rôles (transcription du travail zéro-confiance) ;
4. ✅ **générer** + registre d'audit (`docs/registre-flux.md`, gardé par **P49**) ; puis
**activer** nftables nœud par nœud — fait.
Ce qui a suivi la séquence n'y était pas prévu : le même registre alimente désormais aussi
le **pare-feu est-ouest de l'hyperviseur** (`make proxmox-fw-plan`, garde **P45**) et la
**frontière nord/sud OPNsense** (`make frontiere-plan`). Trois couches, une seule décision —
elles ne peuvent pas se contredire parce qu'elles dérivent de la même source.
## `poste: false` — un service publié qui ne s'adresse pas à un humain
@ -93,8 +122,9 @@ Le registre confondait deux situations :
| le rôle **ouvre** l'écoute | `serveur_dovecot` lie 12345 (SASL réseau) | absent |
| le rôle **décrit** celle d'un autre | `serveur_backup` emprunte le sshd de `serveur_debian` | `true` |
Sans cette distinction, la seule co-location légitime de la flotte — `tcp/22` sur
`backup-01` — serait signalée à tort. Une preuve qui crie sur un cas sain finit par être
Sans cette distinction, la seule co-location légitime de la flotte — `tcp/22` sur le
dépôt de sauvegarde, `site-backup-01` depuis que les écosystèmes déposent chez leur
hébergeur — serait signalée à tort. Une preuve qui crie sur un cas sain finit par être
ignorée, ce qui est pire que de ne pas l'avoir.
**Corollaire à retenir** : un port qu'on **subit** (le défaut amont d'un logiciel) doit être

View file

@ -65,7 +65,7 @@ mais elle décide de l'emplacement de chaque chose :
|---|---|---|
| plan des services, inventaire | le **tenant** | `OPS-<tenant>` |
| `underlay.yml` (fabric physique) | l'**hébergeur** | `OPS-<hébergeur>`, monté par symlink |
| `group_vars/opnsense.yml` (frontière) | l'**hébergeur** | idem — **une** frontière pour tous ses tenants |
| `opnsense.yml` (frontière) | l'**hébergeur** | idem — **une** frontière pour tous ses tenants |
Conséquence pratique : `make instance-utiliser` bascule le **tenant** actif, jamais
l'hébergeur. Le devis lit donc la fabric et les intrants de frontière chez l'hébergeur, quel
@ -84,7 +84,7 @@ silence : chemin présent, politique absente, exactement le mode de panne du 202
Les alias d'hôtes sont préfixés du tenant (`SETOPS_CHEZ17_SERVEUR_NGINX`), et surtout
**chaque tenant a son propre alias d'administration** : `SETOPS_ADMIN_CHEZ17` n'ouvre que
`10.27.0.0/16`. Une union aurait laissé le plan de gestion d'un tenant entrer chez le voisin
`10.17.0.0/16`. Une union aurait laissé le plan de gestion d'un tenant entrer chez le voisin
— ce que les ACL de switch interdisent par ailleurs. La bordure ne doit pas rouvrir ce que
l'isolation inter-tenant ferme.
@ -126,7 +126,9 @@ règle) et un tenant dont `nftables_admin_ssh` est vide (règle SSH omise — l'
exposerait le SSH à Internet).
Le registre des flux distingue les pairs par **mot-clé**. Or `resoudre_flux.py` **saute
volontairement** le pair `externe` (`scripts/resoudre_flux.py:184`) : ces flux-là ne
volontairement** le pair `externe` (fonction de résolution des pairs, aux côtés de
`localhost`, `expositions` et `derive` — chercher le nom, pas un numéro de ligne : celui
qui figurait ici avait vieilli de quarante-sept lignes) : ces flux-là ne
concernent pas le pare-feu d'hôte, ils relèvent de la bordure. Plusieurs `raison` le disent
déjà noir sur blanc — « Frontière publique gérée à l'OPNsense ».
@ -165,9 +167,9 @@ alors **deux règles et deux alias**, chacun ne portant que les sources qui peuv
emprunter ce chemin.
```
SETOPS_ADMIN_CHEZ17_GESTION 10.0.0.0/24 → pass in on lan
SETOPS_ADMIN_TECH11_GESTION 10.0.0.0/24 → pass in on lan
SETOPS_ADMIN_TECH11_WAN 192.168.255.2/32, 192.168.254.2/32 → pass in on wan
SETOPS_ADMIN_CHEZ17_GESTION 10.17.0.0/24 → pass in on lan
SETOPS_ADMIN_TECH23_GESTION 10.17.0.0/24 → pass in on lan
SETOPS_ADMIN_TECH23_WAN 192.168.255.2/32, 192.168.254.2/32 → pass in on wan
```
**Le piège de la case à cocher.** Une source **RFC1918** qui arrive par le WAN se heurte à
@ -179,13 +181,13 @@ entre par la gestion affaiblirait l'interface publique sans rien ouvrir du tout.
**Invariant du dernier octet.** Un point de routage porte **le même dernier octet sur tous
les sous-réseaux où il participe** — on retient une adresse, pas treize. `le commutateur` est
donc `.1` partout : `10.0.0.1`, `10.27.16.1`, `10.27.21.1`… Le chiffre n'est pas codé en dur,
donc `.1` partout : `10.17.16.1`, `10.17.21.1`… Le chiffre n'est pas codé en dur,
il vient de `reservations.passerelle` dans la nomenclature, et `make underlay` (**preuve
P23**) refuse une passerelle qui s'en écarte.
Seule exception, assumée : les liens plus étroits qu'un `/24`. Sur le `/29` de transit,
l'adressage est dicté par les participants du lien — les deux frontières occupent `.1` et
`.2`, le switch prend `.6`.
Seule exception, assumée : le **lien de transit**, dont l'adressage est dicté par ses
participants — les deux frontières occupent `.1` et `.2`, les hyperviseurs `.41`, `.43` et
`.47`.
## 5. La garde anti-lockout
@ -210,7 +212,7 @@ peut dériver d'aucun `index`. Sa place est l'underlay, cluster-global, au même
management, l'iSCSI et Ceph.
**Une route par sous-réseau attribué, jamais une par supernet** (2026-08-09). Router
`10.27.0.0/16` faisait porter à la frontière des destinations qui n'existent nulle part :
`10.17.0.0/16` faisait porter à la frontière des destinations qui n'existent nulle part :
elles atteignaient le nœud de sortie, y arrivaient dans la table *principale* — le VRF n'est
atteint que par les `/24` annoncés en BGP — et repartaient vers la passerelle
d'administration. Les alias `SETOPS_TENANT_*` énumèrent exactement les mêmes `/24`, et
@ -228,20 +230,24 @@ sur ce lien :
```yaml
- nom: transit-frontiere
vlan: 40
sous_reseau: 10.0.4.0/29
passerelle: 10.0.4.6 # SVI du switch L3 (le commutateur)
sous_reseau: 10.0.4.0/24
passerelle_sortie: 10.0.4.1 # bifrost-1 = sortie par défaut de la flotte
```
**Plan du `/29`.** Les frontières occupent le bas de la plage, le SVI du switch le haut :
**Le lien est passé d'un `/29` à un `/24`, et ce n'est pas cosmétique.** En SDN, les
**hyperviseurs** sont eux-mêmes sur ce lien — ce sont eux les nœuds de sortie des VRF, ce
n'était plus le SVI d'un commutateur. Huit adresses n'y suffisaient plus.
| Adresse | Qui |
|---|---|
| `10.0.4.1` | `bifrost-1` — frontière active, sortie par défaut de la flotte |
| `10.0.4.2` | `bifrost-2` — seconde frontière |
| `10.0.4.3` | libre, réservée à une IP virtuelle CARP si les deux passent en HA |
| `10.0.4.4-.5` | libres |
| `10.0.4.6` | SVI du switch routeur (`le commutateur`) |
| `10.0.4.41 / .43 / .47` | `asgard`, `gandalf`, `vishnu` — les hyperviseurs, via `bond3` |
*(Ce bloc annonçait `10.0.4.0/29` et une `passerelle: 10.0.4.6` — le SVI du commutateur —
jusqu'au 2026-09-06. Le commutateur ne route plus les tenants : il transporte du VXLAN
qu'il ne lit pas.)*
Le jour où les deux OPNsense passent en haute disponibilité, `passerelle_sortie` devra
pointer sur l'**IP virtuelle CARP** et non sur un boîtier nommé — c'est le seul changement
@ -318,7 +324,7 @@ Même partage que Proxmox — l'anodin en clair, le secret dans la voûte :
| Quoi | Où | Réglable |
|---|---|---|
| URL de gestion, interfaces, prochain saut | `group_vars/opnsense.yml` (en clair) | **panneau « Intrants de base » du GUI**, section *Frontière* |
| URL de gestion, interfaces, prochain saut | `opnsense.yml` (en clair) | **panneau « Intrants de base » du GUI**, section *Frontière* |
| Clé et secret d'API | voûte unique de l'instance, sous `vault_opnsense_api_key` / `vault_opnsense_api_secret` | `ansible-vault edit` |
Les valeurs non sensibles sont de **vrais intrants** : `opnsense_api_url`,
@ -420,7 +426,7 @@ Reste à trancher sur une interface **physique** : `ip ?`, `access-group ?`, `sp
et `<PORT-TRUNK>` ; rien dans le modèle ne peut les deviner.
- ~~**La sortie générale** n'est pas déclarée~~ — **réglé le 2026-08-02.** Elle est
déclarée dans le registre, donc dérivée comme le reste : `serveur_debian` (le socle, porté
par tous les hôtes) déclare 443, 80 et 123/udp — dépôts apt et horloge ; `client_unbound`
par tous les hôtes) déclare 443, 80 et 123/udp — dépôts apt et horloge ; `client_resolveur`
déclare 53 en UDP et TCP, la récursion depuis la racine que le choix souverain implique.
Le `block out` de la section 5 est donc un vrai default-deny **assumé**, et le devis
l'énonce désormais au lieu de le poser en silence.

View file

@ -0,0 +1,131 @@
# La frontière entre les deux mondes — physique et virtuel
> **Pour qui :** l'**exploitant** et le **mainteneur**. Ce document tranche une question qui
> revenait à chaque décision : *qui possède quoi, et où ça vit ?*
Set-OPS manipule deux mondes qui se ressemblent et n'obéissent pas aux mêmes règles. Les
confondre a coûté plusieurs soirées ; les séparer proprement rend la plupart des questions
suivantes évidentes.
---
## Les deux mondes
| | **Monde PHYSIQUE** — l'underlay | **Monde VIRTUEL** — le tenant |
|---|---|---|
| Ce que c'est | commutateurs, hyperviseurs, frontière, stockage, transport VXLAN | VM, applications, données |
| Possédé par | l'**hébergeur** | l'**organisation** |
| Adressage | bande basse du site : `10.<index>.0-15.x` | zones : `10.<index>.16+.x` |
| Décrit dans | `underlay.yml`, `proxmox-hebergeur.yml` | `plan/` |
| Monté par | le symlink `underlay.yml` | le symlink `instance` |
| Change quand | on touche au **matériel** | on touche au **service** |
| Ses secrets | jeton d'API Proxmox, clé d'API de la frontière, accès aux commutateurs | LDAP, forge, SSO, bases |
| Sa voûte | **la sienne**, chez l'hébergeur | `group_vars/all/vault.yml` |
**Les deux symlinks sont indépendants** (D-80) : `instance` dit *quel tenant*,
`underlay.yml` dit *sur quelle fabric*. Un tenant se déplace d'une fabric à l'autre sans
qu'on touche à son plan — c'est ce qui rend la portabilité possible.
---
## La règle qui rend la frontière opérante
> **Un tenant ne détient jamais un secret du monde physique.**
Chaque tenant a porté dans sa voûte le jeton d'API du cluster — chaque nouveau tenant devait le
recopier pour exister. C'était exactement la faute des **neuf copies** de la résolution d'instance,
appliquée aux secrets : une valeur qui vit à N endroits finit par diverger, et on ne peut
plus révoquer l'une sans révoquer les autres.
**C'est réglé depuis le 2026-08-22** : l'underlay a **sa propre voûte**
(`underlay.vault.yml`), chez l'hébergeur, à côté d'`underlay.yml`. Les opérations qui parlent
au matériel — cloner une VM, poser une zone SDN, écrire sur la frontière — l'y lisent : le
chemin se **dérive** du symlink `underlay.yml`, sans rien redéclarer
(`playbooks/proxmox/cloner_vm_debian.yml`). Les tenants n'y ont pas accès et n'en ont pas
besoin.
Deux compléments arrivés depuis, et qui achèvent la séparation :
- les **clés** aussi sont séparées (2026-08-28) : une voûte, **une clé**. Tant qu'un seul mot
de passe les ouvrait toutes, la séparation était organisationnelle, pas cryptographique ;
- **P25** refuse qu'une clé de l'hébergeur — nœuds, stockages, ponts, API — réapparaisse dans
un `group_vars` de tenant. La garde attrape la rechute : un `make config` lancé d'un autre
poste, une reprise à la main.
---
## Qui administre quoi, et depuis où
L'administration du monde physique se fait **depuis le tenant de l'hébergeur** : c'est lui
qui porte les outils, les clés et les traces. Le plan de gestion du site
(`10.<index>.0.0/24`) est la seule source autorisée à ouvrir SSH sur la flotte et à
franchir la frontière.
Ce n'est pas un détail de commodité. Une machine qui administre depuis l'extérieur des deux
mondes — un portable sur le réseau de la maison — n'est ni sauvegardée, ni reconstructible,
ni prouvée. Le jour où elle disparaît, l'écosystème est intact et personne ne peut plus y
entrer.
---
## Ce que la confusion a coûté
**Une règle qui ne peut jamais correspondre.** `devis_opnsense` dérive l'interface d'une
règle de l'**attachement réel** de sa source (D-61), et cet attachement se lit dans
`underlay.yml`. Un plan d'administration absent du fichier est classé « distant », et sa
règle atterrit sur `wan`. Le 22 août, on s'apprêtait à poser 89 objets sur la frontière
avec une règle d'admin qui n'aurait jamais laissé passer personne.
**Un fichier qui décrit un monde disparu.** Mesuré le même jour :
```
management 10.0.0.0/24 déclaré → PERSONNE
stockage 10.0.1.0/24 déclaré → PERSONNE
ceph 10.0.2-3.0/24 déclaré → PERSONNE
transit 10.0.4.0/24 déclaré → occupé (3 nœuds)
vxlan 10.0.5.0/24 déclaré → occupé (3 nœuds)
et, portés par les nœuds sans être déclarés nulle part :
192.168.11.x 10.11.5-7.x 192.168.50.x 10.1.110.254
```
La frontière avait déjà migré vers `10.17.0.1` ; les hyperviseurs, non. Le fichier était
resté au monde d'avant.
---
## L'instrument
```
make underlay-plan # confronte le fichier au réel, n'écrit rien
```
Il interroge l'API du cluster pour les adresses **réellement portées**, et sonde en TCP ce
qui répond. Deux précautions y sont inscrites, toutes deux apprises en l'écrivant :
- **l'autorité dépend du rôle.** L'API de Proxmox connaît ses hyperviseurs, et eux seuls.
Déclarer un commutateur « porté par personne » parce que le cluster l'ignore, c'est
accuser le monde de ce que l'instrument ne voit pas ;
- **« pas joignable d'ici » n'est pas « absent ».** Les réseaux de *chemin* (transit,
transport VXLAN, stockage) ne sont **jamais** joignables depuis l'extérieur, par
construction (D-78). Le premier jet du devis les déclarait morts.
---
## La séquence — faite, dans cet ordre
Cette section était une liste de tâches ouvertes. **Les trois sont faites** ; on la garde
sous forme d'ordre parce que c'est l'ordre lui-même qui est la leçon — un site neuf le
rejouera tel quel.
- [x] **Reconnaître** — `make underlay-plan` confronte l'underlay *déclaré* au réel (API du
cluster + sondes). `underlay.yml` décrit ce qui **est**, pas ce qui était prévu :
chaque réseau porté et non déclaré est un réseau que le moteur ne sait pas classer.
- [x] **Séparer les voûtes** — fait le **2026-08-28**. Une voûte, une clé
(`scripts/voutes.py`, `ANSIBLE_VAULT_IDENTITY_LIST`) : le jeton Proxmox et la clé de
la frontière vivent dans `underlay.vault.yml`, hors de toute voûte de tenant. La
séparation devait précéder la distribution des clés aux runners — sans elle, poser
« la » clé sur le plus petit locataire lui donnait les secrets de l'hébergeur.
- [x] **Appliquer la frontière** — ses règles dépendent des deux points ci-dessus, et
elles en dérivent : P43 mesure que le devis de la frontière retrouve les machines du
plan (7 machines, 104 règles du site).

View file

@ -34,15 +34,23 @@ poste de l'opérateur et sa forge. Rien à changer.
## 3. Ce qui n'a pas de maison aujourd'hui
Constaté le 2026-08-04 : **aucun équipement de l'hébergeur n'est dans un inventaire
Ansible**, et rien ne sauvegarde leurs configurations.
Constaté le 2026-08-04, **révisé le 2026-09-05**. La moitié du constat a été levée
entre-temps ; l'autre tient toujours, et il faut distinguer les deux.
**Ce qui a une maison désormais** : les **VM du site** ont leur inventaire Ansible
(`scripts/site_inventaire.py` — 7 machines, 23 groupes, dérivé d'`underlay.yml` sans
fichier intermédiaire), leur socle, leur durcissement, leurs sauvegardes et leur
supervision (`site-mon-01`, depuis le 2026-09-02).
**Ce qui n'en a toujours pas** : les **équipements** eux-mêmes — hyperviseurs,
commutateurs, frontière. C'est là que le constat d'origine reste entier.
| Besoin | État |
|---|---|
| Supervision des hyperviseurs, commutateurs, frontière | personne |
| Supervision des hyperviseurs, commutateurs, frontière | personne — `site-mon-01` ne voit que les VM du site |
| Journaux de ces équipements | personne |
| Sauvegarde de leurs configs (`running-config`, `config.xml`, `/etc/pve`) | personne |
| Résolution des noms d'underlay (`asgard`, `bifrost-3`, `bifrost-1`) | personne |
| Sauvegarde de leurs configs (`running-config`, `config.xml`, `/etc/pve`) | personne — **vérifié le 2026-09-05, aucun rôle ne les touche** |
| Résolution des noms d'underlay (`asgard`, `bifrost-3`, `bifrost-1`) | personne — **vérifié : `site-dns-01` ne les résout pas** |
| Certificats pour leurs interfaces web | personne |
Ce n'est pas un oubli de conception : ces besoins tombaient entre les chaises. Ils ne sont
@ -54,7 +62,9 @@ d'aucun tenant, et `underlay.yml` ne décrit que du matériel, sans service.
`proxmox-hebergeur.yml`. Ses services d'exploitation lui appartiennent au même titre que
sa fabric ; les loger ailleurs recréerait la confusion qu'on vient de défaire.
Ce dépôt n'a pas d'inventaire Ansible aujourd'hui : c'est ce que le chantier ajoutera.
Ce dépôt **a désormais son inventaire Ansible** — `scripts/site_inventaire.py`, dynamique
plutôt que généré, parce qu'un site ne dérive de rien : sa déclaration *est* déjà sa forme
finale. Ce qui reste à faire, c'est d'y loger les **équipements**, qui n'y sont pas.
**Rattachement réseau : un pont VLAN ordinaire, jamais un VNet du SDN.** C'est la
contrainte qui découle du §1, et la seule qui distingue ces VM de celles d'un tenant.
@ -143,30 +153,48 @@ emporter — mais il ne devrait pas non plus les nommer. L'interface normalisée
`proxmox_noeuds` et `proxmox_stockages` sont déjà chez lui ; il manque la classe, pas le
catalogue.
## 8. Un dépôt à part pour le réseau (décidé, non fait)
## 8. Un dépôt à part pour l'hébergeur — FAIT
`underlay.yml` et `proxmox-hebergeur.yml` vivent aujourd'hui dans `OPS-Chezlepro`, qui est
aussi le dépôt du **tenant** Chezlepro. C'est ce qui a permis de démarrer, et c'est ce qui
oblige, à chaque commit, à trancher si l'on touche à l'infrastructure ou à l'organisation.
> **Cette section disait « Rien n'est fait » jusqu'au 2026-09-06.** Le dépôt existe :
> **`SITE-Chezlepro`**, frère de `OPS-Chezlepro`, et le symlink `underlay.yml` du moteur y
> pointe (`../SITE-Chezlepro/underlay.yml`).
Ils décrivent des objets différents : l'un une **infrastructure** — des câbles, des VLAN,
un cluster —, l'autre une **organisation** — ses serveurs, ses applications, ses comptes.
Un tenant peut déménager ; une fabric ne déménage pas.
Le raisonnement qui l'a motivé, et qui vaut pour tout hébergeur : `underlay.yml` et
`proxmox-hebergeur.yml` décrivent une **infrastructure** — des câbles, des VLAN, un
cluster ; le plan d'un tenant décrit une **organisation** — ses serveurs, ses applications,
ses comptes. Un tenant peut déménager ; une fabric ne déménage pas. Les garder dans le même
dépôt obligeait, à chaque commit, à trancher lequel des deux on touchait.
Le dépôt réseau de l'hébergeur porterait donc `underlay.yml`, `proxmox-hebergeur.yml`, et
plus tard l'inventaire des services d'exploitation (§4). Le symlink qui désigne
l'hébergeur pointerait vers lui plutôt que vers son tenant — ce qui rendrait enfin la
distinction visible dans les chemins eux-mêmes.
Ce que le dépôt de l'hébergeur porte aujourd'hui :
**Rien n'est fait.** La bascule demande de déplacer deux fichiers, de refaire le symlink,
et de vérifier que les trois générateurs qui les lisent suivent.
| Fichier | Ce qu'il décrit |
|---|---|
| `underlay.yml` | la fabric physique — réseaux, VLAN, MTU, commutateurs, hyperviseurs |
| `proxmox-hebergeur.yml` | l'API du cluster, ses nœuds, ses stockages, ses ponts |
| `opnsense.yml` | la frontière nord/sud (paramètres non sensibles) |
| `underlay.vault.yml` | ses secrets à lui — **voûte séparée**, clé séparée (2026-08-28) |
| `plan/` | ses **propres VM** : sept machines de service (pilotage, autorité, génome, cache, sauvegarde, supervision, DNS) |
## 9. Le VLAN de gestion, et ce qu'il n'est pas
C'est cette dernière ligne qui a le plus changé : l'hébergeur n'est plus seulement un
porteur de fabric, **c'est un exploitant** — avec son inventaire (`scripts/site_inventaire.py`),
son socle, son durcissement, ses sauvegardes et sa supervision.
`10.0.0.0/24` est réservé à l'**IPAM, la gestion des équipements et l'OOB/IPMI**. Accès
sysadmin uniquement.
## 9. Le plan d'administration, et ce qu'il n'est pas
Aucun hyperviseur n'y a d'adresse, aucune VM n'y est branchée, aucun trafic tenant ne le
traverse — ni encapsulé, ni décapsulé. C'est précisément la raison d'être des VLAN 11
(transport VXLAN) et 40 (sortie tenant) : les avoir sortis de ce domaine de diffusion.
> **Les adresses de cette section ont toutes changé** avec la bascule D-77/D-78, terminée le
> 2026-08-22. Elle désignait `10.0.0.0/24` et les « VLAN 11 (transport VXLAN) et 40 (sortie
> tenant) » ; `10.0.0.0/24` n'existe plus, et le transport est passé au VLAN 50.
Le plan d'**administration** — `10.17.0.0/24`, dans la bande basse du supernet du tenant
Chezlepro (D-77) — est réservé aux **équipements** et à l'exploitant. Il n'a **aucun VLAN** :
c'est un segment physique, et **aucun pont d'hyperviseur ne le touche**, donc aucune VM ne
peut y naître. Le validateur refuse d'ailleurs qu'on y déclare une machine.
Distinct de lui, le **contrôle de la grappe** (`192.168.11.0/24`, `vmbr0`) porte l'interface
web de Proxmox et le dialogue entre nœuds.
Aucun trafic tenant ne traverse ni l'un ni l'autre — ni encapsulé, ni décapsulé. C'est
précisément la raison d'être du **VLAN 50** (transport VXLAN) et du **VLAN 40** (transit vers
la frontière) : les avoir sortis de ces domaines de diffusion. *(Le transport a porté le
numéro 11 jusqu'à la bascule ; `192.168.11.0/24` étant pris par le contrôle de la grappe, il
est passé au 50.)*

View file

@ -32,9 +32,15 @@
|---|---|---|
| **Apps web** (Forgejo, Grafana, Nextcloud…) | **Keycloak OIDC** (SSO) | un seul login, MFA, jetons |
| **Mail** (Dovecot, Postfix) | **LDAP direct** (bind) | IMAP/SMTP ne parlent pas OIDC ; username + mot de passe |
| **Système / services** (SSSD, etc.) | **LDAP direct** | annuaire standard |
| **App web SANS OIDC natif** | **`oauth2-proxy` devant**, lui-même client OIDC | met au SSO ce qui ne sait pas y aller seul |
| Tout | → **même OpenLDAP** | identité unifiée, aucun compte en double |
> **L'ouverture de session Unix n'est PAS dans ce tableau, et c'est délibéré.** Cette ligne
> annonçait « Système / services (SSSD, etc.) → LDAP direct » jusqu'au 2026-09-06. Le dépôt
> ne fait pas ça : le rôle `client_ldap` a été **retiré le 2026-07-04, hors conception**, et
> aucun rôle n'installe SSSD. L'annuaire sert les **applications** ; l'accès à un hôte se
> fait par **clé SSH**, et l'élévation par `sudo`. Voir `docs/authentification.md` §2.
## Sens de provisionnement
Les identités se créent et se gèrent dans **OpenLDAP**. Keycloak **fédère** (lit LDAP en
@ -54,7 +60,10 @@ Keycloak : c'est LDAP la référence. Le mail bind directement sur LDAP.
## Conséquences pour Set-OPS
- `serveur_openldap` (déployé) = le pilier annuaire, source de vérité.
- `serveur_keycloak` = pilier SSO, à **fédérer sur OpenLDAP** (User Federation → LDAP).
- `serveur_keycloak` = pilier SSO, **fédéré sur OpenLDAP** — et la fédération est
*automatisée*, pas un geste manuel : `roles/serveur_keycloak/tasks/federation-ldap.yml`
(avec ses mappeurs d'attributs, ses groupes et sa politique de mot de passe dans les
tâches voisines). `make identite-plan` relit ensuite ce qui est réellement en place.
- `serveur_dovecot` / `serveur_postfix` = auth **LDAP direct** (cohérent avec ce modèle).
- Les apps web déclarées dans le plan → clients **OIDC** de Keycloak.

View file

@ -27,7 +27,7 @@ par une **commande qui interroge le système**, jamais par une conviction.
## Phase 0 — au bureau, avant de partir
- [ ] **Recevoir la fiche de l'hébergeur** — les huit lignes du §7 de
- [ ] **Recevoir la fiche de l'hébergeur** — les dix lignes du §7 de
[`preparer-un-site-hebergeur.md`](preparer-un-site-hebergeur.md). Sans le nom exact
du nœud et des stockages, la journée s'arrête à la phase 1.
- [ ] **Fixer l'`index` du site = l'`index` de son tenant.** Il n'y a pas de second
@ -44,11 +44,13 @@ par une **commande qui interroge le système**, jamais par une conviction.
moteur de pare-feu.
> **Un site neuf se construit d'emblée dans l'adressage cible** — gestion en
> `10.<index>.0.0/24`, chemins en `192.168.<vlan>.0/24` (D-77, D-78). Le site historique
> est encore en `10.0.x` et migrera par [`runbooks-exploitation.md`](runbooks-exploitation.md) §6.
> Sur un site vierge, la cible ne coûte rien — et deux sites en `10.0.0.0/24` rendraient
> la **reprise mutuelle impossible** : deux plans de gestion identiques ne peuvent pas
> s'atteindre.
> `10.<index>.0.0/24`, chemins en `192.168.<vlan>.0/24` (D-77, D-78). Sur un site vierge, la
> cible ne coûte rien — et deux sites qui porteraient le **même** plan de gestion rendraient
> la **reprise mutuelle impossible** : deux réseaux identiques ne peuvent pas s'atteindre.
>
> *(État du site historique, mesuré le 2026-09-06 : sa gestion est **déjà** en `10.17.0.0/24`
> et son transport VXLAN en `192.168.50.0/24` ; le transit et le stockage restent dans
> l'ancien espace. Ce paragraphe le donnait entièrement « encore en `10.0.x` ».)*
---
@ -203,17 +205,17 @@ make frontiere-plan make sdn-plan make placement-plan
```yaml
---
underlay:
# LE SEED DU SITE — le même que celui de son tenant, et la clé qu'on oublie.
# C'est elle qui dit au validateur que 10.<index>.0.0/16 est SON supernet, donc que
# la bande basse lui appartient. Sans elle, `make underlay` refuse le réseau de
# gestion en le prenant pour celui d'un AUTRE site — message déroutant, cause triviale.
index: <index>
# LES TENANTS QUE CE SITE PORTE — noms de dossier, pas de fantaisie. Les trois devis
# d'équipement (frontière, commutateur, SDN) découvrent TOUTE la fédération : sans
# cette clé, le second site se voit proposer les règles, les VLAN et les zones du
# premier. Le matériel les accepte, aucune ne correspond jamais à un paquet, et rien
# ne le signale. Un site UNIQUE n'a rien à déclarer ; c'est le second qui se nomme.
tenants: [OPS-<tenant>]
# UN SITE N'A PAS D'INDEX (depuis le 2026-08-25). Il en portait un ; c'était un vestige.
# Un site ne dérive AUCUN adressage — ses machines vivent sur des réseaux de fabric.
# Cette valeur ne disait qu'une chose : quel supernet de tenant est le sien. Et elle le
# disait pour le site ENTIER alors qu'UN SEUL réseau est concerné.
#
# LES TENANTS QUE CE SITE PORTE — nom de dossier -> index. Les trois devis d'équipement
# (frontière, commutateur, SDN) découvrent TOUTE la fédération : sans cette clé, le
# second site se voit proposer les règles, les VLAN et les zones du premier. Le matériel
# les accepte, aucune ne correspond jamais à un paquet, et rien ne le signale.
tenants:
OPS-<tenant>: <index>
routeur: <nom-du-commutateur> # racine du spanning-tree, pas un routeur
# `stp` exige `routeur` : ne pas le déclarer tant qu'aucun commutateur ne l'est.
routage_tenants: sdn # le routage inter-zone vit sur l'hyperviseur
@ -222,7 +224,12 @@ underlay:
dialecte: cisco # cisco | binardat — propriété du MATÉRIEL
stp: { mode: mstp, topologie: etoile }
reseaux:
- { nom: management, vlan: 10, sous_reseau: 10.<index>.0.0/24, passerelle: 10.<index>.0.1, mtu: 1500 }
# `bande_basse_de` DÉCLARE le chevauchement VOULU : ce /24 vit dans le /16 du tenant
# nommé, dans la bande 0-15 que ses zones (3e octet >= 16) n'allouent jamais. Sans
# cette clé, `make underlay` refuse le réseau — et il a raison : il ne peut pas
# distinguer un chevauchement voulu d'un accident. Le nom est vérifié contre `tenants:`,
# donc une faute de frappe est refusée.
- { nom: management, vlan: 10, bande_basse_de: OPS-<tenant>, sous_reseau: 10.<index>.0.0/24, passerelle: 10.<index>.0.1, mtu: 1500 }
- { nom: transit-frontiere, vlan: 40, sous_reseau: 192.168.40.0/24, passerelle_sortie: 192.168.40.1, mtu: 1500 }
- { nom: underlay-vxlan, vlan: 50, sous_reseau: 192.168.50.0/24, mtu: 1500 }
# Stockage : uniquement les réseaux réellement câblés (fabric: stockage, mtu 9000).

View file

@ -7,9 +7,11 @@
Une intégration est de l'un des deux genres, et ils ne se déclarent pas au même endroit.
**Universelle** — supervision, journaux, PKI. Il n'y a aucun choix de cible : un seul
Prometheus, un seul Loki, une seule AC. Elle est déclarée **une fois, par le rôle**, dans
`roles/<role>/meta/integration.yml`, et tout hôte la reçoit :
**Universelle** — métriques, journaux, PKI, **résolution** et **source d'artefacts**
(les cinq qui portent `universelle: true`). Il n'y a aucun choix de cible : un seul
Prometheus, un seul Loki, une seule AC, un seul résolveur, un seul cache. Elle est déclarée
**une fois, par le rôle**, dans `roles/<role>/meta/integration.yml`, et tout hôte la
reçoit :
```yaml
integration:
@ -18,8 +20,16 @@ integration:
sauf_role: serveur_step_ca # facultatif — voir « exemptions »
```
**Facultative** — `client_backup`, `client_smtp`, `client_unbound`. Là il y a un vrai choix,
et il se déclare par serveur, dans `plan/serveurs.yml : integrations`.
**Facultative** — `client_backup` et `client_smtp`. Là il y a un vrai choix, et il se
déclare par serveur, dans `plan/serveurs.yml : integrations`.
> **`client_resolveur` a changé de camp le 2026-08-24, et ce document l'annonçait encore
> facultatif.** Il posait alors un Unbound sur chaque VM — un vrai coût, donc un vrai choix.
> Il n'installe plus rien : il écrit `/etc/resolv.conf` pour désigner le résolveur du
> tenant. Son intégration est devenue **universelle, sans aucune exemption — pas même
> l'hôte qui porte le résolveur** : il se sert lui-même, et l'exempter reviendrait à dire
> que le résolveur ne se fait pas confiance. Une machine restée sur la résolution
> d'amorçage envoie chacune de ses questions dehors, sans que rien ne le signale.
**Pourquoi cette inversion.** Le plan portait 57 lignes d'intégration écrites à la main. 28
d'entre elles disaient oui à quelque chose de vrai pour tous les hôtes — elles n'existaient

View file

@ -9,14 +9,34 @@ de l'écosystème, en distinguant **constantes** et **défauts surchargeables**.
> au même titre que le [dimensionnement](dimensionnement-ressources.md). Décidée le
> 2026-06-26.
> **Statut, revu le 2026-09-06 : le panneau est construit.** Ce document reste la note de
> *conception* — il explique les arbitrages, pas l'état. Trois écarts entre ce qui était
> proposé et ce qui a été fait, et ils comptent :
>
> 1. **La migration en répertoires n'a eu lieu que pour `all/`.** `group_vars/all/` porte
> bien `00-instance.yml` (tenu à la main) et `10-intrants.yml` (écrit par le GUI) ;
> `proxmox.yml` et `modeles_vm.yml` sont restés des **fichiers plats**. Le §3 les
> présente encore comme des répertoires.
> 2. **Les constantes Proxmox ont déménagé chez l'hébergeur.** L'accès au cluster
> (`proxmox_api_*`) n'est plus un intrant du tenant : il vit dans
> `proxmox-hebergeur.yml`, à côté d'`underlay.yml`, parce qu'un cluster appartient à
> qui possède le matériel. Cf. `config-proxmox.md`.
> 3. **Les « points ouverts » du §8 sont tranchés** par ce qui a été bâti : la liste de
> rappel des secrets attendus est bien dans le panneau, en lecture seule, et elle se
> *recense* (`scripts/voute.py lister`) au lieu d'être recopiée — trois copies manuelles
> avaient existé, toutes avaient divergé.
## 1. Décisions cadre (validées)
1. **Secrets : hors périmètre.** Le GUI n'affiche ni ne stocke aucun secret. Les
`vault_*` et tokens Proxmox restent édités via Ansible Vault en ligne de commande.
Le panneau peut, au plus, afficher une **liste de rappel en lecture seule** des
secrets attendus (sans valeur).
2. **Nomenclature : lecture seule** dans le panneau. L'édition de `supernet`/VLAN/
catégories reste dans `plan/nomenclature.yml` (autorité unique du plan réseau).
2. **Nomenclature : lecture seule** dans le panneau — à une exception près, l'**`index`**,
qui est le seul champ d'adressage saisissable (panneau *Réseau*, écrit chirurgicalement).
Tout le reste — supernet, sous-réseaux, passerelles, VLAN, VMID — se **dérive** et **P20
refuse qu'on l'écrive**. *(Ce point disait « l'édition de `supernet`/VLAN/catégories reste
dans `plan/nomenclature.yml` » : ces valeurs n'y sont plus du tout.)*
3. **Conception avant code** (cette note).
## 2. Modèle : constante vs défaut surchargeable
@ -88,9 +108,10 @@ supporté) ; par groupe, dans le `group_vars/<groupe>/` correspondant.
- **Suite** : politiques de durcissement (défauts par groupe), DNS internes, relais
SMTP, endpoints services centraux.
## 8. Points ouverts à confirmer
- OK pour la **migration `group_vars/*.yml` → répertoires** (§3) ? (alternative :
réécrire les fichiers existants en bloc, au prix des commentaires).
- Le panneau affiche-t-il la **liste de rappel des secrets attendus** (lecture seule),
ou on n'en parle pas du tout dans le GUI ?
- Périmètre MVP (§7) suffisant pour une première itération ?
## 8. Points ouverts — tranchés par ce qui a été construit
| Question de juin | Réponse, telle que le code la donne |
|---|---|
| Migrer `group_vars/*.yml` → répertoires ? | **Pour `all/` seulement.** `proxmox.yml` et `modeles_vm.yml` sont restés plats — la migration n'a payé que là où le GUI écrivait vraiment. |
| Afficher la liste de rappel des secrets ? | **Oui, en lecture seule** — et *recensée*, jamais recopiée (`scripts/voute.py lister`, gardée par **P18**). |
| Périmètre MVP suffisant ? | Oui, et il a été dépassé : la couverture du GUI est désormais **prouvée** par **P19**, qui refuse un champ du plan que la console ne saurait pas éditer. |

View file

@ -18,14 +18,22 @@ fois**, depuis un endroit unique, puis les laisser se **dériver** ou se **propa
- `setops_plan_dir` — chemin du plan.
### B. Réseau & nomenclature — `plan/nomenclature.yml`
- `supernet`, `cidr_hote`, `reservations`, `categories` (VLAN/sous-réseau/passerelle),
`fonctions` (catégorie + service).
- **`index`** — le **seed**, et le seul champ d'adressage. Supernet, sous-réseaux,
passerelles, VLAN et VMID en **dérivent** ; la preuve **P20** refuse qu'on les y écrive.
- `cidr_hote`, `reservations`, `categories` (libellés de zones), `fonctions`
(catégorie + service).
> Ce paragraphe listait `supernet` comme un intrant de la nomenclature jusqu'au
> 2026-09-06. C'est exactement ce que **P20 interdit** : un adressage stocké est un
> adressage qui peut contredire celui qu'on dérive.
### C. Hyperviseur Proxmox — `group_vars/proxmox.yml`
- `proxmox_api_host`, `proxmox_api_user`, `proxmox_api_port`, `proxmox_validate_certs`.
- 🔒 `proxmox_api_token_id`, `proxmox_api_token_secret` — dans la **voûte unique** de
l'instance, `group_vars/all/vault.yml`. (L'ancienne `proxmox.vault.yml` reste lue en
compatibilité si elle existe encore ; cf. `docs/config-proxmox.md`.)
l'instance, `group_vars/all/vault.yml`. **`proxmox.vault.yml` n'est plus lue** (retirée le
2026-08-03) : tolérée « en compatibilité », elle était restée le *seul* porteur du jeton
chez un tenant — et comme `*.vault.yml` est gitignoré, ce jeton ne voyageait avec aucun
dépôt. Une voûte unique qui ne l'était pas. Cf. `docs/config-proxmox.md`.
- Golden template : `proxmox_clone_vmid_modele`, `proxmox_clone_source_nom`.
- Placement par défaut : `proxmox_clone_noeud`, `proxmox_clone_stockage`,
`proxmox_clone_pont`, format, complet, timeout, disque, interface, démarrer.
@ -40,7 +48,7 @@ nftables baseline · fail2ban SSH · auditd · AppArmor · sysctl · unattended-
journald (rétention) · core_dumps · systemd_ssh_auto.
### F. Endpoints des services centraux (les rôles `client_*` en dérivent)
- DNS interne : plancher `/etc/hosts` (`hosts_statiques`) + PowerDNS + `client_unbound` (opt-in).
- DNS interne : plancher `/etc/hosts` (`hosts_statiques`) + PowerDNS (autoritatif) + `serveur_resolveur` (LE récursif du tenant), désigné sur chaque nœud par `client_resolveur` — intégration **universelle**, pas opt-in.
- AC/PKI (`client_pki_ca_url` → infra-pki, provisioner).
- IdM/LDAP : annuaire résolu par `resoudre_annuaire` (hôte + base DN dérivés du domaine).
- Relais courriel (`client_smtp_relais` → edge-mta, MTA Postfix, port 25).
@ -84,13 +92,13 @@ domaines publics, `edge`, autorité DNS, FQDN exposés.
| Intrant | Classe | Surcharge où ? |
| --- | --- | --- |
| `domaine_interne` | **Constante** | — |
| Nomenclature (supernet, CIDR, catégories, fonctions) | **Constante** | — (le plan réseau est la loi) |
| Nomenclature (**`index`**, CIDR d'hôte, catégories, fonctions) | **Constante** | — (le seed est la loi ; l'adressage en dérive) |
| Accès Proxmox (API host/user/port/token) | **Constante** | — (un seul cluster) |
| Golden template (vmid_modele, source_nom) | **Constante** | — |
| Secrets Vault | **Constante** 🔒 | — (gérés à part, jamais en clair) |
| `fuseau_horaire` | Défaut | par hôte (rare) |
| `proxmox_clone_noeud` / `stockage` / `pont` | Défaut | par hôte (`serveurs.yml`) |
| DNS internes (plancher `/etc/hosts` + PowerDNS + `client_unbound`) | Défaut | par hôte / groupe |
| DNS internes (plancher `/etc/hosts` + PowerDNS) | Défaut | par hôte / groupe |
| Politiques durcissement (SSH, nftables, fail2ban, journald…) | Défaut | par hôte / groupe |
| Relais SMTP, TLS internes | Défaut | par hôte / groupe |
| `ciuser`, compte `ansible` | Défaut | rarement surchargé |
@ -109,7 +117,17 @@ dossier ; la forme plate `group_vars/all.yml` reste lue en compatibilité —, `
`modeles_vm.yml`, `plan/nomenclature.yml`). À détailler dans la note de conception de la
fonctionnalité GUI.
## 4. Incohérences repérées (à corriger)
- `fuseau_horaire` défini en **lab** seulement, absent de **production**.
- `group_vars/serveur_debian.yml` **référencé** (commentaire de prod `all.yml`) mais
**absent** des deux environnements.
## 4. Incohérences repérées — soldées
Les deux écarts que cette section signalait n'existent plus (vérifié le 2026-09-06), et le
modèle « deux environnements » qui les portait non plus :
- ~~`fuseau_horaire` défini en **lab** seulement, absent de **production**~~ — il est dans
`group_vars/all/10-intrants.yml` (`America/Toronto`), avec `domaine_interne`.
- ~~`group_vars/serveur_debian.yml` référencé mais absent~~ — plus aucune référence.
> **Il n'y a plus d'« environnements ».** Ce document parle de `<env>` par endroits : c'est
> le vocabulaire d'avant la séparation par instance. Une instance = **un dépôt**, avec **un**
> inventaire (le moteur en résout le nom, cf. `plan-et-generation.md`). « Lab » et
> « production » ne sont pas deux environnements d'un même écosystème : ce sont deux
> écosystèmes, chacun avec son plan, son inventaire et sa voûte.

View file

@ -37,7 +37,7 @@ flowchart TB
subgraph INST["③ INSTANCES — la flotte (inventory hosts.yml)"]
direction LR
INV[["hosts.yml"]]
VMS(("11 VM<br/>infra-pki · dns · mail · edge<br/>idm · data · obs · mon · forge · web"))
VMS(("14 VM<br/>infra-pki · infra-dns · infra-mail · infra-edge · edge-mta<br/>idm · data-sql · obs · mon · forge · collab<br/>web-frontal · web-dorsal · ops"))
end
ECO["④ ÉCOSYSTÈME souverain en service<br/>PKI · DNS · IdM/SSO · Données · Observabilité · Forge · Web"]
@ -60,8 +60,9 @@ flowchart TB
réseau (VMID·VLAN·IP·gw) · ressources (cœurs·RAM·disque) · groupes · DSN+DNS
│ = instanciation
▼
③ INSTANCES — la flotte (inventory hosts.yml : 11 VM cohérentes)
infra-pki·dns·mail·edge · idm · data · obs · mon · forge · web
③ INSTANCES — la flotte (inventory hosts.yml : 14 VM cohérentes)
infra-pki · infra-dns · infra-mail · infra-edge · edge-mta · idm · data-sql
obs · mon · forge · collab · web-frontal · web-dorsal · ops
▲ clone du golden template + identité cloud-init
│ make deployer (rôles Ansible par groupe)
▼

View file

@ -0,0 +1,147 @@
# Métriques dérivées des rôles
> **Pour qui :** celui qui ajoute un rôle à Set-OPS et se demande comment ses mesures
> arrivent dans Prometheus — et celui qui exploite et veut savoir d'où sortent les
> courbes qu'il regarde.
> **La règle en une phrase.** Un rôle déclare l'exportateur de ses propres mesures ; le
> moteur en dérive la cible de scrutation et les panneaux. Comme `meta/flux.yml` engendre
> nftables *et* OPNsense, comme `meta/supervision.yml` engendre les services Icinga.
## Pourquoi un second fichier
`meta/supervision.yml` et `meta/metriques.yml` répondent à des questions différentes, sur
des données différentes, pour des consommateurs différents.
| | ce qu'il déclare | la question | le consommateur |
|---|---|---|---|
| `supervision.yml` | une **sonde** qui rend un verdict avec un TTL | *est-ce cassé ?* | Icinga |
| `metriques.yml` | un **exportateur** qui expose une série | *depuis quand, et vers où ?* | Prometheus → Grafana |
Ce n'est pas une frontière inventée pour l'occasion. `docs/supervision-conception.md` la
pose déjà dans l'autre sens :
> Une **métrique à seuil** — durée de collecte, volume de journaux, taux d'occupation —
> appartient à Prometheus et Grafana. Icinga répond à une seule question : *est-ce cassé ?*
> Mélanger les deux rendrait les deux moins lisibles.
Ce document est l'autre moitié de cette phrase.
## Le constat qui l'a rendu nécessaire
Mesure du 2026-09-14 : **aucune métrique de service n'était collectée.** Prometheus ne
scrutait que les `node_exporter` — processeur, mémoire, disques, réseau. Rien de
PostgreSQL, rien de l'annuaire, rien des boîtes, rien du cache.
Le crochet existait pourtant : `serveur_prometheus_cibles_supplementaires`, une liste
libre, documentée, et que **personne ne remplissait**. Une facilité offerte à qui saurait
qu'elle existe n'est pas un mécanisme ; c'est une note de bas de page.
## Ce qu'un rôle déclare
```yaml
# roles/<rôle>/meta/metriques.yml
exportateur:
paquet: prometheus-postgres-exporter
service: prometheus-postgres-exporter
port: 9187
job: postgresql
panneaux:
- titre: "Taux de succès du cache"
expr: "..."
unite: ratio
raison: "..."
```
**L'exportateur** est ce que le rôle installe pour qu'il y ait quelque chose à lire. Le
rôle le pose lui-même : il connaît ses chemins, son compte de service, sa vérité de
terrain.
**Les panneaux** sont ce qui mérite d'être regardé dans le temps.
## Combien de panneaux : un par QUESTION QU'ON SE POSE
Ni un par métrique — un exportateur en publie couramment plus de deux cents — ni un par
rôle. Un par question qu'on se pose vraiment quand quelque chose commence à aller moins
bien.
**Le critère :** *une série a sa place ici si elle **précède** un verdict, ou si elle n'en
aura **jamais**.*
- Les **connexions** précèdent un verdict : la sonde Icinga crie à 70 % et à 90 %, le
graphe dit depuis *quand* ça monte. Une base qui passe de 20 à 60 connexions en trois
semaines n'a rien cassé — elle annonce la date où elle cassera.
- Le **taux de succès du cache** n'aura jamais de verdict, et c'est pourquoi il compte.
Quand les données dépassent `shared_buffers`, la base va chercher sur disque de plus en
plus souvent. Rien ne casse, rien n'alerte : tout devient lent. C'est exactement la panne
qu'un graphe voit et qu'une sonde ne verra jamais.
Ce qui bascule d'un coup appartient à Icinga. Ce qui dérive lentement n'a que le graphe
pour se faire voir.
## Ce que le moteur dérive
**La cible de scrutation.** Le nom du dossier du rôle *est* le nom du groupe ; les cibles
sont donc les hôtes actifs de ce groupe. Un rôle déclaré sans hôte ne produit aucun job —
Prometheus n'a pas à porter une cible qui n'existe pas, ni son journal à se remplir de
refus prévisibles.
**Les panneaux**, pour le tableau de bord.
Les cibles **écrites au plan** (`serveur_prometheus_cibles_supplementaires`) complètent la
dérivation, elles ne la remplacent pas : un équipement ou un service tiers n'a aucun rôle
Set-OPS pour se déclarer.
## Ce que le moteur ne dérive PAS, et c'est délibéré
**Le flux.** Le port de l'exportateur doit s'ouvrir depuis l'observatoire, et c'est
`meta/flux.yml` qui le déclare — là où vivent déjà tous les flux du rôle. Deux fichiers
pour un même fait finissent par diverger, et ce dépôt en a assez d'exemples.
## Le compte de service : le moins de droits possible
Un exportateur lit des compteurs. Il n'a aucune raison de pouvoir lire des données.
Pour PostgreSQL, c'est le rôle `pg_monitor` — fourni par le moteur depuis la version 10 —
qui donne accès aux vues de statistiques **et à elles seules**. Faire tourner un
exportateur sous `postgres` serait donner les clés de la base pour lire des compteurs.
**Le mot de passe vient de la voûte.** Vide, l'exportateur n'est pas posé du tout : le rôle
ne l'installe pas et ne crée pas le compte — jamais un mot de passe par défaut. **Prometheus,
lui, dérive quand même la cible** (`:9187`) de tout hôte de `serveur_postgresql` : sans le
secret, la collecte d'`obs-01` passe au rouge. C'est voulu — une base sans métriques doit se
voir, et renseigner le secret est la façon de l'éteindre. *(Cette page promettait l'inverse
jusqu'au 2026-09-28.)*
**La chaîne de connexion ne passe pas par la ligne de commande.** Un `DATA_SOURCE_NAME` en
argument serait lisible dans `ps` par tout le monde sur la machine ; dans un fichier à
`0600`, il ne l'est que par root et le service.
## Le chiffrement : ce qui est fait, ce qui ne l'est pas
`client_metrique` sert ses métriques en **TLS**, certificat synchronisé par `client_pki`.
Les exportateurs de service ne le font pas encore. La dette est écrite dans la `raison` du
flux concerné, avec son remède — `--web.config.file` et l'abonnement au renouvellement.
Elle n'est pas cachée derrière un silence.
## Où en est la couverture
| déclaration | rôles |
|---|---|
| `meta/flux.yml` | 39 |
| `meta/authentification.yml` | 33 |
| `meta/empreinte.yml` | 32 |
| `meta/supervision.yml` | 28 |
| **`meta/metriques.yml`** | **1** |
Le second versant commence. Un rôle sans `metriques.yml` n'est pas fautif — beaucoup n'ont
aucune série qui mérite un graphe. Mais un service qui porte de l'état et n'en déclare
aucune mérite qu'on se demande pourquoi.
## Quand relire ce document
- un rôle qui se met à porter de l'état → il lui faut probablement un exportateur
- un exportateur qui passe en TLS → la dette du flux se referme, et cette page le dit
- un panneau qu'on regarde sans jamais agir dessus → il n'avait pas sa place ici

View file

@ -82,12 +82,15 @@ VM destinée à devenir un template, pas un serveur de production
Vérifications utiles depuis le poste Ansible :
```bash
ssh ansible@10.0.2.99
ansible -i instance/inventories/lab/hosts.yml modeles_vm -m ping
ansible -i instance/inventories/lab/hosts.yml modeles_vm -m setup
ssh ansible@<ip-de-la-VM-modele> # l'adresse est celle que tu lui as donnee, pas une derivee du plan
ansible -i "$SETOPS_INVENTAIRE" modeles_vm -m ping
ansible -i "$SETOPS_INVENTAIRE" modeles_vm -m setup
```
Adapter l'utilisateur et l'adresse IP selon `instance/inventories/lab/hosts.yml`.
`SETOPS_INVENTAIRE` est exporté par le `Makefile`, qui **résout** le nom de l'inventaire
(`INVENTAIRE_LAB` essaie `lab`, puis `principal`, puis `production`) — cette instance-ci
n'a pas de `lab/`, et un chemin écrit en dur y échouait. Adapter l'utilisateur et
l'adresse IP selon l'inventaire ainsi résolu.
### Prérequis côté dépôt
@ -107,7 +110,7 @@ modeles_vm
Les variables du template sont dans :
```text
instance/inventories/production/group_vars/modeles_vm.yml
instance/inventories/<inventaire>/group_vars/modeles_vm.yml
```
### Commande de préparation
@ -153,7 +156,7 @@ Un résultat `changed=0` à la relance est le signal que le playbook est idempot
| Symptôme | Cause probable | Action |
| --- | --- | --- |
| `UNREACHABLE` | IP, SSH, utilisateur ou clé SSH incorrecte. | Vérifier `instance/inventories/lab/hosts.yml`, cloud-init et tester `ssh`. |
| `UNREACHABLE` | IP, SSH, utilisateur ou clé SSH incorrecte. | Vérifier l'inventaire résolu (`$SETOPS_INVENTAIRE`), cloud-init et tester `ssh`. |
| échec `become` | Sudo NOPASSWD absent ou utilisateur non autorisé. | Corriger l'accès sudo initial, puis relancer `make preparer-modele`. |
| échec APT | DNS, passerelle, miroir Debian ou verrou APT. | Vérifier réseau, DNS et processus APT en cours. |
| erreur handler SSH | Handler manquant ou nom `notify` incohérent. | Vérifier les handlers du rôle SSH avant de relancer. |

View file

@ -50,11 +50,17 @@ statut fédéré/local et production ; signale toute **collision d'index** :
make instances
```
```
★ OPS-Chezlepro 13 1131-1136 fédérée prod
OPS-Technolibre 2 1021-1026 fédérée
OPS-Chezlepro-lab 1 1011-1016 local
INSTANCE INDEX VLAN FEDERE PROD
OPS-Chezlepro-lab 13 1131-1136 LOCAL non
* OPS-Chezlepro 17 1171-1176 oui oui
OPS-Technolibre 23 1231-1236 oui non
* = instance active (symlink 'instance'). Basculer : make instance-utiliser NOM=<depot>
```
*(Sortie réelle du 2026-09-06. **Ne pas se fier aux index d'un exemple** : ils bougent —
celui de Chezlepro a changé au moins une fois, Technolibre est passé de 11 à 23. Le seul
endroit qui dit vrai est `plan/nomenclature.yml` de chaque dépôt, et cette commande.)*
**Basculer l'active** — le symlink, avec garde-fous (le dossier existe, `instance` est
bien un symlink) :
@ -64,8 +70,9 @@ make instance-utiliser NOM=OPS-Technolibre # bascule (l'inventaire suit le
```
Rien à « recharger » : l'inventaire vit **dans** le dépôt de l'instance, il suit le lien.
Le GUI (onglet **Réseau**) montre la même flotte et les collisions ; la bascule reste au
CLI (chirurgie de symlink, mal placée dans une interface web).
Le GUI (onglet **Réseau**) montre la même flotte et les collisions — **et sait basculer**,
par le bouton « Activer » de chaque instance. *(Ce paragraphe affirmait le contraire — « la
bascule reste au CLI » — jusqu'au 2026-09-06 ; c'était vrai avant que le bouton n'existe.)*
**Deux réflexes.** (1) Avant tout déploiement, `make instance-courante` : la seule vraie
façon de se tromper est de déployer sur la mauvaise flotte (la colonne `prod` est là pour
@ -97,7 +104,7 @@ FRERES.glob("*/plan/nomenclature.yml") # tout dépôt frère ayant un plan
Une instance **est** donc un dossier : (1) **frère** du moteur (`../OPS-Chezlepro`,
`../OPS-Technolibre`…), (2) portant un **`plan/nomenclature.yml`**, (3) avec un **`index`**.
De là : **active** = ce que résout le symlink `instance` ; **fédérée** = `index` présent
*et* `federe ≠ false`. Les **modèles** (`Set-OPS-Modeles/integral/…`) sont un cran plus
*et* `federe ≠ false`. Les **modèles** (`exemples/modeles/…` ici, `Set-OPS-modeles/…` pour les modèles privés) sont un cran plus
profond — le glob ne les attrape pas, volontairement.
Conséquence : « inscrire » une instance = la déposer à côté des autres. Rien à éditer,
@ -124,6 +131,9 @@ sable met `false` et affiche « bac à sable ».
et la cible Proxmox (`proxmox.yml`).
3. Créer la voûte unique depuis le gabarit (cf. [`config-proxmox.md`](config-proxmox.md)) :
`cp exemples/vault.exemple.yml …/group_vars/all/vault.yml` puis `ansible-vault encrypt`.
**Et poser SA clé** — une voûte, une clé : `~/.config/setops-vault-ops-clientx`, nom
dérivé du dossier en minuscules. `python3 scripts/voutes.py etat` confirme que le moteur
la trouve.
4. `make instance-utiliser NOM=OPS-ClientX` puis `make instancier-appliquer`,
`make inventaire-ui`.
@ -144,7 +154,7 @@ Pour que plusieurs écosystèmes **coexistent** sur une même fabric sans collis
instance reçoit un **`index`** (unique champ d'adressage de `plan/nomenclature.yml` :
`index: N`). **Rien d'autre n'est écrit à la main** — la nomenclature ne garde que le
*modèle* (libellés de zones + placement des fonctions) ; supernet, sous-réseaux,
passerelles, VLAN et VMID se **dérivent** (`scripts/inventory_rules` : `supernet_de`,
passerelles, VLAN et VMID se **dérivent** (`scripts/inventory_rules.py` : `supernet_de`,
`base3_de`, `passerelle_de`, `vlan_de`). Changer `index` rederive tout le réseau — et la
preuve **P20** interdit tout adressage stocké.
@ -166,10 +176,13 @@ mêmes VLAN/VMID : c'est la collision que `make instances` et **P21** attrapent.
`federe: false` : il est alors **exclu du réseau convergé** (devis, `make instances` le
montre « local »). Il garde son adressage dérivé et reste déployable sur *son* infra.
**Plafond théorique : 255 écosystèmes fédérés** (index 1 à 255), borné par l'IPv4
`10.<index>` (2ᵉ octet 1→255). Le décalage de +10, retiré le 2026-08-12, en confisquait
dix — et surtout, il empêchait de lire l'index directement dans l'adresse. Le VLAN (≤ 4094) autorise jusqu'à 308, le VMID bien
plus — c'est donc l'adressage IP qui plafonne. Au-delà d'une poignée d'instances
**Plafond : le 2ᵉ octet IPv4.** `valider_index` borne l'index à **0–255**
(`INDEX_MIN`/`INDEX_MAX`, `scripts/inventory_rules.py`) — la garde est posée **à la source
de la dérivation**, donc aucune fonction ne peut fabriquer une adresse hors bornes, d'où
qu'on l'appelle. En pratique, **éviter 0** : `10.0.x` porte déjà les réseaux de service du
site. Le décalage de +10, retiré le 2026-08-12, confisquait dix valeurs — et surtout, il
empêchait de lire l'index directement dans l'adresse. Le VLAN (≤ 4094) autoriserait
jusqu'à 308, le VMID bien plus : c'est donc l'adressage IP qui plafonne. Au-delà d'une poignée d'instances
co-localisées, confier l'allocation à un **IPAM** (NetBox) plutôt qu'au moteur — cf.
[`positionnement.md`](positionnement.md).

View file

@ -21,14 +21,16 @@ infra-pki-01
infra-edge-01
infra-mail-01
infra-dns-01
edge-mta-01
idm-01
data-01
data-sql-01
obs-01
mon-01
forge-01
collab-01
web-frontal-01
web-dorsal-01
ops-01
```
La couche applicative web suit le même format. Le tier est porté par la fonction, au singulier puisqu'il nomme une instance :
@ -48,11 +50,13 @@ Les anciens noms de test `web-01` et `web-02` sont retirés. Ils ne doivent pas
| `infra-pki-01` | `serveur_step_ca` |
| `infra-edge-01` | `serveur_nginx` |
| `infra-mail-01` | `serveur_dovecot` (mail-store) |
| `infra-dns-01` | `serveur_powerdns` |
| `edge-mta-01` | `serveur_postfix`, `serveur_rspamd` (ce qui parle à l'extérieur) |
| `infra-dns-01` | `serveur_powerdns`, `serveur_resolveur` |
| `idm-01` | `serveur_openldap`, `serveur_keycloak` |
| `data-01` | `serveur_postgresql`, `serveur_redis` |
| `data-sql-01` | `serveur_postgresql`, `serveur_redis` |
| `obs-01` | `serveur_prometheus`, `serveur_loki`, `serveur_grafana` |
| `mon-01` | `serveur_icinga` |
| `mon-01` | `serveur_icinga`, `serveur_icingaweb2`, `serveur_oauth2_proxy` |
| `ops-01` | `serveur_ops`, `serveur_ops_tenant` (le runner de l'écosystème) |
| `forge-01` | `serveur_forgejo` |
| `collab-01` | `serveur_nextcloud`, `serveur_collabora` |
| `web-frontal-01`, `web-frontal-02` | `serveur_web_frontal` |
@ -60,45 +64,47 @@ Les anciens noms de test `web-01` et `web-02` sont retirés. Ils ne doivent pas
Les groupes restent fins et composables. La cohabitation se fait en associant plusieurs groupes au même hôte.
## Plages VMID
## VMID et adressage : tout dérive du seed `index`
| Plage | Usage |
| --- | --- |
| `91xxx` | fondations transversales : PKI, reverse proxy, SMTP |
| `92xxx` | identité : LDAP, SSO |
| `93xxx` | données et cache : PostgreSQL, Redis |
| `94xxx` | observabilité et supervision |
| `95xxx` | applications internes |
| `99xxx` | modèles, essais initiaux ou exceptions documentées |
> **Cette section décrivait le modèle d'avant le multi-instance**, et rien n'y était plus
> vrai : un réseau unique `10.0.0.0/16`, des VLAN 11 à 15, des plages de VMID à cinq
> chiffres (`91xxx`…`99xxx`). Il n'y a plus de plages à réserver, et il n'y a plus *un*
> réseau : chaque écosystème dérive le sien.
## Plan d'adressage interne
Réseau interne unique : `10.0.0.0/16`. Segmentation par fonction, un `/24` et un VLAN par catégorie, **3ᵉ octet = VLAN** (L2 alignée sur L3).
| Catégorie | VLAN | Sous-réseau | Passerelle |
| --- | --- | --- | --- |
| 1 — Fondations / infra | 11 | `10.0.11.0/24` | `10.0.11.1` |
| 2 — Identité | 12 | `10.0.12.0/24` | `10.0.12.1` |
| 3 — Données | 13 | `10.0.13.0/24` | `10.0.13.1` |
| 4 — Observabilité | 14 | `10.0.14.0/24` | `10.0.14.1` |
| 5 — Applications | 15 | `10.0.15.0/24` | `10.0.15.1` |
Adresse d'hôte (4ᵉ octet) : **`service × 10 + NN`**. `.1` = passerelle ; `.2`–`.9` réservés. Exemple : `web-dorsal-01` (catégorie 5, service 4, NN 01) → `10.0.15.41`.
Tout se dérive de la fonction de l'hôte, et la source unique machine-lisible est **`instance/plan/nomenclature.yml`** :
Une instance reçoit **un seul champ d'adressage** : `index`, dans
`instance/plan/nomenclature.yml`. Tout le reste s'en déduit — et la preuve **P20** interdit
de stocker un adressage quelconque (`supernet`, `sous_reseau`, `passerelle`, `vlan`).
```text
hostname = <fonction>-<NN>
VMID = 9 · catégorie · service · NN
VLAN = catégorie.vlan
IP = 10.0.<vlan>.(service × 10 + NN)
supernet = 10.<index>.0.0/16
zone (3 oct.) = 10.<index>.(15 + catégorie)
sous-réseau = 10.<index>.(15 + catégorie).0/24
passerelle = 10.<index>.(15 + catégorie).1 ← premier hôte du /24
VLAN = 1000 + index × 10 + zone ← unique sur tout le trunk convergé
VMID = <VLAN><hôte sur 3 chiffres><rang sur 2> ← neuf chiffres, miroir de l'IP
adresse IP = 10.<index>.(15 + catégorie).<hôte>
```
`make inventaire-ui` lit ce registre et **propose** automatiquement VMID, VLAN, IP et passerelle quand on nomme un hôte. La sécurité entre zones se fera par règles inter-zones (nftables / edge), pas par l'adressage.
Le VMID **est** l'adresse, relue : `117602101` se lit `1176` (VLAN) · `021` (hôte) · `01`
(rang). On retrouve la VM depuis son adresse, et l'inverse, sans registre.
Contrainte : `NN` de 01 à 09 par fonction (l'octet hôte reste dans le bloc du service). Au-delà, ouvrir une nouvelle fonction/service dans `instance/plan/nomenclature.yml`.
Exemple, l'écosystème de référence (`index: 17`) : supernet `10.17.0.0/16`, zones
`10.17.16.0/24` à `10.17.21.0/24`, VLAN `1171` à `1176`. `infra-edge-01` y vaut
`10.17.16.11`, et son VMID `117101101` se relit `1171` · `011` · `01`.
La segmentation `10.0.0.0/16` remplace l'ancienne plage d'essais `192.168.12.x`.
*(L'index se lit directement dans le second octet — c'est ce qui permet de reconnaître le
tenant d'une adresse à l'œil. `valider_index` le borne, et la même borne protège un second
plafond : à l'index 255, le VLAN vaut `3550 + zone`, sous les 4094 du 802.1Q.)*
**Changer `index` redérive tout le réseau de l'écosystème.** C'est ce qui rend un tenant
portable d'un site à l'autre, et c'est pourquoi rien ne doit être écrit à la main. La preuve
**P21** refuse que deux instances fédérées partagent un index. Détail complet :
[`multi-instances.md`](multi-instances.md).
`make inventaire-ui` lit ce registre et **propose** VMID, VLAN, IP et passerelle quand on
nomme un hôte. La sécurité entre zones ne repose pas sur l'adressage mais sur le registre
des flux (nftables de l'hôte, pare-feu de l'hyperviseur, frontière) —
[`flux-conception.md`](flux-conception.md).
## Variables de provisioning d'hôte

View file

@ -7,9 +7,18 @@ l'inventaire Ansible en est **généré**. Le dépôt est la définition ; chaqu
est une instance. Ce document décrit le modèle, les registres, les commandes et
le flux de travail.
> Règle d'or : **`instance/inventories/production/hosts.yml` est GÉNÉRÉ. Ne jamais l'éditer
> Règle d'or : **`instance/inventories/<inventaire>/hosts.yml` est GÉNÉRÉ. Ne jamais l'éditer
> à la main.** On édite le *plan* puis on régénère (`make instancier-appliquer`).
> **`<inventaire>` n'est pas un nom, c'est une place.** Le dépôt n'impose pas comment une
> instance nomme son inventaire : **le moteur le cherche**, dans l'ordre `principal`, puis
> `production` (`scripts/inventory_rules.py` : `ORDRE_INVENTAIRE` ; pour la construction du
> gabarit, `ORDRE_INVENTAIRE_MODELE` essaie `lab` d'abord). La flotte utilise `principal` ;
> le modèle public livré dans `exemples/modeles/socle/` utilise `production`, et le
> QUICKSTART l'écrit tel quel parce que c'est ce que son lecteur a sous la main. Les
> documents de doctrine, eux, écrivent `<inventaire>` : coder l'un des deux noms en dur y
> serait faux pour la moitié des lecteurs — et ça l'a été jusqu'au 2026-09-06.
---
## 1. Le modèle : deux ancres, cinq liens
@ -34,11 +43,14 @@ liaison — seulement une **capacité** qu'une VM fournit (le rôle appliqué).
## 2. Les registres (source unique de vérité)
Tous sous `docs/`, machine-lisibles, validés, consommés par le GUI, le CLI et Ansible.
Machine-lisibles, validés, consommés par le GUI, le CLI et Ansible. **Ils vivent dans
l'instance** (`instance/plan/`), pas dans le moteur — c'est toute la séparation
moteur/instance. Seul `dependances-groupes.yml` est sous `docs/`, parce qu'il décrit une
propriété des *rôles*, la même pour toutes les instances.
| Registre | Décrit | Champs clés |
| --- | --- | --- |
| `nomenclature.yml` | nommage & adressage | `fonctions` (catégorie/service), `categories` (VLAN/sous-réseau/passerelle), `supernet` |
| `nomenclature.yml` | nommage & adressage | **`index`** (le seed, seul champ d'adressage), `fonctions` (catégorie/service), `categories` (libellés de zones), `cidr_hote`, `reservations`. **Ni `supernet`, ni `vlan`, ni `passerelle` : P20 les refuse** — ils se dérivent. |
| `serveurs.yml` | les VM du plan | `fonction`, `etat` (actif/planifie), placement Proxmox (`noeud`/`stockage`/`disque`/`memoire`/`coeurs`), `integrations` (les `client_*` **facultatives** seulement — les universelles viennent du rôle, voir `integrations-vm.md`) |
| `applications.yml` | les applications | `groupe` (capacité/rôle), `hote` (VM), `port`, `requiert`, `expose`, (+ bases via consommateur) |
| `bases-donnees.yml` | serveurs de BD + bases | `serveurs_bd` ; `bases_donnees` : `serveur`/`base`/`proprietaire`/`secret`(Vault), `consommateur` + `portee` (`application`/`groupe`/`hote`), `usage` |
@ -46,9 +58,12 @@ Tous sous `docs/`, machine-lisibles, validés, consommés par le GUI, le CLI et
| `dependances-groupes.yml` | prérequis entre groupes | `requiert_groupes_actifs` |
### Dérivations clés
- **Nommage/adressage** : tout part de la `fonction` de l'hôte (`web-frontal-03`).
`VMID = 9·catégorie·service·NN`, `VLAN = catégorie.vlan`,
`IP = 10.0.<vlan>.(service×10 + NN)`. Voir `docs/nomenclature-vm.md`.
- **Nommage/adressage** : tout part du seed `index` et de la `fonction` de l'hôte.
`supernet = 10.<index>.0.0/16`, `zone = 10.<index>.(15+catégorie)`,
`VLAN = 1000 + index×10 + zone`, `VMID = <VLAN><octet-hôte><rang>` — neuf chiffres,
miroir de l'IP. Voir `docs/nomenclature-vm.md`. *(Les formules à cinq chiffres et le
réseau unique `10.0.x` qui figuraient ici décrivaient le modèle d'avant le
multi-instance.)*
- **DSN** (lien application↔base) : `<type>://<proprietaire>:<secret>@<hôte>:<port>/<base>`.
Une application reçoit les bases où `(portee=application ET consommateur=elle)`
OU `(portee=groupe ET consommateur=son groupe)` OU `(portee=hote ET consommateur=son hôte)`.
@ -83,19 +98,33 @@ c'est le feu vert pour appliquer.
```
### Via le GUI — `make inventaire-ui`
Cinq vues :
**Douze vues**, éditables ou dérivées :
| Vue | Rôle |
| --- | --- |
| **Inventaire** | **lecture seule** (inventaire généré) — vue d'ensemble des hôtes |
| **Serveurs** | éditer les VM du plan : fonction/état/placement/intégrations (VMID·IP·VLAN dérivés en direct) ; bouton **« Appliquer le plan »** |
| **Chaîne** | vue holistique par hôte : groupes → rôles, et par application ses `expose` / `requiert` / bases (DSN) |
| **Applications** | éditer les applications : groupe/hôte/port/requiert/expose |
| **Bases** | éditer serveurs de BD et bases (portée + consommateur), DSN affiché |
| **Serveurs** *(éditable)* | les VM du plan : fonction/état/placement/intégrations (VMID·IP·VLAN dérivés en direct) ; bouton **« Appliquer le plan »** |
| **Applications** *(éditable)* | groupe/hôte/port/requiert/expose, et les **liens** acceptés par le rôle |
| **Bases** *(éditable)* | serveurs de BD et bases (portée + consommateur), DSN affiché (secret masqué) |
| **Domaines** *(éditable)* | zones publiques vs internes, autorité · edge · DNSSEC, expositions |
| **Nomenclature** *(éditable)* | le modèle dont tout l'adressage dérive : zones, fonctions (catégorie · service), réservations. Chaque fonction affiche **ce qu'elle dérive** (VLAN, sous-réseau, bloc d'hôtes) et les VM qui la portent. L'`index` y est **montré, pas éditable** : il est alloué par le site |
| **Intégrations** *(éditable)* | la matrice serveurs × intégrations ; les universelles en ✓ non décochables |
| **Intrants** *(éditable)* | les intrants de base — identité, Proxmox, fabric, et la liste de rappel des secrets (lecture seule) |
| **Inventaire** *(lecture seule)* | l'inventaire **généré** — vue d'ensemble des hôtes |
| **Chaîne** *(lecture seule)* | vue holistique par hôte : groupes → rôles, et par application ses `expose` / `requiert` / bases |
| **Flux** *(lecture seule)* | la matrice d'audit des flux — source de nftables et justification lisible |
| **Couches** *(lecture seule)* | l'ordre de déploiement en six couches |
| **Réseau** *(lecture seule + bascule)* | la flotte multi-instances, les collisions d'index, et le bouton **« Activer »** |
Édition → **Sauvegarder** (écrit le registre) → **Appliquer le plan** (régénère
`hosts.yml`). L'écriture directe de l'inventaire est refusée (409).
**Les formulaires des six registres sont générés** depuis `docs/audit/schema-plan.json`
(`make schema`), dérivé des constantes du moteur. La sauvegarde en dérive aussi : un
champ ajouté au plan apparaît à l'écran *et* arrive au fichier. Deux exceptions
déclarées au schéma (`x-editeur`) : la matrice des intégrations et l'éditeur de liens
gardent leur éditeur propre, plus riche que ce que le schéma sait dire.
### Via le CLI / `make`
Chaque registre a son script miroir et ses cibles `make` :
@ -137,7 +166,7 @@ registres + **`node --check` du JS du GUI**).
La règle de résolution vit **une seule fois**, en Python (`scripts/inventory_rules.py`),
et est exposée à Ansible par un *filter plugin* (`filter_plugins/registres.py`) :
`bases_de_application`, `applications_de_hote`, `expositions_des_applications`,
`chaine_connexion`. Les playbooks par application (`serveur_web_dorsal`/`_frontaux`)
`chaine_connexion`. Les playbooks par application (`serveur_web_dorsal`, `serveur_web_frontal`)
itèrent ainsi sur les applications de l'hôte et résolvent leurs DSN.
---
@ -159,7 +188,7 @@ C'est ainsi que le plan a été initialisé sans perte, avec diff vide vérifié
## 8. Moteur et instance : deux dépôts
Le **moteur** (ce dépôt, `Set-OPS`) est générique et partageable ; il ne contient
aucune donnée d'instance. Une **instance** (le plan + l'inventaire d'un loup) vit
aucune donnée d'instance. Une **instance** (le plan + l'inventaire d'une organisation) vit
dans son **propre dépôt** (ex. `OPS-monatelier`).
Le moteur localise l'instance via **`SETOPS_INSTANCE`** (défaut : `instance`). Deux
@ -168,7 +197,7 @@ modèles :
- **Modèle A — dépôts frères** (en cours) : moteur et instance côte à côte ; un
symlink `instance -> ../OPS-monatelier` (gitignoré) fait que le défaut résout
l'instance sans configuration. Idéal quand on développe le moteur *et* l'instance.
- **Modèle B — moteur en sous-module** (futur, pour la meute) : l'instance épingle
- **Modèle B — moteur en sous-module** (futur, quand plusieurs écosystèmes vivront chez des exploitants distincts) : l'instance épingle
une version du moteur ; `SETOPS_INSTANCE` pointe la racine de l'instance.
Pour brancher une instance (modèle A) :

View file

@ -44,7 +44,13 @@ Raisons assumées :
1. **Souveraineté** — mission explicite du dépôt. NetBox + AWX + Backstage = trois
applications lourdes à héberger et maintenir (Django + PostgreSQL, etc.). Set-OPS
reste possédé en entier, sans dépendance.
2. **Bon dimensionnement** — ~13 VM. NetBox est de la machinerie d'échelle entreprise.
2. **Bon dimensionnement** — mais l'argument a bougé, et il faut le dire honnêtement.
Ce document a longtemps écrit « ~13 VM ». Le moteur pilote aujourd'hui **quatre plans
vivants** (mesuré le 2026-09-06) : Chezlepro 14 VM, Technolibre 15, le lab 15, plus les
7 machines du site — **51 VM déclarées**, réparties sur des écosystèmes qui ne se parlent
pas. Ça reste très loin de l'échelle entreprise pour laquelle NetBox est fait,
et le seuil du §4 n'est pas franchi. Mais la courbe monte : c'est **le** chiffre à
regarder quand on se demande si la décision tient encore.
3. **Modèle sur-mesure** — NetBox exprimerait nos DSN / expositions à coups de
*custom fields* et de plugins, moins naturellement que nos registres.
4. **Maîtrise** — chaque ligne est comprise et auditable.
@ -53,13 +59,29 @@ Raisons assumées :
## 3. Ce que le maison NE fait PAS (et que les outils mûrs ont)
À garder en tête honnêtement — ce sont des fonctions qu'on n'a pas, pas des bugs :
> **Cette liste a été corrigée le 2026-09-08.** Deux de ses cinq lignes n'étaient plus
> vraies : elles décrivaient des manques que le dépôt a comblés **autrement**, et les
> garder en « manques » aurait fini par justifier d'adopter un outil pour un besoin déjà
> couvert. Une carte des seuils qui se trompe ne fait pas perdre du temps : elle fait
> franchir un seuil qui ne l'est pas.
- historique/audit des changements (au-delà de `git`) ;
- RBAC multi-utilisateurs ;
- détection de conflits IPAM, réservations, gestion d'adresses à grande échelle ;
- webhooks / intégrations tierces, API riche (REST/GraphQL) ;
- écosystème de plugins, communauté, correctifs de sécurité maintenus par d'autres.
Ce qui manque vraiment :
- **historique/audit des changements** au-delà de `git` — un journal applicatif, avec ses
auteurs et ses motifs, que `git log` ne rend qu'imparfaitement ;
- **webhooks / intégrations tierces, API riche** (REST/GraphQL) — il n'y en a aucune, et
aucun consommateur ne la réclame aujourd'hui ;
- **écosystème de plugins, communauté, correctifs de sécurité maintenus par d'autres.**
C'est le seul point qui joue contre le maison **en permanence** : il ne dépend d'aucun
seuil, il s'aggrave tout seul avec le temps. À relire chaque année, pas quand un besoin
apparaît.
Ce qui était listé comme manquant et ne l'est plus :
| Ancien manque | Ce qui le couvre, et pourquoi c'est différent |
|---|---|
| ~~RBAC multi-utilisateurs~~ | **Résolu, et par un mécanisme plus fort.** Trois classes d'acteurs aux pouvoirs disjoints existent — le poste de l'exploitant, le **runner de site** (matérialiser ; ne rentre jamais chez un tenant) et les **runners de tenant** (configurer). La séparation est **cryptographique** — une voûte, une clé (2026-08-28) — et non applicative : c'est la *présence des fichiers* qui borne le pouvoir, jamais une table de permissions qu'une faille de l'application contournerait. |
| ~~Détection de conflits IPAM, réservations~~ | **Sans objet par construction.** Un IPAM sert à *allouer* ; ici rien ne s'alloue, tout dérive du seed `index`. Et cinq preuves tiennent déjà ce qu'un IPAM vérifierait : **P20** (aucun adressage stocké), **P21** (collisions d'index), **P23** (chevauchement d'underlay), **P28** (pools), **P33** (ports co-localisés). Adopter un IPAM remplacerait une propriété *par construction* par un contrôle *a posteriori*. |
---
@ -75,16 +97,38 @@ ne lui ajoute pas de fonctionnalités de type NetBox/AWX.
Adopter l'outil du marché — **sans réécrire**, en branchant Ansible/notre GUI
par-dessus — dès qu'un de ces besoins devient réel :
> **Ce tableau a été écrit avant que les runners existent**, et deux de ses lignes visaient
> des besoins depuis couverts (§3). Corrigé le 2026-09-08.
| Besoin qui apparaît | Adopter |
| --- | --- |
| RBAC, plusieurs opérateurs, historique d'audit, secrets/credentials centralisés | **AWX** (exécution) |
| IPAM sérieux, détection de conflits, source de vérité partagée, API/webhooks | **NetBox** (et `nb_inventory` remplace notre générateur) |
| **Plusieurs HUMAINS, aux portées disjointes, sur des machines qui ne sont pas les tiennes** | à trancher — voir ci-dessous, c'est le seuil qui approche et qu'aucune ligne ne nommait |
| Historique d'audit applicatif, secrets/credentials centralisés pour des tiers | **AWX** (exécution) |
| Source de vérité **partagée** entre organisations, API/webhooks avec des consommateurs réels | **NetBox** (et `nb_inventory` remplacerait notre générateur) |
| Catalogue de services / portail développeur, ownership, scaffolding | **Backstage** |
| Provisionnement de VM déclaratif et reproductible à plus grande échelle | **Terraform** (Proxmox) ou **NixOS + Colmena** |
*Retiré de ce tableau : « RBAC » et « IPAM sérieux, détection de conflits ». Les deux sont
couverts (§3), et les garder ici aurait fait adopter un outil pour un besoin déjà rempli.*
### Le seuil qui approche, et qu'aucune ligne ne nommait
**L'émancipation.** Le GUI écoute sur `127.0.0.1` avec un jeton par session : un modèle
**mono-utilisateur**, parfait tant que l'exploitant est une personne à son poste. Le jour où
un tenant est exploité par **son** organisation — c'est la trajectoire de
[`filiation-emancipation.md`](filiation-emancipation.md), et l'offre destinée aux OBNL y
mène — il y a plusieurs humains, aux portées disjointes, sur des machines qui ne sont pas
les tiennes.
Ce n'est **pas** AWX qu'appelle ce seuil : les runners portent déjà la séparation des
pouvoirs, cryptographiquement. Ce qu'il appelle, c'est une décision sur la **façon dont le
GUI s'ouvre à quelqu'un d'autre** — et elle n'est pas prise. La nommer ici est le minimum :
*un seuil qu'on ne nomme pas est un seuil qu'on franchit sans le voir.*
**Test simple** : si on se surprend à vouloir réimplémenter une de ces fonctions
dans Set-OPS, c'est le signal d'adopter l'outil correspondant plutôt que de
prolonger le maison.
prolonger le maison. **Mais vérifier d'abord que le besoin n'est pas déjà couvert
autrement** — c'est exactement l'erreur que ce tableau portait.
---
@ -92,6 +136,15 @@ prolonger le maison.
- On **garde** le plan de contrôle maison : il fonctionne, il est souverain et
bien dimensionné pour aujourd'hui. Pas de réécriture sur NetBox « par principe ».
- On **gèle** son périmètre fonctionnel (voir §4).
- On **gèle** son périmètre fonctionnel (voir §4). **Le 2026-09-08, c'est la carte des
seuils qui a été corrigée, pas le gel qui a été levé** — la question « et si on retirait
le gel ? » a montré que deux seuils étaient mal posés, pas que le gel était de trop.
- On **réévalue** à l'échéance d'un seuil ci-dessus, ou si la charge de
maintenance du maison dépasse le coût d'héberger l'outil mûr.
> **Ce que le gel n'interdit pas, et qu'on confond souvent avec lui.** Il porte sur les
> *fonctions de type NetBox/AWX*, pas sur les **vues**. Montrer à l'écran ce que le moteur
> sait déjà — l'écart des dix devis, l'état du diff entre « Sauvegarder » et « Appliquer »,
> le périmètre sur lequel un ✅ a porté, les témoins du génome et lequel a décroché — ne
> franchit aucun seuil : rien de tout cela n'existe dans NetBox ou AWX, parce que rien de
> tout cela n'existe hors de ce modèle.

View file

@ -2,8 +2,12 @@
> **Pour qui :** qui **évalue le moteur** — ce qu'il sait faire, et ce qu'il ne sait pas encore.
> Bilan des capacités du moteur, au 2026-07-02.
> Fondé sur l'état réel du dépôt (≈50 rôles, ≈30 playbooks de groupe, le moteur de plan, la GUI).
> Bilan des capacités du moteur. Établi le 2026-07-02, **revu le 2026-09-06** : la
> frontière ⭐/🔧 avait cessé de dire vrai — deux reconstructions depuis zéro (2026-08-13,
> puis 2026-09-02) ont fait passer côté ⭐ presque tout ce que le §5 annonçait « à
> éprouver ». Fondé sur l'état réel du dépôt (≈65 rôles, ≈60 playbooks, le moteur de plan,
> la GUI). **Le détail rôle par rôle vit dans [`catalogue-services.md`](catalogue-services.md)** ;
> ici on ne garde que le bilan.
>
> Deux niveaux de maturité sont distingués honnêtement :
> - **⭐ Prouvé** — déployé et vérifié de bout en bout sur cluster Proxmox réel.
@ -19,8 +23,9 @@
complet — PKI, identité, DNS, web, courriel — cloné depuis un golden template durci, piloté par
une GUI utilisable sans IA, en multi-tenant, tout en logiciel libre.**
Le cœur d'infrastructure est **prouvé de bout en bout** ; la couche des services applicatifs
(SSO, données, forge, observabilité) est **outillée et prête à éprouver**.
Le cœur d'infrastructure **et** la couche des services applicatifs (SSO, données, forge,
observabilité, supervision, collaboration) sont **prouvés de bout en bout** : la flotte a
été rasée puis remontée d'un seul trait, deux fois, sans échec.
---
@ -31,8 +36,11 @@ Pouvoir central : **plan déclaratif → écosystème vivant**. On décrit *quoi
- **`scripts/instancier.py`** — lit `nomenclature / serveurs / applications / bases-donnees.yml`
et **dérive** tout : VMID, IP, groupes d'inventaire, dimensionnement. ⭐
- **Nomenclature fédérée** — un `index` + catégorie + service produit un **VMID
`{index}{cat}{svc}{seq}`** et une IP déterministes, sans collision entre instances. ⭐
- **Nomenclature fédérée** — le seul champ saisi est l'**`index`** ; supernet, VLAN, IP,
passerelle et **VMID** en dérivent, sans collision possible entre instances. Le VMID est le
**miroir de l'adresse** sur neuf chiffres — `<VLAN><octet d'hôte><rang>`, soit `117602101`
pour `1176` · `021` · `01` : on retrouve la VM depuis son adresse, et l'inverse, sans
registre. **P20** interdit d'écrire un adressage au plan. ⭐
- **Dimensionnement automatique** — chaque rôle porte une `meta/empreinte.yml`
(cœurs / RAM / disque) ; l'outil **somme les empreintes et taille la VM** hôte. ⭐
- **Multi-instance = multi-tenant** — le symlink `instance/` pointe vers un dépôt par écosystème,
@ -47,8 +55,10 @@ Impératif fondateur : un sysadmin exploite l'outil **sans IA** ; l'IA n'assiste
- **GUI souveraine** (`scripts/inventory_gui.py`, stdlib pure, aucune dépendance) : visualise le
plan, **bouton « Pousser »** (crée la VM pour un serveur / déploie pour une app ou une base),
**sonde de vivacité**, passage auto en « actif », jeton d'authentification. ⭐
- **Voûte au déploiement** — secrets chiffrés par Ansible Vault, saisis au déploiement, **jamais
en clair** dans la GUI ; usage via le fichier de mot de passe, non interactif. ⭐
- **Voûte au déploiement** — secrets chiffrés par Ansible Vault, **jamais en clair** dans la
GUI, qui n'en montre que les *noms*. **Une voûte, une clé** (2026-08-28) : chaque dépôt a la
sienne, et le `Makefile` les rassemble tout seul (`scripts/voutes.py`), sans rien exporter
ni rendre l'exécution interactive. ⭐
- **Validation intégrée** — `syntax-check`, `ansible-lint` (profil *production*), `verifier`. ⭐
## 3. Le socle — un golden template durci
@ -60,9 +70,13 @@ Impératif fondateur : un sysadmin exploite l'outil **sans IA** ; l'IA n'assiste
`template_cleanup`.
- **SSH** : `ssh_baseline` puis `ssh_hardening` (bascule mot de passe → clé, seulement après
validation de l'accès par clé).
- **Durcissement** : `apparmor`, `auditd`, `fail2ban_ssh`, `sysctl_hardening`,
`hardening_packages`, `unattended_upgrades`, `journald`, `core_dumps`,
`nftables_baseline` (installé et préparé, **non activé par défaut**).
- **Durcissement** — les **dix** rôles que compose le groupe `serveur_durci` :
`hardening_packages`, `sysctl_hardening`, `core_dumps`, `unattended_upgrades`, `apparmor`,
`auditd`, `fail2ban_ssh`, `journald`, `ssh_hardening`,
`nftables_baseline` (**éteint dans le rôle, armé par le plan** : `hotes_actifs` le met à
`true`, le gabarit doré le laisse à `false` — le pare-feu n'est pas une propriété du rôle,
c'est une décision de l'instance). Le jeu de règles n'est pas écrit à la main : il est
**dérivé du registre des flux** (`meta/flux.yml` → `scripts/resoudre_flux.py`).
- **Proxmox** : clonage depuis le golden template + redimensionnement disque (grow-only).
> Séparation nette : **Proxmox + cloud-init** donnent l'identité initiale de la VM ;
@ -83,20 +97,42 @@ par annuaire LDAP, remise LMTP réseau chiffrée step_ca vers le stockage, accè
LDAP, filtrage antispam et signature DKIM en milter. Topologie **MTA dédié en périphérie /
boîtes à l'intérieur** (défense en profondeur).
## 5. Les services outillés — rôles présents 🔧
## 5. Les services applicatifs — prouvés depuis ⭐
Construits et câblables par le plan ; **à éprouver** en déploiement réel avant de les déclarer
prouvés.
Ce paragraphe annonçait, jusqu'au 2026-09-06, des rôles « construits mais à éprouver ».
Ils l'ont été : **la reconstruction depuis zéro les a tous repris sur machine nue**
(2026-08-13, puis 15/15 et 14/14 hôtes le 2026-09-02, `make valider` à 0 échec).
- **SSO web** : `serveur_keycloak` (architecture décidée : OpenLDAP source de vérité, Keycloak
fédéré en OIDC, courriel en bind LDAP direct).
- **Données** : `serveur_postgresql`, `serveur_redis`.
- **Forge logicielle** : `serveur_forgejo`.
- **Observabilité** : `serveur_prometheus`, `serveur_grafana`, `serveur_loki`, `serveur_icinga`,
avec les clients `client_metrique`, `client_journal`, `client_supervision`.
- **Intégrations transverses** : `client_pki`, `client_backup`, `client_smtp`, `client_metrique`, `client_journal`, `client_unbound`.
- **Méta-rôles d'agrégation** : `identity`, `applications`, `database`, `web`, `monitoring`,
`backup`, `storage`.
- **SSO web** : `serveur_keycloak` — OpenLDAP source de vérité, fédération LDAP automatisée,
courriel en bind LDAP direct. ⭐
- **Passerelle SSO** : `serveur_oauth2_proxy` — met au SSO une application sans OIDC natif
(éprouvé devant Icinga Web 2). ⭐
- **Données** : `serveur_postgresql`, `serveur_redis` — bases et comptes dérivés du registre,
TLS `verify-full` de bout en bout. ⭐
- **Forge logicielle** : `serveur_forgejo` — Git + PostgreSQL + SSO OIDC. ⭐
- **Observabilité** : `serveur_prometheus`, `serveur_grafana`, `serveur_loki`, avec les clients
`client_metrique` et `client_journal`. ⭐
- **Supervision** : `serveur_icinga`, `serveur_icingaweb2` — Icinga 2 + IcingaDB + Web 2 + BPM.
**Sans agent sur les hôtes** : contrôles actifs depuis le cœur, résultats passifs poussés par
l'API. ⭐
- **Sauvegardes** : `serveur_backup`, `client_backup` — restic hors-nœud, chaque nœud vérifiant
son **propre dépôt distant**, restauration éprouvée. ⭐
- **Plateforme webapp** : `serveur_web_frontal`, `serveur_web_dorsal` — statique et natif
(venv + systemd + nginx), zéro conteneur. ⭐
- **Intégrations transverses** : `client_pki`, `client_backup`, `client_smtp`, `client_metrique`,
`client_journal`, `client_resolveur`, `client_artefacts`. ⭐
Reste **🔧 outillé** — déployé, mais dont l'*usage* n'est pas consigné comme preuve :
- **Collaboration** : `serveur_nextcloud`, `serveur_collabora` (natif, plus de conteneur). Base
et client OIDC dérivés du plan, posés par la reconstruction ; le dépôt d'un fichier et
l'édition partagée à deux, eux, n'ont pas été consignés. 🔧
> Deux noms ont été retirés de ce paragraphe parce qu'ils n'ont jamais existé :
> `client_supervision` (la supervision est sans agent — cf. `catalogue-services.md`) et les
> « méta-rôles d'agrégation » `identity` / `storage`. La conformité passe par
> `playbooks/groupes/`, un playbook par groupe ; les répertoires `playbooks/applications/`,
> `database/`, `web/`, `monitoring/`, `backup/` ne contiennent qu'un README d'espace réservé.
## 6. Les patrons d'ingénierie — la valeur invisible ⭐
@ -119,9 +155,13 @@ prouvés.
## Prochaines frontières
- **Éprouver** les services outillés (Keycloak fédéré, observabilité, PostgreSQL, Forgejo).
- **Consigner l'usage** de la collaboration (dépôt de fichier, édition partagée) — le seul
🔧 qui reste.
- **Courriel Étape B** (public) : DNS public (MX, SPF, **DKIM** — clé `setops._domainkey` déjà
générée, DMARC), MX externe et réputation, PTR / FCrDNS.
- **Les équipements de l'hébergeur** — hyperviseurs, commutateurs, frontière : ni inventaire,
ni sauvegarde de configuration, ni supervision. Les *VM* du site en ont depuis le 2026-09-02,
les *équipements* non (cf. `hebergeur-exploitation.md`).
- **Seuils d'adoption** d'outils tiers (NetBox, AWX) plutôt que de réimplémenter le plan de
contrôle maison, qui reste volontairement gelé.

View file

@ -25,13 +25,26 @@ listes de stockages et de ponts différentes. Un hébergeur décrit son matérie
## 2. Le plan d'adressage — la seule chose à respecter à la lettre
**Un site dérive du même `index` que son tenant.** Il n'y a pas de second registre à
tenir : le seul seed de tout l'adressage reste l'`index`, et l'underlay occupe la **bande
basse** du supernet — celle que la dérivation des tenants n'alloue jamais.
**Un site prend son PROPRE `index`, comme un tenant.** Il n'y a pas de second registre à
tenir : le seul seed de tout l'adressage reste l'`index`, et **sites et tenants se
partagent la même classe A** — chacun le sien, aucun partagé.
> **Corrigé le 2026-09-12.** Cette règle disait « un site dérive du même index que son
> tenant ». Conséquence mesurée chez l'hébergeur de référence : le plan d'administration
> du site vivait dans `10.17.0.0/24`, **à l'intérieur du supernet du locataire
> `OPS-Chezlepro`**. Ce n'était pas dangereux — les zones d'un tenant commencent à l'octet
> 16 par construction — mais `10.17.0.0/16` désignait alors deux choses : un locataire, et
> le plan d'administration de celui qui l'héberge. Deux sens pour une adresse.
>
> L'exception disparaît : **tout prend un index**. Le seul cas particulier restant serait
> qu'il n'y en ait plus, et l'octet en offre 256.
>
> `make instances` compte désormais les sites avec les tenants — un site et un locataire
> ne peuvent plus réclamer le même nombre sans que la garde le dise.
| Rôle | VLAN | Sous-réseau | MTU | Nature |
|---|---|---|---|---|
| **Gestion** | 10 | `10.<index>.0.0/24` | 1500 | **destination** — unique par site |
| **Gestion** | 10 *(ou aucun — voir ci-dessous)* | `10.<index>.0.0/24` | 1500 | **destination** — unique par site |
| Sortie des tenants | 40 | `192.168.40.0/24` | 1500 | chemin — identique partout |
| Transport VXLAN | 50 | `192.168.50.0/24` | 1500 | chemin — identique partout |
| Stockage iSCSI | 20 | `192.168.20.0/24` | 9000 | chemin — identique partout |
@ -41,6 +54,12 @@ basse** du supernet — celle que la dérivation des tenants n'alloue jamais.
Index 17 → gestion en `10.17.0.0/24` ; index 11 → `10.11.0.0/24`. Deux sites ne peuvent
donc **pas** se chevaucher, sans qu'on ait rien de plus à décider.
> **Le VLAN de gestion peut n'exister pas du tout.** Chez l'hébergeur de référence, ce plan
> est un **segment physique** — une patte dédiée sur la frontière, aucune étiquette, et
> **aucun pont d'hyperviseur ne le touche**. Conséquence recherchée : *aucune VM ne peut y
> naître*, et le validateur refuse qu'on y déclare une machine. Un port d'accès étiqueté 10
> convient aussi ; ce qui compte est que rien du monde virtuel n'y ait de patte.
**Les VLAN ne changent jamais** d'un site à l'autre : ils sont locaux, ils ne traversent
aucun lien inter-sites. Un seul modèle mental, et l'adresse de stockage porte son propre
numéro de VLAN — `192.168.20.x` est sur le VLAN 20.
@ -73,7 +92,7 @@ le seed de son site. Une seule règle, aucun cas particulier.
Le trafic des VM voyage encapsulé en VXLAN, ce qui coûte **50 octets**. Avec un transport
à 1500, les VM tournent à **1450** — automatiquement, mais à condition que 1500 passe
réellement de bout en bout sur les VLAN 11 et 40. Un MTU rogné en chemin donne le pire des
réellement de bout en bout sur les VLAN de transport et de transit — **50** et **40** (le tableau ci-dessus fait foi ; ce paragraphe disait « 11 et 40 », le numéro d'avant). Un MTU rogné en chemin donne le pire des
symptômes : les petites requêtes passent, les grosses meurent, et rien n'est signalé.
Le jumbo (9000) sur le stockage est facultatif. **À moitié configuré, il ne fonctionne
@ -216,8 +235,15 @@ réseaux **défait en silence l'isolation inter-tenant**. WireGuard s'y prête b
## 9. Ce qu'il ne faut pas faire
- **Ne pas créer de VM à la main** pour le tenant. Elles sont toutes dérivées du plan ;
une VM créée à côté est invisible pour l'outil, qui la détruira sans le savoir.
- **Ne pas créer de VM à la main** pour le tenant. Elles sont toutes dérivées du plan ; une
VM créée à côté est **invisible pour l'outil** — et le risque n'est pas celui qu'on croit.
`make raser` dérive sa liste du plan : il ne la détruira **jamais**. Elle survit donc à
tout, sans DNS, sans certificat, sans sauvegarde, sans politique de pare-feu, et **son
VMID n'est gardé par aucune preuve contre une collision**. Un VMID oublié squatte le
cluster sans que rien ne le signale. *(Cette ligne annonçait l'inverse — « l'outil la
détruira sans le savoir » — jusqu'au 2026-09-06.)* Pour une machine d'épreuve jetable, il
existe une voie prévue et documentée : `make cloner-vm`, hors plan, **à détruire à la
main** (cf. `vm-lifecycle.md` §4bis).
- **Ne pas configurer le SDN** : l'outil le fait entièrement.
- **Ne pas écrire les secrets** dans un fichier partagé ni dans un dépôt git.
- **Ne pas contourner un blocage en silence.** Un point resté ouvert et *signalé* se règle

View file

@ -51,13 +51,55 @@ Créer une nouvelle VM dans Proxmox avec des paramètres sobres.
Exemple :
```text
Nom de la VM : debian13-template
Nom de la VM : modeleSetOPS ← voir l'encadré : ce nom N'EST PAS libre
VMID : 9000 ou autre ID réservé aux modèles
OS : Debian 13
BIOS : OVMF / UEFI
Machine : q35
```
> **Le nom du modèle est un contrat, pas une étiquette.** Le clonage cherche sa source
> **par ce nom** : il doit être exactement celui que `make config` a enregistré sous
> `proxmox_clone_source_nom` — **`modeleSetOPS`** par défaut. Un nom qui ne correspond pas
> se solde par un clonage qui ne trouve rien, et le message ne dit pas que c'est le nom qui
> est en cause. *(Cette procédure proposait `debian13-template` jusqu'au 2026-09-06 :
> suivie à la lettre, elle produisait un gabarit que le moteur ne savait pas cloner.)*
>
> Le VMID, lui, est libre — c'est `proxmox_clone_vmid_modele` qui le retient.
### `q35` n'est pas un réglage — c'est la raison de cette procédure
**Ces deux lignes sont pourquoi l'installation est manuelle.** Elles expliquent aussi
pourquoi Set-OPS n'utilise pas l'image cloud officielle de Debian.
`genericcloud` est livrée configurée pour **`i440fx`**, le défaut de Proxmox. La convertir
en `q35` après coup ne change pas un paramètre : ça **remplace le matériel virtuel sous un
système qui croit connaître le sien**. `i440fx` est un chipset PCI, `q35` est PCIe — la
topologie des bus change, donc :
- les **noms d'interfaces prédictibles** changent, puisqu'ils dérivent du chemin PCI
(`enp0s3` devient `enp1s0`) — la machine perd le réseau, et sa configuration réseau
désigne une interface qui n'existe plus ;
- les **chemins de disques** bougent, ce qui peut valoir un initramfs qui ne trouve plus
sa racine ;
- l'ordre d'énumération des périphériques n'est plus le même, et ce qui en dépend suit.
**Constat de l'exploitant, paye en anomalies** : une conversion `i440fx` → `q35` sur une
machine déjà installée produit une série de pannes dont chacune ressemble à autre chose
qu'à sa cause. *La conversion n'est pas une correction — c'est une transplantation.*
D'où la règle : **une machine naît `q35`, ou elle ne le sera jamais proprement.** C'est ce
que cette installation depuis l'ISO garantit, et ce qu'une image préconfigurée pour
`i440fx` interdit.
*Le gabarit hérite ces valeurs à chaque clonage — le vérifier avant de le convertir en
modèle est le dernier moment où la correction est gratuite :*
```sh
qm config <vmid> | grep -E '^machine|^bios'
# attendu : machine: q35 / bios: ovmf
```
### Disque EFI Proxmox
Avec OVMF/UEFI, Proxmox crée un petit disque EFI, par exemple :
@ -729,7 +771,13 @@ Exemple :
qm template 9000
```
À partir de là, le modèle peut être cloné.
À partir de là, le modèle peut être cloné — **à condition que son nom et son VMID
correspondent** à ce que `make config` a enregistré (`proxmox_clone_source_nom`,
`proxmox_clone_vmid_modele`). Vérifier avant de s'en servir :
```bash
make config # affiche la configuration lue par le moteur
```
---
@ -740,14 +788,14 @@ Après conversion du modèle, le flux normal passe par `make` et l'API Proxmox.
Les paramètres communs Proxmox sont dans :
```text
instance/inventories/production/group_vars/proxmox.yml
instance/inventories/<inventaire>/group_vars/proxmox.yml
```
Les secrets d'API vivent dans la voûte unifiée de l'instance (le token Proxmox aux côtés
des autres `vault_*`), semée par `make config` :
```text
instance/inventories/production/group_vars/all/vault.yml
instance/inventories/<inventaire>/group_vars/all/vault.yml
```
Créer le clone et l'ajouter à l'inventaire :
@ -771,7 +819,9 @@ Les paramètres par VM (VMID, IP, VLAN, passerelle) ne sont plus saisis à la ma
Après le premier démarrage, tester l'accès :
```bash
ssh ansible@10.0.15.31
# L'adresse n'est pas à retenir : elle est DÉRIVÉE, et le plan la donne.
make hote-afficher HOTE=web-frontal-01 # VMID · IP · VLAN · passerelle
ssh ansible@10.17.21.31 # (l'IP ainsi obtenue)
```
Ensuite appliquer la conformité Ansible :

View file

@ -11,48 +11,70 @@
| `client_journal` | egress | 3100 | tcp | serveur_loki | tls | Expédition des journaux par Alloy vers le collecteur central Loki (HTTPS, cert step-ca). |
| `client_metrique` | ingress | 9100 | tcp | serveur_prometheus | tls | Scrape des métriques par Prometheus (node_exporter en HTTPS). |
| `client_pki` | egress | 8443 | tcp | serveur_step_ca | tls-requis | Émission/renouvellement des certificats par ACME et récupération de la racine auprès de l'AC interne. |
| `client_resolveur` | egress | 53 | udp | serveur_resolveur | clair | Résoudre auprès du résolveur de l'écosystème, et de personne d'autre. |
| `client_resolveur` | egress | 53 | tcp | serveur_resolveur | clair | Réponses longues et bascule TCP, obligatoires en DNS. |
| `client_sante` | egress | 5665 | tcp | serveur_icinga | tls | Depot d'un resultat passif sur l'API Icinga (process-check-result) : les unites systemd en echec du noeud. Le pair est authentifie par l'AC d'Icinga. |
| `client_smtp` | egress | 25 | tcp | serveur_postfix | starttls | Relais des notifications locales vers le MTA central (Postfix), STARTTLS. |
| `client_unbound` | ingress | 53 | udp | localhost | clair | Résolveur local sur boucle locale (les processus du nœud interrogent 127.0.0.1). |
| `client_unbound` | egress | 53 | tcp | serveur_powerdns | clair | Transfert des requêtes de la zone souveraine vers le DNS autoritatif interne (PowerDNS). |
| `client_unbound` | egress | 53 | udp | externe | clair | Récursion DNS depuis la racine (UDP d'abord), validée par DNSSEC — la confidentialité du transport n'est pas l'enjeu, l'authenticité l'est. |
| `client_unbound` | egress | 53 | tcp | externe | clair | Récursion DNS en TCP : repli obligatoire quand la réponse dépasse la taille UDP (fréquent avec DNSSEC). |
| `serveur_artefacts` | ingress | 3142 | tcp | flotte | clair | Toute la flotte prend ses paquets ici. En clair, et c'est correct : l'intégrité d'un dépôt apt vient de ses signatures, qu'apt vérifie de toute façon — un intermédiaire ne peut pas altérer un paquet sans se faire prendre. |
| `serveur_artefacts` | egress | 80 | tcp | externe | clair | Remplir le cache depuis les dépôts Debian amont (deb.debian.org, security). |
| `serveur_artefacts` | egress | 3142 | tcp | voisins_site | clair | Prendre le cache du site comme amont, plutot que d'aller chez Debian. |
| `serveur_backup` | ingress | 22 | tcp | client_backup | ssh | Dépôt restic servi par SSH (utilisateur restreint restic + clé) ; chaque client_backup pousse ses instantanés. |
| `serveur_backup` | egress | 5665 | tcp | serveur_icinga | tls-requis | Rapport passif des sauvegardes vers l'API Icinga : le depot est le seul a voir ce qui est reellement arrive. |
| `serveur_backup_site` | ingress | 22 | tcp | voisins_site | ssh | Les écosystèmes de ce site déposent leur état ici tant qu'ils n'ont pas leur propre dépôt. SFTP par un utilisateur restreint, jamais un compte d'administration : le site reçoit des octets chiffrés par restic, il ne peut pas les lire. |
| `serveur_cache_site` | ingress | 3142 | tcp | voisins_site | clair | Servir les caches des écosystèmes voisins. Debian n'est ainsi téléchargé qu'une fois pour tout le site, et le cache ne voit que des requêtes AGRÉGÉES — jamais quelle machine installe quoi. |
| `serveur_cache_site` | egress | 80 | tcp | externe | clair | Remplir le cache depuis les dépôts Debian amont. En clair parce que les dépôts apt sont signés : l'intégrité vient de la signature, pas du transport. |
| `serveur_cache_site` | egress | 443 | tcp | externe | tls-requis | Les dépôts tiers qui n'existent qu'en HTTPS (smallstep, Grafana, Icinga). Le cache les relaie pour que la flotte n'ait pas à sortir elle-même. |
| `serveur_collabora` | ingress | 9980 | tcp | edge | clair | Éditeur servi au navigateur via l'edge (WebSocket WOPI ; TLS terminé à l'edge). |
| `serveur_collabora` | ingress | 9980 | tcp | localhost | clair | Vérifications WOPI serveur→Collabora depuis Nextcloud co-localisé. |
| `serveur_debian` | ingress | 22 | tcp | flotte, externe | ssh | Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). |
| `serveur_debian` | ingress | echo-request | icmp | serveur_icinga | n-a | La supervision verifie que ce noeud repond (hostalive). Sans lui, elle le tient pour mort et supprime ses notifications. |
| `serveur_debian` | ingress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. |
| `serveur_debian` | egress | 53 | udp | frontiere | n-a | Résolution de noms à l'amorçage, avant que le résolveur de l'écosystème n'existe. Sans elle, les machines d'un site neuf ne peuvent pas résoudre leurs dépôts de paquets — et rien ne peut donc s'installer, y compris le résolveur lui-même. |
| `serveur_debian` | egress | 53 | tcp | frontiere | n-a | Réponses longues et bascule TCP, obligatoires en DNS. Déclarer l'UDP sans le TCP donne une résolution qui marche jusqu'à la première réponse tronquée. |
| `serveur_debian` | egress | 80 | tcp | externe | clair | Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport). |
| `serveur_debian` | egress | 123 | udp | externe | n-a | Synchronisation d'horloge (NTP). Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO. |
| `serveur_debian` | egress | 123 | udp | frontiere | n-a | Synchronisation d'horloge (NTP) contre la frontière, autorité de temps de l'écosystème. Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO. |
| `serveur_debian` | egress | 443 | tcp | externe | tls-requis | Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre. |
| `serveur_debian` | egress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » sortant : c'est ainsi que nos hôtes signalent l'overlay à 1450 aux correspondants distants. |
| `serveur_dns_public` | ingress | 53 | udp | voisins_site | clair | NOTIFY des primaires des locataires du site : une zone publique a change. Le contenu d'une zone publique n'a rien de secret ; ce qui compte est QUI peut le modifier, et c'est TSIG qui le garde, sur le transfert. |
| `serveur_dns_public` | ingress | 53 | tcp | voisins_site | clair | Repli TCP des notifications des primaires des locataires. |
| `serveur_dns_public` | ingress | 1053 | udp | externe | clair | Les resolveurs de l'Internet interrogent les zones publiques des locataires du site. Les reponses sont signees DNSSEC : leur integrite ne depend ni du transport, ni de nous. |
| `serveur_dns_public` | ingress | 1053 | tcp | externe | clair | Repli TCP : reponses tronquees par dnsdist (ANY, debit) et reponses signees trop grosses pour l'UDP. |
| `serveur_dns_public` | egress | 5300 | tcp | primaires_dns_locataires | clair | AXFR signe TSIG vers l'autoritatif de chaque locataire du site (5300 : il partage sa machine avec le resolveur, qui tient le 53). La signature authentifie, elle ne chiffre pas — et une zone publique n'a rien a cacher. |
| `serveur_dns_public` | egress | 5300 | udp | primaires_dns_locataires | clair | SOA demande au primaire avant chaque transfert : c'est lui qui dit si la zone a change. Sans lui, le secondaire garde sa premiere copie pour toujours. |
| `serveur_dovecot` | ingress | 24 | tcp | serveur_postfix | tls-requis | Remise LMTP depuis Postfix (edge-mta -> mailstore), en TLS vérifié (lmtp_tls_security_level=verify). |
| `serveur_dovecot` | ingress | 993 | tcp | externe | tls-requis | Accès courriel des utilisateurs (IMAPS). Frontière publique gérée à l'OPNsense. |
| `serveur_dovecot` | ingress | 12345 | tcp | serveur_postfix | tls | Authentification SASL déléguée : Postfix valide les identifiants de soumission contre Dovecot. |
| `serveur_dovecot` | egress | 636 | tcp | serveur_openldap | tls-requis | userdb/passdb : Dovecot résout et authentifie les comptes sur l'annuaire (LDAPS). |
| `serveur_forgejo` | ingress | 3000 | tcp | edge | clair | Interface web + Git HTTP servis via l'edge (TLS terminé à l'edge). |
| `serveur_forge_site` | ingress | 443 | tcp | voisins_site | tls-requis | Servir le génome aux écosystèmes de ce site : c'est de cette forge qu'ils clonent leur moteur, leurs modèles et la carte de la fabric (D-81). Sans ce flux, un écosystème neuf ne peut pas se reproduire. |
| `serveur_forgejo` | ingress | 3000 | tcp | edge | clair | Interface web + Git HTTP derrière un edge : le nginx termine le TLS et parle en clair à la forge. C'est le cas de tout tenant. |
| `serveur_forgejo` | ingress | derive | tcp | flotte, admin | tls | Sans edge devant elle — la forge du SITE — elle sert son propre TLS sur le port du schéma (443), avec le certificat de la machine. `derive` parce que le port vient de `serveur_forgejo_http_port` : écrire 3000 en dur ici serait faux pour elle, et rien ne le signalerait puisque ce flux ne traverse pas la frontière. |
| `serveur_forgejo` | egress | 25 | tcp | serveur_postfix | starttls | Notifications courriel (relais via le MTA Postfix). |
| `serveur_forgejo` | egress | 443 | tcp | edge | tls-requis | Découverte OIDC et jetons auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_forgejo` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Base de données Forgejo (verify-full). |
| `serveur_grafana` | ingress | 3000 | tcp | edge | clair | Interface web servie via l'edge (TLS terminé à l'edge). |
| `serveur_grafana` | ingress | 3000 | tcp | edge, admin | clair | Interface web : servie via l'edge la ou il y en a un, joignable depuis le plan d'administration partout — sans quoi un deploiement sans edge n'a plus de console. |
| `serveur_grafana` | egress | 443 | tcp | edge | tls-requis | Authentification OIDC auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_icinga` | ingress | 5665 | tcp | localhost | clair | API Icinga 2 consommée en local par Icinga Web 2 co-localisé. |
| `serveur_icinga` | ingress | 5665 | tcp | client_backup | tls-requis | Rapport passif de chaque detenteur d'etat sur SON depot distant : le depot du site heberge du chiffre et ne peut pas le juger. |
| `serveur_icinga` | ingress | 5665 | tcp | serveur_backup | tls-requis | Le depot de sauvegarde depose ses resultats passifs (portee : process-check-result sur « sauvegarde: * »). |
| `serveur_icinga` | ingress | 5665 | tcp | serveur_debian | tls-requis | Rapport passif de sante de chaque noeud (unites systemd en echec). |
| `serveur_icinga` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Base relationnelle du moteur Icinga (verify-full). |
| `serveur_icingaweb2` | ingress | 8080 | tcp | edge | clair | Interface web servie via l'edge (TLS terminé à l'edge ; SSO possible via oauth2-proxy). |
| `serveur_icinga` | egress | echo-request | icmp | serveur_debian | n-a | La supervision verifie que ses hotes repondent (hostalive) : sans ce flux, elle les tient tous pour morts et supprime leurs notifications. |
| `serveur_icinga` | egress | echo-request | icmp | fabric | n-a | La supervision verifie que la frontiere sert encore chaque zone. Aucun agent ne peut vivre sur un pare-feu : le controle ACTIF est le seul chemin, et il n'existait pas. |
| `serveur_icingaweb2` | ingress | 8080 | tcp | edge, admin | clair | Interface web servie via l'edge (TLS terminé à l'edge ; SSO possible via oauth2-proxy), et joignable depuis le plan d'administration là où il n'y a pas d'edge. |
| `serveur_icingaweb2` | egress | 636 | tcp | serveur_openldap | tls-requis | Authentification des utilisateurs sur l'annuaire (LDAPS). |
| `serveur_icingaweb2` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Lecture d'IcingaDB (base relationnelle, verify-full). |
| `serveur_keycloak` | ingress | 8080 | tcp | edge | clair | Console et endpoints OIDC servis au navigateur et aux applications via l'edge (TLS terminé à l'edge). |
| `serveur_keycloak` | egress | 636 | tcp | serveur_openldap | tls-requis | Fédération de l'annuaire OpenLDAP (LDAPS). |
| `serveur_keycloak` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Persistance Keycloak dans PostgreSQL (verify-full). |
| `serveur_loki` | ingress | 1514 | tcp | fabric | clair | Journaux de la frontiere. Elle n'accueille aucun agent et n'emet que du syslog ; sans ce flux, le seul equipement qui voit passer TOUT le trafic n'ecrit nulle part. |
| `serveur_loki` | ingress | 3100 | tcp | client_journal | tls | Réception des journaux poussés par Alloy (client_journal) en HTTPS (http_tls_config, cert step-ca). |
| `serveur_loki` | ingress | 3100 | tcp | localhost | clair | Requêtes de Grafana co-localisé (datasource Loki en localhost). |
| `serveur_loki` | ingress | 3100 | tcp | fabric | tls | Journaux des hyperviseurs. La machine qui PORTE les VM ecrivait ses journaux nulle part ailleurs que sur son propre disque — invisibles le jour ou c'est elle qui casse. |
| `serveur_nextcloud` | ingress | 80 | tcp | edge | clair | Interface web servie via l'edge (TLS terminé à l'edge). |
| `serveur_nextcloud` | egress | 443 | tcp | edge | tls-requis | Découverte OIDC auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_nextcloud` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Base de données Nextcloud (verify-full). |
| `serveur_nextcloud` | egress | 9980 | tcp | serveur_collabora | tls-cible | Vérifications WOPI serveur->Collabora (édition en ligne). TLS interne = feuille de route edge->backends. |
| `serveur_nginx` | ingress | 80 | tcp | externe | clair | HTTP entrant — redirection permanente vers HTTPS. |
| `serveur_nginx` | ingress | 443 | tcp | externe | tls-requis | HTTPS entrant — services exposés (terminaison TLS à l'edge). |
| `serveur_nginx` | ingress | 80 | tcp | admin | clair | HTTP depuis le reseau d'administration — redirection permanente vers HTTPS. |
| `serveur_nginx` | ingress | 443 | tcp | flotte | tls-requis | HTTPS depuis le tenant : les FQDN publiés vivent à l'edge (découverte OIDC, appels inter-services par nom). |
| `serveur_nginx` | ingress | 443 | tcp | admin | tls-requis | HTTPS depuis le reseau d'administration : l'exploitant administre les services par leur interface web, servie par l'edge. |
| `serveur_nginx` | egress | derive | tcp | expositions | tls-cible | Proxy vers les backends exposés (host:port dérivés des expose ; TLS interne = roadmap edge→backends). |
@ -61,35 +83,55 @@
| `serveur_oauth2_proxy` | egress | 8080 | tcp | localhost | clair | Relais vers l'application co-localisée protégée (upstream en localhost). |
| `serveur_openldap` | ingress | 389 | tcp | flotte | starttls | LDAP + STARTTLS pour les clients internes qui préfèrent la mise à niveau TLS sur 389. |
| `serveur_openldap` | ingress | 636 | tcp | serveur_keycloak, serveur_dovecot, serveur_icingaweb2, serveur_postfix | tls-requis | LDAPS : fédération (Keycloak), userdb courriel (Dovecot), auth web (Icinga Web 2), tables virtuelles (Postfix). |
| `serveur_ops` | ingress | 8090 | tcp | edge, admin | clair | Console d'exploitation servie par l'edge (TLS terminé à l'edge), et joignable depuis le plan d'administration là où il n'y a pas d'edge. Le GUI lui-même reste sur la boucle locale : c'est nginx qui authentifie devant. |
| `serveur_ops` | egress | 22 | tcp | flotte | ssh | Piloter la flotte — c'est la raison d'être du poste. |
| `serveur_ops` | egress | 443 | tcp | edge | tls-requis | Cloner et resynchroniser le génome depuis la forge de l'écosystème. |
| `serveur_ops_site` | egress | 22 | tcp | fabric | ssh | Shell des hyperviseurs : ce que l'API ne couvre pas — configuration reseau, ponts, deplacement de disques. Un pouvoir distinct de l'API, donc declare a part. |
| `serveur_ops_site` | egress | 22 | tcp | serveur_ops_tenant | ssh | Insemination : amorcer le runner d'un tenant — socle, moteur, plan, plancher de resolution — pour qu'il prenne ensuite le relais sur ses propres machines. Ne transporte aucun secret : la voute est remise par un humain. |
| `serveur_ops_site` | egress | 443 | tcp | fabric | tls-requis | API de la frontiere OPNsense : poser les alias et les regles qui ouvrent les flux du tenant qu'on materialise. Preparer le terrain sans cela laisserait un terrain injoignable. |
| `serveur_ops_site` | egress | 8006 | tcp | fabric | tls-requis | API de l'hyperviseur : créer, cloner et détruire les VM de la fabric. Le seul flux par lequel un écosystème peut en matérialiser un autre. |
| `serveur_ops_tenant` | ingress | 22 | tcp | runner_site | ssh | Insémination : le runner du SITE amorce ce runner-ci — socle, moteur, plan, plancher de résolution — jusqu'à ce qu'un humain lui remette sa voûte. Vers cette machine seule, jamais vers le reste de l'écosystème. |
| `serveur_postfix` | ingress | 25 | tcp | externe, client_smtp | starttls | SMTP entrant : courrier externe (MX) et notifications internes (client_smtp). |
| `serveur_postfix` | ingress | 587 | tcp | flotte | starttls | Soumission authentifiée (submission) pour les agents internes qui envoient du courrier. |
| `serveur_postfix` | ingress | 465 | tcp | flotte, externe | tls-requis | Soumission authentifiée en TLS direct (submissions, RFC 8314) : clients de courriel des utilisateurs. |
| `serveur_postfix` | ingress | 587 | tcp | flotte, externe | starttls | Soumission authentifiée (STARTTLS obligatoire) : agents internes et clients de courriel des utilisateurs. |
| `serveur_postfix` | egress | 24 | tcp | serveur_dovecot | tls-requis | Remise finale par LMTP au mailstore (Dovecot), en TLS vérifié. |
| `serveur_postfix` | egress | 25 | tcp | externe | starttls | Relais sortant vers les MX distants (STARTTLS opportuniste). |
| `serveur_postfix` | egress | 636 | tcp | serveur_openldap | tls-requis | Tables virtuelles (domaines/alias/boîtes) résolues sur l'annuaire (LDAPS). |
| `serveur_postfix` | egress | 11332 | tcp | localhost | clair | Filtre milter rspamd co-localisé (antispam + signature DKIM). |
| `serveur_postfix` | egress | 12345 | tcp | serveur_dovecot | tls | Validation SASL des identifiants de soumission contre Dovecot. |
| `serveur_postgresql` | ingress | 5432 | tcp | serveur_keycloak, serveur_forgejo, serveur_icinga, serveur_nextcloud | tls-requis | Connexions applicatives à PostgreSQL (verify-full ; pg_hba hostssl). |
| `serveur_powerdns` | ingress | 53 | udp | flotte | clair | Résolution DNS interne (zone souveraine). DoT/DoH = feuille de route (chiffrement DNS). |
| `serveur_powerdns` | ingress | 53 | tcp | flotte | clair | Résolution DNS interne en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips). |
| `serveur_postgresql` | ingress | 9187 | tcp | serveur_prometheus | clair | Metriques PostgreSQL lues par l'observatoire. Series, pas verdicts : ce qui derive lentement — cache qui decroche, connexions qui montent, bases qui grossissent — n'a que le graphe pour se faire voir. En clair pour l'instant, contrairement a `client_metrique` : dette inscrite, a fermer par `--web.config.file` + `client_pki`. |
| `serveur_powerdns` | ingress | 5300 | tcp | dns_public_site | clair | AXFR signe TSIG par le serveur DNS public du site, vers l'instance publique uniquement. |
| `serveur_powerdns` | ingress | 5300 | udp | dns_public_site | clair | Interrogation du SOA par le secondaire du site avant chaque transfert. |
| `serveur_powerdns` | ingress | derive | udp | flotte | clair | Zone souveraine. 53 seul sur son hôte, 5300 sur la loopback derrière le résolveur. |
| `serveur_powerdns` | ingress | derive | tcp | flotte | clair | Idem en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips). |
| `serveur_powerdns` | egress | 53 | udp | externe | clair | Résolution sortante du serveur autoritatif POUR SES PROPRES besoins (apt, NTP) — il ne récurse pour aucun autre hôte. |
| `serveur_powerdns` | egress | 53 | tcp | externe | clair | Repli TCP de la résolution sortante du serveur autoritatif (réponses dépassant la taille UDP). |
| `serveur_prometheus` | ingress | 9090 | tcp | localhost | clair | Console Prometheus consommée en local par Grafana co-localisé (pas d'exposition inter-nœud). |
| `serveur_prometheus` | egress | 9100 | tcp | client_metrique | tls | Scrape des node_exporter (HTTPS via cert step-ca) sur chaque nœud instrumenté. |
| `serveur_prometheus` | egress | 9100 | tcp | frontiere | clair | Scrape de l'exportateur de la frontiere (os-node_exporter), sur sa seule patte de supervision : l'equipement qui voit passer TOUT le trafic n'avait ni temperature ni charge dans la supervision. |
| `serveur_prometheus` | egress | 9100 | tcp | fabric | clair | Scrape des hyperviseurs : la machine qui PORTE les VM etait invisible de la supervision qui les surveille. Elle ne peut pas pousser (route par defaut gelee, D-57), donc on la tire. |
| `serveur_redis` | ingress | 6379 | tcp | localhost | clair | Cache/verrous consommés uniquement par l'application co-localisée (ex. Nextcloud). Aucune exposition inter-nœud. |
| `serveur_resolveur` | ingress | 53 | udp | flotte | clair | Toute la flotte du tenant résout ici — et nulle part ailleurs. |
| `serveur_resolveur` | ingress | 53 | tcp | flotte | clair | Réponses longues et bascule TCP, obligatoires en DNS. |
| `serveur_resolveur` | egress | 53 | udp | externe | clair | Récursion depuis les serveurs racine. Aucun transitaire : l'écosystème ne confie ses questions à personne. |
| `serveur_resolveur_site` | ingress | 53 | udp | voisins_site | clair | Les écosystèmes de ce site résolvent ici tant qu'ils n'ont pas leur propre résolveur. `client_resolveur` les bascule chez eux dès qu'il existe ; retirer cet emprunt est alors une émancipation. |
| `serveur_resolveur_site` | ingress | 53 | tcp | voisins_site | clair | Réponses longues et bascule TCP, obligatoires en DNS. |
| `serveur_rspamd` | ingress | 11332 | tcp | localhost | clair | Protocole milter consommé par Postfix co-localisé (analyse + signature DKIM). Local uniquement. |
| `serveur_rspamd` | ingress | 11334 | tcp | localhost | clair | Interface de contrôle rspamd (statistiques, apprentissage) en local. |
| `serveur_step_ca` | ingress | 8443 | tcp | flotte | tls-requis | ACME + API step-ca : chaque nœud (client_pki) émet/renouvelle ses certificats et récupère la racine. |
| `serveur_web_dorsal` | ingress | 80 | tcp | edge | clair | Front nginx local des webapps, proxie par l'edge (TLS termine a l'edge). |
| `serveur_web_dorsal` | ingress | 80 | tcp | edge, serveur_web_frontal | clair | Front nginx local des webapps et des sites statiques, relaye par l'edge (noms internes) et par le web frontal (noms publics). |
| `serveur_web_dorsal` | egress | 443 | tcp | flotte | tls | git clone/pull du depot de chaque app (Forgejo souverain) au deploiement. |
| `serveur_web_frontal` | ingress | 80 | tcp | edge | clair | Contenu statique servi au navigateur via l'edge (TLS termine a l'edge). |
| `serveur_web_frontal` | egress | 443 | tcp | flotte | tls | git clone/pull du depot du site (Forgejo souverain) au deploiement. |
| `serveur_web_frontal` | ingress | 80 | tcp | externe | clair | HTTP public, relaye a travers le WAF — et le defi HTTP d'ACME ; renverra vers HTTPS quand le certificat sera la. |
| `serveur_web_frontal` | ingress | 443 | tcp | externe | tls-requis | HTTPS public — les sites et services que le locataire publie sur l'Internet (attend son certificat Let's Encrypt). |
| `serveur_web_frontal` | egress | 80 | tcp | serveur_web_dorsal | clair | Relais vers le web dorsal, qui porte les sites et les applications publiques. |
## Synthèse chiffrement
- **clair** : 29 flux
- **n-a** : 3 flux
- **ssh** : 3 flux
- **clair** : 51 flux
- **n-a** : 8 flux
- **ssh** : 8 flux
- **starttls** : 6 flux
- **tls** : 8 flux
- **tls** : 10 flux
- **tls-cible** : 2 flux
- **tls-requis** : 26 flux
- **tls-requis** : 34 flux

133
docs/remise-au-client.md Normal file
View file

@ -0,0 +1,133 @@
> **Pour qui :** l'hébergeur, le jour où il remet un écosystème à celui qui en est le
> propriétaire. À lire **avant** de fabriquer le paquet, pas pendant.
# Remettre un écosystème à son propriétaire
## 1. Le problème que ce document ferme
La livraison se terminait par une phrase : *« tes clés te seront remises séparément »*.
Ce qui se passait ensuite n'était écrit nulle part — ni ce qu'on remet, ni dans quel
ordre, ni **ce qu'on garde**. Le geste qui donne le contrôle d'une organisation était le
seul geste lourd du dépôt sans procédure, sans outil et sans garde.
Et il portait une faute silencieuse : les clés vivent toutes dans le même dossier
(`~/.config/setops-vault-*`). Remettre « les clés » d'un revers de main, c'est remettre
celles du site et celles des autres locataires. Personne ne s'en apercevrait — ni celui
qui donne, ni celui qui reçoit.
## 2. Les deux temps, et pourquoi ils ne se confondent pas
> **Temps 1 — l'identité.** Le client reçoit de quoi gouverner **ses gens** tout de suite.
> **Temps 2 — la machine.** À une date convenue, il reçoit le pouvoir sur **ses serveurs**,
> et l'hébergeur le perd.
Ce découpage n'est pas une précaution d'hébergeur : c'est ce qui rend les deux gestes
honnêtes.
| | Temps 1 | Temps 2 |
|---|---|---|
| Ce qui passe | la clé de **sa** voûte, sa voûte chiffrée, la racine de **son** AC | sa clé SSH entre, celle de l'hébergeur sort, la voûte change de mot de passe, les secrets tournent |
| Ce que le client peut | créer, retirer, habiliter des personnes — sans nous | tout, y compris se passer de nous |
| Ce que l'hébergeur garde | l'accès **machine**, parce qu'il exploite encore | rien qui ne lui soit redonné |
| Quand | le jour de la livraison | à l'échéance inscrite (30 jours par défaut) |
Le second temps applique à une livraison ce que
[`migration-tenant.md`](migration-tenant.md) §6 étape 8 applique déjà à un départ :
**révoquer, pas transmettre**. Sans lui, l'hébergeur garde **à vie** l'accès aux secrets
d'un client qui se croit chez lui — et aucune procédure ne rattrape ça après coup.
## 3. Temps 1 — le paquet
```
make ca-racine # la racine de SON AC, et son empreinte
make ca-empreinte # la même, lue SUR l'AC : le témoin à comparer
make remise-recenser # ce qui partirait, sans rien écrire
make remise-paquet VERS=/media/…/CLE
make remise-inscrire RECU_PAR="Prénom Nom" COURRIEL="…"
```
**Lance `remise-paquet` toi-même**, dans ton terminal : `gpg` demande une phrase de passe,
et elle ne doit passer ni par un journal, ni par le contexte d'un assistant.
L'outil **refuse** quatre choses, et chacune ferme une faute réelle :
- **une destination dans l'infrastructure** — le dépôt de sauvegarde est chiffré par un
mot de passe qui vit dans la voûte que ce paquet ouvre ; l'y déposer ferait un coffre
dont la clé est à l'intérieur ;
- **écraser un paquet existant** — c'est peut-être celui qu'on vient de vérifier ;
- **partir sans la racine de l'AC** — sans elle, le client apprend à cliquer sur
« continuer quand même », ce qui vaut pire que pas de TLS du tout ;
- **un paquet qu'il n'arrive pas à rouvrir** — il est alors supprimé. Un paquet de remise
qu'on ne sait pas rouvrir donne le sentiment d'avoir remis.
Il n'emporte **que l'écosystème monté** : la clé du site et celles des autres locataires
vivent dans le même dossier, et c'est une seule ligne de code qui les en écarte
(`remise.py:_cle_de_voute`). Il n'affiche jamais le contenu d'un secret — noms, tailles,
empreintes SHA256, rien d'autre.
**Deux gestes restent, et ils n'ont pas d'outil :** transmettre la phrase de passe par un
**autre canal** que le support, et transmettre l'empreinte de l'AC de la même façon.
Séparés, le support et la phrase ne valent rien l'un sans l'autre.
## 4. Temps 2 — le re-clé
**L'ordre ne se permute pas.** Retirer sa propre clé avant que celle du client soit posée
ferme l'écosystème à tout le monde, et le seul moyen de le réparer est justement celui
qu'on vient de retirer.
1. **La clé du client entre** — son entrée dans `ssh_baseline_cles_admin`, `etat: present`.
2. **Celle de l'hébergeur sort** — `etat: absent` sur son entrée. On ne la supprime pas du
plan : une entrée retirée n'est plus appliquée, donc la clé **resterait** sur les
machines. `absent` la fait *retirer*.
3. **Déployer**, pour que le plan devienne l'état des machines.
4. **La voûte change de mot de passe** — `ansible-vault rekey`, la nouvelle clé étant
choisie par le client. Le mot de passe de l'hébergeur ne se *communique* pas.
5. **Les secrets applicatifs tournent** — `voute.py saisir --remplacer`, sans écho, puis
déploiement.
6. **Estampiller** : `make remise-recleer CONFIRMER=true`.
`remise-recleer` **mesure avant d'estampiller**, et refuse si l'un des trois faits manque :
une clé présente, une clé révoquée, une clé de voûte différente de celle remise au temps 1.
Un registre qui dirait « révoqué » pendant que le plan garde la clé de l'hébergeur serait
le seul mensonge que ce fichier puisse porter sans que personne ne s'en aperçoive — parce
qu'il flatte tout le monde.
## 5. Le registre — `remise.yml` chez le locataire
Généré, versionné, dans le dépôt du locataire, à côté de `parente.yml` : *de qui il
descend* d'un côté, *à qui il appartient* de l'autre.
Il porte l'organisation, la date du temps 1, qui a remis, **qui a reçu**, les empreintes
SHA256 des pièces remises, l'échéance du temps 2 et son constat. **Aucun secret**, par
construction : une empreinte prouve qu'on a remis *ce fichier-là* sans rien révéler de son
contenu.
> **Il déclare enfin le responsable désigné.** D-18 décide depuis longtemps que chaque
> locataire en a un ; `migration-tenant.md` §9 laissait ouverte la question « **où est-il
> déclaré ?** ». La réponse est ici, et elle est la seule qui ne devine rien : c'est la
> personne qui **reçoit**.
## 6. La garde
**P80** lit les registres de tous les écosystèmes et refuse trois états :
- un registre incomplet — remis à personne, ou sans échéance ;
- un temps 2 **échu** et non fait : l'hébergeur garde l'accès machine d'un client qui se
croit chez lui, et le silence le laisserait devenir un état de fait ;
- un temps 2 déclaré fait pendant que le plan ne révoque **aucune** clé.
Elle ne juge **pas** un écosystème sans registre : tous ne sont pas remis, et beaucoup ne
le seront jamais — le lab, l'écosystème de l'hébergeur lui-même.
## 7. Ce que cette procédure ne couvre pas
- **Ce que le client fait de son paquet.** Une clé remise sur un support qu'il laisse
dans un tiroir déverrouillé n'est plus notre affaire, et le LISEZ-MOI le lui dit.
- **La rotation des secrets applicatifs**, qui reste un geste humain : `voute.py` ne
génère pas les valeurs, il les reçoit sans écho. Un script qui engendrerait et écrirait
tout seul connaîtrait ce qu'il écrit.
- **La preuve que l'hébergeur ne peut plus entrer.** Le plan déclare la révocation et le
déploiement l'applique ; le vérifier *depuis l'extérieur* demande d'essayer d'entrer,
donc une machine vivante. C'est le même partage que partout ici : le dépôt prouve ce
qu'il a **demandé**, `make emancipation-prouver` prouve ce qui **tient**.

View file

@ -0,0 +1,118 @@
> **Pour qui :** le locataire **et** l'hébergeur — les deux lisent le même texte, et c'est
> le but. Un partage de responsabilités dont chaque partie aurait sa version est un
> désaccord qui attend son incident.
# Responsabilités du locataire et de l'hébergeur
## 1. Le principe : une responsabilité est la conséquence d'un pouvoir
Ce document ne distribue pas des devoirs par bonne volonté. Il les **dérive** :
> **Qui peut, doit. Qui ne peut pas, ne peut pas être tenu — et personne ne peut se
> décharger sur celui qui ne peut pas.**
Trois règles en découlent, et elles décident de tout le reste :
1. **À chaque pouvoir sa responsabilité.** Un pouvoir sans devoir correspondant est une
prise sur autrui ; un devoir sans pouvoir correspondant est un piège.
2. **Une responsabilité sans mécanisme est un vœu.** Chaque ligne du tableau nomme
l'endroit du système qui la rend vraie — un rôle, une garde, une commande. Ce qui n'a
pas de mécanisme est écrit au §5, parmi les points non tranchés, plutôt que promis.
3. **Le silence ne crée pas de responsabilité.** Si une ligne n'a pas de vis-à-vis, ce
n'est pas une zone partagée : c'est un trou, et il se voit.
## 2. La ligne de partage : trois portées, et aucun pouvoir total
C'est la structure même du moteur, pas une convention de contrat
(`roles/serveur_ops_tenant/README.md`) :
```
calculer plan → inventaire aucune voûte n'importe quel runner
configurer des rôles sur ses machines voûte du TENANT le runner du locataire
matérialiser créer / détruire des VM voûte du SITE le runner de l'hébergeur
```
**Aucun runner n'est omnipotent.** Celui de l'hébergeur matérialise le terrain et n'entre
jamais chez un locataire ; celui du locataire configure son écosystème et ne touche jamais
la fabric. Tout ce qui suit découle de cette coupure.
## 3. Le tableau
<!-- TABLE:RESPONSABILITES — canonique. Le PDF remis aux locataires LIT ce tableau ;
il n'en porte pas de copie. Le modifier ici le modifie partout. -->
| Le pouvoir | Qui le détient | La responsabilité qui en découle | Ce que l'autre ne peut donc pas faire à sa place |
|---|---|---|---|
| **Créer et détruire les machines** (`raser`, `site-raser`, clonage du gabarit) | l'hébergeur | Que les machines existent, démarrent et tiennent ; qu'aucune destruction n'ait lieu sans confirmation explicite ni sans sauvegarde hors du site au préalable | Le locataire ne peut pas garantir l'existence de ses machines, ni se les rendre lui-même |
| **Le réseau, la frontière et le stockage** (SDN, VLAN, OPNsense, Ceph) | l'hébergeur | Que le chemin existe et que la bordure refuse ce qui n'est pas déclaré ; prévenir avant toute coupure planifiée | Le locataire ne peut pas ouvrir un flux vers l'extérieur sans que l'hébergeur l'applique |
| **Déclarer ses flux** (`roles/*/meta/flux.yml`, pare-feu d'hôte) | le locataire | Déclarer ce que ses services ont besoin d'échanger ; ce qui n'est pas déclaré est refusé, et c'est voulu | L'hébergeur ne peut pas deviner un flux applicatif ; il n'ouvrira rien « au cas où » |
| **Configurer les services** (son runner, sa voûte) | le locataire | L'état de ses services : ce qui est déployé, à jour, et conforme à son plan | L'hébergeur ne peut pas configurer un service du locataire : il n'a pas sa voûte |
| **L'annuaire et l'entrée unique** (LDAP, Keycloak) | le locataire | Qui entre, qui sort, et **quand** — créer, désactiver, révoquer sans attendre personne | L'hébergeur ne peut retirer l'accès de personne : mutualiser l'annuaire serait lui confier ses gens |
| **Les habilitations** (appartenance aux groupes, D-66/D-67) | le locataire | Tenir ses groupes à jour. Le moteur amorce **un** accès puis se retire : il ne réconcilie jamais les appartenances | Ni l'hébergeur ni l'outillage ne corrigeront un groupe : un redéploiement n'efface pas le compte créé la veille |
| **Son autorité de certification** (step-ca, D-87) | le locataire | Renouveler ses certificats, et **recharger** les services qui les servent — un certificat renouvelé sur disque reste servi périmé en mémoire | L'hébergeur ne peut pas émettre de certificat au nom du locataire, et c'est délibéré : il pourrait sinon se faire passer pour n'importe lequel de ses services |
| **Ses journaux** (Loki, D-87) | le locataire | Les lire, les conserver, et les fournir s'il demande de l'aide | L'hébergeur ne voit pas les journaux du locataire : il ne peut donc pas diagnostiquer un incident applicatif à sa place |
| **La disponibilité de la fabric** (une VM est-elle debout ?) | l'hébergeur | Constater qu'une machine est tombée et le dire — c'est sa fabric | Le locataire n'a pas de vue sur l'hyperviseur qui porte ses VM |
| **Les métriques d'hébergement** (charge, disque) | l'hébergeur, **avec réserve** | S'en servir pour dimensionner et prévenir, pas pour lire l'activité d'une organisation — elles disent *quand* et *combien* | — (elles ne disent pas *quoi* : le contenu reste hors de portée) |
| **Le dépôt de sauvegarde** (`serveur_backup`, chiffré côté client) | l'hébergeur | Que le dépôt **accepte encore une écriture** : espace, droits, système de fichiers en lecture seule. Sa sonde constate l'endroit, jamais le contenu | L'hébergeur **ne peut pas** juger un instantané : il héberge des octets qu'il ne peut pas ouvrir |
| **La clé de ses sauvegardes** (restic) | le locataire | Juger que ses instantanés sont **récents, complets et restaurables**, et éprouver une restauration. *La vérification suit la clé* | Personne ne peut vérifier une sauvegarde à la place de qui détient la clé — et sa perte les rend définitivement illisibles |
| **Sa voûte et ses secrets** (jamais mutualisée) | le locataire | Garder sa clé hors de l'infrastructure qu'elle ouvre, en double, et faire tourner ses secrets | L'hébergeur ne peut pas recouvrer une clé de voûte perdue : il n'en a aucune copie, par construction |
| **Le courriel** (relais à la bordure) | partagé | L'**enveloppe** transite chez l'hébergeur, qui en répond ; le **contenu** appartient au locataire, qui en répond | L'hébergeur ne lit pas le contenu ; le locataire ne tient pas la réputation de la bordure |
| **La forge du génome** (D-81) | l'hébergeur | Tenir l'autorité du génome disponible : c'est de là qu'un écosystème se reproduit | Le locataire ne dépend pas d'elle pour **vivre** — seulement pour se reconstruire ; il peut en garder son propre miroir |
| **L'accès de secours par `sudo`** (D-40) | l'hébergeur, **tant qu'il exploite** | Le dire plutôt que de le taire : exploiter une machine, c'est pouvoir tout y lire. C'est la raison d'être du second temps de la remise, et de sa date | Le locataire ne peut pas le supprimer sans reprendre lui-même l'exploitation — d'où une **échéance écrite**, pas une confiance indéfinie |
| **La remise des clés** (`make remise-*`, garde **P80**) | l'hébergeur remet, le locataire reçoit | Remettre en deux temps : l'identité le jour de la livraison, la machine à l'échéance — sa clé entre, la nôtre sort, la voûte est re-clétée | Un accès qu'on oublie de rendre ne devient pas légitime en vieillissant : la garde signale tout retard |
| **Nommer le responsable désigné** (D-18, `remise.yml`) | le locataire | Nommer la personne qui engage l'organisation, et la maintenir à jour | L'hébergeur ne peut pas la désigner : ce serait choisir qui a le droit de le quitter |
| **Décider de partir** (migration, émancipation) | le locataire | Mandater, et emporter ce qui est à lui : son plan, ses données, sa voûte, ses domaines | L'hébergeur ne peut ni retenir ni décider à sa place ; la machine **instruit**, l'humain décide |
| **Libérer un partant** (`migration-tenant.md` §6) | l'hébergeur | **Révoquer, pas transmettre** : ses accès tombent, puis rétention convenue, puis purge | Le locataire ne peut pas vérifier lui-même la purge ; elle est un engagement daté, pas une mesure |
<!-- /TABLE:RESPONSABILITES -->
## 4. Ce que personne ne peut déléguer
Trois choses ne se confient à aucune des deux parties, parce que les confier les annule :
- **La phrase de passe d'un paquet de remise** ne voyage jamais avec le support qu'elle
ouvre. Séparés, ils ne valent rien l'un sans l'autre ; ensemble, ils valent l'écosystème.
- **La seconde copie** d'une clé, dans un autre lieu physique. Un support unique dans un
tiroir unique n'est pas une sauvegarde, c'est le même risque déplacé de quelques mètres.
- **La comparaison d'une empreinte** avant d'installer une autorité de certification.
Installer une AC, c'est lui donner le droit de signer n'importe quel nom : la
comparaison est ce qui distingue sa racine d'une racine interceptée.
## 5. Ce qui n'est pas tranché — et qui reste donc à convenir
Nommer un point ouvert vaut mieux qu'une ligne rassurante sans mécanisme derrière.
- **Le recouvrement de la clé du responsable désigné.** Elle se perd, se compromet, ou la
personne quitte l'organisation. Sans procédure, un locataire devient *inmigrable* :
captif non par contrat mais par accident. Piste retenue : un **contact de secours nommé
en même temps que le responsable**, tant que personne n'est en situation d'urgence.
- **Comment on change de responsable désigné** — acte au moins aussi sensible que la
migration, puisqu'il décide qui pourra la mandater ensuite.
- **La durée de rétention** avant purge chez un hébergeur sortant : elle se convient, elle
ne se dérive pas.
- **La preuve que l'hébergeur ne peut plus entrer.** Le plan déclare la révocation et le
déploiement l'applique ; le vérifier depuis l'extérieur demande d'essayer d'entrer, donc
une machine vivante (`make emancipation-prouver`).
## 6. Comment chaque ligne se vérifie
Aucune de ces responsabilités n'est laissée à la parole :
```
make prouver le dépôt est-il cohérent avec lui-même (94 preuves, zéro réseau)
make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80)
make certificats-plan ce que le disque porte contre ce que la mémoire sert
make expositions-plan chaque service publié répond-il, et depuis où
make identite-plan royaume, fédération, politique de mot de passe, comptes
make emancipation-prouver un lien d'hébergement est-il réellement coupé
```
`make deployer` **répare**, les devis **constatent** : les confondre fait perdre
l'information au moment où elle sert.
---
*Le partage décrit ici n'est pas une politique commerciale : c'est la forme du système.
Chaque fois qu'il a été possible de donner un pouvoir au locataire, il lui a été donné —
et chaque fois que l'hébergeur a conservé un pouvoir, c'est parce que quelqu'un doit porter
la fabric, et cela s'écrit ici plutôt que de se découvrir le jour d'un incident.*

View file

@ -0,0 +1,990 @@
---
# LES RUNBOOKS DE CONSTRUCTION — l'ordre des gestes, et pourquoi celui-la.
#
# CE QUE CE FICHIER AJOUTE AU MAKEFILE, ET CE QU'IL NE REPETE PAS.
#
# Les 132 cibles documentees du Makefile disent chacune CE QU'ELLE FAIT. Aucune ne dit
# dans quel ORDRE, ni pourquoi maintenant, ni ce qu'il faut avoir mesure avant. Cette
# connaissance-la vivait en prose dans `docs/runbooks-exploitation.md`,
# `docs/implanter-un-tenant-sur-un-site.md` et `docs/preparer-un-site-hebergeur.md` — et
# la console offrait des boutons sans sequence.
#
# ON NE RECOPIE PAS LE LIBELLE D'UNE CIBLE. `scripts/runbooks.py` va le lire dans le
# Makefile au moment de servir. Un libelle recopie ici serait une seconde liste, et une
# liste qui suit une autre prend du retard sur elle. Ce fichier ne porte donc que ce que
# le Makefile ne peut pas porter : l'ordre, la nature du geste, la portee, le pourquoi.
#
# LA GARDE EST ECRITE AVEC LA LISTE, PAS APRES. `python3 scripts/runbooks.py verifier`
# (et P83) refusent qu'une cible documentee ne soit ni portee par un runbook ni exemptee
# avec un motif. Sans cela, la console cacherait des pouvoirs que le moteur possede.
#
# NATURE D'UNE ETAPE
# mesure n'ecrit rien, rejouable sans consequence, proposee meme apres un echec
# ecriture change l'etat du monde ; exige que l'etape precedente ait reussi
# destructif detruit ; exige une confirmation ecrite en plus de CONFIRMER=true
#
# PORTEE D'UN RUNBOOK — ce que la MACHINE porte, au sens de `contexte()` :
# tenant un ecosysteme est monte (`instance/`) : on le configure
# site une fabric est montee (`underlay.yml`) : on materialise
# poste les deux — l'atelier du mainteneur
# toute ni l'un ni l'autre n'est requis
#
# LA PORTEE SE PESE A L'ETAPE (2026-09-20). Un runbook donne le defaut ; une etape qui
# exige davantage le declare avec son propre `portee:`. Mesure faite sur la console de
# TechnoLibre : declaree au seul runbook, une unique etape qui materialise (`flotte-creer`,
# `creer-vm`) faisait basculer TOUTE la sequence en `poste` — et un locataire ne pouvait
# plus deployer sa propre flotte, ce qui est exactement son metier. Le locataire conduit
# donc sa sequence, et bute precisement la ou il faut : sur la machine a engendrer.
# CE QUE LA CONSOLE DOIT DEMANDER A L'EXPLOITANT. Une variable absente d'ici est refusee
# par la garde : la console ne saurait pas quoi afficher, et un champ libre sans invite
# est une invitation a se tromper.
variables:
HOTE:
invite: "La machine"
source: hotes # la liste vient de l'inventaire actif
GROUPE:
invite: "Le groupe (role)"
source: groupes
NOM:
invite: "Nom du dossier de l'ecosysteme"
exemple: "OPS-Machin"
MODELE:
invite: "Modele de depart"
source: modeles
INSTANCE:
invite: "Nom de l'ecosysteme a detruire (il doit etre ecrit en toutes lettres)"
SITE:
invite: "Depot du site a detruire (ecrit en toutes lettres)"
TENANT:
invite: "Dossier du locataire a amorcer"
exemple: "OPS-Machin"
DEPOT:
invite: "Un seul depot (vide = tous ceux que le runner porte)"
facultatif: true
VERS:
invite: "Repertoire de destination (une cle chiffree, hors du poste)"
ARCHIVE:
invite: "Fichier d'archive a restaurer"
CIBLE:
invite: "Hote ou adresse a sonder"
SERVICE:
invite: "Le lien dont on prouve la coupure"
valeurs: [artefacts, genome, resolveur]
ROLE:
invite: "Un seul role (vide = tous)"
facultatif: true
LIMITE:
invite: "Motif d'hotes"
facultatif: true
RECU_PAR:
invite: "Qui recoit la remise (nom complet)"
COURRIEL:
invite: "Courriel de la personne qui recoit"
DANS:
invite: "Jours avant le second temps"
facultatif: true
JEU:
invite: "Le jeu d'etat (openldap, postgresql, nextcloud... — voir restauration-etat)"
DEPUIS:
invite: "Reprendre a cette etape (vide = depuis le debut)"
valeurs: [sauvegarder, raser, creer, inseminer, armer, monter, parefeu, bilan]
facultatif: true
ARMER:
invite: "Armer sans marquer la pause"
valeurs: [oui]
facultatif: true
SOURCE:
invite: "Comparer a quel instantane (vide = celui d'avant rasage)"
valeurs: [candidat]
facultatif: true
VMID:
invite: "Identifiant Proxmox de la VM"
DIALECTE:
invite: "Dialecte du commutateur"
valeurs: [cisco, binardat]
facultatif: true
PARALLELE:
invite: "Combien de VM a la fois"
facultatif: true
runbooks:
# ─────────────────────────────────────────────────────────────────────────────
- id: site-premier-jour
titre: "Le premier jour d'un site"
portee: site
doc: docs/runbooks-exploitation.md
but: >-
Faire naitre un site hebergeur depuis une fabric nue : les machines, puis le moteur,
puis la forge qui permettra aux locataires de se reproduire. L'ordre n'est pas une
preference — le premier passage s'ARRETE sur une forge vide, et c'est normal.
etapes:
- cible: underlay
nature: mesure
pourquoi: >-
La fabric declaree tient-elle debout toute seule ? Rien ne sert de creer des
machines sur un plan d'adressage qui se contredit.
- cible: underlay-plan
nature: mesure
pourquoi: >-
Le declare face au REEL, par l'API du cluster et une sonde TCP. C'est ici qu'on
apprend qu'un pont n'existe pas, pas au milieu de la creation des VM.
- cible: site-creer
nature: ecriture
fixes: {CONFIRMER: "true"}
duree: "~9 min"
pourquoi: >-
Les machines du site, depuis l'underlay. Elles naissent ; elles ne repondent pas
encore.
- cible: site-deployer-tout
nature: ecriture
fixes: {CONFIRMER: "true"}
duree: "~20 min"
pourquoi: >-
Premier passage. Il va jusqu'a `serveur_ops` et s'arrete sur la forge vide :
ce n'est pas un echec, c'est le maillon suivant.
- cible: forge-amorcer
nature: ecriture
fixes: {CONFIRMER: "true"}
duree: "~45 s"
pourquoi: >-
La forge tourne et n'a ni organisation ni depot. Sans cet amorcage, le runner
clone le vide et le deploiement ne finira jamais.
- cible: site-deployer-tout
nature: ecriture
fixes: {CONFIRMER: "true"}
duree: "~4 min"
pourquoi: >-
Second passage. Celui-ci doit finir a zero echec ; s'il n'y arrive pas, c'est
un vrai defaut, plus un maillon manquant.
- cible: publier
nature: ecriture
variables: [DEPOT]
pourquoi: >-
Le geste quotidien : eregion ET la forge du site, puis la verification. Un
`git push` seul laisse la forge du site en retard sans que rien le dise.
- cible: genome-pousser
nature: ecriture
variables: [DEPOT]
pourquoi: >-
La forge du site FAIT AUTORITE : tant qu'elle est en retard, tout ecosysteme qui
s'y reproduit reproduit un moteur perime.
- cible: routes-fabric-etat
nature: mesure
pourquoi: >-
Les hyperviseurs routent-ils toutes les zones ? Une zone non routee ne se voit
qu'au moment ou un locataire y pose sa premiere machine.
- cible: gabarit-etat
nature: mesure
pourquoi: >-
Sans gabarit conforme, aucun locataire ne pourra cloner quoi que ce soit ici.
# ─────────────────────────────────────────────────────────────────────────────
- id: site-tenir
titre: "Tenir un site en etat"
portee: site
doc: docs/hebergeur-exploitation.md
but: >-
Les gestes reguliers de l'hebergeur : porter le genome a jour, appliquer un role aux
machines du site, et regarder ce que la fabric fait vraiment.
etapes:
- cible: genome-etat
nature: mesure
pourquoi: >-
Ce que la forge porte, face au poste. Un ecart ici se paie chez TOUS les
locataires qui clonent ensuite.
- cible: genome-pousser
nature: ecriture
variables: [DEPOT]
pourquoi: "Remettre la forge au niveau du poste, depot par depot si besoin."
- cible: site-appliquer
nature: ecriture
variables: [GROUPE]
pourquoi: >-
Rejouer un role sur les machines du site — c'est ainsi qu'un runner reprend le
genome courant, rien ne le tire tout seul.
- cible: site-decrire
nature: mesure
pourquoi: "Ce que l'underlay declare comme machines de l'hebergeur."
- cible: site-inventaire
nature: mesure
pourquoi: "L'inventaire dynamique du site, tel qu'Ansible le voit — pas tel qu'on l'imagine."
- cible: site-intrants
nature: mesure
pourquoi: "Ce que ce site expose a ses locataires, derive et non declare deux fois."
- cible: fiches-site-deposer
nature: ecriture
pourquoi: >-
Deposer chez chaque locataire la fiche que le site lui destine. Son runner n'a pas
le depot du site : c'est par elle que son inventaire se genere sans lui. A commiter
dans le depot de chaque locataire.
- cible: site-verifier
nature: mesure
pourquoi: "Le playbook du site correspond-il encore aux couches declarees ?"
- cible: site
nature: ecriture
pourquoi: "Regenerer playbooks/site.yml quand les couches ont bouge."
- cible: routes-fabric-etat
nature: mesure
pourquoi: "Une route de zone manquante est invisible jusqu'au premier invite qui la traverse."
- cible: locataire-creer
nature: ecriture
variables: [TENANT, PARALLELE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Cloner les VM d'un locataire NOMME, d'apres la face reseau qu'il publie : le
runner du site materialise sans monter son depot. Memes machines, memes
parametres que `flotte-creer` sur l'instance montee (P93, P94).
- cible: locataire-raser
nature: destructif
variables: [TENANT, INSTANCE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Detruire les VM d'un locataire NOMME, d'apres sa face : les memes VMID que le
plan derive (P94). Son nom court s'ecrit en toutes lettres, comme pour `raser`.
- cible: site-raser
nature: destructif
variables: [SITE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Detruire les VM du SITE. A ne faire que sur un site de chantier : la
reconstruction est prouvee, elle n'est pas gratuite.
# ─────────────────────────────────────────────────────────────────────────────
- id: locataire-naitre
titre: "Faire naitre un ecosysteme de locataire"
portee: tenant
doc: docs/multi-instances.md
but: >-
Du modele au plan monte : creer le dossier de l'ecosysteme, le rendre actif, puis
generer son inventaire. Tout l'adressage descend du seed `index` — on ne l'ecrit
jamais a la main.
etapes:
- cible: instance-modeles
nature: mesure
pourquoi: "Quels modeles de depart existent, avant d'en choisir un."
- cible: instances
nature: mesure
pourquoi: >-
Les ecosystemes deja decouverts, et surtout les COLLISIONS d'index : deux
ecosystemes sur le meme seed se marcheraient dessus en silence.
- cible: instance-creer
nature: ecriture
variables: [NOM, MODELE]
pourquoi: "Le dossier de l'ecosysteme, depuis un modele, avec son index reserve."
- cible: instance-utiliser
nature: ecriture
variables: [NOM]
pourquoi: >-
Basculer le symlink `instance/`. C'est lui qui decide QUEL ecosysteme la console
configure — et quelle voute elle ouvrira.
- cible: instance-courante
nature: mesure
pourquoi: "Verifier vers quoi on pointe avant d'ecrire quoi que ce soit."
- cible: intrants-verifier
nature: mesure
pourquoi: >-
Tout intrant qu'un role EXIGE est-il fourni ? Un intrant manquant ne se voit
sinon qu'au milieu d'un deploiement.
- cible: ports-verifier
nature: mesure
pourquoi: "Deux roles co-localises qui reclament le meme port ne cohabiteront pas."
- cible: instancier
nature: mesure
pourquoi: >-
Generer hosts.yml depuis le plan SANS l'appliquer — on regarde le diff avant de
le prendre.
- cible: instancier-appliquer
nature: ecriture
pourquoi: >-
Prendre l'inventaire genere. `hosts.yml` est un ARTEFACT : on edite le plan, on
ne le corrige jamais a la main.
- cible: inventaire-verifier
nature: mesure
pourquoi: "L'inventaire se parse-t-il, voute dechiffree ? Sinon rien ne partira."
# ─────────────────────────────────────────────────────────────────────────────
- id: locataire-materialiser
titre: "Preparer le terrain d'un locataire sur la fabric"
portee: site
doc: docs/implanter-un-tenant-sur-un-site.md
but: >-
Tout ce que l'HEBERGEUR pose avant qu'un locataire puisse exister : le placement, les
pools, le SDN, les pare-feux et la frontiere. Chaque devis se mesure avant de
s'appliquer — un devis qu'on applique sans l'avoir lu est un pari.
etapes:
- cible: placement-plan
nature: mesure
pourquoi: >-
Le noeud, le stockage, le pont et le gabarit existent-ils VRAIMENT sur ce
cluster ? C'est la premiere chose qui manque, et la derniere qu'on regarde.
- cible: devis-proxmox-pools
nature: mesure
pourquoi: "Un pool par locataire, derive du plan — la cloison la plus simple."
- cible: devis-proxmox-pools-verifier
nature: mesure
pourquoi: "Le devis tient-il ses propres regles avant qu'on le pose ?"
- cible: devis-sdn
nature: mesure
pourquoi: "Zone, VNets et sous-reseaux, derives du seed de l'ecosysteme."
- cible: devis-sdn-verifier
nature: mesure
pourquoi: "Relire le devis SDN avant de toucher au reseau du cluster."
- cible: sdn-plan
nature: mesure
pourquoi: "L'ecart entre le SDN en service et ce devis — ce qui manque, ce qui est perime."
- cible: sdn-appliquer
nature: ecriture
fixes: {CONFIRMER: "true"}
pourquoi: >-
Reconcilier : creer ce qui manque et RETIRER ce qui est perime. Le retrait est la
moitie qu'on oublie.
- cible: flux
nature: ecriture
pourquoi: >-
Regenerer le registre des flux et les regles nftables depuis les `meta/flux.yml`
des roles. Tout ce qui suit en descend.
- cible: face-reseau-publier
nature: ecriture
pourquoi: >-
Publier ce que ce locataire demande a son site (`face-reseau.yml`) : ses machines,
ses zones, ses flux deja resolus. Le site ne lit plus que ce fichier. Apres `flux`,
et a commiter dans le depot du locataire.
- cible: devis-proxmox-fw
nature: mesure
pourquoi: "Le pare-feu est-ouest intra-locataire, derive du registre des flux."
- cible: devis-proxmox-fw-verifier
nature: mesure
pourquoi: "Relire ce devis avant de le poser sur le cluster."
- cible: proxmox-fw-plan
nature: mesure
pourquoi: "L'ecart entre le pare-feu en service et le devis."
- cible: proxmox-fw-appliquer
nature: ecriture
fixes: {CONFIRMER: "true"}
pourquoi: "Poser IPSets, groupes et affectations — et retirer ce qui ne se declare plus."
- cible: proxmox-fw-eprouver
nature: mesure
variables: [TENANT, HOTE]
pourquoi: >-
Avant d'activer le pare-feu d'UNE VM : les regles du devis tiennent-elles face aux
flux que la VM recoit REELLEMENT ? Une VM reconstruite a perdu ses options.
- cible: proxmox-fw-activer-vm
nature: ecriture
variables: [TENANT, HOTE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Activer une VM a la fois, matrice avant et apres, Icinga lu. Chaque defaut du
2026-09-28 ne s'est montre qu'a l'activation d'UNE VM.
- cible: devis-opnsense
nature: mesure
pourquoi: "La frontiere nord/sud, derivee du meme registre de flux."
- cible: devis-opnsense-verifier
nature: mesure
pourquoi: "Relire le devis de frontiere : c'est la porte de l'exterieur."
- cible: frontiere-plan
nature: mesure
pourquoi: "Ce que la frontiere porte aujourd'hui, face a ce devis."
- cible: frontiere-appliquer
nature: ecriture
fixes: {CONFIRMER: "true"}
pourquoi: >-
Reconcilier la frontiere. Les routes creees ETEINTES ont deja coute une journee :
une route eteinte compte « posee » et ne route rien.
- cible: devis-reseau
nature: mesure
variables: [DIALECTE]
pourquoi: >-
Le devis des commutateurs physiques (VLANs, SVIs, ACLs). Il se pose a la main sur
le materiel — le moteur ne configure pas les switches.
- cible: frontiere-mesurer
nature: mesure
pourquoi: >-
La frontiere refuse-t-elle ce qui n'est pas declare ? Un connect() qui aboutit ne
prouve rien : seule la LIVRAISON compte.
# ─────────────────────────────────────────────────────────────────────────────
- id: locataire-deployer
titre: "Materialiser et deployer la flotte d'un locataire"
portee: tenant
doc: docs/vm-lifecycle.md
but: >-
Des VM au service rendu : creer les machines manquantes, deployer couche par couche,
puis mesurer. Rien n'est « pret » avant la recette.
etapes:
- cible: flotte-creer
nature: ecriture
portee: poste
variables: [PARALLELE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Les VM manquantes, clonees depuis le gabarit dore. VMID, IP et VLAN sont DERIVES
du plan : on ne les saisit nulle part.
- cible: monter-flotte
nature: ecriture
duree: "long"
fixes: {CONFIRMER: "true"}
pourquoi: >-
Des VM qui viennent de naitre (ou de renaitre) a la flotte recettee : flux, AC et
DNS d'abord, tout le reste, puis `valider`. C'est la sequence du runner apres une
reconstruction ; chaque proprietaire d'etat y remet celui d'avant.
- cible: deployer-tout
nature: ecriture
duree: "long"
fixes: {CONFIRMER: "true"}
pourquoi: >-
Toute la flotte, dans l'ordre des couches. L'ordre vient du graphe de
dependances, pas d'une liste tenue a la main.
- cible: deployer-groupe
nature: ecriture
variables: [GROUPE]
facultative: true
pourquoi: >-
Un seul role, sur toute la flotte. C'est le geste d'apres : quand un role a change
et qu'on ne veut pas tout rejouer.
- cible: appliquer
nature: ecriture
variables: [GROUPE]
facultative: true
pourquoi: >-
Appliquer un groupe a la flotte. Meme usage que `deployer-groupe`, par le chemin
court — sans le graphe des couches.
- cible: verifier-deploiement
nature: mesure
pourquoi: "L'etat de la flotte apres coup — avant de croire que c'est fini."
- cible: valider
nature: mesure
pourquoi: >-
La recette de validation sur la flotte. C'est elle qui autorise le mot « pret »,
pas l'absence d'erreur rouge.
# ─────────────────────────────────────────────────────────────────────────────
- id: machine-une
titre: "Ajouter ou reprendre une seule machine"
portee: tenant
doc: docs/vm-lifecycle.md
but: >-
Le cycle d'UNE machine, du plan au service : la voir derivee, la creer, la deployer,
la verifier. Le meme chemin sert pour une machine neuve et pour une machine a
reprendre.
etapes:
- cible: hote-afficher
nature: mesure
variables: [HOTE]
pourquoi: >-
Tout ce que le plan derive pour cette machine — avant de la creer, pour verifier
qu'on va bien poser ce qu'on croit.
- cible: creer-vm
nature: ecriture
portee: poste
variables: [HOTE]
duree: "~4 min 30"
pourquoi: "Cloner depuis le gabarit et ATTENDRE que la machine reponde."
- cible: deployer
nature: ecriture
variables: [HOTE]
pourquoi: "Les roles de cette machine, couche par couche, dans l'ordre du graphe."
- cible: verifier-hote
nature: mesure
variables: [HOTE]
pourquoi: "Le playbook de verification sur cette machine seule."
- cible: cloner-vm
nature: ecriture
portee: poste
variables: [HOTE, VMID]
facultative: true
pourquoi: >-
Cloner SANS passer par le plan. Chemin de reprise : a n'emprunter que lorsque le
plan ne peut pas encore deriver la machine.
# ─────────────────────────────────────────────────────────────────────────────
- id: flotte-refaire
titre: "Raser et reconstruire un ecosysteme"
portee: tenant
doc: docs/vm-lifecycle.md
but: >-
La preuve la plus dure du moteur : detruire un ecosysteme et le refaire depuis le
code seul. A ne lancer que sur un chantier — la prod vit ailleurs.
etapes:
- cible: reconstruire-locataire
nature: destructif
portee: poste
duree: "long"
variables: [TENANT, DEPUIS, ARMER]
fixes: {CONFIRMER: "true"}
pourquoi: >-
TOUT, d'une commande, depuis le poste : sauvegarder, raser et recreer (runner du
site), amorcer, armer, monter (runner du locataire, etat remis), pare-feu, bilan.
Arret a la premiere etape en echec ; `DEPUIS` reprend. Les etapes ci-dessous sont
les memes, une a une.
- cible: sauvegarder-maintenant
nature: ecriture
pourquoi: >-
Deposer l'etat de chaque noeud JUSTE AVANT de raser. La reconstruction remet le
dernier instantane anterieur a la naissance des machines : sans ce depot, c'est
celui de la nuit, et la journee est perdue. Il est etiquete « avant-raser » et
garde jusqu'a la reconstruction suivante : c'est contre lui que jugent les temoins.
- cible: raser
nature: destructif
portee: poste
variables: [INSTANCE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Detruire les VM derivees du plan. Le nom de l'ecosysteme s'ecrit en toutes
lettres : c'est le seul garde-fou qui resiste a un clic distrait.
- cible: reconstruire
nature: ecriture
portee: poste
duree: "long"
fixes: {CONFIRMER: "true"}
pourquoi: >-
Refaire tout depuis zero : les VM, puis le deploiement complet — ou chaque role
proprietaire REMET l'etat de l'incarnation precedente (AC, annuaire, bases,
Nextcloud, courriel, DKIM). Si le code ne suffit pas, c'est ici qu'on l'apprend.
- cible: restauration-etat
nature: mesure
pourquoi: >-
Ce que chaque jeu est devenu : restaure (et depuis quel instantane), neuf, en
place. Un jeu EN ATTENTE bloque la sauvegarde de son noeud — c'est voulu.
- cible: temoins-etat
nature: mesure
variables: [HOTE, SOURCE]
pourquoi: >-
L'etat vivant est-il celui d'avant le rasage ? Une perte, ou une identite changee
(cles de l'AC, DKIM, instanceid de Nextcloud, mot de passe d'une entree de
l'annuaire), est un ecart. Le bilan dit ce que les roles ont fait ; les temoins
mesurent ce qui est revenu.
- cible: restauration-renoncer
nature: destructif
variables: [HOTE, JEU]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Ecarter l'etat d'avant d'un jeu, sans le remettre. La sauvegarde reprend, et
l'ancien etat sortira de la retention : une decision, pas une reparation.
- cible: valider
nature: mesure
pourquoi: "Une reconstruction sans recette n'a rien prouve."
- cible: parefeu-verifier-flotte
nature: mesure
portee: poste
variables: [INSTANCE]
pourquoi: >-
La sonde `connectivite` est-elle saine sur chaque VM ? C'est le prealable : une VM
clonee nait sans ses options de pare-feu, et on n'active que sur une flotte saine.
- cible: parefeu-activer-flotte
nature: ecriture
portee: poste
variables: [INSTANCE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Tout activer d'un coup, puis lire la sonde `connectivite` que chaque VM rapporte a
la minute : seul ce qui change apres l'activation compte. Environ trois minutes.
# ─────────────────────────────────────────────────────────────────────────────
- id: gabarit
titre: "Le gabarit dore"
portee: site
doc: docs/procedure-template-debian13-proxmox.md
but: >-
La VM de reference que toute la flotte clone. Elle nait en q35/OVMF et ne se convertit
jamais : convertir depuis i440fx casse interfaces et disques.
etapes:
- cible: gabarit-etat
nature: mesure
pourquoi: "Le gabarit porte-t-il ce que le SITE declare ? A regarder avant d'y toucher."
- cible: preparer-modele
nature: ecriture
duree: "long"
pourquoi: "Preparer la VM de reference, celle qui sera clonee pour chaque hote."
- cible: verifier-modele
nature: mesure
pourquoi: "Le gabarit tient-il ses promesses avant qu'on le capture ?"
- cible: nettoyer-modele
nature: destructif
fixes: {CONFIRMER: "true"}
pourquoi: >-
Nettoyer avant capture. Destructif pour la VM de reference : ce qui est efface ne
se retrouve pas.
# ─────────────────────────────────────────────────────────────────────────────
- id: mesurer
titre: "Mesurer sans rien ecrire"
portee: tenant
doc: docs/devis-services.md
but: >-
Les devis de service : ils confrontent ce que le plan derive a ce que le systeme rend
VRAIMENT, et sortent en erreur s'il y a un ecart. Une tache verte ne prouve pas qu'un
service rend son service.
etapes:
- cible: identite-plan
nature: mesure
pourquoi: "L'identite deployee face a ce que le plan derive."
- cible: certificats-plan
nature: mesure
pourquoi: >-
Les certificats sur disque face a ceux reellement SERVIS. Un cert renouvele mais
non recharge reste perime en memoire.
- cible: expositions-plan
nature: mesure
pourquoi: "Chaque exposition du plan repond-elle, depuis l'edge et depuis le poste ?"
- cible: expositions-etat
nature: mesure
pourquoi: "Chaque exposition est-elle servie sous un certificat qui la porte ?"
- cible: courriel-plan
nature: mesure
pourquoi: "La chaine Postfix → LDAP → Dovecot → IMAP, de bout en bout."
- cible: postgresql-plan
nature: mesure
pourquoi: "Le chiffrement impose et la portee reelle des acces."
- cible: mtu-mesurer
nature: mesure
pourquoi: "L'invite porte-t-il le MTU de sa zone ? Un MTU faux ne se voit qu'aux gros paquets."
- cible: versions-mesurer
nature: mesure
pourquoi: "De combien nos epinglages ont-ils vieilli face aux amonts ?"
- cible: ports-verifier
nature: mesure
pourquoi: "Deux roles co-localises revendiquent-ils le meme port ?"
- cible: intrants-verifier
nature: mesure
pourquoi: "Un intrant exige et non fourni est une panne differee."
- cible: flux-verifier
nature: mesure
pourquoi: "Le registre des flux correspond-il encore aux `meta/flux.yml` des roles ?"
- cible: site-intrants-verifier
nature: mesure
pourquoi: "Le locataire monte suit-il encore les intrants de son site ?"
- cible: sonder
nature: mesure
variables: [CIBLE]
pourquoi: >-
Sonder une cible et DIRE ce qui distingue absence, politique et frontiere. Un
« echec » nu ecrase ces trois causes et envoie chercher la panne ailleurs.
- cible: faits
nature: mesure
variables: [LIMITE]
pourquoi: "Les faits Ansible de la flotte, quand une hypothese demande un fait."
# ─────────────────────────────────────────────────────────────────────────────
- id: recette
titre: "La recette — avant de dire « pret »"
portee: tenant
doc: docs/audit/plan-de-recette.md
but: >-
Ce qui separe « ca a tourne sans erreur » de « c'est livrable ». Les preuves lisent le
depot ; la recette interroge la flotte. Il faut les deux.
etapes:
- cible: verifier
nature: mesure
pourquoi: "Rejouer les preuves sans reecrire le rapport — la verification rapide."
- cible: prouver
nature: mesure
pourquoi: >-
Executer les preuves et ECRIRE le rapport date. C'est le document qu'on montre,
et celui qu'on relit dans six mois.
- cible: valider
nature: mesure
pourquoi: "La recette de validation sur la flotte reelle."
- cible: verifier-deploiement
nature: mesure
pourquoi: "L'etat de la flotte, apres coup."
- cible: inventaire
nature: mesure
pourquoi: "L'inventaire se verifie et se montre — lab puis production."
# ─────────────────────────────────────────────────────────────────────────────
- id: acces-admin
titre: "L'acces d'administration (tunnel WireGuard)"
portee: site
doc: docs/acces-administration.md
but: >-
Un tunnel nominatif par locataire, declare par lui et borne a lui. Sans garde, un
ecosysteme s'ouvrirait un acces chez son voisin depuis son propre plan.
etapes:
- cible: vpn-admin-plan
nature: mesure
pourquoi: "Ce que la frontiere porte aujourd'hui, face aux pairs declares au plan."
- cible: vpn-admin-appliquer
nature: ecriture
fixes: {CONFIRMER: "true"}
pourquoi: "Poser l'instance et les pairs declares — et retirer ceux qui ne le sont plus."
# ─────────────────────────────────────────────────────────────────────────────
- id: dns-public
titre: "Le DNS public et sa signature"
portee: tenant
doc: docs/dns-interne.md
but: >-
Publier les zones du locataire, signees avant d'etre exposees. Les DS partent chez le
registraire : c'est le seul maillon que le moteur ne peut pas poser lui-meme.
etapes:
- cible: dnssec-verifier
nature: mesure
pourquoi: "Une cle en voute pour chaque zone signee, et des DS conformes au registre."
- cible: dnssec-ds
nature: mesure
pourquoi: >-
Les DS a remettre au registraire, calcules depuis la voute du locataire. A porter
a la main chez le registraire : aucun automate ne le fera.
- cible: dns-bascule-devis
nature: mesure
pourquoi: >-
Basculer nos serveurs de noms changerait-il quelque chose ? Le plan face au DNS
reellement en service, avant de toucher a la delegation.
# ─────────────────────────────────────────────────────────────────────────────
- id: filiation
titre: "Filiation, insemination, emancipation"
portee: tenant
doc: docs/filiation-emancipation.md
but: >-
D'ou vient cet ecosysteme, de quoi depend-il encore, et que faut-il couper pour qu'il
tienne seul. L'emancipation se mesure ; elle ne se decrete pas.
etapes:
- cible: genome
nature: mesure
pourquoi: "Les depots requis a la reproduction de cet ecosysteme, et leur etat."
- cible: genome-inscrire
nature: ecriture
pourquoi: "Inscrire la parente dans l'instance — sans quoi la filiation n'est qu'un souvenir."
- cible: genome-verifier
nature: mesure
pourquoi: "La parente inscrite tient-elle encore ? Le code de sortie repond."
- cible: inseminer
nature: ecriture
variables: [TENANT, HOTE]
pourquoi: >-
Le SITE amorce le runner d'un locataire, SANS ses secrets. Trois murs connus :
apt, pip, et le genome lui-meme.
- cible: depots-perimes
nature: destructif
fixes: {CONFIRMER: "true"}
# FACULTATIVE, SINON ELLE BARRE LA PREUVE QUI LA SUIT. La console
# debloque d'office une etape « mesure » ; toute autre attend que la
# precedente non facultative ait REUSSI dans la session. Un menage
# qu'on peut ne pas avoir a faire — aucun depot perime ce jour-la —
# rendrait alors l'emancipation injouable. Le ménage n'est pas un
# prealable a la preuve : il nettoie ce que la filiation a laisse.
facultative: true
pourquoi: >-
Un depot raye du plan reste sur le disque du runner, qui garde de quoi lire un
ecosysteme qu'il ne declare plus. Trois choses ne sont jamais retirees : ce qui
n'est pas un depot git, ce qui porte des modifications non validees, et ce qui
porte des commits qu'aucun distant ne porte.
- cible: emancipation-prouver
# ECRITURE, ET NON MESURE, MALGRE LE MOT « PROUVER ». La cible COUPE
# l'amont quelques secondes pour mesurer : elle refuse d'ailleurs sans
# CONFIRMER=true, et le dit. Le fait qu'elle rapporte un constat ne la
# rend pas inerte. Declarée « mesure », elle se serait offerte a tout
# outil qui ne propose que ce qui n'agit pas.
nature: ecriture
variables: [SERVICE, HOTE]
fixes: {CONFIRMER: "true"}
pourquoi: >-
Prouver qu'un lien est coupe, en le coupant. La coupure est retiree quoi
qu'il arrive, mais la fonction eprouvee peut echouer pendant ce temps.
La mesure instruit ; l'humain decide. Une emancipation automatique serait
une expulsion.
# ─────────────────────────────────────────────────────────────────────────────
- id: remise
titre: "La remise au client"
portee: poste
doc: docs/remise-au-client.md
but: >-
Deux temps qui ne se confondent pas : le paquet qu'on remet, puis le re-cle qui
mesure la revocation reelle. Tant que le temps 2 n'est pas fait, on detient encore
les cles de quelqu'un d'autre.
etapes:
- cible: remise-recenser
nature: mesure
pourquoi: "Ce qu'une remise emporterait, sans rien ecrire. A lire avant de fabriquer."
- cible: remise-paquet
nature: ecriture
variables: [VERS]
pourquoi: "Temps 1 : le paquet chiffre, et relu apres ecriture."
- cible: remise-inscrire
nature: ecriture
variables: [RECU_PAR, COURRIEL, DANS]
pourquoi: >-
Inscrire la remise chez le locataire : qui a recu, quand, et dans combien de
jours le second temps est du.
- cible: remise-verifier
nature: mesure
pourquoi: "Temps 1 fait ? Temps 2 du, ou echu ? La seule reponse qui compte est datee."
- cible: remise-recleer
nature: ecriture
fixes: {CONFIRMER: "true"}
pourquoi: >-
Temps 2 : mesurer la revocation REELLE, puis estampiller. On ne coche pas cette
case, on la mesure.
# ─────────────────────────────────────────────────────────────────────────────
- id: cles
titre: "Les cles hors du poste"
portee: poste
doc: docs/sortir-les-cles-du-poste.md
but: >-
Ce qui n'existe QUE sur le poste meurt avec lui : les CLES, et les VOUTES qu'elles
ouvrent (gitignorees, donc sur aucune forge). Les deux sortent, sur un support qu'on
relit — et on refait l'operation a chaque voute nouvelle ou modifiee.
etapes:
- cible: cles-recenser
nature: mesure
pourquoi: "Ce qui n'existe que sur ce poste, sans rien ecrire. La liste fait peur, c'est le but."
- cible: cles-exporter
nature: ecriture
variables: [VERS]
pourquoi: "Sortir les cles, chiffrees, et les RELIRE apres ecriture."
- cible: voutes-recenser
nature: mesure
pourquoi: "Les voutes de ce poste, et leur empreinte. Une voute en clair est refusee avant tout."
- cible: voutes-exporter
nature: ecriture
variables: [VERS]
pourquoi: >-
Sortir les voutes, deja chiffrees, dans leur propre archive, et la RELIRE. Sans
elles, les cles restaurees n'ouvrent rien (2026-09-28).
- cible: cles-compagnons
nature: ecriture
variables: [VERS]
pourquoi: >-
Deposer le script de restauration sur la cle. Une archive qu'on ne sait pas
rouvrir dans cinq ans n'est pas une sauvegarde.
- cible: cles-restaurer
nature: ecriture
variables: [ARCHIVE]
pourquoi: "Remettre les cles en place. A eprouver AVANT d'en avoir besoin."
- cible: depot-hors-site
nature: ecriture
variables: [VERS]
pourquoi: >-
Copier le depot de sauvegarde HORS du site. Le site heberge du chiffre et ne peut
pas le juger : la verification suit la cle.
# ─────────────────────────────────────────────────────────────────────────────
- id: lire-le-plan
titre: "Lire le plan et ce qu'il derive"
portee: tenant
doc: docs/plan-et-generation.md
but: >-
Tout ce qui se regarde sans rien changer : les registres du plan, leur validation, et
l'inventaire qui en descend. Le premier geste devant un ecosysteme qu'on ne connait pas.
etapes:
- cible: serveurs
nature: mesure
pourquoi: "Les serveurs declares au plan."
- cible: serveurs-verifier
nature: mesure
pourquoi: "Le registre des serveurs tient-il ses regles ?"
- cible: applications
nature: mesure
pourquoi: "Les applications declarees au plan."
- cible: applications-verifier
nature: mesure
pourquoi: "Le registre des applications tient-il ses regles ?"
- cible: bases
nature: mesure
pourquoi: "Les bases de donnees declarees au plan."
- cible: bases-verifier
nature: mesure
pourquoi: "Le registre des bases tient-il ses regles ?"
- cible: domaines
nature: mesure
pourquoi: "Les domaines declares au plan."
- cible: domaines-verifier
nature: mesure
pourquoi: "Le registre des domaines tient-il ses regles ?"
- cible: inventaire-lister
nature: mesure
pourquoi: "L'inventaire complet, en JSON — la verite generee."
- cible: inventaire-graphe
nature: mesure
pourquoi: "Le graphe des groupes : qui herite de quoi."
- cible: inventaire-hote
nature: mesure
variables: [HOTE]
pourquoi: "Les variables derivees d'une machine, telles qu'Ansible les verra."
- cible: inventaire-lab
nature: mesure
pourquoi: "Le graphe de l'inventaire de laboratoire."
- cible: inventaire-production
nature: mesure
pourquoi: "Le graphe de l'inventaire de production."
- cible: config
nature: mesure
pourquoi: "La configuration Proxmox telle que le moteur la lit."
# ─────────────────────────────────────────────────────────────────────────────
- id: depot-verifier
titre: "Verifier le depot avant de livrer"
portee: toute
doc: AGENTS.md
but: >-
Ce que le depot se doit a lui-meme : syntaxe, lint, tests, schema, fiches. Rien n'est
« pret » sans au moins la syntaxe du playbook touche et l'entree de CHANGELOG.
etapes:
- cible: syntaxe
nature: mesure
pourquoi: "La syntaxe de TOUS les playbooks. Jamais declarer pret si elle echoue."
- cible: lint
nature: mesure
pourquoi: "`ansible-lint` sur tout le depot."
- cible: test
nature: mesure
pourquoi: "Les tests unitaires de derivation — nomenclature et inventaire."
- cible: ci
nature: mesure
pourquoi: >-
Verifier le depot comme la CI, sur un modele public monte a l'ecart : sans jamais
toucher a l'ecosysteme monte.
- cible: schema
nature: ecriture
pourquoi: >-
Regenerer le schema du plan depuis les registres et les validateurs. C'est lui qui
construit les formulaires : un champ nouveau apparait sans toucher a l'interface.
- cible: fiches
nature: ecriture
variables: [ROLE]
pourquoi: "Regenerer la fiche de chaque role, depuis le role lui-meme."
- cible: plan-recette
nature: ecriture
pourquoi: "Regenerer le plan de recette depuis le wiki."
- cible: wiki-publier
nature: ecriture
pourquoi: "Publier le wiki vers la forge, une fois qu'il dit vrai."
# CE QUE LA CONSOLE N'OFFRE PAS, ET POURQUOI. Une exemption muette serait un oubli
# deguise : chaque ligne porte son motif, et la garde refuse une exemption vide.
hors_assistant:
aide: >-
Aide en ligne de commande. La console porte la meme information autrement — chaque
etape affiche le libelle lu dans le Makefile.
ansible-runtime: >-
Prerequis interne, appele par les cibles qui deploient. L'offrir seul donnerait un
bouton qui ne fait rien de visible.
inventaire-ui: >-
C'est cette console elle-meme. Un bouton qui la relance depuis elle-meme n'a pas d'objet.
syntaxe-modele: "Couverte par `syntaxe`, qui passe tous les playbooks d'un coup."
syntaxe-verification-modele: "Couverte par `syntaxe`."
syntaxe-nettoyage: "Couverte par `syntaxe`."
syntaxe-verification-hote: "Couverte par `syntaxe`."
syntaxe-groupes: "Couverte par `syntaxe`."
syntaxe-proxmox: "Couverte par `syntaxe`."
model-creer: >-
Fabrique un MODELE d'ecosysteme — un geste de mainteneur du moteur, pas d'exploitant.
Il se fait au poste, en connaissance du catalogue des modeles.
serveurs-bootstrap: >-
Chemin de REPRISE : reconstitue le plan depuis un inventaire existant. Il ecrit par
dessus le plan, et ne doit pas etre a un clic d'un exploitant qui explore.
applications-bootstrap: >-
Meme raison que `serveurs-bootstrap` : amorcage de reprise, pas geste courant.
cacher-paquets: >-
Se fait EN LIGNE depuis le poste, avant de partir sur un site hors ligne. Le runner,
lui, est deja derriere la frontiere : le bouton serait au mauvais endroit.
ca-racine: >-
Recupere la racine de l'AC pour l'installer sur un poste. L'empreinte doit etre
verifiee A LA MAIN avant installation — un bouton encouragerait a sauter ce controle.
ca-empreinte: >-
Le temoin de comparaison de `ca-racine`. Meme raison : il se lit, il ne se clique pas.

View file

@ -20,10 +20,10 @@ l'edge) **n'a pas été rechargé** → il sert l'ancien cert en mémoire.
**Diagnostic-réflexe** — comparer le cert *servi* au cert *fichier* :
```bash
# SERVI (en mémoire par nginx)
echo | openssl s_client -connect infra-edge-01…:443 -servername keycloak.lab… 2>/dev/null \
echo | openssl s_client -connect infra-edge-01.chezlepro.internal:443 -servername keycloak.chezlepro.internal 2>/dev/null \
| openssl x509 -noout -enddate
# FICHIER (sur disque)
openssl x509 -in /etc/step/certs/infra-edge-01….crt -noout -enddate
openssl x509 -in /etc/step/certs/infra-edge-01.chezlepro.internal.crt -noout -enddate
```
Dates différentes (servi < fichier) ⇒ nginx sert un cert périmé.
@ -50,8 +50,15 @@ Par défaut, un utilisateur SSO est **Viewer** (dashboards seulement, pas Explor
3. L'utilisateur doit **se déconnecter/reconnecter** (Grafana applique le rôle à la connexion).
Mapping (défaut du rôle grafana) : `grafana-admin`→Admin, `grafana-editor`→Editor, sinon Viewer
(`serveur_grafana_oidc_role_path`). Idéal souverain : piloter par un **groupe d'annuaire** plutôt
qu'un utilisateur explicite. Voir l'unité wiki *Autorisation & RBAC*.
(`serveur_grafana_oidc_role_path`).
> **Préférer le groupe à la personne — et c'est construit, pas un idéal.** Les groupes LDAP
> sont projetés dans Keycloak et émis en claim (`tasks/groupes-ldap.yml`, `claim-groupes.yml`),
> et `roles/serveur_grafana/meta/acces.yml` déclare déjà `sysadmin ⇒ Admin`,
> `personnel ⇒ Viewer`. Ajouter quelqu'un au **groupe** lui ouvre Grafana, Forgejo, Icinga et
> le courriel d'un seul geste — alors qu'une assignation nominative crée une dette qu'on
> découvre le jour du départ, service par service. L'assignation explicite ci-dessus reste
> le geste de dépannage, pas la façon normale d'accorder un accès. Voir `docs/autorisation.md` §5.
---
@ -105,6 +112,64 @@ Note : ne s'applique qu'aux Forgejo **gérées par Set-OPS**.
## 5. Restaurer — et d'abord : prouver qu'on peut
### 5.0 La reconstruction remet l'état (depuis le 2026-09-30)
Jusqu'au 2026-09-30, **une reconstruction repartait d'un état neuf** : nouvelle racine
d'AC, annuaire vierge (`sysadmin` au mot de passe d'amorçage), bases, Nextcloud et courriel
vides. Les instantanés se restauraient pour *prouver* qu'ils s'ouvrent, jamais pour être
remis en service.
Désormais **chaque rôle propriétaire remet l'état de l'incarnation précédente**, au moment
où il le créerait neuf — la bonne séquence vient des couches de déploiement :
| Rôle | Quand | Condition « vierge » |
|---|---|---|
| `serveur_step_ca` | avant `step ca init` | pas de `ca.json` — et la voûte doit ouvrir les clés restaurées |
| `serveur_postgresql` | juste après la création des bases | base sans table (hors `nextcloud`, `icinga`) |
| `serveur_openldap` | en fin de rôle (schéma et `ppolicy` chargés) | aucune entrée hors la racine |
| `serveur_dovecot` | après le répertoire des boîtes | répertoire vide |
| `serveur_rspamd` | avant la génération DKIM | pas de clé DKIM |
| `serveur_forgejo` | avant l'arborescence de données | répertoire vide |
| `serveur_nextcloud` | **en fin de rôle** : base + fichiers + config ensemble, puis `occ upgrade` | pas de `config.php` au départ, base vide |
| `serveur_web_dorsal` | après sa racine | racine vide |
**L'instantané remis** est le dernier pris **avant la naissance de la machine** (date de
sa clé d'hôte SSH). Un état en place n'est jamais écrasé d'office. Chaque jeu laisse un
marqueur dans `/etc/setops/restauration/<jeu>` ; ce qui a été remplacé est mis de côté dans
`/var/backups/setops-avant-restauration/`.
**La sauvegarde refuse de déposer** (code 3) tant qu'un état d'avant n'a été ni remis ni
écarté : sinon `restic forget --keep-daily` chasserait l'instantané d'avant au profit de
l'état neuf du même jour.
**L'instantané d'avant rasage est étiqueté** `avant-raser` (depuis le 2026-10-07) et la
rétention le garde **jusqu'à la reconstruction suivante**, qui lui retire l'étiquette. Sans
elle, le premier dépôt de la machine reconstruite le chassait le jour même : après la
reconstruction de Technolibre (M4), il ne restait rien à quoi comparer l'état remis.
**Les témoins** comparent l'état vivant à cet instantané. Un écart, c'est une **perte**
(fichier, courriel, entrée de l'annuaire, base, rôle, table, ligne d'historique, ou une
base dont plus aucune ligne d'avant ne subsiste) ou une **identité changée** : clés et
certificats de l'AC, AC d'Icinga et environnement d'Icinga DB, clé DKIM, `instanceid` de
Nextcloud, mot de passe d'une entrée de l'annuaire. PostgreSQL se compare par clé (première
colonne de chaque table), pas par nombre de lignes. Le reste (un courriel reçu depuis, une table de sessions, le bayes de rspamd,
un cache) est listé sans être un écart. La reconstruction les lance à son étape `bilan`.
Les gestes :
```
make sauvegarder-maintenant # JUSTE AVANT de raser : étiqueté « avant-raser »
make restauration-etat [HOTE=...] # ce que chaque jeu est devenu, et si son instantané est encore au dépôt
make temoins-etat [HOTE=...] # l'état vivant contre l'instantané d'avant rasage
make temoins-etat SOURCE=candidat # faute d'étiquette : contre le dernier d'avant la naissance
make restauration-renoncer HOTE=.. JEU=.. CONFIRMER=true # écarter un état, sans le remettre
-e client_backup_restauration_instantane=<id> # imposer un instantané, par hôte
```
Sur un nœud, sans Ansible : `setops-restaurer etat`, `setops-restaurer temoins`, et les répétitions qui ne touchent à
rien — `setops-restaurer annuaire --essai <base_dn>`, `base <nom> --vers epreuve`,
`fichiers --vers /var/tmp/epreuve <chemin>`.
> Éprouvé le 2026-08-12 sur Chezlepro. `make valider` rejoue la partie automatisable ;
> la restauration d'une base reste manuelle, et porte un piège décrit plus bas.
@ -136,10 +201,11 @@ la base badger de step-ca, qui avance à **chaque** émission de certificat.
`pg_dumpall` écrit `CREATE DATABASE <suivante>` **avant** le `\connect` correspondant.
Découper « du `\connect X` au `\connect` suivant » emporte donc un ordre qui vise une
**autre** base. Couper aussi sur `CREATE DATABASE`, et **vérifier avant de rejouer** :
**autre** base. Couper aussi sur `CREATE DATABASE` et sur `DROP DATABASE` (avec `--clean`,
la section de `nextcloud` finissait par `DROP DATABASE postgres;` — 2026-09-30), et **vérifier avant de rejouer** :
```
awk '/^\\connect forgejo$/{f=1;next} f && (/^\\connect /||/^CREATE DATABASE /){exit} f' \
awk '/^\\connect forgejo$/{f=1;next} f && (/^\\connect /||/^CREATE DATABASE /||/^DROP DATABASE /){exit} f' \
toutes-bases.sql > section.sql
grep -qE '^(DROP|CREATE|ALTER) DATABASE|^\\connect' section.sql \
@ -157,49 +223,286 @@ Mesuré sur `forgejo` : rejeu en **0 erreur**, **130 tables**, et les comptes r
**Ne jamais rejouer un `pg_dumpall` entier sur un cluster vivant** : il contient les
`DROP DATABASE` de toutes les bases. Une restauration réelle se fait sur un cluster neuf.
## 6. La frontière porte deux adresses de gestion (transition D-77)
## 6. La bascule d'adressage de la fabric (D-77) — FAITE
> Posée le 2026-08-12 par l'API (`interfaces/vip_settings`, mode `ipalias`). **Ce n'est
> pas une anomalie** : c'est une bascule d'adressage en cours.
> **Cette section décrivait une transition en cours jusqu'au 2026-09-06.** Elle est
> terminée : mesuré le 2026-08-22, **`10.0.0.0/24` n'existe plus** — ni `10.0.0.1`, ni
> `10.0.0.41` ne répondent. Le plan d'administration est `10.17.0.0/24` : la frontière y
> répond en `10.17.0.1` sur un port physique à elle, les commutateurs sont en `10.17.0.3`
> et `.4` avec cette passerelle par défaut, le poste de l'exploitant en `10.17.0.17`. Le
> renumérotage du tenant (`10.27` → `10.17`) est fait lui aussi.
>
> On garde la **méthode**, parce qu'elle est ce qui a permis de le faire sans coupure, et
> qu'un autre site la rejouera :
>
> **Ajouter avant de retirer, jamais l'inverse.** Un point de routage qui change d'adresse
> d'un coup coupe simultanément l'exploitant, les commutateurs qui l'ont en passerelle par
> défaut, et l'outil qui devait faire la bascule. La seconde adresse a donc vécu à côté de
> l'ancienne (`ipalias`), et l'ancienne n'est tombée qu'en **dernier**.
>
> **Distinguer une destination d'un chemin (D-78).** Un réseau qui n'est **jamais** une
> destination — seulement un chemin — n'a aucune raison d'être unique entre deux hébergeurs :
> il sort de l'espace dérivé, vers `192.168.<vlan>.0/24`. Seule la **gestion** doit rester
> unique d'un site à l'autre, parce que le poste de l'exploitant, un VPN et demain un lien
> inter-sites doivent l'atteindre.
>
> **Appliqué pour le transport VXLAN seulement, à ce jour** (mesuré le 2026-09-06) :
> `underlay-vxlan` est bien en `192.168.50.0/24`. Le **transit** (`10.0.4.0/24`, VLAN 40) et
> le **stockage** (`10.11.5-7.x`, VLAN 5/6/7) sont encore dans l'ancien espace. Ce n'est pas
> une urgence — ces réseaux ne quittent jamais leur site — mais la carte doit dire ce qui est,
> pas ce qui a été décidé. Un **site neuf** se monte directement au schéma final : il n'a
> aucune transition à subir.
>
> **Un plan d'adressage ne doit pas dépendre de l'ordre d'une migration.** Le VLAN de
> transport est passé de 11 à 50 — non parce que 11 était mauvais, mais parce que
> `192.168.11.0/24` est occupé par le contrôle de la grappe. Faire dépendre un plan
> d'adressage de l'**ordre** d'une migration est exactement la dette qui se paie un an
> plus tard.
### Ce qui reste, et qui n'est PAS un reliquat de la bascule
Les hyperviseurs gardent **deux** plans, et c'est voulu :
| Plan | Réseau | Ce qu'il porte |
|---|---|---|
| administration | `10.17.0.0/24` (`vmbr3`, segment physique) | les **équipements** et l'exploitant ; **aucune VM ne peut y naître** (aucun pont ne le touche, et le validateur refuse qu'on y déclare une machine) |
| contrôle de la grappe | `192.168.11.0/24` (`vmbr0`, carte dédiée) | l'**interface web Proxmox** et le dialogue entre nœuds — c'est par là qu'on atteint `ansible@192.168.11.4x` |
> **Le `/24` de gestion vit à l'intérieur du `/16` du tenant, et ce n'est pas un conflit.**
> Les zones d'un tenant commencent au 3ᵉ octet 16 ; la bande 0-15 est libre pour la fabric,
> et la route connectée du `/24` est plus spécifique que celle du `/16` — la règle du
> préfixe le plus long, pas une coïncidence. Il faut cependant le **déclarer**
> (`bande_basse_de:` dans `underlay.yml`), sinon le validateur ne peut pas distinguer ce
> chevauchement voulu d'un chevauchement accidentel.
## 7. Le nœud qui porte le gabarit tombe — la reproduction s'arrête
**Symptôme.** Aucune VM nouvelle ne peut naître. `make creer-vm`, `make flotte-creer` et
`make reconstruire` échouent au clonage. Les machines existantes, elles, ne bronchent pas.
**Ce qui se passe.** Le gabarit doré vit sur un nœud nommé — `vishnu` chez l'hébergeur de
référence — et sa configuration porte ce nom dans son chemin même :
```
10.0.0.1/24 adresse historique — TOUJOURS ACTIVE, rien ne l'a quittée
10.17.0.1/24 adresse cible (D-77 : underlay dans la bande basse du /16 du site)
/etc/pve/nodes/vishnu/qemu-server/9006.conf
```
**Ajouter avant de retirer**, jamais l'inverse. Un point de routage qui change d'adresse
d'un coup coupe simultanément l'exploitant, les commutateurs qui l'ont en passerelle par
défaut, et l'outil qui devait faire la bascule.
Le clonage appelle `nodes/vishnu/qemu/9006/clone` : c'est l'API de **ce nœud-là** qui doit
répondre. Nœud éteint, API muette, aucune naissance.
### Ce qui reste à déplacer, et dans quel ordre
**Ce qui ne se passe PAS, et qu'il faut savoir avant de paniquer.** Les données du gabarit
ne sont pas perdues. Mesure du 2026-09-10 :
| # | À déplacer | Vers | Nature (D-78) |
|---|---|---|---|
| 1 | le poste de l'exploitant | `10.17.0.x/24` (seconde adresse) | — |
| 2 | les commutateurs `10.0.0.3/.4` **et leur `ip default-gateway`** | `10.17.0.3/.4`, passerelle `10.17.0.1` | destination |
| 3 | l'administration des hyperviseurs (`vmbr0`, aujourd'hui `192.168.11.x`), l'OOB/IPMI | `10.17.0.41/.43/.47` | destination |
| 4 | le transit `10.0.4.x` | **`192.168.40.x`** | chemin |
| 5 | le transport VXLAN `10.0.5.x`, **VLAN 11 → 50** | **`192.168.50.x`** | chemin |
| 6 | le stockage `10.0.1–3.x` | **`192.168.20/30/31.x`** | chemin |
| 7 | **en dernier seulement**, retirer `10.0.0.1` | — | — |
```
pool CephNVMe size=3 min_size=2
OSD sur les trois hôtes : asgard, gandalf, vishnu
base-9006-disk-0, base-9006-disk-1 présentes dans le pool
```
Les étapes 4 à 6 sortent définitivement de l'espace dérivé (D-78) : ces réseaux ne sont
jamais des destinations, seulement des chemins. Une fois faites, **seule la gestion** doit
rester unique d'un site à l'autre.
L'image est répliquée trois fois et reste lisible avec un nœud en moins. Et les quatorze VM
d'un écosystème tournent ailleurs, sur leurs propres disques Ceph : elles ne s'aperçoivent
de rien.
> **L'étape 5 change aussi le numéro de VLAN**, côté commutateur (trunk) et côté
> hyperviseurs (`bond3.11` → `bond3.50`). Le 11 est écarté parce que `192.168.11.0/24` est
> occupé par l'étape 3 tant qu'elle n'est pas faite — et un plan d'adressage ne doit pas
> dépendre de l'ordre d'une migration.
> **`vishnu` n'est pas un point unique de défaillance pour l'exploitation. Il l'est pour la
> reproduction** — et la reproduction est ce que ce dépôt existe pour garantir.
### Ce qui ne dépend PAS de cette bascule
**La manœuvre.** Deux gestes, quelques minutes, aucun mouvement de données :
**Le renumérotage du tenant** (`10.27` → `10.17`) est **indépendant**. La frontière route
le supernet du tenant vers le même prochain saut, quelle que soit sa propre adresse de
gestion : il suffit que la route et les alias suivent. Les deux chantiers peuvent donc
être menés séparément — et c'est préférable.
```bash
# 1. re-héberger la configuration sur un nœud debout
mv /etc/pve/nodes/vishnu/qemu-server/9006.conf \
/etc/pve/nodes/asgard/qemu-server/9006.conf
> **Longueur de préfixe.** Une fois les deux faits, la gestion (`10.17.0.0/24`) vit
> *à l'intérieur* du supernet du tenant (`10.17.0.0/16`). Aucun conflit : la route
> connectée du `/24` est plus spécifique que celle du `/16`. C'est la règle du préfixe le
> plus long, pas une coïncidence.
# 2. le déclarer, sinon la garde refusera
# SITE-Chezlepro/plan/10-intrants.yml : gabarit.noeud: asgard
```
Puis vérifier :
```bash
make gabarit-etat # doit rendre « Conforme »
```
**Pourquoi c'est aussi court.** Parce que le disque du gabarit vit sur un stockage
**partagé** depuis le 2026-09-10 : le déplacement ne bouge qu'un fichier de configuration.
Sur un stockage local, il aurait fallu recopier 16 Go — ou refabriquer le gabarit.
**L'ordre compte.** Déplacer sans déclarer laisse `gabarit_etat` en écart ; déclarer sans
déplacer fait échouer le clonage sur un nœud qui ne détient pas le modèle. Faire les deux,
dans cet ordre.
**Ce que cette section ne fait pas.** Elle ne supprime pas la dépendance : elle la rend
connue et courte. Deux remèdes de fond existent, aucun n'est appliqué :
- **déplacer le gabarit là où vivent déjà les VM** — ça ne supprime pas le point unique,
ça cesse d'en avoir *deux* (le nœud des VM et celui du modèle) ;
- **une garde** qui refuse quand le nœud du gabarit n'héberge aucune machine de la flotte,
c'est-à-dire quand la reproduction dépend d'un nœud qui ne porte rien d'autre.
*Une dépendance qu'on documente sans la mesurer reste une dépendance qu'on découvrira au
mauvais moment.*
## 8. Les contrôles à la demande — ce que `make prouver` ne peut pas voir
`make prouver` est **statique** : il lit le dépôt, zéro appel réseau. C'est ce qui le rend
rejouable partout, par n'importe qui, et présentable comme pièce justificative. Le prix de
cette propriété : il ne peut rien dire de ce qui ne s'observe qu'en ouvrant une connexion.
Quatre contrôles comblent ce creux. Aucun ne corrige quoi que ce soit — ils regardent, et
rendent `0` si tout concorde.
| Contrôle | Ce qu'il compare | Le piège qu'il attrape |
| --- | --- | --- |
| `make expositions-etat` | Expositions du plan ↔ **SAN du certificat servi** ↔ code du vhost | Un renommage déployé partout **sauf** dans le certificat |
| `make gabarit-etat` | Gabarit déclaré ↔ VM modèle réelle | Le modèle a dérivé de ce que le plan promet |
| `make routes-fabric-etat` | Zones déclarées ↔ routes déclarées ↔ routes vivantes | Une route **vivante mais non déclarée** — elle part au redémarrage |
| `make frontiere-plan` | Registre des flux ↔ règles de la frontière | Une règle posée à la main, qu'aucune déclaration ne porte |
Ajouter `SITE=1` à `expositions-etat` pour interroger l'écosystème du SITE plutôt que
l'instance montée.
### Pourquoi `expositions-etat` existe
Renommer une exposition touche cinq choses. Quatre suivent au déploiement ; la cinquième,
non :
```
serveur_powerdns la zone publie le nouveau nom ✓
serveur_keycloak le client OIDC accepte le retour ✓
le service il fabrique ses URL avec le bon nom ✓ (P67)
serveur_nginx le vhost répond sur le nouveau nom ✓
client_pki le SAN du certificat porte le nom ✗ il faut le rejouer
```
Le symptôme est trompeur : le site répond, la page s'affiche, et c'est le **navigateur**
qui refuse — avec une erreur de certificat que personne ne relie à un renommage fait la
veille. Mesuré deux fois le 2026-09-10, sur `grafana → observatoire` puis `icinga → vigie`.
Le contrôle regarde **dans les deux sens**. Un nom resté dans le SAN après avoir quitté le
plan est un nom que le certificat continue d'authentifier : c'est exactement ce qu'avait
laissé le premier renommage, jusqu'au passage de `client_pki`.
> Un `INJOIGNABLE` ne condamne pas le service : il dit que **ce poste** n'a pas pu ouvrir
> la connexion. Les zones du SITE ne sont pas routées depuis le plan d'administration du
> locataire — le mur est la frontière, pas le vhost.
## 9. Le premier jour d'un site — la séquence, et les dix-huit murs
> **Écrit le 2026-09-12**, au sortir de la première reconstruction d'un site depuis zéro.
> Avant elle, `SITE-Chezlepro` n'avait jamais été rasé : il avait été monté par ajouts
> successifs, sur des semaines, avec un service déjà debout à chaque étape.
>
> La limite qu'on répétait — « l'infrastructure d'accueil n'a jamais été reconstruite
> depuis zéro » — se lisait comme de la prudence. C'était **dix-huit défauts** que rien
> d'autre n'aurait pu révéler.
>
> **Trois reconstructions complètes** ont été nécessaires : la première pour les trouver,
> la deuxième pour vérifier les correctifs — elle en a révélé deux de plus, invisibles
> tant que l'état n'était pas assez neuf — et la troisième pour prouver la séquence.
>
> | | Passages | Durée | Défauts trouvés |
> |---|---|---|---|
> | Tour 1 | 8 | ~2 h 30 | 16 |
> | Tour 2 | 3 | 53 min | 2 |
> | Tour 3 | **2** | **37 min 24** | **0** |
>
> La séquence ci-dessous est celle du troisième tour. Elle est mesurée, pas reconstituée.
### Ce qui rend un site différent d'un locataire
Un locataire naît dans un monde déjà peuplé : le site lui fournit les paquets, les noms,
le génome, l'heure et le dépôt de sauvegarde. **Un site n'a personne au-dessus de lui**,
sauf sa frontière. Tout ce qu'un locataire reçoit, un site doit se le donner — et pendant
qu'il se le donne, il ne l'a pas.
C'est de là que viennent onze des quinze murs.
### La séquence, dans l'ordre
```bash
# 0. AVANT TOUT — l'état sort du bâtiment
make depot-hors-site VERS=<support hors site>
# 1. Le résolveur d'amorçage, DÉCLARÉ dans le plan du site AVANT de créer quoi que ce soit
# plan/10-intrants.yml : dns_amorcage: <patte de la frontière>
# Sans lui, les machines pointent sur le DNS du site — qui est l'une d'elles.
# Et `nftables_admin_ssh: [<plan d'administration>]`, sans quoi l'exploitant ne
# pourra pas atteindre ce qu'il vient de construire.
# 2. Les VM, depuis l'underlay ~9 min
make site-creer CONFIRMER=true
# 3. ATTENDRE QU'ELLES REPONDENT — `site-creer` ne le fait pas ~3 min
until ansible -i scripts/site_inventaire.py all,'!<hyperviseurs>' -m ping >/dev/null 2>&1
do sleep 10; done
# 4. Passage 1 — va jusqu'à `serveur_ops`, s'arrête sur la forge vide ~20 min
V="$(dirname "$(readlink -f underlay.yml)")/underlay.vault.yml"
ansible-playbook -i scripts/site_inventaire.py playbooks/site.yml -e "@$V"
# 5. Amorcer la forge — elle tourne, elle est vide ~45 s
export SETOPS_FORGE_MDP=… # jamais en argument de ligne de commande
make forge-amorcer CONFIRMER=true
# 6. Passage 2 — doit finir à 0 échec ~4 min
ansible-playbook -i scripts/site_inventaire.py playbooks/site.yml -e "@$V"
```
**Total mesuré : 37 min 24** pour sept machines, de rien du tout à un site complet.
**La voûte se passe en `-e @`** — `make site-appliquer` la dérive du symlink `underlay.yml`,
mais il n'existe aucune cible qui déploie le site EN ENTIER. Un `ansible-playbook` direct
l'oublie, et l'échec parle d'une assertion, jamais d'un fichier manquant.
**Deux passages, et le second ne sert qu'à la forge.** Le premier va jusqu'à la couche 45
sur 46 ; seul `serveur_ops` manque, faute de génome dans une forge neuve.
> **Il en fallait TROIS avant le 2026-09-12.** `client_pki` posait les droits de la clé
> d'hôte pour le groupe `git`, que le paquet de Forgejo crée dans une couche postérieure :
> au premier passage la clé restait fermée, Forgejo ne démarrait pas — après **300 secondes
> d'attente perdue** — et il fallait un passage entier pour qu'il démarre, un autre pour la
> forge. Aucun ordre de couches ne dénoue ce cycle ; c'est la **propriété du geste** qui a
> changé de main : `serveur_forgejo` revendique désormais la clé au moment où il crée le
> groupe. `client_pki` garde la sienne et la repose à chaque passage, parce que `step`
> réécrit la clé à chaque renouvellement.
### Les quinze murs, et ce que chacun enseigne
| # | Le mur | Ce qu'il enseigne |
|---|---|---|
| 1 | Aucun moyen de raser le site | `make site-raser` — la limite était un trou d'outillage, pas une fatalité |
| 2 | La forge naît vide, le runner y clone | `make forge-amorcer` — un amorçage vient de l'extérieur de ce qu'il amorce |
| 3 | `dns_amorcage` pointe sur le DNS du site | le mécanisme existait, la **surcharge** n'avait jamais été posée |
| 4 | La garde du résolveur teste l'adresse écrite | **une adresse écrite ne prouve pas qu'elle répond** |
| 5 | Aucune cible « déployer tout le site » | la voûte n'était jointe nulle part à la séquence complète |
| 6 | `client_pki` bloque sur un groupe absent | blocage **circulaire** : l'échec empêchait d'atteindre ce qui créait le groupe |
| 7 | PostgreSQL du site sans TLS de l'AC | un écart de **sécurité**, révélé par le premier client exigeant `verify-full` |
| 8 | `pg_hba` n'autorisait personne | le site ne déclarait aucun réseau client |
| 9 | `/etc/setops` absent sur le dépôt | un rôle supposait qu'une couche ultérieure était déjà passée |
| 10 | Forgejo attend 300 s une clé illisible | une attente devrait abandonner quand la cause est déjà au journal |
| 11 | La clé SSH de l'exploitant inconnue de la forge | une forge neuve ne connaît personne |
| 12 | L'accès de l'exploitant était **accidentel** | il tenait au chevauchement d'adressage que le renumérotage a supprimé |
| 13 | Le devis reconnaît l'administration à son port | `"22" in ports` plutôt que `"admin" in pairs` |
| 14 | Un flux à deux paires n'obtient qu'une branche | la chaîne de `elif` rangeait `[flotte, admin]` dans un seul cas |
| 15 | Dépôts créés privés, runner anonyme | `could not read Username` — un message qui pointe ailleurs que sa cause |
| 16 | Le wiki n'existe pas sur une forge neuve | Forgejo ne crée `<dépôt>.wiki.git` qu'à la **première page**, posée à la main dans l'interface |
| 17 | `site-creer` rend la main avant que les machines répondent | mesuré à **3 min 20** — un enchaînement automatique échouerait sur `UNREACHABLE` |
| 18 | Les clés d'hôte changent à chaque reconstruction | `accept-new` couvre la première rencontre, **jamais un changement** : `forge-amorcer` purge l'entrée périmée de l'hôte que `git` va contacter |
### Le motif
Douze des dix-huit sont **du code juste en régime établi**, faux le premier jour : un résolveur
qui se pointe sur lui-même, une clé dont le consommateur n'existe pas encore, un répertoire
créé par une couche ultérieure, une forge vide qu'on croit remplie.
Trois sont des **gardes qui vérifiaient la forme au lieu du résultat**. C'est la famille la
plus coûteuse : elles donnent l'apparence d'une vérification.
Un seul touchait la sécurité — et il était invisible tant qu'aucun client n'exigeait la
vérification. **Le défaut n'a pas cassé la construction : la construction a révélé le
défaut.**
> **Pour le prochain site.** Poser `dns_amorcage` dès le départ, prévoir deux passages,
> amorcer la forge entre les deux, déclarer `nftables_admin_ssh` — sans quoi l'exploitant ne
> peut pas atteindre ce qu'il vient de construire — et créer la première page du wiki dans
> l'interface avant `make wiki-publier`.

96
docs/schemas-flux.md Normal file
View file

@ -0,0 +1,96 @@
# Les pages publiées — où elles sont, et ce qu'elles montrent
> **Pour qui :** le **mainteneur**, quand il cherche une page existante ou qu'il en
> écrit une neuve. Les pages elles-mêmes visent d'autres lecteurs — ce tableau le dit
> pour chacune.
**Ce fichier est la liste entière**, pas seulement celle de la série des flux. Une page
publiée qui n'y figure pas est une page qu'on ne retrouvera pas : elle vit sur une adresse
que personne ne devine, et elle vieillira sans que quiconque s'en aperçoive.
La série des flux, elle, obéit à une règle propre : ces pages expliquent **comment les
choses sont configurées et interconnectées**. Ce ne sont pas des comptes rendus — elles ne
relatent aucun incident, ne datent aucune panne et ne comptent aucune machine tombée. Ce
travail-là appartient au `CHANGELOG.md` et à `docs/decisions-architecture.md`. Une page de
cette série répond à une seule question : *par où passe telle chose, et pourquoi par là*.
## La série
| # | Page | Ce qu'elle montre |
|---|---|---|
| 1 | [La chaîne de l'heure](https://claude.ai/code/artifact/fd050fd8-12b1-4db0-91a3-eac7cf19ee65) | Du satellite aux machines par la frontière — une seule sortie |
| 2 | [La vie d'un certificat](https://claude.ai/code/artifact/77f067e0-336d-445a-ac31-c6f8eab10d73) | Racine, émission, renouvellement, consommateurs, contrôle |
| 3 | [Comment un nom devient une adresse](https://claude.ai/code/artifact/8cdc642f-f1ea-4ff2-ab87-41d2e1d93fd3) | Le plancher, le résolveur, l'autoritatif — dans cet ordre |
| 4 | [D'où vient un paquet](https://claude.ai/code/artifact/5c669f67-ad83-4b4f-9255-98dc0040db7b) | Le cache, ses deux faces, et les deux langues d'apt |
| 5 | [Une identité, un mot de passe](https://claude.ai/code/artifact/3ea86b63-893f-4795-b391-1f75876dace7) | L'annuaire, la fédération, la passerelle — et qui a droit à quoi |
| 6 | [Ce qu'on ne peut pas refaire](https://claude.ai/code/artifact/fd0073dc-6ad8-401d-a5a6-f31d304e7092) | La sauvegarde : ce qui part, ce qui ne part pas, qui vérifie |
| 7 | [Trois canaux, trois sens](https://claude.ai/code/artifact/306bde34-f29c-4396-b43c-7dfeefc259aa) | Verdicts poussés, chiffres tirés, journaux expédiés |
| 8 | [Le trajet d'un courriel](https://claude.ai/code/artifact/0b9b96ea-8d7d-4c40-8461-897a2c11a829) | Deux machines, un annuaire, une remise vérifiée |
## Ce qui les relie
| Page | Ce qu'elle montre |
|---|---|
| [Ce qui relie un locataire à son site](https://claude.ai/code/artifact/28f81e5d-4e71-48ee-aadc-43c4ba9353f9) | Les six liens de la filiation, et ce que coupe chacun |
## Pages destinées à quelqu'un d'autre que le mainteneur
Registre différent : pas de nom de logiciel en titre, pas de vocabulaire de doctrine.
| Page | Pour qui |
|---|---|
| [La maison TechnoLibre](https://claude.ai/code/artifact/bc2b441f-3f39-4659-8399-e61615f778cf) | Le propriétaire d'un écosystème — ce qu'il ouvre, où sont ses affaires |
| [Où commence le système](https://claude.ai/code/artifact/3bffbc9c-018e-4e42-95d1-103b98139589) | Positionnement face à Coolify et Cloud in a Bottle |
| [Deux sites, un tunnel](https://claude.ai/code/artifact/8cbc0ddc-6110-4bc6-906d-90e6a0eba987) | Plan de niveau 3 des deux sites reliés |
| [Un écosystème Set-OPS](https://claude.ai/code/artifact/6ca4514e-6227-4bb6-b07e-ef6690cc156a) | Article promotionnel — résultat et capacités |
| [Inventaire libre](https://claude.ai/code/artifact/ee444b6a-0605-4dce-972d-b9f090f2011e) | La liste des logiciels libres de la solution |
## La page commerciale
Une seule page en quatre parties — le moteur, les offres, les tarifs, la valeur. C'est la
seule qui **affirme un état** (« éprouvé ») et **un prix** ; elle se relit donc chaque fois
qu'une capacité change de camp.
| Page | Ce qu'elle porte |
|---|---|
| [Capacités, offres, tarifs et valeur](https://claude.ai/code/artifact/28369c71-c7c4-43f3-abfe-8dcf0fc50c8b) | Dix piliers, huit offres, la grille tarifaire, le calculateur et les réserves |
Les chiffres qu'elle avance se **mesurent** : nombre de rôles, de preuves, d'affirmations au
registre, de lignes de documentation. Les recompter avant de republier, plutôt que de les
reconduire.
## Plus anciennes, gardées pour mémoire
Elles n'ont pas été revues depuis leur publication et peuvent décrire un état dépassé.
| Page | Ce qu'elle montrait | Publiée |
|---|---|---|
| [Plan, dérivation, preuve](https://claude.ai/code/artifact/c7d996bf-8d99-4377-b5a8-91ad7d63ab39) | La méthode du moteur, en une page | 2026-08-26 |
| [Préparer ton site pour TechnoLibre](https://claude.ai/code/artifact/1edbb622-6bb5-4560-9e93-8953b5caec42) | La préparation du site d'un partenaire | 2026-08-12 |
| [Réseau Chezlepro — les trois plans](https://claude.ai/code/artifact/78584a01-7b45-4e2a-b8bd-e4aa3ecd5341) | Les trois plans du réseau, avant la fusion du lien de sortie | 2026-08-04 |
## La règle d'écriture
**Garder le mécanisme et sa raison. Retirer l'anecdote, la date, la durée, le nombre de
machines touchées.**
Un réglage mérite souvent son explication — `harden-below-nxdomain: no` n'a aucun sens sans
savoir que la racine signée nie le domaine `internal.`. Ça reste. Ce qui ne reste pas, c'est
combien de temps il a fallu pour le comprendre.
Les chiffres sont admis quand ils donnent un **ordre de grandeur utile** (« un écosystème
de quatorze machines demande environ 1,3 Go de paquets »), pas quand ils racontent une
soirée particulière.
## Quand les relire
Une page décrit une configuration : elle vieillit quand la configuration change. Les points
à revérifier après une modification d'architecture sont, dans l'ordre :
- un **service prêté** ajouté ou retiré → pages 4, 6 et celle des liens
- un **renumérotage** → toutes les pages qui portent une adresse
- une **bascule** (résolveur, cache, sauvegarde) → pages 3, 4, 6
- un **rôle neuf avec une sonde** → page 7
- une **capacité qui passe de la feuille de route au service** → la page commerciale, où
elle porte un badge d'état et parfois un prix. C'est la relecture la plus facile à
oublier, parce qu'une bonne nouvelle ne ressemble pas à une tâche.

View file

@ -27,26 +27,38 @@ C'est le VRF qu'on regrettait de ne pas avoir dans le matériel, obtenu en logic
## 2. La projection du modèle
Vérifiée sur les deux tenants fédérés, elle ne demande **aucun changement de dérivation** :
Vérifiée sur chaque tenant fédéré (ils étaient deux au moment de la décision, ils sont
trois), elle ne demande **aucun changement de dérivation** :
| Objet Proxmox SDN | Vient de | Exemple (Chezlepro, zone Services-infra) |
|---|---|---|
| **zone** (un VRF) | le tenant | `CHEZ17` |
| **VNet** | la zone de sécurité | `chez174` |
| **zone** (un VRF) | `zone_de(index)` → `t<index>` | `t17` |
| **VNet** | `vnet_de(index, libellé)` → `t<index><zone abrégée>` | `t17serv` |
| **tag** (VNI) | `vlan_de(index, zone)` | `1174` |
| **subnet** | `sous_reseau_de(index, zone)` | `10.17.19.0/24` |
| **gateway** | `passerelle_de(index, zone)` | `10.17.19.1` |
Les six VNets d'un tenant à l'index 17 : `t17fron`, `t17iden`, `t17donn`, `t17serv`,
`t17obse`, `t17appl`.
> **Rectification du 2026-08-03.** Ce tableau annonçait `chez17-services-infra`, qui
> aurait été **refusé à l'application** : zones et VNets sont limités à **8 caractères**
> par Proxmox — l'identifiant sert de base aux noms de bridge, veth et tap. Message
> amont : *« zone ID … can't be more length than 8 characters »*.
>
> Le nommage dérive du tenant, comme tout le reste : `<PRÉFIXE><index>` pour la zone,
> `<préfixe><index><zone>` pour le VNet. Le préfixe vient de `devis_reseau.prefixe()` —
> la même fonction que le devis des commutateurs, donc un seul endroit fabrique le nom
> court d'un tenant. Éprouvé jusqu'au pire cas de la fédération : `COOP245` = 7,
> `coop2459` = 8. **P30** refuse tout dépassement, sur les deux objets.
> **Le nommage a changé une seconde fois, et ce tableau ne l'avait pas suivi.** Il annonçait
> `CHEZ17` / `chez174`, dérivés de `devis_reseau.prefixe()` — le nom court du *dossier* du
> tenant. La forme en vigueur est plus simple et ne dépend que du seed :
> **`t<index>`** pour la zone, **`t<index><zone abrégée>`** pour le VNet
> (`scripts/devis_sdn.py` : `zone_de`, `vnet_de`). Même préfixe `t` que les IPSets du
> pare-feu Proxmox, donc un seul vocabulaire d'un bout à l'autre de la fabric ; minuscules,
> parce que cet identifiant devient une base de nom d'interface.
>
> La contrainte qui avait motivé la première rectification tient toujours : zones et VNets
> sont limités à **8 caractères** par Proxmox — l'identifiant sert de base aux noms de
> bridge, veth et tap. Message amont : *« zone ID … can't be more length than 8
> characters »*. Avec `t<index>`, la marge est confortable jusqu'à l'index 255 (`t255` = 4,
> `t255serv` = 8). **P30** refuse tout dépassement, sur les deux objets.
>
> **Les zones créées à la main (`VRF0011`, `VRF0017`) sont remplacées.** Ce nommage ne
> disait ni de quel tenant il s'agissait, ni rien qu'on puisse relier au plan : il
@ -65,8 +77,9 @@ porteur** : du SVI d'un commutateur vers la passerelle **anycast** du VNet, pré
chaque hyperviseur — donc plus proche de la VM, et sans point unique de défaillance.
Effet de bord favorable : un VNI est codé sur 24 bits là où un VLAN plafonne à 4094. La limite
du nombre de tenants n'est plus l'espace de VLAN mais le second octet IPv4 du supernet — le
plafond de 245 tenants reste, sa cause change.
du nombre de tenants n'est plus l'espace de VLAN mais le **second octet IPv4** du supernet :
`valider_index` borne l'index à **0–255**, et 0 est à éviter (les réseaux de service du site
y vivent). Le plafond reste, sa cause change.
## 3. Le partage des responsabilités
@ -81,8 +94,10 @@ Deux conséquences qui méritent d'être dites.
**L'inter-tenant ne peut plus être « oublié ».** Il ne circule pas latéralement : il doit
sortir du VRF, donc traverser la bordure, qui est en `block` par défaut. Un flux inter-tenant
légitime devra être **déclaré** pour exister — le registre des flux n'a pas encore de mot-clé
pour ça, c'est un point ouvert.
légitime doit donc être **déclaré** pour exister — et le registre des flux a désormais le
mot-clé qui manquait : **`voisins_site`**, « les tenants d'à côté »
(`scripts/resoudre_flux.py`, `MOTS_PAIR`). Ce paragraphe l'annonçait comme un point ouvert
jusqu'au 2026-09-06 ; il est fermé.
**La défense est en profondeur, sans coût de maintenance.** Le filtrage est-ouest est appliqué
deux fois : par l'hyperviseur, puis par l'hôte destinataire. Une VM compromise doit franchir
@ -168,29 +183,31 @@ Lecture seule par l'API Proxmox. **Le plan de contrôle existe, le plan de donn
| VNets | **aucun** |
| Nœuds de sortie | **aucun** — un VRF sans sortie n'a aucun chemin vers la frontière |
### Deux blocages à lever avant d'aller plus loin
### Deux blocages — levés
**Les VTEP sont adressés dans un tenant.** `vmbr3` porte `10.27.19.{41,43,47}` — le
sous-réseau *Services-infra de Chezlepro*. Le transport du cluster dérive donc de l'index d'un
tenant : un changement d'index le casse, une migration l'emporte. Et une VM de cette zone
partage son sous-réseau avec les trois VTEP, ce qui perce l'isolation à l'endroit même que
l'EVPN devait fermer.
> **Cette section décrivait l'état du 2026-08-02.** Les deux sont levés ; on la garde parce
> que le *raisonnement* explique le plan d'adressage actuel, qui paraîtrait arbitraire sans
> lui.
Le modèle **refuse d'ailleurs d'exprimer cet état** : déclarer `10.27.19.0/24` comme réseau
d'underlay ferait échouer **P23**, qui interdit tout chevauchement avec un supernet tenant. La
garde détecte la faute avant qu'on ne la documente.
**Les VTEP étaient adressés dans un tenant.** `vmbr3` portait `10.27.19.{41,43,47}` — le
sous-réseau *Services-infra de Chezlepro*. Le transport du cluster dérivait donc de l'index
d'un tenant : un changement d'index le cassait, une migration l'emportait. Et une VM de cette
zone partageait son sous-réseau avec les trois VTEP, ce qui perçait l'isolation à l'endroit
même que l'EVPN devait fermer.
`underlay.yml` déclare donc les trois hyperviseurs à leur adresse **cible** — `10.0.0.{41,43,47}`,
dernier octet conservé comme sur `vmbr0`. Le déplacement réel de l'adresse sur les nœuds reste
à faire : c'est une modification du réseau d'un hyperviseur en service.
Le modèle **refusait d'ailleurs d'exprimer cet état** : déclarer `10.27.19.0/24` comme réseau
d'underlay faisait échouer **P23**, qui interdit tout chevauchement accidentel avec un
supernet tenant. La garde a détecté la faute avant qu'on ne la documente.
**Le pont `vmbr3` n'est pas *VLAN-aware*** (pas de `bridge_vlan_aware`, contrairement à
`vmbr2`). L'adresse du VTEP y est donc **non étiquetée** : elle vit dans le VLAN natif du port
de commutateur. Déplacer le VTEP vers l'underlay suppose soit un VLAN natif 10, soit une
interface étiquetée dédiée (`bond3.10`) — ce n'est pas qu'un changement d'adresse.
**Aujourd'hui** : les VTEP vivent sur `underlay-vxlan` — `192.168.50.{41,43,47}`, VLAN 50,
sur une interface étiquetée dédiée (`bond3.50`). Le transport ne dérive plus d'aucun index.
*Pourquoi 50 et pas 11 : sous la règle `192.168.<vlan>`, le VLAN 11 aurait produit
`192.168.11.0/24` — déjà occupé par le contrôle de la grappe. Un plan d'adressage ne doit pas
dépendre de l'ordre d'une migration.*
**`vishnu` n'est pas câblé.** Son `vmbr3` n'a **aucun port physique** : le pont existe, porte
une adresse, et ne mène nulle part. Un pair VXLAN pointé sur lui ne fonctionnera jamais.
**Le pont `vmbr3` n'était pas *VLAN-aware***, et l'adresse du VTEP y vivait donc dans le VLAN
natif du port. C'est ce qui rendait le déplacement plus qu'un changement d'adresse — d'où
l'interface étiquetée dédiée retenue.
## 8. Ce qui reste à trancher
@ -199,9 +216,8 @@ une adresse, et ne mène nulle part. Un pair VXLAN pointé sur lui ne fonctionne
- **Le contrôleur EVPN** : ASN, voisins, et si l'on fait du BGP avec la bordure ou des routes
statiques comme aujourd'hui.
- **Le nombre de nœuds de sortie** et leur redondance.
- **Le mot-clé d'un flux inter-tenant** dans le registre. Aujourd'hui aucun ne l'exprime, donc
tout inter-tenant tombe dans le `block` de la bordure — un défaut sûr, mais qui rend
impossible de *déclarer* une exception légitime.
- ~~**Le mot-clé d'un flux inter-tenant** dans le registre.~~ **Tranché** : c'est
`voisins_site` (2026-08-24), né du besoin de chaîner les caches d'artefacts. Cf. §3.
- **La migration depuis l'existant** : le tenant Chezlepro tourne déjà sur des VLAN. Passer à
EVPN est un changement de plan de transport pour des VM en service — la recette de
`docs/migration-tenant.md` s'applique-t-elle, ou faut-il un chemin plus court ?

View file

@ -0,0 +1,130 @@
> **Pour qui :** l'exploitant, le jour où il réalise que 1 644 octets valent toute
> l'installation. À faire une fois, puis à refaire quand une clé change.
# Sortir les clés du poste
## Ce qui est en jeu
Le **code** de Set-OPS est répliqué **deux fois** : `eregion` et la forge du site. Les
**voûtes chiffrées**, elles, n'y sont **pas** — elles sont gitignorées. Chacune vit sur
ce poste et sur le runner de son écosystème, qui en reçoit une copie chiffrée par
`serveur_ops_tenant`. *(Corrigé le 2026-09-28 : cette page les disait sur les forges.)*
**Elles sortent donc aussi, dans une archive à part** (`make voutes-exporter VERS=<support>`,
`scripts/exporter_voutes.py`) : déjà chiffrées par `ansible-vault`, elles n'ont pas besoin
d'une seconde couche ; le script refuse tout fichier dont l'en-tête n'est pas
`$ANSIBLE_VAULT`, garde le chemin de chaque voûte (elles s'appellent presque toutes
`vault.yml`) et relit l'archive empreinte par empreinte. Restauration :
`tar -xf "$(ls -t setops-voutes-*.tar | head -1)" -C <dossier des dépôts>` (la plus récente).
Chaque export porte sa date (`setops-voutes-<date>-<poste>.tar`, l'heure en plus au second
export du jour) et n'écrit rien si les voûtes n'ont pas changé depuis la dernière archive.
**À refaire après toute écriture
dans une voûte**, comme les clés après toute voûte nouvelle.
> **Ce chiffre était trois, et il a baissé sans que rien ne le signale.** Un coffre répliqué deux fois reste
> solide — mais c'est la **redondance du génome** qui a diminué, pas le chiffrement, et
> c'est exactement ce que la page *Filiation, signatures & témoins* appelle la vraie mesure
> de résistance d'une lignée : combien de copies **vivantes**, sur combien de machines
> distinctes.
Les **clés** qui l'ouvrent vivent dans neuf fichiers de ce poste — 1 644 octets — sans
aucune copie ailleurs. S'y ajoutent les clés SSH par lesquelles on entre sur les
hyperviseurs, la frontière et chaque machine.
Ce qu'on perd avec le poste, par ordre de gravité :
| Ce qui disparaît | Conséquence |
|---|---|
| Le poste seul | Les mots de passe restic restent lisibles sur les machines vivantes (`/etc/setops/restic.pass`) — récupérable, mais douloureux, et plus aucun déploiement possible entre-temps |
| Le poste **et** une machine | L'état de cette machine devient illisible |
| Le poste **et** le site | Terminal |
## La manœuvre
```bash
make cles-recenser # voir ce qui sortirait, sans rien écrire
make cles-exporter VERS=/media/…/CLE # sortir, chiffrer, RELIRE
```
**Lance-la toi-même**, dans ton terminal. `gpg` demande une phrase de passe : elle ne doit
passer ni par un journal, ni par le contexte d'un assistant. C'est la seule façon de
s'en assurer.
L'outil refuse trois choses, et chacune a été éprouvée en la faisant échouer :
- une destination **dans** l'infrastructure — ces clés ouvrent les sauvegardes ; les y
ranger ferait un coffre dont la clé est à l'intérieur ;
- **écraser** une archive existante — elle est peut-être la seule ;
- une archive qu'il **n'arrive pas à rouvrir** — elle est alors supprimée. Une sauvegarde
de clés qu'on ne sait pas ouvrir est pire que rien : elle donne le sentiment d'être
protégé.
Il ne montre jamais le contenu des clés — seulement leur nom, leur taille et leur
empreinte. De quoi vérifier sans divulguer.
## Les deux gestes qui restent, et qui ne sont pas facultatifs
**1. La phrase de passe va ailleurs que le support.** Séparés, ils ne valent rien l'un
sans l'autre ; ensemble, ils valent l'installation. Un papier dans un autre lieu, ou un
gestionnaire de mots de passe qui n'est pas sur ce poste.
**2. Une seconde copie, dans un autre lieu physique.** Un support unique dans un tiroir
unique, c'est le problème qu'on vient de fermer, déplacé de quelques mètres.
## Les rouvrir — la moitié qui compte
Le jour où l'on s'en sert, **le poste est mort**. Le dépôt Set-OPS est répliqué trois
fois, mais le *cloner* demande la clé SSH… qui est dans l'archive qu'on essaie d'ouvrir.
Une procédure rangée dans le dépôt serait donc inaccessible exactement quand elle sert.
La clé se suffit donc à elle-même. À côté de l'archive :
```
setops-cles-<date>-<poste>.tar.gpg les clés, chiffrées (une archive datée par export)
restaurer_cles.py le script, autonome
LISEZ-MOI-RESTAURATION.txt le mode d'emploi, et les commandes manuelles
```
> **Une clé faite avant cette version ne porte pas les compagnons.**
> `make cles-compagnons VERS=/media/…/CLE` les y dépose **sans refaire l'archive** : ni
> phrase de passe redemandée, ni risque d'écraser ce qui est déjà vérifié.
Sur la machine neuve, il ne faut que `gpg`, `python3` et la phrase de passe :
```bash
A="$(ls -t setops-cles-*.tar* | head -1)" # la plus récente
python3 restaurer_cles.py "$A" --lister # voir sans rien écrire
python3 restaurer_cles.py "$A" # remettre en place
```
Le script remet chaque fichier à sa place selon son nom, et **repose les droits à 0600**.
Ce n'est pas cosmétique : `ssh` refuse une clé privée que d'autres peuvent lire, et son
message ne dit pas qu'il s'agit d'un droit — une archive extraite depuis un support FAT
arrive systématiquement dans cet état. Il **refuse** aussi d'écraser une clé déjà là :
lancé par erreur sur un poste qui fonctionne, il ne détruit rien.
Si même ce script refuse de tourner, le LISEZ-MOI porte les commandes manuelles — `gpg`,
`tar`, `chmod`. **Les deux chemins ont été éprouvés** le 2026-09-05 : export, poste vidé,
restauration depuis la clé seule, empreintes comparées — identiques, 4 sur 4, droits
700/600.
## L'éprouver pendant qu'on a encore le choix
```bash
make cles-restaurer ARCHIVE=/media/…/setops-cles-*.tar.gpg
```
Il refusera, puisque les clés sont déjà en place — et ce refus est en soi la preuve que
l'archive s'ouvre et que la phrase de passe est la bonne. C'est le test le moins cher
qui existe, et il vaut d'être refait après chaque changement de clé.
## Quand recommencer
Quand une voûte est créée ou sa clé changée (`voutes.py`), quand une paire SSH de runner
est refaite, et à l'arrivée d'un écosystème. `make cles-recenser` dit en une seconde si
l'archive rangée est encore complète : compare le nombre de fichiers et les empreintes.
## Ce que ça ne couvre pas
La **phrase de passe** elle-même : si elle est perdue, l'archive est du bruit. C'est le
prix du chiffrement, et c'est pour ça que le geste 1 n'est pas décoratif.

View file

@ -0,0 +1,279 @@
# Supervision dérivée des rôles
> **Pour qui :** celui qui ajoute un rôle à Set-OPS et se demande comment sa supervision
> arrive dans Icinga — et celui qui exploite et veut savoir d'où sortent les services
> qu'il voit.
> **La règle en une phrase.** Un rôle déclare les sondes de sa propre supervision ; le
> moteur en dérive les objets Icinga, la permission d'API et les preuves. Comme
> `meta/flux.yml` engendre nftables *et* OPNsense.
## Pourquoi
Le 2026-09-09, mesure du dépôt sur lui-même :
```
au 2026-09-09 :
39 rôles déclarent leurs flux -> nftables + OPNsense, dérivés
32 déclarent leur empreinte -> ressources des VM, dérivées
32 déclarent leur authentification -> habilitations, dérivées
19 groupes déclarent `surveillance:` -> RIEN
```
Au 2026-09-09, les dix-neuf lignes `surveillance:` de `docs/dependances-groupes.yml` sont écrites,
versionnées, relues — et **aucune n'est exécutée**. Icinga surveillait deux choses :
`sauvegarde` et `sante`.
C'est la classe d'échec que ce dépôt nomme partout ailleurs, en version documentaire : la
carte dit ce qui est surveillé, et personne ne surveille. *Une intention écrite n'est pas
une mesure.*
## Le contrat d'une sonde : c'est celui des greffons Nagios
Une sonde est un **script local**, déposé par le rôle qui la possède, dans
`/usr/local/lib/setops/sondes/<nom>.sh` (0750, root).
| | |
|---|---|
| **sortie standard** | `TEXTE lisible` puis, optionnellement, `\| métriques` |
| **code de sortie** | `0` OK · `1` AVERTISSEMENT · `2` CRITIQUE · `3` INCONNU |
| **réseau** | aucun besoin : c'est le porteur qui pousse le résultat |
| **durée** | courte ; le porteur passe toutes les 15 min |
> **Ce contrat n'est pas de nous.** C'est l'**API des greffons Nagios**, que
> `monitoring-plugins` implémente depuis vingt ans et qu'Icinga parle nativement. La
> première rédaction de ce document la décrivait sans la nommer — autrement dit la
> réinventait. `positionnement.md` dit l'inverse : *adopter aux seuils, ne pas
> réimplémenter*.
**La conséquence pratique est grande.** Un greffon standard **est** une sonde valide, sans
la moindre colle : `check_disk`, `check_load`, `check_procs`, `check_ntp_time`,
`check_file_age`, `check_smtp`… Une sonde ne s'écrit à la main que lorsque la vérité à
mesurer est propre à Set-OPS — et c'était le cas pour `client_pki/certificat`, qui compare
l'empreinte *servie* à celle du disque : aucun greffon ne sait ça.
### Ce que la flotte a vraiment sous la main — et ce qu'elle n'a pas
`client_sante` installe **`monitoring-plugins-basic`** : **53** greffons, mesurés le
2026-09-14 sur la flotte. Ce document a d'abord annoncé « 54 » et cité `check_pgsql` en
exemple. **`check_pgsql` n'en fait pas partie**, ni `check_dns`, ni `check_ldap` : ils sont
dans `monitoring-plugins-standard`.
Et ce paquet-là ne s'ajoute pas :
apt-get install -s monitoring-plugins-standard
→ samba, python3-samba, smbclient, snmp, tdb-tools…
Une pile Samba et une pile SNMP sur **chaque machine** de la flotte, pour obtenir trois
greffons. Le durcissement dit le contraire de ça.
**La conséquence pour qui écrit une sonde :** vérifier que le greffon appelé est dans
`-basic` avant de s'appuyer dessus. **Un greffon absent sort en 127** — qui n'est pas un
code Nagios (0/1/2/3) : la sonde devient illisible au lieu d'échouer proprement. Quand le
greffon manque, prendre l'outil natif du service (`dig` sur un résolveur, `psql` sur une
base) ; c'est déjà ce que fait `serveur_resolveur/resolution`.
Écrire du shell là où un greffon existe, c'est se donner du code à maintenir *et* se priver
de vingt ans de cas limites déjà rencontrés par d'autres.
Le rôle qui possède la sonde la **déploie lui-même** : il connaît ses chemins, ses
secrets, sa vérité de terrain. Il la **déclare** dans `meta/supervision.yml`, et c'est
cette déclaration que le moteur lit.
```yaml
# roles/<rôle>/meta/supervision.yml
sondes:
- nom: certificat
ttl: 5400
raison: "..."
```
## Passif, et à durée de vie — jamais actif
Un contrôle **actif** ne voit pas la machine **muette** : si elle ne répond plus, la sonde
échoue et on met ça sur le compte du réseau. Ici c'est le nœud qui parle, et le `ttl` de
son envoi fait la fraîcheur — sans nouvelle, Icinga périme le service tout seul.
**Le silence alerte autant que l'échec.** C'est exactement ce qui a manqué à
`openipmi.service` : une unité en échec à chaque démarrage pendant une semaine, et
`systemctl --failed` à zéro partout parce qu'aucune machine n'avait redémarré.
C'est aussi ce qu'impose *la vérification suit la clé* : la sonde tourne là où vit la
vérité, pas sur le superviseur. Un dépôt de sauvegarde chiffré côté client ne peut être
jugé que par qui détient la clé.
## Ce qui n'a rien à faire ici
Une **métrique à seuil** — durée de collecte, volume de journaux, taux d'occupation —
appartient à Prometheus et Grafana. Icinga répond à une seule question : *est-ce cassé ?*
Mélanger les deux rendrait les deux moins lisibles.
## L'autre moitié : `meta/metriques.yml`
Cette phrase appelle son pendant, et il porte le même patron — **le rôle déclare, le
moteur dérive**.
| fichier | ce qu'il produit | la question |
| --- | --- | --- |
| `meta/supervision.yml` | une **sonde** → un verdict, avec un TTL | *est-ce cassé ?* |
| `meta/metriques.yml` | un **exportateur** → une série ; des **panneaux** → un tableau | *depuis quand, et vers où ?* |
**Deux fichiers, deux consommateurs.** `serveur_prometheus` lit l'`exportateur` pour en
tirer sa cible de scrutation ; `serveur_grafana` lit les `panneaux` pour en assembler un
tableau par rôle. Les deux moitiés sont indépendantes : un rôle peut déclarer un
exportateur sans panneau (des séries collectées, regardées ailleurs), et des panneaux sans
exportateur (`client_metrique` — le job `node` est universel, écrit une fois, et le
dériver le ferait exister deux fois).
**Le flux n'est pas dérivé d'ici, et c'est voulu.** Le port de l'exportateur doit s'ouvrir
depuis l'observatoire, et c'est `meta/flux.yml` qui le déclare — là où vivent déjà tous les
flux du rôle. Deux fichiers pour un même fait finiraient par diverger.
### Le critère d'un panneau
*Une série a sa place si elle **précède** un verdict, ou si elle n'en aura **jamais**.*
Ce qui bascule d'un coup appartient à Icinga ; ce qui dérive lentement n'a que le graphe
pour se faire voir. Une base qui passe de 20 à 60 connexions en trois semaines n'a rien
cassé — elle annonce la date où elle cassera.
### La `raison` devient la description du panneau
C'est le seul champ qui ne produit aucun pixel de graphe, et c'est le plus important.
Grafana l'affiche au survol du titre. Sans elle, celui qui regarde six mois plus tard voit
une courbe sans savoir ce qu'elle annonce.
### Agréger, toujours
node_exporter publie une série par interface, par point de montage, par cœur : **339
séries** pour le seul trafic réseau du site, mesuré le 2026-09-14. Un panneau qui les
montre toutes ne montre rien. Et se méfier de `min()` / `max()` : un seul montage
pathologique — `/var/lib/lxcfs`, qui rapporte toujours zéro — suffit à rendre un panneau
faux **pour toujours**, avec une courbe plate parfaitement lisible. Filtrer par **liste
blanche** : un montage inconnu manque au graphe, ce qui se voit, au lieu de l'écraser, ce
qui ne se voit pas.
### Ce que les gardes tiennent
**P77** exige de chaque panneau un titre, une expression, une unité et une raison — et que
l'unité figure dans la table de traduction de `serveur_grafana`. Une unité inventée ne
casse rien : le panneau retombe sur `short`, et un graphe d'octets gradué en unités brutes
reste parfaitement lisible et parfaitement faux.
Ce que P77 **ne** dit pas : si l'expression répond. Aucune lecture statique ne le dira. Un
panneau se prouve comme une sonde — en le regardant rendre des données, et en le notant au
CHANGELOG.
## Chaque sonde vient avec son contrôle négatif
Une sonde qu'on n'a jamais vue échouer n'est pas une sonde, c'est une habitude. Dix-neuf
voyants verts non éprouvés seraient un recul par rapport à deux voyants prouvés : ils
rassureraient.
Toute sonde ajoutée doit donc être accompagnée de la manière de la faire échouer
**pour de vrai**, et cette manière doit être rejouée au moins une fois, sur une machine
réelle. Le CHANGELOG en porte la trace.
**Et contre un état sain, tout autant.** La première version de la sonde du certificat a
rendu **14 machines sur 14 en CRITIQUE** — sur une PKI qui se portait très bien. Elle
utilisait `openssl verify -CAfile racine`, alors que nos certificats sont signés par un
*intermédiaire* que seul `step certificate verify` sait retrouver. Une alarme toujours
allumée ne vaut pas mieux qu'une alarme jamais allumée : elle apprend à ne plus regarder.
Une sonde se prouve donc **deux fois** — verte sur le sain, rouge sur le cassé.
## Combien de sondes : une par CAUSE D'ACTION
Ni une par rôle, ni une par vérification. **Une par geste que le verdict appelle.**
Les quatorze premières sondes agrègent chacune de deux à six chemins d'échec, et ce choix
avait été fait **sans être écrit** — d'où cette section, ajoutée le 2026-09-10 après la
question : *« est-ce parce qu'elles agrègent plusieurs vérifications ? »*
**Ce que coûte une agrégation abusive**, dans le modèle d'Icinga : un service = **un état,
un historique, un acquittement**. Fondre deux causes qui appellent des gestes différents,
c'est ne plus pouvoir acquitter l'une pendant que l'autre alerte — et lire comme *un
service instable* ce qui est en réalité *deux faits distincts qui alternent*.
**Ce que coûte l'excès inverse** : un voyant de plus est un voyant de moins regardé. Le
dépôt le répète assez — *une supervision creuse est pire qu'aucune*.
Le test pratique, à appliquer sans état d'âme :
> Les deux échecs appellent-ils **le même geste, de la même personne, dans le même délai** ?
> Si oui, une seule sonde, et le message nomme lequel des deux a parlé.
> Si non, deux sondes.
**Exemple d'agrégation légitime** — `moteur` : `icinga2` mort, `icingadb` mort, état figé en
base. Trois causes, un seul geste : *va regarder la supervision*. Le message dit laquelle.
**Exemple d'agrégation abusive** — `cache-apt` : « ne répond pas » arrête tout `apt` de
l'écosystème, « volume à 90 % » est un billet pour demain. Même personne, gestes et
**délais** différents : deux sondes.
## Une sonde doit pouvoir être mise en défaut *par paramètre*
Corollaire des deux règles précédentes, et c'est ce qui rend la discipline tenable à
l'échelle : une sonde dont on ne peut prouver le rouge qu'en **cassant un service** ne sera
prouvée qu'une fois, puis plus jamais.
Chaque sonde expose donc sa cible et ses seuils en variables du rôle. On la met en défaut
en lui donnant un port fermé, un nom qui n'existe pas, un seuil impossible — sur une
machine réelle, sans rien abîmer, et aussi souvent qu'on veut.
## Où vit la sonde : là où vit la vérité, pas là où vit le symptôme
*« La vérification suit la clé. »* Un nœud sait qu'il a **lancé** sa sauvegarde ; seul le
dépôt sait qu'elle a **abouti**. Un nœud sait qu'il **expose** ses métriques ; seul
Prometheus sait qu'il les **collecte**.
D'où un choix qui peut surprendre : *« ce nœud est-il bien collecté ? »* est une sonde de
**`serveur_prometheus`**, pas de `client_metrique`. Une seule sonde y voit les N nœuds à la
fois — et surtout, elle voit le cas **silencieux** : celui qui a cessé d'être collecté et
qui, par définition, ne peut pas s'en plaindre lui-même.
## Un contrôle négatif ne doit pas pouvoir abîmer
Éprouver la sonde du certificat, le 2026-09-09, a consisté à **remplacer le certificat
d'hôte** par un auto-signé. La sonde a bien viré au rouge — et le script de synchronisation
a propagé ce certificat vers `node_exporter`, dont la clé était restée l'ancienne :
```
failed to load X509KeyPair: tls: private key does not match public key
```
Un service réel est tombé pour éprouver une sonde. Le contrôle avait un **rayon d'action**
que je ne lui avais pas donné volontairement.
La règle qui en découle : un contrôle négatif se fait sur une **copie**, ou sur un chemin
que la sonde accepte en paramètre — jamais en substituant l'artefact que d'autres
consomment. Quand c'est impossible, on le fait sur la machine la moins critique, on
l'annonce, et on vérifie l'état des consommateurs **après**, pas seulement celui de la
sonde.
## Le matériel : un catalogue, deux lecteurs (2026-09-17)
Les hyperviseurs et la frontière n'ont aucun rôle Set-OPS pour déclarer leurs capteurs, et
ils ne peuvent rien pousser. Prometheus les **tire** déjà ; on juge donc ce qu'il a récolté.
`scripts/materiel.py` est la seule source : familles de températures (processeur, carte
mère, NVMe, disques SATA, DDR5, cartes réseau, frontière) avec leurs seuils et leur raison,
et contrôles de santé (ventilateur arrêté, SMART en échec, secteurs défaillants ou **en
progression**, alerte et usure NVMe, relevés SMART périmés). Deux lecteurs :
| lecteur | ce qu'il en fait |
|---|---|
| Grafana — tableau « Set-OPS — Matériel » | tuiles colorées aux seuils, courbes avec seuils tracés, ventilateurs, puissance, usure et âge des disques |
| Icinga — hôte par équipement, service `materiel` | `check_materiel.py` lit Prometheus avec les mêmes seuils ; l'hôte est jugé par `up`, pas par un ping qu'aucun flux ne permet |
Règles apprises en le construisant :
- **Le matériel ment sur ses limites.** Le Super I/O annonce 125 °C « critique » pour la carte
mère : les seuils sont déclarés, avec leur raison.
- **Un connecteur vide n'est pas un ventilateur arrêté** : on ne crie que pour un ventilateur
qui a tourné dans les 24 h.
- **La progression, pas le compte.** Un disque aux secteurs défaillants stables est un
avertissement ; c'est un compte qui **monte** qui est critique. Une alarme toujours rouge
est une alarme qu'on cesse de lire.
- **Le côté droit d'une jointure PromQL est agrégé.** Un réétiquetage laisse cinq minutes
deux séries pour une même clé, et la requête est refusée.
- **Une requête refusée n'est pas un Prometheus injoignable** : la sonde rapporte l'erreur.

View file

@ -193,7 +193,14 @@ L'hôte est d'abord déclaré dans le **plan** (`instance/plan/serveurs.yml`) et
make deployer HOTE=web-frontal-01
```
La cible `deployer` lit les groupes de l'hôte, cherche les playbooks correspondants dans `playbooks/groupes/`, puis les applique dans l'ordre de l'inventaire.
La cible `deployer` lit les groupes de l'hôte, cherche les playbooks correspondants dans `playbooks/groupes/`, puis les applique **dans l'ordre des couches** — le socle (`serveur_debian`, `serveur_durci`) d'abord, puis le rang de `docs/couches-deploiement.yml`, le même registre que `make site`.
> **Ce n'est pas « l'ordre de l'inventaire », et la nuance a coûté un déploiement.** Un tri
> alphabétique plaçait `client_metrique` avant `serveur_step_ca` : l'intégration réclamait un
> certificat que l'autorité, pas encore déployée, ne pouvait pas avoir émis. Constaté le
> 2026-08-06 sur les deux premières VM — dont l'hôte de l'AC lui-même. La couche
> « intégrations » dit en toutes lettres *déployées en dernier, quand leurs cibles sont
> debout* ; `make deployer` l'ignorait.
Ensuite, elle lance la vérification post-déploiement :
@ -213,9 +220,9 @@ Cette commande limite le playbook au croisement entre le groupe demandé et `hot
## 9. Variables template et conformité
Les variables du template servent à construire le golden template dans `instance/inventories/production/group_vars/modeles_vm.yml`.
Les variables du template servent à construire le golden template dans `instance/inventories/<inventaire>/group_vars/modeles_vm.yml`.
Les variables de conformité servent aux VM déployées dans `instance/inventories/production/group_vars/serveur_debian.yml`.
Les variables de conformité servent aux VM déployées dans `instance/inventories/<inventaire>/group_vars/serveur_debian.yml`.
Différence attendue :

View file

@ -16,4 +16,19 @@ domaine_interne: "exemple.internal"
fuseau_horaire: "America/Toronto"
organisation: "Exemple"
identite_realm: "exemple"
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"
# LE PLAN SUIT L'INVENTAIRE, PAS LE SYMLINK (2026-08-23).
#
# La valeur precedente etait `{{ playbook_dir }}/../../instance/plan` : le lien
# `instance` du moteur, en dur. Tout role lisant le plan lisait donc celui de
# l'instance POINTEE PAR LE LIEN, et non celle qu'on deploie.
#
# CE QUE CA A DONNE. En deployant un autre ecosysteme avec `SETOPS_INSTANCE`, le plancher
# /etc/hosts de `ops-01` a recu les FQDN de CHEZLEPRO -- auth.chezlepro.internal,
# forge.chezlepro.internal... -- pointes sur l'edge de l'autre. Un ecosysteme
# annoncait les noms d'un autre. Neuf roles lisent cette variable ; le plancher est
# simplement celui qui l'a rendu visible.
#
# `inventory_dir` est le dossier de l'inventaire REELLEMENT charge. Le plan qui a
# engendre cet inventaire est son voisin : les deux ne peuvent plus se contredire,
# et l'expression reste juste qu'on passe par le symlink ou par SETOPS_INSTANCE.
setops_plan_dir: "{{ inventory_dir }}/../../plan"

View file

@ -0,0 +1,24 @@
---
# L'EDGE DOIT PORTER LES NOMS QU'IL PUBLIE (2026-08-23).
#
# Sans ce fichier, `client_pki` n'emet le certificat de l'edge qu'avec ses propres noms
# (`infra-edge-01.genese.internal`), et nginx retombe sur le certificat auto-signe de
# Debian pour tout FQDN expose. Le service repond, la page s'affiche apres un
# avertissement — et rien ne signale la panne. C'est ce qui s'est passe ici : une forge
# etait publiee derriere un `ssl-cert-snakeoil.pem`, et `git clone` a ete le
# premier a refuser, a juste titre.
#
# Ce fichier appartient au MODELE parce que trois instances sur quatre le portaient
# et que la quatrieme, plus recente, ne l'avait pas : un cablage recopie a la main
# finit toujours par manquer quelque part. La preuve P42 le verifie desormais.
#
# `sans_exposition` est derive du plan (les `expose:` des applications).
client_pki_sans: >-
{{ ([ansible_fqdn | default(ansible_hostname), ansible_hostname, ansible_host]
+ (sans_exposition | default([])))
| select | unique | list }}
serveur_nginx_certificat: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.crt"
serveur_nginx_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.key"
# Un cert renouvele sur disque reste PERIME en memoire tant que nginx n'a pas recharge.
client_pki_reload_services:
- nginx

View file

@ -32,6 +32,9 @@ vault_postgresql_keycloak: ""
vault_bd_keycloak: ""
vault_bd_forgejo: ""
vault_bd_icingadb: ""
# La base de la console (vigie) dans tous les modes : comptes en mode `db`, préférences
# et migrations partout — distincte de celle du moteur.
vault_bd_icingaweb2: ""
# --- Forge (Forgejo) ---
vault_forgejo_admin: ""
@ -41,3 +44,42 @@ vault_forgejo_internal_token: ""
# --- Observabilité / divers ---
vault_grafana_admin: ""
vault_redis: ""
# LA CONSOLE DE SUPERVISION, QUAND ELLE S'AUTHENTIFIE SEULE.
#
# `serveur_icingaweb2_auth: db` — le mode d'un SITE, qui n'a ni annuaire ni Keycloak.
# Icinga Web 2 gère ses comptes nativement ; ce mot de passe est celui du compte
# D'AMORÇAGE, celui qui permet d'entrer la première fois pour créer les autres dans
# l'interface. Inutile en mode `ldap` ou `external`.
vault_icingaweb2_admin: ""
# LA CONSOLE D'EXPLOITATION, QUAND ELLE S'AUTHENTIFIE SEULE.
#
# `serveur_ops_gui_auth: locale` — le repli d'un ecosysteme SANS annuaire (un SITE).
# Un ecosysteme qui a Keycloak reste en `oidc` et laisse cette cle VIDE : la console
# lance des deploiements et peut raser, un mot de passe partage devant ce pouvoir est un
# accident qui attend.
vault_setops_gui_admin: ""
# LE SECRET OIDC DE LA CONSOLE D'EXPLOITATION (mode `oidc`).
#
# Le GUI de Set-OPS n'a aucune authentification a lui : la passerelle est sa seule
# serrure, et ce secret est ce qui la lie a Keycloak. Vide chez un ecosysteme sans
# annuaire — un SITE — qui emploie alors le vestibule local.
vault_setops_console_oidc: ""
# LE COMPTE DE METRIQUES DE POSTGRESQL — lecture seule, role `pg_monitor`.
#
# Il ne lit que les vues de statistiques : pas une ligne de donnee applicative. Faire
# tourner l'exportateur en `postgres` serait donner les cles de la base pour lire des
# compteurs.
#
# VIDE = PAS D'EXPORTATEUR DU TOUT. Le role ne le pose pas et ne cree pas le compte —
# jamais un mot de passe par defaut. MAIS PROMETHEUS DERIVE QUAND MEME SA CIBLE (`:9187`)
# pour tout hote de `serveur_postgresql`, secret ou pas : sans lui, la sonde `collecte`
# d'obs-01 passe au rouge (« muettes : ...:9187 »). C'est voulu — une base sans metriques
# se voit ; renseigner ce secret est la facon de l'eteindre. (Cette ligne promettait
# l'inverse jusqu'au 2026-09-28 ; Chezlepro l'a vecu.)
vault_pg_exportateur: ""

View file

@ -18,6 +18,7 @@ from inventory_rules import ( # noqa: E402
bases_du_groupe,
chaine_connexion,
expositions_des_applications,
zones_inverses,
)
@ -31,4 +32,5 @@ class FilterModule:
"bases_du_groupe": bases_du_groupe,
"chaine_connexion": chaine_connexion,
"expositions_des_applications": expositions_des_applications,
"zones_inverses": zones_inverses,
}

64
paquets-tiers.yml Normal file
View file

@ -0,0 +1,64 @@
---
# LES PAQUETS QUI NE VIENNENT PAS DE DEBIAN — et pourquoi ils sont ici.
#
# Trois dépôts tiers sont configurés sur la flotte, tous en HTTPS. Or `client_artefacts`
# pose `Acquire::https::Proxy "DIRECT"` — obligatoire, parce que le cache refuse les
# tunnels HTTPS (« 403 CONNECT denied ») et que sans cette ligne aucun dépôt tiers
# n'était joignable. La conséquence n'avait pas été vue : **ces trois dépôts contournent
# le cache par conception**, et chaque construction de VM va les chercher sur Internet.
#
# CE QUE ÇA COÛTAIT. `client_pki` est une intégration UNIVERSELLE : chaque machine de
# chaque écosystème installe `step-cli` depuis Smallstep au moment de sa naissance. Sans
# Internet, une VM neuve n'obtient pas son client d'AC, donc pas de certificat, donc
# n'entre dans aucun flux chiffré. `alloy` (métriques) est dans le même cas.
#
# Les versions sont ÉPINGLÉES, comme les collections Ansible. Une mise à niveau devient
# alors un geste délibéré — `make cacher-paquets` — au lieu d'arriver toute seule le jour
# où l'amont publie.
depots:
smallstep:
uri: https://packages.smallstep.com/stable/debian
suite: debs
composant: main
# Sur CHAQUE machine, via `client_pki`. Le paquet le plus critique de la liste.
paquets:
step-cli: 0.31.0-1
icinga:
uri: https://packages.icinga.com/debian
suite: icinga-trixie
composant: main
# La supervision et sa vue web, avec leurs dépendances PHP propres au dépôt.
paquets:
icinga2: 2.16.5-1+debian13
icinga2-bin: 2.16.5-1+debian13
icinga2-common: 2.16.5-1+debian13
icinga2-doc: 2.16.5-1+debian13
icinga-archive-keyring: 2.0.0-1+debian13
icingacli: 2.14.0-2+debian13
icingadb: 1.5.1-8+debian13
icingadb-redis: 8.2.10-1+debian13
icingadb-web: 1.4.0-1+debian13
icinga-l10n: 1.4.0-1+debian13
icinga-php-legacy: 1.1.0-1+debian13
icinga-php-library: 1.0.1-1+debian13
icinga-php-thirdparty: 1.0.1-1+debian13
icingaweb2: 2.14.0-2+debian13
icingaweb2-common: 2.14.0-2+debian13
icingaweb2-module-monitoring: 2.12.6-1+debian13
php-icinga: 2.14.0-2+debian13
grafana:
uri: https://apt.grafana.com
suite: stable
composant: main
# `alloy` est universel comme `step-cli` : `client_metrique` le pose partout.
# RELEVES LE 2026-10-03 sur ce que portent les locataires depuis leur reconstruction du
# 2026-10-01 : leurs runners n'ont pas ce cache et avaient tire la derniere version
# publiee. Le cache du poste, lui, servait encore les anciennes — un deploiement depuis
# le poste aurait tente de les RETROGRADER (`paquets_tiers` installe les .deb du cache).
# Le site, encore en 1.19.2 / 13.2.1 / 3.7.7, montera a son prochain passage.
paquets:
alloy: 1.20.1-1
grafana: 13.2.3
loki: 3.7.8

View file

@ -0,0 +1,121 @@
---
# L'ETAT D'UNE INCARNATION PRECEDENTE — voir roles/client_backup/templates/restaurer.sh.j2
#
# make restauration-etat [HOTE=...]
# ce qui attend dans le depot, et ce que chaque jeu est devenu (lecture seule) ;
# make sauvegarder-maintenant [HOTE=...]
# deposer TOUT DE SUITE, et attendre que ce soit fait. A faire juste avant `raser` :
# sinon la reconstruction remet l'etat de la derniere nuit, et perd la journee ;
# make temoins-etat [HOTE=...] [SOURCE=candidat]
# l'etat VIVANT est-il celui d'avant le rasage ? Compare a l'instantane etiquete
# « avant-raser » (lecture seule). Une perte, ou une identite changee (cles de l'AC,
# DKIM, instanceid, mot de passe d'une entree de l'annuaire), fait echouer le jeu ;
# make restauration-renoncer HOTE=<hote> JEU=<jeu> CONFIRMER=true
# ECARTER un etat anterieur sans le remettre. La sauvegarde du noeud, qui refusait
# de deposer pour ne pas le chasser de la retention, reprend — et l'etat d'avant
# finira par sortir de la retention. C'est une decision, pas une reparation.
#
# La REMISE, elle, n'a pas de cible : elle est faite par le role proprietaire de l'etat,
# au deploiement, au moment ou il le creerait neuf.
- name: L'etat d'une incarnation precedente
hosts: client_backup
become: true
gather_facts: false
vars:
restauration_action: etat
restauration_jeu: ""
restauration_source: avant-raser
tasks:
- name: Refuser un renoncement sans jeu nomme
ansible.builtin.assert:
that:
- restauration_jeu | length > 0
fail_msg: "JEU=<jeu> requis (voir `make restauration-etat` pour les noms)."
when: restauration_action == 'renoncer'
- name: Lire ce qui attend, et ce qui a ete tranche
ansible.builtin.command:
argv: [/usr/local/sbin/setops-restaurer, etat]
register: restauration_lue
changed_when: false
failed_when: false
when: restauration_action == 'etat'
- name: Etat
ansible.builtin.debug:
msg: "{{ (restauration_lue.stdout_lines | default([])) + (restauration_lue.stderr_lines | default([])) }}"
when: restauration_action == 'etat'
# L'unite est `oneshot` : `systemctl start` rend la main quand le depot est fait, et
# echoue s'il a echoue — y compris quand la garde refuse (etat d'avant non remis).
- name: Ce noeud a-t-il quelque chose a deposer ?
ansible.builtin.stat:
path: /etc/systemd/system/setops-sauvegarde.service
register: restauration_unite
when: restauration_action == 'sauvegarder'
# LE DEPOT D'AVANT RASAGE EST ETIQUETE (2026-10-07), et la retention le garde jusqu'a la
# reconstruction suivante. Un noeud qui n'a pas encore cette unite deposerait SANS
# etiquette, et la premiere sauvegarde d'apres reconstruction le chasserait : c'est
# exactement ce qui s'est passe a M4. On refuse plutot que de deposer a moitie.
- name: L'unite du depot d'avant rasage est-elle deployee ?
ansible.builtin.stat:
path: /etc/systemd/system/setops-sauvegarde-avant-raser.service
register: restauration_unite_avant_raser
when:
- restauration_action == 'sauvegarder'
- restauration_unite.stat.exists
- name: Refuser un depot que la retention ne garderait pas
ansible.builtin.assert:
that:
- restauration_unite_avant_raser.stat.exists
fail_msg: >-
setops-sauvegarde-avant-raser.service absente : redeployer client_backup
(make appliquer GROUPE=client_backup) avant de raser.
when:
- restauration_action == 'sauvegarder'
- restauration_unite.stat.exists
- name: Deposer maintenant, etiquete avant rasage
ansible.builtin.systemd:
name: setops-sauvegarde-avant-raser.service
state: started
when:
- restauration_action == 'sauvegarder'
- restauration_unite.stat.exists
# Code 4 = rien a comparer (aucun etat, ou aucun instantane d'avant rasage) : ce n'est
# pas un echec. Code 1 = ecart : on affiche d'abord, on echoue ensuite.
- name: Temoins — l'etat vivant contre l'instantane d'avant rasage
ansible.builtin.command:
argv: >-
{{ ['/usr/local/sbin/setops-restaurer', 'temoins']
+ (['--candidat'] if restauration_source == 'candidat' else []) }}
register: restauration_temoins
changed_when: false
failed_when: false
when: restauration_action == 'temoins'
- name: Temoins
ansible.builtin.debug:
msg: "{{ (restauration_temoins.stdout_lines | default([])) + (restauration_temoins.stderr_lines | default([])) }}"
when: restauration_action == 'temoins'
- name: Temoins — verdict
ansible.builtin.assert:
that:
- restauration_temoins.rc in [0, 4]
fail_msg: >-
{{ 'ECART : une perte, ou une identite changee (voir ci-dessus).'
if restauration_temoins.rc == 1 else
'setops-restaurer temoins a echoue (code ' ~ restauration_temoins.rc ~ ') : '
~ 'client_backup est-il deploye ?' }}
quiet: true
when: restauration_action == 'temoins'
- name: Ecarter l'etat anterieur de ce jeu
ansible.builtin.command:
argv: [/usr/local/sbin/setops-restaurer, acter, "{{ restauration_jeu }}", abandonne]
changed_when: true
when: restauration_action == 'renoncer'

View file

@ -0,0 +1,38 @@
---
# client_artefacts — L'ORDRE FAIT PARTIE DE L'INTEGRATION (2026-08-25).
#
# Cette integration depend de `serveur_artefacts`, et sa metadonnee le declare
# (`roles/client_artefacts/meta/integration.yml`). L'hote qui PORTE ce serveur est donc
# configure AVANT ceux qui s'y adressent — sinon les clients s'enrolent aupres d'un
# service qui n'est pas encore la, ou qui redemarre au meme instant.
#
# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart.
#
# `any_errors_fatal` sur le premier play : si le serveur n'a pas pu etre configure,
# y raccrocher des clients ne produit que des echecs qui accusent le reseau.
- name: Intégration client_artefacts — le serveur d'abord
hosts: client_artefacts:&serveur_artefacts
become: true
any_errors_fatal: true
module_defaults: &defauts_artefacts
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks: &pre_artefacts
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_artefacts
- name: Intégration client_artefacts — puis les hôtes qui s'y adressent
hosts: client_artefacts:!serveur_artefacts
become: true
module_defaults: *defauts_artefacts
gather_facts: true
pre_tasks: *pre_artefacts
roles:
- client_artefacts

View file

@ -1,22 +1,38 @@
---
- name: Appliquer le groupe client_journal
hosts: client_journal
# client_journal — L'ORDRE FAIT PARTIE DE L'INTEGRATION (2026-08-25).
#
# Cette integration depend de `serveur_loki`, et sa metadonnee le declare
# (`roles/client_journal/meta/integration.yml`). L'hote qui PORTE ce serveur est donc
# configure AVANT ceux qui s'y adressent — sinon les clients s'enrolent aupres d'un
# service qui n'est pas encore la, ou qui redemarre au meme instant.
#
# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart.
#
# `any_errors_fatal` sur le premier play : si le serveur n'a pas pu etre configure,
# y raccrocher des clients ne produit que des echecs qui accusent le reseau.
- name: Intégration client_journal — le serveur d'abord
hosts: client_journal:&serveur_loki
become: true
# Le verrou dpkg est tenu par les maj automatiques de Debian, par vagues, plusieurs
# minutes apres le premier demarrage. Pose ICI plutot que dans chaque role : toute
# tache apt du play en herite, y compris celles des roles inclus.
module_defaults:
any_errors_fatal: true
module_defaults: &defauts_journal
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks:
pre_tasks: &pre_journal
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_journal
- name: Intégration client_journal — puis les hôtes qui s'y adressent
hosts: client_journal:!serveur_loki
become: true
module_defaults: *defauts_journal
gather_facts: true
pre_tasks: *pre_journal
roles:
- client_journal

View file

@ -1,22 +1,38 @@
---
- name: Appliquer le groupe client_pki
hosts: client_pki
# client_pki — L'ORDRE FAIT PARTIE DE L'INTEGRATION (2026-08-25).
#
# Cette integration depend de `serveur_step_ca`, et sa metadonnee le declare
# (`roles/client_pki/meta/integration.yml`). L'hote qui PORTE ce serveur est donc
# configure AVANT ceux qui s'y adressent — sinon les clients s'enrolent aupres d'un
# service qui n'est pas encore la, ou qui redemarre au meme instant.
#
# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart.
#
# `any_errors_fatal` sur le premier play : si le serveur n'a pas pu etre configure,
# y raccrocher des clients ne produit que des echecs qui accusent le reseau.
- name: Intégration client_pki — le serveur d'abord
hosts: client_pki:&serveur_step_ca
become: true
# Le verrou dpkg est tenu par les maj automatiques de Debian, par vagues, plusieurs
# minutes apres le premier demarrage. Pose ICI plutot que dans chaque role : toute
# tache apt du play en herite, y compris celles des roles inclus.
module_defaults:
any_errors_fatal: true
module_defaults: &defauts_pki
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks:
pre_tasks: &pre_pki
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_pki
- name: Intégration client_pki — puis les hôtes qui s'y adressent
hosts: client_pki:!serveur_step_ca
become: true
module_defaults: *defauts_pki
gather_facts: true
pre_tasks: *pre_pki
roles:
- client_pki

View file

@ -0,0 +1,38 @@
---
# client_resolveur — L'ORDRE FAIT PARTIE DE L'INTEGRATION (2026-08-25).
#
# Cette integration depend de `serveur_resolveur`, et sa metadonnee le declare
# (`roles/client_resolveur/meta/integration.yml`). L'hote qui PORTE ce serveur est donc
# configure AVANT ceux qui s'y adressent — sinon les clients s'enrolent aupres d'un
# service qui n'est pas encore la, ou qui redemarre au meme instant.
#
# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart.
#
# `any_errors_fatal` sur le premier play : si le serveur n'a pas pu etre configure,
# y raccrocher des clients ne produit que des echecs qui accusent le reseau.
- name: Intégration client_resolveur — le serveur d'abord
hosts: client_resolveur:&serveur_resolveur
become: true
any_errors_fatal: true
module_defaults: &defauts_resolveur
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks: &pre_resolveur
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_resolveur
- name: Intégration client_resolveur — puis les hôtes qui s'y adressent
hosts: client_resolveur:!serveur_resolveur
become: true
module_defaults: *defauts_resolveur
gather_facts: true
pre_tasks: *pre_resolveur
roles:
- client_resolveur

View file

@ -0,0 +1,56 @@
---
# client_sante — L'ORDRE FAIT PARTIE DE L'INTEGRATION.
#
# Cette integration depend de `serveur_icinga`, et sa metadonnee le declare
# (`roles/client_sante/meta/integration.yml`). L'hote qui PORTE la supervision est donc
# configure AVANT ceux qui lui rapportent — sinon les noeuds poussent un resultat passif
# vers une API qui n'existe pas encore, et le premier verdict de chaque machine est un
# echec qui accuse le reseau.
#
# Ce fichier est le miroir de la declaration ; la preuve P44 refuse tout ecart.
#
# `any_errors_fatal` sur le premier play : si la supervision n'a pas pu etre configuree,
# y raccrocher des rapporteurs ne produit que du bruit.
- name: Intégration client_sante — le serveur d'abord
hosts: client_sante:&serveur_icinga
become: true
any_errors_fatal: true
module_defaults: &defauts_sante
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks: &pre_sante
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_sante
- name: Intégration client_sante — puis les hôtes qui s'y adressent
hosts: client_sante:!serveur_icinga
become: true
module_defaults: *defauts_sante
gather_facts: true
pre_tasks: *pre_sante
roles:
- client_sante
# LE RETRAIT, SUR LES HOTES SORTIS DU GROUPE (2026-10-03). Un role qu'on cesse d'appliquer
# laisse son minuteur derriere lui : les hyperviseurs du site ont pousse vers une adresse
# perimee pendant des semaines (voir `roles/client_sante/tasks/retirer.yml`).
#
# LE GROUPE EST NOMME, pas deduit (`all:!client_sante`) : un hyperviseur n'est pas une VM
# de la flotte, et un play qui le touche doit le dire. Chez un tenant, `hyperviseurs`
# n'existe pas et ce play ne trouve personne.
- name: Intégration client_sante — retrait sur les hyperviseurs qui n'y sont plus
hosts: hyperviseurs:!client_sante
become: true
gather_facts: false
tasks:
- name: Retirer le porteur de santé
ansible.builtin.include_role:
name: client_sante
tasks_from: retirer.yml

View file

@ -0,0 +1,19 @@
---
- name: Appliquer le groupe serveur_artefacts
hosts: serveur_artefacts
become: true
module_defaults:
ansible.builtin.apt:
lock_timeout: 300
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_artefacts

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_backup_site
hosts: serveur_backup_site
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_backup_site

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_cache_site
hosts: serveur_cache_site
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_cache_site

Some files were not shown because too many files have changed in this diff Show more