fiches de role au wiki : 68 generees, l index, et trois gardes qui ont mordu

Generees depuis ce que chaque role declare, avec un schema mermaid et la raison
de chaque ligne. Une section vide est une information : l index recompte ce que
la flotte ne declare pas — 36 sans flux, 40 sans sonde, 67 sans metrique.

La charte du wiki disait de ne pas recopier le depot, pour eviter la derive. Le
motif ne vaut pas pour une page qui relit sa source ; l exception est nommee
plutot que prise en silence.

La navigation exigeait une citation DIRECTE alors que son motif parle
d atteignabilite. L exigence litterale interdisait toute page d index — la garde
forcait a degrader ce qu elle protegeait. Elle suit maintenant les liens de
proche en proche, et son motif reconnait le souligne : un motif trop etroit ne
rend pas une garde prudente, il la rend aveugle.

Et le compte du wiki serait passe de 27 a 96 : une fiche generee n est pas une
unite d apprentissage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-14 14:09:13 -04:00
parent 1b71884871
commit d76ce574f0
74 changed files with 237 additions and 8 deletions

View file

@ -1,5 +1,48 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-09-14 (9) — Une fiche par role, generee, et publiee au wiki
Soixante-huit roles, soixante-huit fiches : qui lui parle, ce qu'il rend a la supervision,
ce qu'il expose en series, ce qu'il coute, qui entre. Un schema **mermaid** en tete, puis
les tableaux — et chaque ligne porte la **raison** que le role a declaree.
**Generees, jamais ecrites.** Soixante-huit pages redigees a la main seraient perimees
avant la fin du mois : c'est « une liste qui suit une autre prend du retard », et une
soixante-neuvieme liste n'y echapperait pas. `make fiches` les relit depuis `meta/flux`,
`supervision`, `metriques`, `empreinte`, `authentification`.
**Une section vide est une information.** Un role sans sonde l'affiche. L'index `Rôles`
recompte a chaque generation ce que la flotte ne declare pas encore : **36** sans flux
entrant, **40** sans sonde, **67** sans metrique. La carte de ce qui reste, tenue a jour
toute seule.
### Elles vivent au wiki, et la charte a du etre amendee
`Home.md` disait : « le detail du comment vit dans le depot — ce wiki y POINTE, ne le
RECOPIE pas (pour eviter la derive) ». La regle reste juste ; son MOTIF ne s'applique pas
a une page qui relit sa source a chaque generation. L'exception est donc nommee dans la
charte plutot que prise en silence.
### Trois gardes ont mordu, et toutes avaient raison
**Un script qu'aucune cible n'appelle est du code mort.** Le premier commit est parti sans
`make fiches` ; la preuve l'a vu, pas moi.
**La navigation exigeait une citation DIRECTE.** Son propre motif dit pourtant « n'est lue
par personne » — or une page citee par une page citee EST lue. L'exigence litterale
interdisait toute page d'index : citer les soixante-huit fiches dans la navigation en
ferait un mur ou plus personne ne trouverait les vingt-sept unites redigees. **La garde
forcait a degrader ce qu'elle protegeait.** Elle suit desormais les liens de proche en
proche. Eprouvee dans les deux sens : coupe le lien depuis `Home`, elle refuse les 69.
Au passage, son motif de lien ne contenait pas `_` : un lien vers `Rôle-serveur_postgresql`
n'etait NI suivi, NI signale casse. **Un motif trop etroit ne rend pas une garde prudente,
il la rend aveugle.**
**Le compte du wiki serait passe de 27 a 96.** Une fiche generee n'est pas une unite
d'apprentissage — celles-la suivent le moule en quatre temps. Le nombre aurait ete exact
et l'affirmation fausse.
## 2026-09-14 (8) — La chaine des metriques, de la declaration au graphe ## 2026-09-14 (8) — La chaine des metriques, de la declaration au graphe
Premiere verticale complete du second versant, eprouvee sur TechnoLibre. Premiere verticale complete du second versant, eprouvee sur TechnoLibre.

