P03 : la preuve du diff-vide peut enfin echouer — et elle echoue

instancier.py comparer affichait l'ecart puis renvoyait TOUJOURS 0. P03
« Diff-vide du plan » ne pouvait donc pas echouer : elle est passee au vert
pendant que quatorze hotes divergeaient du plan.

Deux appelants, deux besoins — d'ou un MODE plutot qu'un changement de
comportement : `make instancier` est une INSPECTION (voir le diff avant de
decider ; un code d'erreur y transformerait la lecture en panne), P03 est une
AFFIRMATION (« le plan reproduit l'inventaire »).

prouver sort desormais a 1, et c'est EXACT : depuis le retrait du decalage de
+10, le plan derive 10.17.x.x tandis que l'inventaire applique — et les quatorze
VM qui tournent — portent 10.27.x.x. Le depot est sciemment dans cet etat
jusqu'au renumerotage.

Une preuve rouge qui dit vrai vaut mieux qu'une verte qui ne regarde rien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-12 16:28:30 -04:00
parent 8b4ad276ca
commit 2c7849f6b3
3 changed files with 39 additions and 7 deletions

View file

@ -1,5 +1,23 @@
# CHANGELOG — Set-OPS
## 2026-08-12 — P03 peut enfin échouer, et elle échoue
`instancier.py comparer` affichait l'écart puis renvoyait **toujours `0`**. La preuve P03
« Diff-vide du plan » ne pouvait donc pas échouer : elle a passé au vert pendant que
quatorze hôtes divergeaient du plan.
Deux appelants, deux besoins — d'où un mode plutôt qu'un changement de comportement :
| Appelant | Attente |
|---|---|
| `make instancier` | **inspection** : voir le diff avant de décider. Un code d'erreur y transformerait la lecture en panne |
| **P03** | **affirmation** : « le plan reproduit l'inventaire ». Sans `--strict`, elle n'affirmait rien |
**`prouver` sort désormais à 1**, et c'est exact : depuis le retrait du décalage de `+10`,
le plan dérive `10.17.x.x` tandis que l'inventaire appliqué — et les quatorze VM qui
tournent — portent `10.27.x.x`. Le dépôt est sciemment dans cet état jusqu'au
renumérotage. Une preuve rouge qui dit vrai vaut mieux qu'une verte qui ne regarde rien.
## 2026-08-12 — L'index est borné : `10.300.0.0/16` n'est pas un réseau
Rien ne bornait `index`. `supernet_de(300)` rendait `"10.300.0.0/16"` — **une chaîne qui

View file

@ -296,14 +296,26 @@ def compter_ecarts(reference: Path, genere: Path) -> int:
return ecarts
def comparer() -> int:
def comparer(strict: bool = False) -> int:
"""Compare le genere a l'applique. `strict` fait ECHOUER sur un ecart.
DEUX APPELANTS, DEUX BESOINS. `make instancier` est une INSPECTION : on veut voir
le diff avant de decider, et un code d'erreur y transformerait la lecture en panne.
La preuve P03, elle, AFFIRME que « le plan reproduit l'inventaire » — sans `strict`,
elle renvoyait 0 quel que soit le nombre d'hotes divergents et ne pouvait donc pas
echouer. Constate le 2026-08-12 : quatorze hotes divergeaient, la preuve passait.
"""
ecrire(GENERE)
ecarts = compter_ecarts(INVENTAIRE, GENERE)
if ecarts == 0:
print("DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible.")
else:
print(f"\n{ecarts} hote(s) avec ecart.")
return 0
return 0
print(f"\n{ecarts} hote(s) avec ecart.")
if strict:
print("L'inventaire APPLIQUE ne correspond plus au plan. Soit le plan a change et "
"il faut le basculer (`make instancier-appliquer`), soit hosts.yml a ete "
"edite a la main — ce que le depot interdit.")
return 1 if strict else 0
def appliquer(force: bool = False) -> int:
@ -325,7 +337,9 @@ def main() -> int:
parser = argparse.ArgumentParser(description="Generateur d'inventaire Set-OPS (Phase 3).")
sub = parser.add_subparsers(dest="commande", required=True)
sub.add_parser("generer", help="Ecrit hosts.genere.yml depuis le plan.")
sub.add_parser("comparer", help="Compare le genere a l'inventaire actuel (semantique).")
sp_cmp = sub.add_parser("comparer", help="Compare le genere a l'inventaire actuel (semantique).")
sp_cmp.add_argument("--strict", action="store_true",
help="Sortir en erreur si le diff n'est pas vide (P03).")
pa = sub.add_parser("appliquer", help="Ecrit hosts.yml depuis le plan (refuse si diff non vide).")
pa.add_argument("--force", action="store_true", help="Applique meme si le diff n'est pas vide.")
args = parser.parse_args()
@ -334,7 +348,7 @@ def main() -> int:
ecrire()
print(f"{os.path.relpath(GENERE, RACINE)} genere depuis le plan.")
elif args.commande == "comparer":
return comparer()
return comparer(strict=getattr(args, "strict", False))
elif args.commande == "appliquer":
return appliquer(force=args.force)
except Exception as exc:

View file

@ -703,7 +703,7 @@ PREUVES: list[dict] = [
{"id": "P03", "titre": "Diff-vide du plan (inventaire genere)", "refs": ["AFF-001", "AFF-004", "AFF-030", "AFF-031", "AFF-032"],
"refs": ["AFF-001", "AFF-004", "AFF-030", "AFF-031", "AFF-032"],
"cmds": [[sys.executable, "scripts/instancier.py", "generer"],
[sys.executable, "scripts/instancier.py", "comparer"]]},
[sys.executable, "scripts/instancier.py", "comparer", "--strict"]]},
{"id": "P04", "titre": "Groupes <-> playbooks homonymes", "refs": ["AFF-008"],
"cmds": [[sys.executable, "scripts/inventory_host.py", "--inventaire", INV,
"verifier-playbooks", "--dossier-playbooks", GROUPES]]},