Set-OPS-Public/scripts/applications.py

165 lines
6.5 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Lit et valide le registre des applications Set-OPS (instance/plan/applications.yml)."""
from __future__ import annotations
import os
import argparse
import sys
from pathlib import Path
import yaml
from inventory_gui import charger_yaml, liste_hotes
from inventory_rules import (
plan : l ecriture des registres devient atomique — tout, ou rien `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
ecriture_atomique,
bases_de_application,
chaine_connexion,
charger_applications,
charger_bases_donnees,
charger_domaines,
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
charger_serveurs,
resolution d'instance : une seule, partagee — au lieu de neuf copies 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
instance_courante,
inventaire_de,
valider_applications,
)
RACINE = Path(__file__).resolve().parents[1]
resolution d'instance : une seule, partagee — au lieu de neuf copies 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
INSTANCE = instance_courante()
FICHIER = INSTANCE / "plan/applications.yml"
FICHIER_BASES = INSTANCE / "plan/bases-donnees.yml"
FICHIER_DOMAINES = INSTANCE / "plan/domaines.yml"
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
FICHIER_SERVEURS = INSTANCE / "plan/serveurs.yml"
resolution d'instance : une seule, partagee — au lieu de neuf copies 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
INVENTAIRE = inventaire_de(INSTANCE)
# Groupes 'serveurs_*' qui sont des capacites de SOCLE (sur toutes les VM),
# pas des applications a part entiere.
GROUPES_SOCLE = {"serveur_debian", "serveur_durci"}
def _liste(valeur: str) -> list:
return [v.strip() for v in (valeur or "").split(",") if v.strip()]
def ecrire(registre: dict) -> None:
entete = (
"# Registre des applications Set-OPS (application = entite pivot).\n"
"# Edite par make inventaire-ui ou scripts/applications.py.\n"
"# Une VM peut porter plusieurs applications ; une base se lie a une application.\n"
"---\n"
)
plan : l ecriture des registres devient atomique — tout, ou rien `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
with ecriture_atomique(FICHIER) as fichier:
fichier.write(entete)
yaml.safe_dump({"applications": registre.get("applications", {}) or {}},
fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
def lister(registre: dict, bases: dict) -> None:
apps = registre.get("applications", {})
if not apps:
print("Aucune application declaree.")
return
for nom, app in apps.items():
extras = []
if app.get("port"):
extras.append(f"port {app['port']}")
if app.get("requiert"):
extras.append(f"requiert {', '.join(app['requiert'])}")
if app.get("expose"):
extras.append(f"expose {', '.join(app['expose'])}")
suffixe = (" {" + " ; ".join(extras) + "}") if extras else ""
print(f"{nom} [groupe {app.get('groupe', '?')}, hote {app.get('hote', '?')}]{suffixe}")
for entree in bases_de_application(bases, application=nom,
groupe=app.get("groupe"), hote=app.get("hote")):
base = entree["base"]
portee = base.get("portee", "groupe")
print(f" DSN ({portee}/{base.get('usage', '-')}): {chaine_connexion(base, entree['serveur'])}")
def bootstrap(registre_existant: dict) -> dict:
"""(Re)genere les applications depuis les appartenances de groupes de l'inventaire.
Un groupe 'serveurs_*' (hors socle) sur un hote = une application. Les entrees
existantes correspondant a un couple (groupe, hote) toujours present sont
preservees telles quelles (port / expose / requiert ...).
"""
existant = registre_existant.get("applications") or {}
par_cle = {(a.get("groupe"), a.get("hote")): (cle, a) for cle, a in existant.items()}
apps: dict = {}
for h in liste_hotes(charger_yaml(INVENTAIRE)):
hote = h.get("nom")
if not hote:
continue
for groupe in sorted(h.get("groupes", [])):
if not groupe.startswith("serveurs_") or groupe in GROUPES_SOCLE:
continue
garde = par_cle.get((groupe, hote))
if garde:
apps[garde[0]] = garde[1]
continue
base = groupe[len("serveurs_"):]
app_id = base if base not in apps else f"{base}-{hote}"
apps[app_id] = {"groupe": groupe, "hote": hote}
return {"applications": apps}
def main() -> int:
parser = argparse.ArgumentParser(description="Registre des applications Set-OPS.")
sub = parser.add_subparsers(dest="commande", required=True)
sub.add_parser("lister", help="Affiche les applications et leurs DSN resolus.")
sub.add_parser("verifier", help="Valide la coherence du registre.")
sub.add_parser("bootstrap", help="(Re)genere applications.yml depuis les groupes de l'inventaire.")
pa = sub.add_parser("ajouter", help="Ajoute ou met a jour une application.")
pa.add_argument("--id", required=True)
pa.add_argument("--groupe", required=True)
pa.add_argument("--hote", required=True)
pa.add_argument("--port", type=int)
pa.add_argument("--requiert", default="", help="Applications dont elle depend (separees par virgule).")
pa.add_argument("--expose", default="", help="FQDN publics qui la publient (separes par virgule).")
pr = sub.add_parser("retirer", help="Retire une application.")
pr.add_argument("--id", required=True)
args = parser.parse_args()
try:
registre = charger_applications(FICHIER)
domaines = charger_domaines(FICHIER_DOMAINES)
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
serveurs = charger_serveurs(FICHIER_SERVEURS)
if args.commande == "lister":
lister(registre, charger_bases_donnees(FICHIER_BASES))
elif args.commande == "verifier":
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
valider_applications(registre, domaines, serveurs)
print("Registre des applications valide.")
elif args.commande == "bootstrap":
registre = bootstrap(registre)
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
valider_applications(registre, domaines, serveurs)
ecrire(registre)
print(f"instance/plan/applications.yml genere depuis l'inventaire ({len(registre['applications'])} applications).")
elif args.commande == "ajouter":
app = {"groupe": args.groupe, "hote": args.hote}
if args.port is not None:
app["port"] = args.port
if _liste(args.requiert):
app["requiert"] = _liste(args.requiert)
if _liste(args.expose):
app["expose"] = _liste(args.expose)
registre["applications"][args.id] = app
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
valider_applications(registre, domaines, serveurs)
ecrire(registre)
print(f"Application '{args.id}' enregistree.")
elif args.commande == "retirer":
registre["applications"].pop(args.id, None)
Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:32:42 -04:00
valider_applications(registre, domaines, serveurs)
ecrire(registre)
print(f"Application '{args.id}' retiree.")
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())