View file

@ -46,12 +46,12 @@
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 66 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 66 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 35 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 35 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (108 genere(s) exempte(s)). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (40 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. | | P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. | | P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. | | P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. |
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. | | P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 62 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | | P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 62 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre |

View file

@ -34,7 +34,16 @@ import yaml
RACINE = Path(__file__).resolve().parents[1] RACINE = Path(__file__).resolve().parents[1]
ROLES = RACINE / "roles" ROLES = RACINE / "roles"
SORTIE = RACINE / "docs" / "roles" # UN SEUL DOMICILE, ET C'EST LE WIKI. Ces fiches sont faites pour etre LUES, et le wiki
# est l'endroit ou l'on lit — « on lit la-bas, on ecrit ici ». Les garder aussi dans
# `docs/` en ferait deux copies du meme contenu, donc deux listes dont l'une prendrait du
# retard sur l'autre. On connait la suite.
#
# LE PREFIXE GROUPE. Vingt-sept pages redigees a la main vivent deja ici ; soixante-huit
# fiches generees les noieraient dans l'index de la forge. `Role-` les rassemble, et
# `Roles.md` sert de porte d'entree.
SORTIE = RACINE / "wiki"
PREFIXE = "Rôle-"
def lire(role: str, nom: str): def lire(role: str, nom: str):
@ -146,6 +155,50 @@ def fiche(role: str) -> str:
return "\n".join(L) return "\n".join(L)
def index(noms: list[str]) -> None:
"""La porte d'entree des fiches, avec le compte de ce qui manque.
CE TABLEAU EST LA VRAIE VALEUR DE L'ENSEMBLE. Une fiche dit comment UN role est
branche ; l'index dit ce que la flotte ne declare pas encore — et il se recompte a
chaque generation, sans que personne ait a tenir la liste.
"""
lignes = [
"# Les rôles, un par un", "",
"> **Généré** par `make fiches`. Ne pas éditer à la main : corriger la "
"déclaration du rôle, puis régénérer.", "",
"> **Pour qui :** celui qui doit agir sur un rôle et veut savoir, avant de toucher "
"quoi que ce soit, qui lui parle, ce qu'il rend et ce qu'il coûte.", "",
"Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — "
"`meta/flux.yml`, `meta/supervision.yml`, `meta/metriques.yml`, "
"`meta/empreinte.yml`, `meta/authentification.yml`. Aucune n'est écrite à la main : "
"soixante-huit pages rédigées seraient périmées avant la fin du mois.", "",
"**Une colonne vide est une information**, pas un trou dans la fiche. C'est la "
"carte de ce qui reste à déclarer, tenue à jour toute seule.", "",
"| rôle | flux entrants | sonde | métriques |", "|---|---|---|---|",
]
manque = {"flux": 0, "sonde": 0, "metriques": 0}
for r in noms:
fl = lire(r, "flux") or {}
entrants = len([f for f in fl.get("flux", []) if f.get("sens") == "ingress"])
sondes = len((lire(r, "supervision") or {}).get("sondes", []))
expo = 1 if (lire(r, "metriques") or {}).get("exportateur") else 0
for cle, v in (("flux", entrants), ("sonde", sondes), ("metriques", expo)):
if not v:
manque[cle] += 1
lignes.append(f"| [`{r}`]({PREFIXE}{r}) | {entrants or '—'} | {sondes or '—'} "
f"| {'oui' if expo else '—'} |")
lignes += ["", "## Ce qui reste à déclarer", "",
f"Sur **{len(noms)}** rôles :", "",
f"- **{manque['flux']}** ne déclarent aucun flux entrant",
f"- **{manque['sonde']}** ne déclarent aucune sonde",
f"- **{manque['metriques']}** ne déclarent aucune métrique", "",
"Ces nombres ne sont pas tous des dettes : un rôle de durcissement n'a "
"rien à exposer, et beaucoup de `client_*` n'ont aucun verdict à rendre. "
"Mais un service qui porte de l'état et ne déclare rien mérite qu'on se "
"demande pourquoi.", ""]
(SORTIE / "Rôles.md").write_text("\n".join(lignes) + "\n", encoding="utf-8")
if __name__ == "__main__": if __name__ == "__main__":
noms = sys.argv[1:] or sorted(d.name for d in ROLES.iterdir() if d.is_dir()) noms = sys.argv[1:] or sorted(d.name for d in ROLES.iterdir() if d.is_dir())
SORTIE.mkdir(parents=True, exist_ok=True) SORTIE.mkdir(parents=True, exist_ok=True)
@ -153,5 +206,6 @@ if __name__ == "__main__":
if not (ROLES / r).is_dir(): if not (ROLES / r).is_dir():
print(f"Refus: role inconnu « {r} ».", file=sys.stderr) print(f"Refus: role inconnu « {r} ».", file=sys.stderr)
raise SystemExit(2) raise SystemExit(2)
(SORTIE / f"{r}.md").write_text(fiche(r) + "\n", encoding="utf-8") (SORTIE / f"{PREFIXE}{r}.md").write_text(fiche(r) + "\n", encoding="utf-8")
print(f"{len(noms)} fiche(s) dans {SORTIE.relative_to(RACINE)}/") index(noms)
print(f"{len(noms)} fiche(s) + index dans {SORTIE.relative_to(RACINE)}/")

View file

@ -1891,7 +1891,19 @@ def _carte_chiffres_mesures() -> dict[str, int]:
"documents": len(list((RACINE / "docs").glob("*.md"))), "documents": len(list((RACINE / "docs").glob("*.md"))),
"pieces d'audit": len([p for p in (RACINE / "docs" / "audit").glob("*") "pieces d'audit": len([p for p in (RACINE / "docs" / "audit").glob("*")
if p.is_file()]), if p.is_file()]),
"unites de wiki": len(list((RACINE / "wiki").glob("*.md"))), # UNE FICHE GENEREE N'EST PAS UNE UNITE D'APPRENTISSAGE (2026-09-14). Les
# unites suivent le moule en quatre temps — le concept, comment Set-OPS le fait,
# pourquoi c'est transferable, a toi de jouer. Les fiches de role sont de la
# REFERENCE : generees, consultees pour agir, jamais lues pour apprendre.
#
# Les compter ensemble ferait annoncer « 96 unites » a un wiki qui en enseigne
# vingt-sept. Le nombre serait exact et l'affirmation fausse.
"unites de wiki": len([p for p in (RACINE / "wiki").glob("*.md")
# On ne retire QUE ce que la generation a ajoute. Que
# `_Sidebar` soit compte ou non est un autre debat, et le
# melanger a celui-ci rendrait le chiffre incomparable
# avec celui d'hier.
if not p.name.startswith("Rôle")]),
"decisions en vigueur": len(re.findall(r"^\| \*\*D-\d+\*\* \|", texte, re.M)), "decisions en vigueur": len(re.findall(r"^\| \*\*D-\d+\*\* \|", texte, re.M)),
"decisions renversees": len(re.findall(r"^\| \*\*D-\d+\*\* —", texte, re.M)), "decisions renversees": len(re.findall(r"^\| \*\*D-\d+\*\* —", texte, re.M)),
} }
@ -2899,16 +2911,44 @@ def preuve_glossaire_enseigne() -> tuple[bool, str]:
if not re.search(rf"\b{re.escape(_sans_accent(t))}\b", nu, re.IGNORECASE)] if not re.search(rf"\b{re.escape(_sans_accent(t))}\b", nu, re.IGNORECASE)]
pages = {p.stem for p in wiki.glob("*.md")} pages = {p.stem for p in wiki.glob("*.md")}
liens = set(re.findall(r"\]\(([A-Za-zÀ-ÿ0-9'’\-]+)\)", texte))
# `_` EST UN CARACTERE DE NOM DE PAGE (2026-09-14). La classe n'en contenait pas, donc
# un lien vers `Role-serveur_postgresql` n'etait NI suivi, NI signale casse : il
# n'existait tout simplement pas pour cette preuve. Un motif trop etroit ne rend pas
# une garde prudente, il la rend aveugle.
MOTIF = r"\]\(([A-Za-zÀ-ÿ0-9'’_\-]+)\)"
liens = set(re.findall(MOTIF, texte))
casses = sorted(l for l in liens if l not in pages) casses = sorted(l for l in liens if l not in pages)
# ATTEIGNABLE, PAS CITE (2026-09-14). La version d'avant exigeait que `_Sidebar.md`
# cite CHAQUE page. Son propre motif dit pourtant « n'est lue par personne » — or une
# page citee par une page citee EST lue.
#
# CE QUE L'EXIGENCE LITTERALE COUTAIT : elle interdisait toute page d'index. Les
# soixante-huit fiches de role passent par `Roles`, qui les rassemble ; les citer une
# a une dans la navigation en ferait un mur de noms ou plus personne ne trouverait les
# vingt-sept unites redigees. La garde forcait donc a degrader ce qu'elle protegeait.
#
# On suit maintenant les liens de proche en proche depuis la navigation. Une page
# qu'AUCUN chemin n'atteint reste orpheline, et c'est bien ce qu'on voulait mesurer.
sidebar = (wiki / "_Sidebar.md") sidebar = (wiki / "_Sidebar.md")
orphelines: list[str] = [] orphelines: list[str] = []
if sidebar.is_file(): if sidebar.is_file():
nav = sidebar.read_text(encoding="utf-8", errors="ignore") nav = sidebar.read_text(encoding="utf-8", errors="ignore")
atteintes = {l for l in re.findall(MOTIF, nav) if l in pages}
a_suivre = list(atteintes)
while a_suivre:
courante = a_suivre.pop()
f = wiki / f"{courante}.md"
if not f.is_file():
continue
for l in re.findall(MOTIF, f.read_text(encoding="utf-8", errors="ignore")):
if l in pages and l not in atteintes:
atteintes.add(l)
a_suivre.append(l)
orphelines = sorted(p for p in pages orphelines = sorted(p for p in pages
if p not in ("_Sidebar", "README", "Home") if p not in ("_Sidebar", "README", "Home")
and f"({p})" not in nav) and p not in atteintes)
echecs = [] echecs = []
if manquants: if manquants:

