carte : P48 — l'index du mainteneur ne peut plus mentir
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
`docs/carte-set-ops.md` est l'index du MAINTENEUR : l'ordre de lecture du corpus, et surtout le catalogue des MECANISMES TRANSVERSES avec, pour chacun, OU IL VIT DANS LE CODE. Son but est ecrit en toutes lettres : « ne plus re-deterrer ce qui existe ». Elle n'avait aucune garde, alors que `catalogue-services.md` a la sienne depuis P38. Ses sept chiffres etaient faux — 54 roles annonces contre 60, 34 documents contre 38, 15 pieces d'audit contre 27, 70 decisions contre 78. Le defaut couteux n'est pourtant pas la. C'est le POINTEUR MORT : la carte dit ou vit un mecanisme, quelqu'un ne l'y trouve pas, et le reimplemente a cote — exactement la panne qu'elle existe pour prevenir. Aucun de ces nombres ne fait travailler personne ; mais un document dont les faits verifiables sont faux cesse d'etre consulte, et c'est alors ses pointeurs qu'on perd. P48 verifie les deux : 84 chemins cites existent, et 7 chiffres correspondent a la mesure. Controle negatif verifie sur les DEUX moities — un chiffre fausse, un pointeur casse, la preuve echoue dans les deux cas. QUATRE DISTINCTIONS ont du etre ecrites pour qu'elle ne soit pas fausse dans l'autre sens : un gabarit de nom (`preuve-<date>.md`) decrit une forme, pas un fichier ; un chemin hors depot (`~/.config/setops-vault-pass`) vit sur le poste de l'exploitant, et c'est tout l'interet de la doctrine des voutes ; un fragment (`meta/acces.yml`) vaut comme SUFFIXE, parce qu'un index se lit ainsi ; et un artefact GENERE (`hosts.yml`) n'a pas a exister dans le moteur. Les quatre sont nommees dans le code plutot que sautees en silence. Le tableau « Le depot en chiffres » remplace les comptes en prose : ce qu'on n'entretient pas, on ne l'affirme pas — ou bien on le fait recompter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
87716cefc7
commit
9e8679215d
4 changed files with 205 additions and 7 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -59,6 +59,33 @@ qu'elle porte, et on refuse si ça diffère de ce que porte le runner.
|
|||
sans que rien ne le signale — dont le correctif qui désarme le pare-feu Proxmox. Un
|
||||
écosystème qui se reproduit depuis une forge en retard reproduit ses défauts.*
|
||||
|
||||
### P48 — la carte d'orientation ne peut plus mentir
|
||||
|
||||
`docs/carte-set-ops.md` est l'index du **mainteneur** : l'ordre de lecture du corpus, et
|
||||
surtout le catalogue des **mécanismes transverses** avec, pour chacun, **où il vit dans le
|
||||
code**. Son but est écrit en toutes lettres : *« ne plus re-déterrer ce qui existe »*.
|
||||
|
||||
Elle n'avait aucune garde, alors que `catalogue-services.md` a la sienne depuis P38. Ses
|
||||
**sept** chiffres étaient faux — 54 rôles annoncés contre 60, 34 documents contre 38, 15
|
||||
pièces d'audit contre 27, 70 décisions contre 78.
|
||||
|
||||
Le défaut coûteux n'est pourtant pas là. C'est le **pointeur mort** : la carte dit où vit
|
||||
un mécanisme, quelqu'un ne l'y trouve pas, et le réimplémente à côté — exactement la panne
|
||||
qu'elle existe pour prévenir. *Aucun de ces nombres ne fait travailler personne ; mais un
|
||||
document dont les faits vérifiables sont faux cesse d'être consulté, et c'est alors ses
|
||||
pointeurs qu'on perd.*
|
||||
|
||||
P48 vérifie les deux : **84 chemins** cités existent, et **7 chiffres** correspondent à la
|
||||
mesure. Quatre distinctions ont dû être écrites pour qu'elle ne soit pas fausse dans
|
||||
l'autre sens — un gabarit de nom (`preuve-<date>.md`) décrit une forme, pas un fichier ; un
|
||||
chemin hors dépôt (`~/.config/setops-vault-pass`) vit sur le poste de l'exploitant ; un
|
||||
fragment (`meta/acces.yml`) vaut comme suffixe ; et un artefact **généré** (`hosts.yml`) n'a
|
||||
pas à exister dans le moteur. Les quatre sont nommées dans le code plutôt que sautées en
|
||||
silence.
|
||||
|
||||
*Le tableau « Le dépôt en chiffres » remplace les comptes en prose : ce qu'on n'entretient
|
||||
pas, on ne l'affirme pas — ou bien on le fait recompter.*
|
||||
|
||||
### D-81 — la forge du site fait autorité, et `make genome-etat` le vérifie
|
||||
|
||||
Décision de l'exploitant : **la forge du SITE fait autorité pour le génome.** Toute autre
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/production/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (46 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (47 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
|
||||
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
|
||||
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
|
||||
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 84 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,26 @@ Point d'entrée vers le corpus documentaire, et **catalogue des mécanismes tran
|
|||
ceux qui vivent dans le code et qu'on *re-découvre* sinon. Créée le 2026-07-03 après un audit
|
||||
du dépôt, **revue le 2026-07-29**. But : ne plus re-déterrer ce qui existe.
|
||||
|
||||
Le dépôt est **déjà bien documenté** (34 docs + 15 pièces d'audit + 23 unités de wiki, et un
|
||||
README pour chacun des 54 rôles). Le manque n'était pas la doc du *modèle*,
|
||||
mais (a) un index « par où commencer » et (b) une carte des *mécanismes* (dispersés dans le
|
||||
code + les README de rôles). Cette page comble ces deux trous.
|
||||
Le dépôt est **déjà bien documenté**. Le manque n'était pas la doc du *modèle*, mais (a) un
|
||||
index « par où commencer » et (b) une carte des *mécanismes* (dispersés dans le code + les
|
||||
README de rôles). Cette page comble ces deux trous.
|
||||
|
||||
### Le dépôt en chiffres
|
||||
|
||||
> Ces valeurs sont **mesurées**, pas recopiées : **P48** les recompte et refuse tout écart.
|
||||
> Elles étaient toutes fausses le 2026-08-26 — de 6 rôles, de 12 pièces d'audit, de 8
|
||||
> décisions. Aucune ne faisait travailler personne ; mais une carte dont les faits
|
||||
> vérifiables sont faux cesse d'être consultée, et c'est alors ses **pointeurs** qu'on perd.
|
||||
|
||||
| Ce qu'on compte | Combien | Comment on le mesure |
|
||||
|---|---|---|
|
||||
| rôles | 60 | `roles/*/` |
|
||||
| README de rôles | 60 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
||||
| documents | 38 | `docs/*.md` |
|
||||
| pièces d'audit | 27 | `docs/audit/*` |
|
||||
| unités de wiki | 27 | `wiki/*.md` |
|
||||
| décisions en vigueur | 78 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
||||
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
||||
|
||||
## 1. À lire d'abord (dans l'ordre)
|
||||
|
||||
|
|
@ -28,11 +44,11 @@ code + les README de rôles). Cette page comble ces deux trous.
|
|||
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
|
||||
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
|
||||
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md` — **statique** : lit le dépôt, aucun appel réseau ; la conformité du déployé est l'affaire des devis de service (ligne au-dessus), `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
|
||||
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **70 décisions en vigueur** (D-01 → D-73, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
||||
| **Décisions d'architecture** | `docs/decisions-architecture.md` — les décisions en vigueur (comptées ci-dessus), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
||||
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
|
||||
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
|
||||
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
|
||||
| **Pédagogie (le wiki)** | `wiki/` — 21 unités (+ `_Sidebar`) publiées par `make wiki-publier` ; entrer par `wiki/Home.md` |
|
||||
| **Pédagogie (le wiki)** | `wiki/` — les unités (comptées ci-dessus) publiées par `make wiki-publier` ; entrer par `wiki/Home.md` |
|
||||
| **Vision / positionnement** | `docs/ecosysteme-chezlepro.md`, `docs/positionnement.md`, `docs/pouvoirs-set-ops.md` |
|
||||
|
||||
## 2. Les mécanismes transverses (et OÙ ils vivent)
|
||||
|
|
|
|||
|
|
@ -1244,6 +1244,158 @@ def preuve_zones_inverses_couvrent_l_occupe() -> tuple[bool, str]:
|
|||
f"a une).")
|
||||
|
||||
|
||||
# Ce que la carte nomme a juste titre et que ce depot ne contient pas : le produit de
|
||||
# `make instancier`, qui vit dans le depot d'un ecosysteme.
|
||||
ARTEFACTS_GENERES = {"hosts.yml"}
|
||||
|
||||
|
||||
def _sans_accents(texte: str) -> str:
|
||||
return "".join(c for c in unicodedata.normalize("NFD", texte)
|
||||
if unicodedata.category(c) != "Mn")
|
||||
|
||||
_ECARTES = {".git", "__pycache__", ".venv", "node_modules", "collections"}
|
||||
|
||||
|
||||
def _propre(chemin: Path) -> bool:
|
||||
return not any(part in _ECARTES for part in chemin.relative_to(RACINE).parts)
|
||||
|
||||
|
||||
def _existe_en_suffixe(fragment: str) -> bool:
|
||||
"""Un fichier du depot se termine-t-il par ce fragment de chemin ?"""
|
||||
nom = fragment.rsplit("/", 1)[-1]
|
||||
return any(_propre(p) and str(p).endswith("/" + fragment)
|
||||
for p in RACINE.rglob(nom))
|
||||
|
||||
|
||||
def _existe_quelque_part(nom: str) -> bool:
|
||||
"""Un fichier ou dossier de ce nom existe-t-il dans le depot ?
|
||||
|
||||
La carte nomme volontiers un fichier sans son dossier — c'est ce qui la rend lisible.
|
||||
On cherche donc par NOM, en ecartant ce qui n'appartient pas au depot (`.git`, les
|
||||
caches Python, les dependances installees).
|
||||
"""
|
||||
return any(_propre(chemin) for chemin in RACINE.rglob(nom))
|
||||
|
||||
|
||||
def _carte_chiffres_mesures() -> dict[str, int]:
|
||||
"""Ce que la carte affirme, recompte a la source."""
|
||||
dec = (RACINE / "docs" / "decisions-architecture.md")
|
||||
texte = dec.read_text(encoding="utf-8") if dec.is_file() else ""
|
||||
return {
|
||||
"roles": len([p for p in (RACINE / "roles").glob("*") if p.is_dir()]),
|
||||
"README de roles": len(list((RACINE / "roles").glob("*/README.md"))),
|
||||
"documents": len(list((RACINE / "docs").glob("*.md"))),
|
||||
"pieces d'audit": len([p for p in (RACINE / "docs" / "audit").glob("*")
|
||||
if p.is_file()]),
|
||||
"unites de wiki": len(list((RACINE / "wiki").glob("*.md"))),
|
||||
"decisions en vigueur": len(re.findall(r"^\| \*\*D-\d+\*\* \|", texte, re.M)),
|
||||
"decisions renversees": len(re.findall(r"^\| \*\*D-\d+\*\* —", texte, re.M)),
|
||||
}
|
||||
|
||||
|
||||
def preuve_carte_dit_vrai() -> tuple[bool, str]:
|
||||
"""La carte d'orientation designe des choses qui existent, et compte juste.
|
||||
|
||||
CE QU'EST LA CARTE. `docs/carte-set-ops.md` est l'index du MAINTENEUR : l'ordre de
|
||||
lecture du corpus, et surtout le catalogue des MECANISMES TRANSVERSES avec, pour
|
||||
chacun, OU IL VIT DANS LE CODE. Son but est ecrit en toutes lettres : « ne plus
|
||||
re-deterrer ce qui existe ».
|
||||
|
||||
POURQUOI UNE GARDE (mesure du 2026-08-26). Un POINTEUR MORT est le defaut couteux :
|
||||
la carte dit ou vit un mecanisme, quelqu'un ne l'y trouve pas, et le reimplemente a
|
||||
cote. C'est exactement la panne que la carte existe pour prevenir.
|
||||
|
||||
LES CHIFFRES SONT L'AUTRE MOITIE, et ils etaient TOUS faux : 54 roles annonces contre
|
||||
60, 34 documents contre 38, 15 pieces d'audit contre 27, 70 decisions contre 78.
|
||||
Aucun de ces nombres ne fait travailler personne. Mais un document dont les faits
|
||||
verifiables sont faux cesse d'etre consulte — et c'est alors ses pointeurs qu'on perd.
|
||||
|
||||
`catalogue-services.md` avait deja sa garde (P38) ; la carte n'en avait aucune.
|
||||
"""
|
||||
carte = RACINE / "docs" / "carte-set-ops.md"
|
||||
if not carte.is_file():
|
||||
return False, "docs/carte-set-ops.md est introuvable."
|
||||
texte = carte.read_text(encoding="utf-8")
|
||||
manques: list[str] = []
|
||||
|
||||
# --- Les pointeurs ---------------------------------------------------------
|
||||
#
|
||||
# On ne retient que ce qui RESSEMBLE a un chemin du depot : un segment avec un `/`
|
||||
# ou une extension connue. Les `make cible`, les noms de variables et les fragments
|
||||
# de code entre accents graves ne sont pas des chemins, et les exiger rendrait la
|
||||
# preuve fausse dans l'autre sens.
|
||||
chemins = set()
|
||||
for brut in re.findall(r"`([^`\n]+)`", texte):
|
||||
c = brut.strip()
|
||||
# Un GABARIT de nom n'est pas un chemin : `docs/audit/preuve-<date>.md` decrit
|
||||
# une forme de fichier, pas un fichier. L'exiger rendrait la preuve fausse.
|
||||
# Ni un gabarit de nom (`preuve-<date>.md` decrit une FORME), ni un chemin HORS
|
||||
# depot (`~/.config/setops-vault-pass` vit sur le poste de l'exploitant, et c'est
|
||||
# tout l'interet de la doctrine des voutes). Les exiger rendrait la preuve fausse.
|
||||
if " " in c or c.startswith(("$", "-", "~", "/")) or "<" in c or ">" in c:
|
||||
continue
|
||||
if not ("/" in c or c.endswith((".md", ".yml", ".py", ".j2"))):
|
||||
continue
|
||||
chemins.add(c.rstrip("/"))
|
||||
|
||||
for c in sorted(chemins):
|
||||
# DEUX SEMANTIQUES, ET C'EST VOULU.
|
||||
#
|
||||
# Un chemin AVEC dossier (`roles/serveur_nginx/tasks/main.yml`) affirme un
|
||||
# emplacement : on l'exige tel quel. Un nom SANS dossier (`devis_reseau.py`,
|
||||
# `expositions.conf.j2`) affirme seulement qu'une chose de ce nom existe — c'est
|
||||
# ainsi qu'un index se lit, et exiger le chemin complet le rendrait illisible.
|
||||
# UN ARTEFACT GENERE N'A PAS A EXISTER ICI. La carte nomme `hosts.yml` en disant
|
||||
# elle-meme « genere » : c'est le produit de `make instancier`, il vit dans le
|
||||
# depot d'un ECOSYSTEME, pas dans le moteur. L'exiger reprocherait a la carte
|
||||
# d'etre juste. On les nomme plutot que de les sauter en silence.
|
||||
if c in ARTEFACTS_GENERES:
|
||||
continue
|
||||
if "/" in c:
|
||||
# Un fragment (`meta/acces.yml`) designe un emplacement RELATIF a un role ou
|
||||
# a une instance. Il vaut donc comme SUFFIXE : `roles/serveur_forgejo/meta/
|
||||
# acces.yml` le satisfait. Exiger la racine rendrait la carte illisible.
|
||||
if any((RACINE / base / c).exists() for base in ("", "docs")):
|
||||
continue
|
||||
if _existe_en_suffixe(c):
|
||||
continue
|
||||
elif _existe_quelque_part(c):
|
||||
continue
|
||||
# Les globs et les chemins d'une INSTANCE (`plan/*.yml`, `roles/*/meta/...`)
|
||||
# se verifient par expansion : ils designent une forme, pas un fichier.
|
||||
# UN GLOB DESIGNE UNE FORME, ou qu'elle se trouve. `plan/*.yml` est le plan d'un
|
||||
# ECOSYSTEME : il n'existe pas a la racine du moteur, mais bien sous `instance/`
|
||||
# ou dans un depot voisin. On cherche donc la forme partout.
|
||||
if "*" in c and (list(RACINE.glob(c)) or list(RACINE.glob("**/" + c))):
|
||||
continue
|
||||
manques.append(f"`{c}` : cite par la carte, introuvable dans le depot")
|
||||
|
||||
# --- Les chiffres ----------------------------------------------------------
|
||||
#
|
||||
# L'appariement se fait SANS ACCENTS : la carte ecrit « rôles » et « pièces d'audit »,
|
||||
# ce code ecrit sans diacritiques comme tout le reste du depot. Comparer les deux
|
||||
# tels quels ferait echouer la preuve sur son propre alphabet.
|
||||
annonces = {}
|
||||
for etiq, valeur in re.findall(r"^\| *([^|]+?) *\| *(\d+) *\|", texte, re.M):
|
||||
annonces[_sans_accents(etiq).lower()] = int(valeur)
|
||||
|
||||
for etiquette, attendu in _carte_chiffres_mesures().items():
|
||||
cle = _sans_accents(etiquette).lower()
|
||||
if cle not in annonces:
|
||||
manques.append(f"« {etiquette} » : absent du tableau « Le depot en chiffres » "
|
||||
f"(mesure : {attendu})")
|
||||
continue
|
||||
if annonces[cle] != attendu:
|
||||
manques.append(f"« {etiquette} » : la carte annonce {annonces[cle]}, "
|
||||
f"le depot en compte {attendu}")
|
||||
|
||||
if manques:
|
||||
return False, ("La carte d'orientation ne dit plus vrai :\n - "
|
||||
+ "\n - ".join(manques))
|
||||
return True, (f"La carte designe {len(chemins)} chemin(s) qui existent, et ses "
|
||||
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
||||
|
||||
|
||||
|
||||
def preuve_glossaire_enseigne() -> tuple[bool, str]:
|
||||
"""Tout mot que le depot emploie devant l'exploitant est explique au glossaire.
|
||||
|
|
@ -1591,6 +1743,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_un_seul_maitre_du_plancher},
|
||||
{"id": "P47", "titre": "Zones inverses : couvrir l'occupe, et rien de plus",
|
||||
"refs": [], "func": preuve_zones_inverses_couvrent_l_occupe},
|
||||
{"id": "P48", "titre": "La carte d'orientation designe ce qui existe, et compte juste",
|
||||
"refs": [], "func": preuve_carte_dit_vrai},
|
||||
{"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [],
|
||||
"func": preuve_devis_frontiere_du_site},
|
||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||
|
|
|
|||
Loading…
Reference in a new issue