fiche_role : la fiche d un role, generee depuis ce qu il declare
68 roles. Autant de pages faites a la main seraient perimees avant la fin du mois — c est le defaut que ce depot traque partout sous le nom une liste qui suit une autre prend du retard. La fiche lit meta/flux, supervision, metriques, empreinte, authentification. Un schema mermaid au centre, puis les tableaux, et chaque ligne porte la RAISON que le role a declaree. Une section vide est une information, pas un defaut de la fiche : elle devient la carte de ce qui reste a faire, tenue a jour toute seule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
818a100ded
commit
a4a8fc6065
4 changed files with 241 additions and 4 deletions
|
|
@ -7,7 +7,7 @@
|
||||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
> [`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/principal/hosts.yml`
|
- **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/principal/hosts.yml`
|
||||||
- **Verdict** : ✅ CONFORME (74 OK · 0 echec · 1 saute)
|
- **Verdict** : ❌ NON CONFORME (73 OK · 1 echec · 1 saute)
|
||||||
|
|
||||||
## Preuves
|
## Preuves
|
||||||
|
|
||||||
|
|
@ -43,17 +43,17 @@
|
||||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
|
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. |
|
||||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 65 scripts expliques et atteignables, 122 cibles make documentees, 68 roles avec README. |
|
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ❌ ECHEC | 1 script(s) qu'aucune cible ni aucun outil n'atteint : fiche_role.py |
|
||||||
| 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 (40 genere(s) exempte(s)). |
|
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (42 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, 27 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 : 61 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 |
|
||||||
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 154 regle(s) du site. |
|
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 154 regle(s) du site. |
|
||||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
|
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
|
||||||
|
|
|
||||||
48
docs/roles/serveur_postgresql.md
Normal file
48
docs/roles/serveur_postgresql.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Rôle `serveur_postgresql`
|
||||||
|
|
||||||
|
> **Généré** par `scripts/fiche_role.py` depuis les `meta/` de ce rôle.
|
||||||
|
> Ne pas éditer à la main : corriger la déclaration, puis régénérer.
|
||||||
|
|
||||||
|
> **Pour qui :** celui qui doit agir sur ce 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.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
R["<b>postgresql</b>"]
|
||||||
|
E0["forgejo"] -->|"5432 · tls-requis"| R
|
||||||
|
E1["icinga"] -->|"5432 · tls-requis"| R
|
||||||
|
E2["keycloak"] -->|"5432 · tls-requis"| R
|
||||||
|
E3["nextcloud"] -->|"5432 · tls-requis"| R
|
||||||
|
E4["prometheus"] -->|"9187 · clair"| R
|
||||||
|
R -.->|"sonde « base »"| ICINGA[["Icinga"]]
|
||||||
|
R -.->|"9187 · postgresql"| PROM[["Prometheus"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Qui lui parle
|
||||||
|
|
||||||
|
| port | depuis | chiffrement | pourquoi |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `5432` | `serveur_keycloak`, `serveur_forgejo`, `serveur_icinga`, `serveur_nextcloud` | tls-requis | Connexions applicatives à PostgreSQL (verify-full ; pg_hba hostssl). |
|
||||||
|
| `9187` | `serveur_prometheus` | clair | Metriques PostgreSQL lues par l'observatoire. Series, pas verdicts : ce qui derive lentement — cache qui decroche, connexions qui montent, bases qui grossissent — n'a que le graphe pour se faire voir. En clair pour l'instant, contrairement a `client_metrique` : dette inscrite, a fermer par `--web.config.file` + `client_pki`. |
|
||||||
|
|
||||||
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `base` | 5400 s | La base repond-elle a une VRAIE requete, et lui reste-t-il des connexions ? Un PostgreSQL a court de connexions accepte encore le port et refuse tout le monde : chaque application tombe en meme temps sans que la base ait l'air morte. |
|
||||||
|
|
||||||
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
Exportateur `prometheus-postgres-exporter` — port **9187**, job `postgresql`.
|
||||||
|
|
||||||
|
| panneau | unité | pourquoi on le regarde |
|
||||||
|
|---|---|---|
|
||||||
|
| Connexions utilisées | connexions | La sonde Icinga crie a 70 % et a 90 %. Ce panneau dit ce qu'elle ne peut pas dire : depuis QUAND ca monte. Une base qui passe de 20 a 60 connexions en trois semaines n'a rien casse — elle annonce la date ou elle cassera. |
|
||||||
|
| Taux de succès du cache | ratio | CELUI-LA N'AURA JAMAIS DE VERDICT, et c'est pourquoi il compte. Quand les donnees depassent `shared_buffers`, la base va chercher sur disque de plus en plus souvent. Rien ne casse, rien n'alerte : tout devient lent. C'est exactement la panne qu'un graphe voit et qu'une sonde ne verra jamais. |
|
||||||
|
| Taille des bases | octets | La croissance est la seule chose qu'on ne peut pas mesurer apres coup. Savoir qu'une base a double en deux mois se decide deux mois plus tot — ou jamais. |
|
||||||
|
| Transactions par seconde | tps | Le contexte des trois autres. Des connexions qui montent a charge CONSTANTE ne disent pas la meme chose que des connexions qui montent parce que le travail a double — et le geste qui suit n'est pas le meme non plus. |
|
||||||
|
|
||||||
|
## Ce qu'il coûte, et qui entre
|
||||||
|
|
||||||
|
- **Empreinte** : 2 cœur(s), 2048 Mo, 20 Go.
|
||||||
|
- **Authentification** : `sans-auth-humaine` — Comptes de service, secrets en voute ; pas d'humain.
|
||||||
|
|
||||||
32
docs/roles/serveur_web_frontal.md
Normal file
32
docs/roles/serveur_web_frontal.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Rôle `serveur_web_frontal`
|
||||||
|
|
||||||
|
> **Généré** par `scripts/fiche_role.py` depuis les `meta/` de ce rôle.
|
||||||
|
> Ne pas éditer à la main : corriger la déclaration, puis régénérer.
|
||||||
|
|
||||||
|
> **Pour qui :** celui qui doit agir sur ce 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.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
R["<b>web_frontal</b>"]
|
||||||
|
E0["edge"] -->|"80 · clair"| R
|
||||||
|
```
|
||||||
|
|
||||||
|
## Qui lui parle
|
||||||
|
|
||||||
|
| port | depuis | chiffrement | pourquoi |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `80` | `edge` | clair | Contenu statique servi au navigateur via l'edge (TLS termine a l'edge). |
|
||||||
|
|
||||||
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
|
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
||||||
|
|
||||||
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
*Aucun exportateur déclaré — voir `docs/metriques-conception.md`.*
|
||||||
|
|
||||||
|
## Ce qu'il coûte, et qui entre
|
||||||
|
|
||||||
|
- **Empreinte** : 1 cœur(s), 512 Mo, 10 Go.
|
||||||
|
- **Authentification** : `sans-auth-humaine` — Sert des fichiers statiques.
|
||||||
|
|
||||||
157
scripts/fiche_role.py
Executable file
157
scripts/fiche_role.py
Executable file
|
|
@ -0,0 +1,157 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""La fiche d'un role : comment il est branche dans l'ecosysteme.
|
||||||
|
|
||||||
|
GENEREE, JAMAIS ECRITE — ET C'EST TOUT L'INTERET.
|
||||||
|
|
||||||
|
Soixante-huit roles. Autant de pages faites a la main seraient perimees avant la fin du
|
||||||
|
mois : c'est le defaut que ce depot traque partout ailleurs sous le nom « une liste qui
|
||||||
|
suit une autre prend du retard ». Une soixante-neuvieme liste ne ferait pas exception.
|
||||||
|
|
||||||
|
CE QUE LA FICHE LIT, ELLE NE L'INVENTE PAS. Chaque rôle DECLARE deja ce qu'il faudrait
|
||||||
|
dessiner, et chaque declaration porte sa RAISON :
|
||||||
|
|
||||||
|
meta/flux.yml qui lui parle, sur quel port, et pourquoi
|
||||||
|
meta/authentification.yml qui il laisse entrer
|
||||||
|
meta/empreinte.yml ce qu'il coute
|
||||||
|
meta/supervision.yml quel verdict il rend
|
||||||
|
meta/metriques.yml quelles series il expose
|
||||||
|
|
||||||
|
UNE SECTION VIDE EST UNE INFORMATION, PAS UN DEFAUT DE LA FICHE. Un role qui ne declare
|
||||||
|
aucune supervision l'affiche ; la fiche devient alors la carte de ce qui reste a faire,
|
||||||
|
tenue a jour toute seule.
|
||||||
|
|
||||||
|
CE QU'ELLE N'EST PAS : les pages de flux ecrites a la main racontent « par ou passe
|
||||||
|
l'heure, un certificat, un courriel » — un mecanisme qui traverse plusieurs machines, et
|
||||||
|
qu'on LIT pour comprendre. Cette fiche-ci repond a « ce role, il est branche comment ? »,
|
||||||
|
et on la CONSULTE pour agir. Aucune ne remplace l'autre.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
RACINE = Path(__file__).resolve().parents[1]
|
||||||
|
ROLES = RACINE / "roles"
|
||||||
|
SORTIE = RACINE / "docs" / "roles"
|
||||||
|
|
||||||
|
|
||||||
|
def lire(role: str, nom: str):
|
||||||
|
f = ROLES / role / "meta" / f"{nom}.yml"
|
||||||
|
if not f.is_file():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return yaml.safe_load(f.read_text(encoding="utf-8"))
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
return {"_erreur": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def court(r: str) -> str:
|
||||||
|
"""`serveur_postgresql` -> `postgresql`. Les diagrammes se lisent mieux courts."""
|
||||||
|
return r.split("_", 1)[1] if "_" in r else r
|
||||||
|
|
||||||
|
|
||||||
|
def diagramme(role: str, flux, supervision, metriques) -> list[str]:
|
||||||
|
"""Le role au centre ; ce qui entre a gauche, ce qu'il rend a droite.
|
||||||
|
|
||||||
|
MERMAID, PAS UNE IMAGE. Une image se regenere a la main et diverge ; un bloc mermaid
|
||||||
|
vit dans le fichier, se relit en diff, et se rend aussi bien sur la forge que dans une
|
||||||
|
page publiee.
|
||||||
|
"""
|
||||||
|
L = ["```mermaid", "graph LR"]
|
||||||
|
moi = court(role)
|
||||||
|
L.append(f' R["<b>{moi}</b>"]')
|
||||||
|
|
||||||
|
entrants = []
|
||||||
|
for fl in (flux or {}).get("flux", []):
|
||||||
|
if fl.get("sens") != "ingress":
|
||||||
|
continue
|
||||||
|
ports = fl["port"] if isinstance(fl["port"], list) else [fl["port"]]
|
||||||
|
for p in (fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")]):
|
||||||
|
if p:
|
||||||
|
entrants.append((court(str(p)), "/".join(str(x) for x in ports),
|
||||||
|
str(fl.get("chiffrement", ""))))
|
||||||
|
for i, (src, ports, chif) in enumerate(sorted(set(entrants))):
|
||||||
|
sur = f"{ports}" + (f" · {chif}" if chif and chif not in ("n-a", "aucun") else "")
|
||||||
|
L.append(f' E{i}["{src}"] -->|"{sur}"| R')
|
||||||
|
|
||||||
|
for s in (supervision or {}).get("sondes", []):
|
||||||
|
L.append(f' R -.->|"sonde « {s["nom"]} »"| ICINGA[["Icinga"]]')
|
||||||
|
expo = (metriques or {}).get("exportateur")
|
||||||
|
if expo:
|
||||||
|
L.append(f' R -.->|"{expo.get("port")} · {expo.get("job")}"| PROM[["Prometheus"]]')
|
||||||
|
|
||||||
|
L.append("```")
|
||||||
|
return L
|
||||||
|
|
||||||
|
|
||||||
|
def fiche(role: str) -> str:
|
||||||
|
flux = lire(role, "flux")
|
||||||
|
sup = lire(role, "supervision")
|
||||||
|
met = lire(role, "metriques")
|
||||||
|
emp = lire(role, "empreinte")
|
||||||
|
aut = lire(role, "authentification")
|
||||||
|
|
||||||
|
L = [f"# Rôle `{role}`", "",
|
||||||
|
"> **Généré** par `scripts/fiche_role.py` depuis les `meta/` de ce rôle.",
|
||||||
|
"> Ne pas éditer à la main : corriger la déclaration, puis régénérer.", "",
|
||||||
|
"> **Pour qui :** celui qui doit agir sur ce 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.", ""]
|
||||||
|
|
||||||
|
L += diagramme(role, flux, sup, met) + [""]
|
||||||
|
|
||||||
|
L += ["## Qui lui parle", ""]
|
||||||
|
entrants = [f for f in (flux or {}).get("flux", []) if f.get("sens") == "ingress"]
|
||||||
|
if entrants:
|
||||||
|
L += ["| port | depuis | chiffrement | pourquoi |", "|---|---|---|---|"]
|
||||||
|
for f in entrants:
|
||||||
|
pairs = f.get("pair") if isinstance(f.get("pair"), list) else [f.get("pair")]
|
||||||
|
L.append(f"| `{f['port']}` | {', '.join(f'`{p}`' for p in pairs if p) or '—'} "
|
||||||
|
f"| {f.get('chiffrement', '—')} | {f.get('raison', '').strip()} |")
|
||||||
|
else:
|
||||||
|
L.append("*Aucun flux entrant déclaré — `meta/flux.yml` absent ou sans `ingress`.*")
|
||||||
|
L.append("")
|
||||||
|
|
||||||
|
L += ["## Ce qu'il rend à la supervision", ""]
|
||||||
|
if (sup or {}).get("sondes"):
|
||||||
|
L += ["| sonde | TTL | ce qu'elle voit |", "|---|---|---|"]
|
||||||
|
for s in sup["sondes"]:
|
||||||
|
L.append(f"| `{s['nom']}` | {s.get('ttl', '—')} s | {s.get('raison', '').strip()} |")
|
||||||
|
else:
|
||||||
|
L.append("*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare "
|
||||||
|
"aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*")
|
||||||
|
L.append("")
|
||||||
|
|
||||||
|
L += ["## Ce qu'il expose en séries", ""]
|
||||||
|
if (met or {}).get("exportateur"):
|
||||||
|
e = met["exportateur"]
|
||||||
|
L += [f"Exportateur `{e.get('paquet')}` — port **{e.get('port')}**, "
|
||||||
|
f"job `{e.get('job')}`.", "",
|
||||||
|
"| panneau | unité | pourquoi on le regarde |", "|---|---|---|"]
|
||||||
|
for p in met.get("panneaux", []):
|
||||||
|
L.append(f"| {p['titre']} | {p.get('unite', '—')} | {p.get('raison', '').strip()} |")
|
||||||
|
else:
|
||||||
|
L.append("*Aucun exportateur déclaré — voir `docs/metriques-conception.md`.*")
|
||||||
|
L.append("")
|
||||||
|
|
||||||
|
L += ["## Ce qu'il coûte, et qui entre", ""]
|
||||||
|
e = (emp or {}).get("setops_empreinte")
|
||||||
|
L.append(f"- **Empreinte** : {e['coeurs']} cœur(s), {e['memoire_mo']} Mo, "
|
||||||
|
f"{e['disque_go']} Go." if e else "- **Empreinte** : non déclarée.")
|
||||||
|
a = (aut or {}).get("authentification")
|
||||||
|
L.append(f"- **Authentification** : `{a['portee']}` — {a.get('raison', '').strip()}"
|
||||||
|
if a else "- **Authentification** : non déclarée.")
|
||||||
|
L.append("")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
noms = sys.argv[1:] or sorted(d.name for d in ROLES.iterdir() if d.is_dir())
|
||||||
|
SORTIE.mkdir(parents=True, exist_ok=True)
|
||||||
|
for r in noms:
|
||||||
|
if not (ROLES / r).is_dir():
|
||||||
|
print(f"Refus: role inconnu « {r} ».", file=sys.stderr)
|
||||||
|
raise SystemExit(2)
|
||||||
|
(SORTIE / f"{r}.md").write_text(fiche(r) + "\n", encoding="utf-8")
|
||||||
|
print(f"{len(noms)} fiche(s) dans {SORTIE.relative_to(RACINE)}/")
|
||||||
Loading…
Reference in a new issue