View file

@ -27,6 +27,7 @@ des **méthodes 100 % génériques**.
| **Opérations (runbooks)** | Les procédures ne vivent **pas** ici : elles sont dans le dépôt, `docs/runbooks-exploitation.md`. Le wiki y pointe (même règle que la référence technique, ci-dessous). | | **Opérations (runbooks)** | Les procédures ne vivent **pas** ici : elles sont dans le dépôt, `docs/runbooks-exploitation.md`. Le wiki y pointe (même règle que la référence technique, ci-dessous). |
| **[Glossaire](Glossaire)** | Les concepts-clés en une phrase (seed, bindings, le plancher, hôte fantôme, voûte…). | | **[Glossaire](Glossaire)** | Les concepts-clés en une phrase (seed, bindings, le plancher, hôte fantôme, voûte…). |
| **Référence technique** | Le *détail du « comment »* vit **dans le dépôt** (`docs/`, README des rôles) — ce wiki y **pointe**, ne le **recopie pas** (pour éviter la dérive). | | **Référence technique** | Le *détail du « comment »* vit **dans le dépôt** (`docs/`, README des rôles) — ce wiki y **pointe**, ne le **recopie pas** (pour éviter la dérive). |
| **[Les rôles, un par un](Rôles)** | La seule exception à la règle ci-dessus, et elle la confirme : ces 68 fiches sont **générées** depuis les `meta/` de chaque rôle (`make fiches`). Le motif de la règle est la *dérive* ; une page qui relit sa source à chaque génération ne peut pas dériver. Corriger une fiche veut dire corriger la **déclaration**, jamais la page. |
## Le moule d'une unité d'apprentissage ## Le moule d'une unité d'apprentissage
Chaque unité suit **quatre temps** : Chaque unité suit **quatre temps** :

