adressage : l'index est borne [0-255] — 10.300.0.0/16 n'est pas un reseau
Rien ne bornait `index`. supernet_de(300) rendait « 10.300.0.0/16 » : une CHAINE
qui ressemble a un reseau. Elle traverse tout le moteur sans bruit et n'echoue
qu'au premier ip_network() qui la lit, tres loin de l'index fautif.
La borne est posee A LA SOURCE (valider_index), pas dans un validateur de plan :
supernet_de, base3_de et vlan_de y passent toutes, donc aucune ne peut fabriquer
une adresse invalide — d'ou qu'on l'appelle : plan, GUI, devis ou test.
Elle protege un SECOND plafond, moins visible : a l'index 255 le VLAN vaut
3550+zone, sous les 4094 du 802.1Q. Un index a trois chiffres debordait aussi la.
Garde statique ajoutee au controle de federation (P21) : elle nomme le depot
fautif au lieu de laisser l'erreur remonter d'une bibliotheque.
LE TEST A TROUVE CE QUE LA RELECTURE N'AVAIT PAS VU : valider_index n'attrapait
que TypeError et ValueError, or int(float('inf')) leve OverflowError — un infini
faisait PLANTER la garde au lieu d'etre refuse.
test_underlay_bande_basse.py -> test_adressage_derive.py (il ne parlait plus
seulement de la bande basse). 12 cas, dont les refus.
Piege de structure au passage : les nouveaux cas, ajoutes apres le bloc
`if __name__ == "__main__"`, ne s'executaient pas — le bloc tourne avant que les
fonctions suivantes ne soient definies, et le compte affichait « 7 tests » au lieu
de 12. Un harnais qui compte ses propres tests doit etre lu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5ace6bdb95
commit
8b4ad276ca
5 changed files with 130 additions and 7 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -1,5 +1,37 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 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
|
||||
ressemble à un réseau**. Elle traverse tout le moteur sans bruit et n'échoue qu'au premier
|
||||
`ip_network()` qui la lit, très loin de l'index fautif.
|
||||
|
||||
La borne est posée **à la source** (`valider_index`), pas dans un validateur de plan :
|
||||
toutes les fonctions dérivées y passent — `supernet_de`, `base3_de`, `vlan_de` — donc
|
||||
aucune ne peut fabriquer une adresse invalide, d'où qu'on l'appelle : plan, GUI, devis ou
|
||||
test.
|
||||
|
||||
Elle protège **un second plafond, moins visible** : à l'index 255 le VLAN vaut `3550+zone`,
|
||||
sous les 4094 du 802.1Q. Un index à trois chiffres débordait aussi là.
|
||||
|
||||
Et une garde statique dans le contrôle de fédération (**P21**), qui nomme le dépôt fautif
|
||||
au lieu de laisser l'erreur remonter d'une bibliothèque.
|
||||
|
||||
### Le test a trouvé ce que la relecture n'avait pas vu
|
||||
|
||||
`valider_index` n'attrapait que `TypeError` et `ValueError`. Or `int(float('inf'))` lève
|
||||
**`OverflowError`** : un infini flottant passait la garde en la faisant planter au lieu de
|
||||
la faire refuser. Corrigé — et c'est le cas de test qui l'a levé, pas ma relecture.
|
||||
|
||||
`test_underlay_bande_basse.py` devient `test_adressage_derive.py` : il ne parlait plus
|
||||
seulement de la bande basse. **12 cas**, dont les refus.
|
||||
|
||||
> **Un piège de structure, au passage.** Les nouveaux cas, ajoutés après le bloc
|
||||
> `if __name__ == "__main__":`, ne s'exécutaient pas — le bloc tourne avant que les
|
||||
> fonctions suivantes ne soient définies, et le compte affichait tranquillement « 7 tests »
|
||||
> au lieu de 12. Un harnais qui compte ses propres tests doit être lu : *sept* était la
|
||||
> bonne réponse à la mauvaise question.
|
||||
|
||||
## 2026-08-12 — Le décalage de `+10` est retiré : l'index se lit dans l'adresse
|
||||
|
||||
`supernet_de(index)` rendait `10.(10+index).0.0/16`. **Personne ne savait plus pourquoi** —
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -211,7 +211,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
|||
python3 scripts/tests/test_inventory_host.py
|
||||
python3 scripts/tests/test_raser.py
|
||||
python3 scripts/tests/test_raser_resultat.py
|
||||
python3 scripts/tests/test_underlay_bande_basse.py
|
||||
python3 scripts/tests/test_adressage_derive.py
|
||||
|
||||
.PHONY: verifier
|
||||
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe ## Rejoue les preuves SANS reecrire le rapport (verification rapide)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ RACINE = Path(__file__).resolve().parents[1]
|
|||
FRERES = RACINE.parent
|
||||
LIEN_INSTANCE = RACINE / "instance"
|
||||
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
from inventory_rules import INDEX_MIN, INDEX_MAX # noqa: E402
|
||||
|
||||
|
||||
def _actif() -> Path | None:
|
||||
try:
|
||||
|
|
@ -98,8 +101,19 @@ def main() -> int:
|
|||
|
||||
instances = decouvrir()
|
||||
coll = collisions(instances)
|
||||
# Un index hors bornes ne produit pas une adresse mais une chaine qui y ressemble.
|
||||
# Le lever ICI, avec le nom du depot fautif, plutot que de laisser `ip_network()`
|
||||
# echouer plus tard sur une valeur dont on ne saura plus d'ou elle vient.
|
||||
hors_bornes = [(i["nom"], i["index"]) for i in instances
|
||||
if not (INDEX_MIN <= int(i["index"]) <= INDEX_MAX)]
|
||||
|
||||
if args.verifier:
|
||||
if hors_bornes:
|
||||
print(f"erreur: index hors bornes [{INDEX_MIN}-{INDEX_MAX}] :", file=sys.stderr)
|
||||
for nom, idx in sorted(hors_bornes):
|
||||
print(f" - {nom} : index {idx} — le 2e octet d'une IPv4 ne peut pas le porter",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
if coll:
|
||||
print("erreur: collision d'index entre instances FEDEREES "
|
||||
"(memes VLAN/VMID) :", file=sys.stderr)
|
||||
|
|
|
|||
|
|
@ -472,17 +472,45 @@ def fonction_seq(nom: str) -> tuple[str, int | None]:
|
|||
# plage utile passe de 0-245 a 0-255.
|
||||
OCTET_ZONE = 15
|
||||
SOCLE_VLAN = 1000
|
||||
|
||||
# BORNES DE L'INDEX. Le 2e octet d'une IPv4 vaut 0-255 : au-dela, la derivation ne
|
||||
# produit plus une adresse mais une CHAINE qui y ressemble — `10.300.0.0/16` passe
|
||||
# silencieusement tant que personne ne la donne a `ip_network()`, et l'erreur remonte
|
||||
# alors tres loin de sa cause. Depuis le retrait du decalage de +10 (2026-08-12), la
|
||||
# plage utile est exactement celle de l'octet.
|
||||
INDEX_MIN, INDEX_MAX = 0, 255
|
||||
INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable)
|
||||
|
||||
|
||||
def valider_index(index) -> int:
|
||||
"""L'index, verifie et rendu en entier. Leve `ValueError` hors bornes.
|
||||
|
||||
Pose a la SOURCE de la derivation plutot que dans un validateur de plan : toutes
|
||||
les fonctions derivees passent par ici, donc aucune ne peut fabriquer une adresse
|
||||
invalide, d'ou qu'on l'appelle — plan, GUI, devis ou test.
|
||||
"""
|
||||
try:
|
||||
i = int(index)
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
# OverflowError : `int(float('inf'))`. Trouve par le test, pas par relecture —
|
||||
# un infini flottant n'est ni un entier valide ni une TypeError.
|
||||
raise ValueError(f"index invalide : {index!r} n'est pas un entier") from None
|
||||
if not (INDEX_MIN <= i <= INDEX_MAX):
|
||||
raise ValueError(
|
||||
f"index {i} hors bornes [{INDEX_MIN}-{INDEX_MAX}] : le 2e octet d'une IPv4 "
|
||||
f"ne peut pas le porter. La derivation produirait « 10.{i}.0.0/16 », qui "
|
||||
f"ressemble a un reseau sans en etre un.")
|
||||
return i
|
||||
|
||||
|
||||
def supernet_de(index: int) -> str:
|
||||
"""/16 du tenant : 10.<index>.0.0/16 — l'index se lit dans l'adresse."""
|
||||
return f"10.{int(index)}.0.0/16"
|
||||
return f"10.{valider_index(index)}.0.0/16"
|
||||
|
||||
|
||||
def base3_de(index: int, categorie: int) -> str:
|
||||
"""Trois premiers octets d'une zone : 10.<index>.(15+categorie)."""
|
||||
return f"10.{int(index)}.{OCTET_ZONE + int(categorie)}"
|
||||
return f"10.{valider_index(index)}.{OCTET_ZONE + int(categorie)}"
|
||||
|
||||
|
||||
def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str:
|
||||
|
|
@ -496,8 +524,12 @@ def passerelle_de(index: int, categorie: int) -> str:
|
|||
|
||||
|
||||
def vlan_de(index: int, zone: int) -> int:
|
||||
"""VLAN 802.1Q sur le trunk convergE : 1000 + index x 10 + zone (unique globalement)."""
|
||||
return SOCLE_VLAN + int(index) * 10 + int(zone)
|
||||
"""VLAN 802.1Q sur le trunk convergE : 1000 + index x 10 + zone (unique globalement).
|
||||
|
||||
La meme borne y protege un second plafond : a l'index 255 le VLAN vaut 3550+zone,
|
||||
sous les 4094 du 802.1Q. Sans elle, un index de trois chiffres deborderait aussi la.
|
||||
"""
|
||||
return SOCLE_VLAN + valider_index(index) * 10 + int(zone)
|
||||
|
||||
|
||||
def deriver_nomenclature(fonction: str, seq: int, nomenclature: dict) -> dict | None:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""D-77 — l'underlay tient dans la BANDE BASSE du supernet de son propre site.
|
||||
"""Adressage derive : la bande basse de l'underlay (D-77) et les bornes de l'index.
|
||||
|
||||
Ce que ce test garde, et pourquoi il existe. La regle « underlay dans la bande basse »
|
||||
n'est sure que parce que les zones d'un tenant commencent a `OCTET_ZONE + 1`. C'est une
|
||||
|
|
@ -73,10 +73,55 @@ def test_sans_index_on_retombe_sur_la_regle_stricte():
|
|||
"sans index, tout chevauchement doit etre refuse"
|
||||
|
||||
|
||||
|
||||
|
||||
# --- Bornes de l'index (2026-08-12) -----------------------------------------------
|
||||
# Sans elles, `supernet_de(300)` rendait « 10.300.0.0/16 » : une chaine qui RESSEMBLE
|
||||
# a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier
|
||||
# `ip_network()` qui la lit — tres loin de l'index fautif.
|
||||
|
||||
def test_les_bornes_couvrent_exactement_l_octet():
|
||||
from inventory_rules import INDEX_MIN, INDEX_MAX
|
||||
assert (INDEX_MIN, INDEX_MAX) == (0, 255)
|
||||
|
||||
|
||||
def test_index_aux_bornes_accepte():
|
||||
from inventory_rules import supernet_de
|
||||
assert supernet_de(0) == "10.0.0.0/16"
|
||||
assert supernet_de(255) == "10.255.0.0/16"
|
||||
|
||||
|
||||
def test_index_hors_bornes_refuse():
|
||||
from inventory_rules import supernet_de, base3_de, vlan_de
|
||||
for fn, args in ((supernet_de, (256,)), (supernet_de, (-1,)),
|
||||
(base3_de, (300, 1)), (vlan_de, (256, 1))):
|
||||
try:
|
||||
fn(*args)
|
||||
except ValueError:
|
||||
continue
|
||||
raise AssertionError(f"{fn.__name__}{args} aurait du etre refuse")
|
||||
|
||||
|
||||
def test_index_non_entier_refuse():
|
||||
from inventory_rules import supernet_de
|
||||
for mauvais in ("abc", None, 1.5e400):
|
||||
try:
|
||||
supernet_de(mauvais)
|
||||
except ValueError:
|
||||
continue
|
||||
raise AssertionError(f"index {mauvais!r} aurait du etre refuse")
|
||||
|
||||
|
||||
def test_le_vlan_reste_sous_le_plafond_802_1q():
|
||||
"""La meme borne protege un SECOND plafond, moins visible."""
|
||||
from inventory_rules import vlan_de, INDEX_MAX
|
||||
assert vlan_de(INDEX_MAX, 9) < 4094
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
n = 0
|
||||
for nom, fn in sorted(globals().items()):
|
||||
if nom.startswith("test_") and callable(fn):
|
||||
fn()
|
||||
n += 1
|
||||
print(f"OK — {n} test(s) D-77 (bande basse, frontiere derivee d'OCTET_ZONE={OCTET_ZONE}).")
|
||||
print(f"OK — {n} test(s) adressage derive : bande basse (OCTET_ZONE={OCTET_ZONE}) + bornes de l'index.")
|
||||
Loading…
Reference in a new issue