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
This commit is contained in:
Daniel Allaire 2026-09-13 20:03:58 -04:00
parent bd0e40753b
commit 77cf63f1cf
3 changed files with 41 additions and 4 deletions

View file

@ -1,5 +1,23 @@
# CHANGELOG — Set-OPS
## 2026-09-13 (7) — Un hote retire du plan laissait son pare-feu derriere lui
En retirant `backup-01` du plan de TechnoLibre, `flux-genere/backup-01.nft` est reste sur
le disque : un ruleset complet, pour une machine qui n'existe plus, indiscernable des
autres au premier coup d'oeil. La generation ECRIVAIT sans jamais RETIRER.
Ce que ca coute : `flux-genere/` cesse d'etre une IMAGE du plan pour devenir le cumul de
tous les plans successifs. Qui lit le dossier pour savoir ce qu'un ecosysteme expose lit
alors un etat qui n'a jamais existe.
**P53 le voyait deja** — un ruleset perime n'a pas de refus audible — mais il designait le
fichier, pas la cause. La generation supprime desormais les orphelins **et le dit** :
note : backup-01.nft retire — cet hote n'est plus au plan.
C'est le meme patron que les neuf resolutions d'instance : un dossier GENERE doit etre le
reflet exact de sa source, donc la generation doit aussi supprimer.
## 2026-09-13 (6) — `balloon: 0` ne desactive pas le ballooning : il retire le peripherique
Soixante-et-un gigaoctets de RAM etaient **declares** a vingt-et-une machines. Mesure a

View file

@ -30,7 +30,7 @@
| 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 : 27 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| 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 : 5 instance(s) federee(s), aucun index en collision. |
@ -44,7 +44,7 @@
| 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 | 63 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 (140 cle(s) declaree(s) par l'instance). |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (146 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). |
@ -65,9 +65,9 @@
| 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 236 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 |
| 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. |
| 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 0. |
| 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 (74 preuves, 68 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |

View file

@ -685,6 +685,25 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -
chemin = sortie_dir / f"{hote}.nft"
chemin.write_text(contenu, encoding="utf-8")
ecrits.append(chemin)
# UN HOTE RETIRE DU PLAN LAISSAIT SON PARE-FEU DERRIERE LUI (2026-09-13).
#
# Cette boucle ECRIVAIT sans jamais RETIRER. En retirant `backup-01` du plan d'un
# locataire, son `backup-01.nft` est reste sur le disque — un ruleset complet pour une
# machine qui n'existe plus, impossible a distinguer des autres au premier coup d'oeil.
#
# CE QUE CA COUTE : `flux-genere/` cesse d'etre une IMAGE du plan pour devenir un
# cumul de tous les plans successifs. Quiconque lit le dossier pour savoir ce que
# l'ecosysteme expose lit alors un etat qui n'a jamais existe.
#
# C'est le meme patron que les neuf resolutions d'instance et que les listes qui se
# suivent : un dossier GENERE doit etre le reflet exact de sa source, donc la
# generation doit aussi SUPPRIMER. La garde est posee en meme temps que le remede —
# **P53** voyait deja l'orphelin, puisqu'un ruleset perime n'a pas de refus audible.
for orphelin in sorted(sortie_dir.glob("*.nft")):
if orphelin not in ecrits:
orphelin.unlink()
print(f"note : {orphelin.name} retire — cet hote n'est plus au plan.",
file=sys.stderr)
return ecrits