91
wiki/Rôles.md Normal file
View file

@ -0,0 +1,91 @@
# Les rôles, un par un
> **Généré** par `make fiches`. Ne pas éditer à la main : corriger la déclaration du rôle, puis régénérer.
> **Pour qui :** celui qui doit agir sur un rôle et veut savoir, avant de toucher quoi que ce soit, qui lui parle, ce qu'il rend et ce qu'il coûte.
Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — `meta/flux.yml`, `meta/supervision.yml`, `meta/metriques.yml`, `meta/empreinte.yml`, `meta/authentification.yml`. Aucune n'est écrite à la main : soixante-huit pages rédigées seraient périmées avant la fin du mois.
**Une colonne vide est une information**, pas un trou dans la fiche. C'est la carte de ce qui reste à déclarer, tenue à jour toute seule.
| rôle | flux entrants | sonde | métriques |
|---|---|---|---|
| [`amorcage_acces`](Rôle-amorcage_acces) | — | — | — |
| [`apparmor`](Rôle-apparmor) | — | — | — |
| [`auditd`](Rôle-auditd) | — | — | — |
| [`chrony`](Rôle-chrony) | — | — | — |
| [`client_artefacts`](Rôle-client_artefacts) | — | — | — |
| [`client_backup`](Rôle-client_backup) | — | — | — |
| [`client_journal`](Rôle-client_journal) | 1 | 1 | — |
| [`client_metrique`](Rôle-client_metrique) | 1 | 1 | — |
| [`client_pki`](Rôle-client_pki) | — | 1 | — |
| [`client_resolveur`](Rôle-client_resolveur) | — | — | — |
| [`client_sante`](Rôle-client_sante) | — | — | — |
| [`client_smtp`](Rôle-client_smtp) | — | — | — |
| [`cloud_init`](Rôle-cloud_init) | — | — | — |
| [`cloud_init_retrait`](Rôle-cloud_init_retrait) | — | — | — |
| [`common_packages`](Rôle-common_packages) | — | — | — |
| [`core_dumps`](Rôle-core_dumps) | — | — | — |
| [`fail2ban_ssh`](Rôle-fail2ban_ssh) | — | — | — |
| [`hardening_packages`](Rôle-hardening_packages) | — | — | — |
| [`hosts_statiques`](Rôle-hosts_statiques) | — | — | — |
| [`journald`](Rôle-journald) | — | — | — |
| [`motd`](Rôle-motd) | — | — | — |
| [`nftables_baseline`](Rôle-nftables_baseline) | — | — | — |
| [`paquets_tiers`](Rôle-paquets_tiers) | — | — | — |
| [`qemu_guest_agent`](Rôle-qemu_guest_agent) | — | — | — |
| [`resoudre_annuaire`](Rôle-resoudre_annuaire) | — | — | — |
| [`resoudre_base`](Rôle-resoudre_base) | — | — | — |
| [`resoudre_idp`](Rôle-resoudre_idp) | — | — | — |
| [`resoudre_politique_mdp`](Rôle-resoudre_politique_mdp) | — | — | — |
| [`serveur_artefacts`](Rôle-serveur_artefacts) | 1 | 2 | — |
| [`serveur_backup`](Rôle-serveur_backup) | 1 | 1 | — |
| [`serveur_backup_site`](Rôle-serveur_backup_site) | 1 | — | — |
| [`serveur_cache_site`](Rôle-serveur_cache_site) | 1 | — | — |
| [`serveur_collabora`](Rôle-serveur_collabora) | 2 | 1 | — |
| [`serveur_debian`](Rôle-serveur_debian) | 3 | 2 | — |
| [`serveur_dovecot`](Rôle-serveur_dovecot) | 3 | 1 | — |
| [`serveur_durci`](Rôle-serveur_durci) | — | 1 | — |
| [`serveur_forge_site`](Rôle-serveur_forge_site) | 1 | — | — |
| [`serveur_forgejo`](Rôle-serveur_forgejo) | 2 | 1 | — |
| [`serveur_grafana`](Rôle-serveur_grafana) | 1 | 1 | — |
| [`serveur_icinga`](Rôle-serveur_icinga) | 4 | 1 | — |
| [`serveur_icingaweb2`](Rôle-serveur_icingaweb2) | 1 | — | — |
| [`serveur_keycloak`](Rôle-serveur_keycloak) | 1 | 1 | — |
| [`serveur_loki`](Rôle-serveur_loki) | 4 | 2 | — |
| [`serveur_nextcloud`](Rôle-serveur_nextcloud) | 1 | 1 | — |
| [`serveur_nginx`](Rôle-serveur_nginx) | 4 | 1 | — |
| [`serveur_oauth2_proxy`](Rôle-serveur_oauth2_proxy) | 1 | 1 | — |
| [`serveur_openldap`](Rôle-serveur_openldap) | 2 | 1 | — |
| [`serveur_ops`](Rôle-serveur_ops) | — | 1 | — |
| [`serveur_ops_site`](Rôle-serveur_ops_site) | — | — | — |
| [`serveur_ops_tenant`](Rôle-serveur_ops_tenant) | 1 | 1 | — |
| [`serveur_postfix`](Rôle-serveur_postfix) | 2 | 1 | — |
| [`serveur_postgresql`](Rôle-serveur_postgresql) | 2 | 1 | oui |
| [`serveur_powerdns`](Rôle-serveur_powerdns) | 2 | 1 | — |
| [`serveur_prometheus`](Rôle-serveur_prometheus) | 1 | 1 | — |
| [`serveur_redis`](Rôle-serveur_redis) | 1 | 1 | — |
| [`serveur_resolveur`](Rôle-serveur_resolveur) | 2 | 1 | — |
| [`serveur_resolveur_site`](Rôle-serveur_resolveur_site) | 2 | — | — |
| [`serveur_rspamd`](Rôle-serveur_rspamd) | 2 | 1 | — |
| [`serveur_step_ca`](Rôle-serveur_step_ca) | 1 | 1 | — |
| [`serveur_web_dorsal`](Rôle-serveur_web_dorsal) | 1 | — | — |
| [`serveur_web_frontal`](Rôle-serveur_web_frontal) | 1 | — | — |
| [`ssh_baseline`](Rôle-ssh_baseline) | — | — | — |
| [`ssh_hardening`](Rôle-ssh_hardening) | — | — | — |
| [`sudo_ansible`](Rôle-sudo_ansible) | — | — | — |
| [`sysctl_hardening`](Rôle-sysctl_hardening) | — | — | — |
| [`systemd_ssh_auto`](Rôle-systemd_ssh_auto) | — | — | — |
| [`template_cleanup`](Rôle-template_cleanup) | — | — | — |
| [`unattended_upgrades`](Rôle-unattended_upgrades) | — | — | — |
## Ce qui reste à déclarer
Sur **68** rôles :
- **36** ne déclarent aucun flux entrant
- **40** ne déclarent aucune sonde
- **67** ne déclarent aucune métrique
Ces nombres ne sont pas tous des dettes : un rôle de durcissement n'a rien à exposer, et beaucoup de `client_*` n'ont aucun verdict à rendre. Mais un service qui porte de l'état et ne déclare rien mérite qu'on se demande pourquoi.