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
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Les runbooks de construction : l'ordre des gestes, et la garde qui l'empeche de mentir.
|
|
|
|
|
|
|
|
|
|
CE QUE CE MODULE RESOUT (2026-09-20). Le Makefile porte 132 cibles documentees. Elles
|
|
|
|
|
disent chacune ce qu'elle fait, aucune ne dit DANS QUEL ORDRE ni POURQUOI MAINTENANT.
|
|
|
|
|
Un exploitant qui decouvre un site n'a donc aucun moyen de savoir que `site-creer` vient
|
|
|
|
|
avant `forge-amorcer`, que le premier passage s'arrete sur une forge vide, ni que rien
|
|
|
|
|
n'est « pret » avant `valider`. Cette connaissance vit dans les documents, en prose, et
|
|
|
|
|
la console ne la porte pas : elle offre des boutons sans sequence.
|
|
|
|
|
|
|
|
|
|
CE QUI EST DECLARE ICI, ET CE QUI NE L'EST PAS. `docs/runbooks-construction.yml` declare
|
|
|
|
|
l'ORDRE, la NATURE (mesure / ecriture / destructif), la PORTEE et le POURQUOI de chaque
|
|
|
|
|
geste. Il ne recopie JAMAIS le libelle d'une cible : celui-ci est lu dans le Makefile au
|
|
|
|
|
moment de servir. Une seconde liste qui recopierait la premiere prendrait du retard sur
|
|
|
|
|
elle — c'est arrive quatre fois dans ce depot en une seule journee, et la regle qui en
|
|
|
|
|
est sortie est d'ecrire la garde EN MEME TEMPS que la seconde liste.
|
|
|
|
|
|
|
|
|
|
LA GARDE, C'EST `verifier()`. Elle refuse quatre etats :
|
|
|
|
|
|
|
|
|
|
- une etape qui vise une cible que le Makefile n'a pas (le registre a pris du retard) ;
|
|
|
|
|
- une cible documentee que nul runbook ne porte et que nul motif n'exempte (la console
|
|
|
|
|
cacherait un pouvoir que le moteur possede) ;
|
|
|
|
|
- une cible a la fois portee et exemptee (deux verites, aucune) ;
|
|
|
|
|
- une valeur hors vocabulaire : nature, portee, ou variable inconnue du catalogue.
|
|
|
|
|
|
|
|
|
|
P83 rejoue cette garde. Sans elle, ce fichier deviendrait une carte perimee — et une
|
|
|
|
|
carte perimee est pire que pas de carte, parce qu'on la suit.
|
|
|
|
|
"""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
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:15:03 -04:00
|
|
|
import json
|
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
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
RACINE = Path(__file__).resolve().parent.parent
|
|
|
|
|
REGISTRE = RACINE / "docs/runbooks-construction.yml"
|
|
|
|
|
|
|
|
|
|
# Le vocabulaire ferme du registre. Une valeur hors de ces ensembles est refusee : mieux
|
|
|
|
|
# vaut un registre qui bloque qu'une console qui interprete une nature qu'elle ignore.
|
|
|
|
|
NATURES = {
|
|
|
|
|
# `mesure` n'ecrit rien et peut etre rejouee sans consequence. La console la propose
|
|
|
|
|
# toujours, meme quand l'etape precedente a echoue : mesurer pour comprendre est
|
|
|
|
|
# exactement ce qu'on fait apres un echec.
|
|
|
|
|
"mesure",
|
|
|
|
|
# `ecriture` change l'etat du monde. La console exige que l'etape precedente ait
|
|
|
|
|
# reussi, sinon on batit sur un terrain qu'on n'a pas verifie.
|
|
|
|
|
"ecriture",
|
|
|
|
|
# `destructif` detruit. La console exige une confirmation ecrite, en plus de la
|
|
|
|
|
# variable CONFIRMER que la cible reclame deja d'elle-meme.
|
|
|
|
|
"destructif",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# La portee, au sens de `contexte()` dans la console : ce que la MACHINE porte.
|
|
|
|
|
# tenant — un ecosysteme est monte (`instance/`), on a sa voute : on 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 (verifications du depot lui-meme).
|
|
|
|
|
PORTEES = {"tenant", "site", "poste", "toute"}
|
|
|
|
|
|
|
|
|
|
MOTIF_CIBLE = re.compile(r"^([a-z0-9][a-z0-9_-]*):[^#\n]*##\s*(.+?)\s*$", re.MULTILINE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cibles_du_makefile(racine: Path = RACINE) -> dict[str, str]:
|
|
|
|
|
"""Les cibles DOCUMENTEES du Makefile, avec leur libelle — la source unique.
|
|
|
|
|
|
|
|
|
|
Une cible sans `##` est un detail d'implementation (`_instance-requise`,
|
|
|
|
|
`ansible-runtime`) : elle n'est pas offerte a l'exploitant, donc elle n'est pas
|
|
|
|
|
attendue dans un runbook.
|
|
|
|
|
"""
|
|
|
|
|
texte = (racine / "Makefile").read_text(encoding="utf-8")
|
|
|
|
|
return {nom: aide for nom, aide in MOTIF_CIBLE.findall(texte)}
|
|
|
|
|
|
|
|
|
|
|
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:15:03 -04:00
|
|
|
def cibles_qui_exigent_confirmation(racine: Path = RACINE) -> set[str]:
|
|
|
|
|
"""Les cibles dont la RECETTE teste `CONFIRMER`.
|
|
|
|
|
|
|
|
|
|
POURQUOI LIRE LA RECETTE ET NON LE REGISTRE. Comparer `nature` a `fixes`
|
|
|
|
|
ne compare que deux champs ecrits par la meme main : le meme mensonge
|
|
|
|
|
repasse en EFFACANT la ligne `fixes`. La recette, elle, ne ment pas — elle
|
|
|
|
|
refuse, et c'est ce refus que l'exploitant rencontre.
|
|
|
|
|
|
|
|
|
|
Une recette va de sa ligne de cible jusqu'a la prochaine ligne non
|
|
|
|
|
indentee ; une ligne vide ne l'interrompt pas.
|
|
|
|
|
"""
|
|
|
|
|
texte = (racine / "Makefile").read_text(encoding="utf-8")
|
|
|
|
|
exigent: set[str] = set()
|
|
|
|
|
etat: dict = {"cible": "", "recette": []}
|
|
|
|
|
|
|
|
|
|
def _clore() -> None:
|
|
|
|
|
if etat["cible"] and "CONFIRMER" in "\n".join(etat["recette"]):
|
|
|
|
|
exigent.add(etat["cible"])
|
|
|
|
|
|
|
|
|
|
for ligne in texte.splitlines():
|
|
|
|
|
debut = re.match(r"^([A-Za-z0-9_.-]+):", ligne)
|
|
|
|
|
if debut:
|
|
|
|
|
_clore()
|
|
|
|
|
etat["cible"], etat["recette"] = debut.group(1), []
|
|
|
|
|
elif etat["cible"] and (ligne.startswith("\t") or ligne.startswith(" ")):
|
|
|
|
|
etat["recette"].append(ligne)
|
|
|
|
|
elif ligne.strip():
|
|
|
|
|
_clore()
|
|
|
|
|
etat["cible"], etat["recette"] = "", []
|
|
|
|
|
_clore()
|
|
|
|
|
return exigent
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
def charger(chemin: Path = REGISTRE) -> dict:
|
|
|
|
|
"""Le registre brut, tel qu'il est ecrit. Aucune jointure, aucune validation."""
|
|
|
|
|
if not chemin.is_file():
|
|
|
|
|
raise FileNotFoundError(f"Registre des runbooks introuvable : {chemin}")
|
|
|
|
|
data = yaml.safe_load(chemin.read_text(encoding="utf-8")) or {}
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
raise ValueError(f"{chemin} ne contient pas une table YAML.")
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assembler(racine: Path = RACINE, chemin: Path | None = None) -> list[dict]:
|
|
|
|
|
"""Les runbooks, joints au Makefile — la forme que la console sert.
|
|
|
|
|
|
|
|
|
|
Le libelle de chaque etape vient d'`aide`, lue dans le Makefile a CET instant. Le
|
|
|
|
|
registre n'en porte pas de copie, donc il ne peut pas en porter une perimee.
|
|
|
|
|
"""
|
|
|
|
|
data = charger(chemin or REGISTRE)
|
|
|
|
|
aides = cibles_du_makefile(racine)
|
|
|
|
|
catalogue = data.get("variables", {}) or {}
|
|
|
|
|
sortie = []
|
|
|
|
|
for rb in data.get("runbooks", []) or []:
|
|
|
|
|
etapes = []
|
|
|
|
|
for etape in rb.get("etapes", []) or []:
|
|
|
|
|
cible = etape.get("cible", "")
|
|
|
|
|
etapes.append({
|
|
|
|
|
"cible": cible,
|
|
|
|
|
# LE LIBELLE N'EST PAS DANS LE REGISTRE. Il est lu ici, chaque fois.
|
|
|
|
|
"libelle": aides.get(cible, ""),
|
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
|
|
|
# LA PORTEE SE PESE A L'ETAPE, PAS AU RUNBOOK (2026-09-20, mesure sur la
|
|
|
|
|
# console de TechnoLibre). Une seule etape qui materialise faisait basculer
|
|
|
|
|
# toute la sequence en `poste`, et un locataire ne pouvait plus deployer
|
|
|
|
|
# SA PROPRE flotte — exactement son metier. Le runbook donne le defaut ;
|
|
|
|
|
# l'etape qui exige davantage le declare.
|
|
|
|
|
"portee": etape.get("portee", rb.get("portee", "toute")),
|
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
|
|
|
"nature": etape.get("nature", "mesure"),
|
|
|
|
|
"pourquoi": etape.get("pourquoi", ""),
|
|
|
|
|
"duree": etape.get("duree", ""),
|
|
|
|
|
"variables": [
|
|
|
|
|
{"nom": v, **(catalogue.get(v, {}) or {})}
|
|
|
|
|
for v in (etape.get("variables", []) or [])
|
|
|
|
|
],
|
|
|
|
|
"fixes": etape.get("fixes", {}) or {},
|
|
|
|
|
"facultative": bool(etape.get("facultative", False)),
|
|
|
|
|
})
|
|
|
|
|
sortie.append({
|
|
|
|
|
"id": rb.get("id", ""),
|
|
|
|
|
"titre": rb.get("titre", ""),
|
|
|
|
|
"portee": rb.get("portee", "toute"),
|
|
|
|
|
"but": rb.get("but", ""),
|
|
|
|
|
"doc": rb.get("doc", ""),
|
|
|
|
|
"etapes": etapes,
|
|
|
|
|
})
|
|
|
|
|
return sortie
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def etape_declaree(runbook_id: str, cible: str, racine: Path = RACINE,
|
|
|
|
|
index: int | None = None) -> dict | None:
|
|
|
|
|
"""L'etape declaree, ou None — LE point de controle du serveur.
|
|
|
|
|
|
|
|
|
|
La console ne lance JAMAIS ce que le navigateur lui nomme : elle lance ce que le
|
|
|
|
|
registre declare a cette place-la. Un navigateur qui reclamerait `raser` dans un
|
|
|
|
|
runbook de mesure n'obtient rien, et c'est le seul verrou qui compte.
|
|
|
|
|
|
|
|
|
|
L'INDEX COMPTE PARCE QU'UNE CIBLE PEUT REVENIR. « Le premier jour d'un site » lance
|
|
|
|
|
`site-deployer-tout` DEUX fois — le premier passage s'arrete sur la forge vide, le
|
|
|
|
|
second doit finir a zero echec. Chercher par nom seul confondrait les deux places.
|
|
|
|
|
Quand l'appelant donne un index, la cible doit s'y trouver : sinon la page et le
|
|
|
|
|
registre ne parlent pas de la meme chose, et on ne lance rien.
|
|
|
|
|
"""
|
|
|
|
|
for rb in assembler(racine):
|
|
|
|
|
if rb["id"] != runbook_id:
|
|
|
|
|
continue
|
|
|
|
|
etapes = rb["etapes"]
|
|
|
|
|
if index is not None:
|
|
|
|
|
if not (0 <= index < len(etapes)) or etapes[index]["cible"] != cible:
|
|
|
|
|
return None
|
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
|
|
|
return {**etapes[index], "runbook": rb["titre"],
|
|
|
|
|
"portee_runbook": rb["portee"]}
|
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
|
|
|
for etape in etapes:
|
|
|
|
|
if etape["cible"] == cible:
|
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
|
|
|
return {**etape, "runbook": rb["titre"],
|
|
|
|
|
"portee_runbook": rb["portee"]}
|
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
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def verifier(racine: Path = RACINE, chemin: Path | None = None) -> list[str]:
|
|
|
|
|
"""La garde. Rend la liste des ecarts — vide quand le registre dit vrai."""
|
|
|
|
|
data = charger(chemin or REGISTRE)
|
|
|
|
|
aides = cibles_du_makefile(racine)
|
|
|
|
|
catalogue = data.get("variables", {}) or {}
|
|
|
|
|
hors = data.get("hors_assistant", {}) or {}
|
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:15:03 -04:00
|
|
|
exigent = cibles_qui_exigent_confirmation(racine)
|
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
|
|
|
ecarts: list[str] = []
|
|
|
|
|
portees_vues: dict[str, set[str]] = {}
|
|
|
|
|
|
|
|
|
|
vues: set[str] = set()
|
|
|
|
|
ids: set[str] = set()
|
|
|
|
|
for rb in data.get("runbooks", []) or []:
|
|
|
|
|
rid = rb.get("id", "")
|
|
|
|
|
if not rid:
|
|
|
|
|
ecarts.append("Un runbook n'a pas d'identifiant.")
|
|
|
|
|
continue
|
|
|
|
|
if rid in ids:
|
|
|
|
|
ecarts.append(f"{rid} : deux runbooks portent cet identifiant.")
|
|
|
|
|
ids.add(rid)
|
|
|
|
|
if rb.get("portee") not in PORTEES:
|
|
|
|
|
ecarts.append(f"{rid} : portee « {rb.get('portee')} » hors vocabulaire "
|
|
|
|
|
f"({', '.join(sorted(PORTEES))}).")
|
|
|
|
|
if not rb.get("but"):
|
|
|
|
|
ecarts.append(f"{rid} : aucun but declare — un runbook sans but ne se choisit pas.")
|
|
|
|
|
if not (rb.get("etapes") or []):
|
|
|
|
|
ecarts.append(f"{rid} : aucune etape.")
|
|
|
|
|
for etape in rb.get("etapes", []) or []:
|
|
|
|
|
cible = etape.get("cible", "")
|
|
|
|
|
if cible not in aides:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : le Makefile ne porte pas cette cible "
|
|
|
|
|
f"(registre en retard, ou cible renommee).")
|
|
|
|
|
vues.add(cible)
|
|
|
|
|
portees_vues.setdefault(cible, set()).add(rb.get("portee", ""))
|
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
|
|
|
if "portee" in etape and etape["portee"] not in PORTEES:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : portee d'etape « {etape['portee']} » hors "
|
|
|
|
|
f"vocabulaire ({', '.join(sorted(PORTEES))}).")
|
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
|
|
|
if etape.get("nature") not in NATURES:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : nature « {etape.get('nature')} » hors "
|
|
|
|
|
f"vocabulaire ({', '.join(sorted(NATURES))}).")
|
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:15:03 -04:00
|
|
|
# LA RECETTE TRANCHE, DANS LES DEUX SENS. Ce que la cible EXIGE
|
|
|
|
|
# se lit dans son Makefile ; ce que le registre PROMET se lit dans
|
|
|
|
|
# `fixes`. Comparer le registre a lui-meme laissait passer le meme
|
|
|
|
|
# mensonge ecrit dans l'autre sens — il suffisait d'effacer
|
|
|
|
|
# `fixes`, et trois cibles le portaient ainsi : un assistant les
|
|
|
|
|
# lancait sans confirmation, et sortait en 2.
|
|
|
|
|
declare = "CONFIRMER" in (etape.get("fixes") or {})
|
|
|
|
|
if cible in exigent and not declare:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : la recette refuse sans CONFIRMER "
|
|
|
|
|
f"et le registre ne le declare pas. Un assistant la "
|
|
|
|
|
f"lancerait telle quelle, et sortirait en 2.")
|
|
|
|
|
if declare and cible not in exigent:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : le registre declare CONFIRMER que "
|
|
|
|
|
f"la recette n'exige pas. Une confirmation de plus "
|
|
|
|
|
f"s'apprend a cliquer sans lire.")
|
|
|
|
|
# UNE MESURE QUI AGIT N'EN EST PAS UNE : une cible qui refuse sans
|
|
|
|
|
# confirmation TOUCHE au systeme, quel que soit le fait qu'elle en
|
|
|
|
|
# rapporte ensuite.
|
|
|
|
|
if etape.get("nature") == "mesure" and (declare or cible in exigent):
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : declaree « mesure » alors qu'elle "
|
|
|
|
|
f"exige CONFIRMER. Une mesure qui demande "
|
|
|
|
|
f"confirmation agit.")
|
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
|
|
|
if not etape.get("pourquoi"):
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : aucun « pourquoi ». Une etape qui ne dit "
|
|
|
|
|
f"pas pourquoi elle est la se fait sauter.")
|
|
|
|
|
for v in etape.get("variables", []) or []:
|
|
|
|
|
if v not in catalogue:
|
|
|
|
|
ecarts.append(f"{rid} → {cible} : la variable {v} n'est pas au "
|
|
|
|
|
f"catalogue — la console ne saurait pas quoi demander.")
|
|
|
|
|
|
|
|
|
|
# LA COUVERTURE, DANS LES DEUX SENS.
|
|
|
|
|
for cible in sorted(set(aides) - vues - set(hors)):
|
|
|
|
|
ecarts.append(f"{cible} : cible documentee que nul runbook ne porte et que nul "
|
|
|
|
|
f"motif n'exempte. La console cacherait un pouvoir du moteur.")
|
|
|
|
|
for cible in sorted(vues & set(hors)):
|
|
|
|
|
ecarts.append(f"{cible} : portee par un runbook ET declaree hors assistant — "
|
|
|
|
|
f"deux verites, aucune.")
|
|
|
|
|
for cible in sorted(set(hors) - set(aides)):
|
|
|
|
|
ecarts.append(f"{cible} : exemptee alors que le Makefile ne la porte pas "
|
|
|
|
|
f"(exemption devenue sans objet).")
|
|
|
|
|
for cible, motif in sorted(hors.items()):
|
|
|
|
|
if not str(motif or "").strip():
|
|
|
|
|
ecarts.append(f"{cible} : exemptee sans motif. Une exemption muette est un oubli.")
|
|
|
|
|
return ecarts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
|
|
|
parser.add_argument("action", choices=("verifier", "lister", "couverture"),
|
|
|
|
|
nargs="?", default="verifier")
|
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:15:03 -04:00
|
|
|
# POURQUOI UN --json. Le registre est la seule source qui dise la NATURE
|
|
|
|
|
# d'une etape et la confirmation qu'elle exige. Un outil tiers qui veut
|
|
|
|
|
# n'offrir que ce qui ne detruit pas doit donc le lire — et sans drapeau,
|
|
|
|
|
# il n'a le choix qu'entre analyser un arbre fait pour l'oeil humain, qui
|
|
|
|
|
# derive au premier changement de mise en page, et importer ce module,
|
|
|
|
|
# ce qui le lie a nos noms internes. Les deux se paient plus tard.
|
|
|
|
|
parser.add_argument("--json", action="store_true",
|
|
|
|
|
help="Rend le registre assemble en JSON (avec "
|
|
|
|
|
"« lister »), pour un outil tiers.")
|
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
|
|
|
args = parser.parse_args()
|
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:15:03 -04:00
|
|
|
# RENDRE LA PROSE HUMAINE A QUI DEMANDE DU JSON est pire qu'un refus :
|
|
|
|
|
# l'appelant lit une reponse qu'il croit analysable, et ne s'en apercoit
|
|
|
|
|
# qu'au premier caractere inattendu.
|
|
|
|
|
if args.json and args.action != "lister":
|
|
|
|
|
parser.error("--json n'existe qu'avec « lister ».")
|
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
|
|
|
|
|
|
|
|
if args.action == "verifier":
|
|
|
|
|
ecarts = verifier()
|
|
|
|
|
if ecarts:
|
|
|
|
|
print(f"Registre des runbooks : {len(ecarts)} ecart(s).\n")
|
|
|
|
|
for e in ecarts:
|
|
|
|
|
print(f" - {e}")
|
|
|
|
|
return 1
|
|
|
|
|
rbs = assembler()
|
|
|
|
|
etapes = sum(len(r["etapes"]) for r in rbs)
|
|
|
|
|
print(f"Registre des runbooks : {len(rbs)} runbooks, {etapes} etapes, "
|
|
|
|
|
f"{len(cibles_du_makefile())} cibles documentees, aucun ecart.")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if args.action == "lister":
|
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:15:03 -04:00
|
|
|
if args.json:
|
|
|
|
|
# Sortie NUE, sans en-tete ni resume : un appelant la lit d'un
|
|
|
|
|
# bloc. `ensure_ascii=False` garde les accents du registre.
|
|
|
|
|
print(json.dumps(assembler(), ensure_ascii=False))
|
|
|
|
|
return 0
|
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
|
|
|
for rb in assembler():
|
|
|
|
|
print(f"\n{rb['id']} [{rb['portee']}] {rb['titre']}")
|
|
|
|
|
print(f" {rb['but']}")
|
|
|
|
|
for i, e in enumerate(rb["etapes"], 1):
|
|
|
|
|
marque = {"mesure": " ", "ecriture": "*", "destructif": "!"}[e["nature"]]
|
|
|
|
|
var = (" " + " ".join(f"{v['nom']}=…" for v in e["variables"])) if e["variables"] else ""
|
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
|
|
|
sup = "" if e["portee"] == rb["portee"] else f" [{e['portee']}]"
|
|
|
|
|
print(f" {marque} {i:2}. make {e['cible']}{var}{sup}")
|
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
|
|
|
print(f" {e['libelle']}")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
# couverture
|
|
|
|
|
aides = cibles_du_makefile()
|
|
|
|
|
data = charger()
|
|
|
|
|
vues = {e.get("cible") for rb in data.get("runbooks", []) or []
|
|
|
|
|
for e in rb.get("etapes", []) or []}
|
|
|
|
|
hors = set(data.get("hors_assistant", {}) or {})
|
|
|
|
|
print(f"cibles documentees : {len(aides)}")
|
|
|
|
|
print(f" portees par un runbook : {len(vues & set(aides))}")
|
|
|
|
|
print(f" hors assistant (motivees) : {len(hors & set(aides))}")
|
|
|
|
|
print(f" ni l'un ni l'autre : {len(set(aides) - vues - hors)}")
|
|
|
|
|
for c in sorted(set(aides) - vues - hors):
|
|
|
|
|
print(f" - {c} ({aides[c]})")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|