Compare commits
3 commits
1f47e9bca8
...
79461fdc38
| Author | SHA1 | Date | |
|---|---|---|---|
| 79461fdc38 | |||
| 742bcbf4b0 | |||
| 643604c3e3 |
9 changed files with 549 additions and 1 deletions
11
.git-allowed-signers
Normal file
11
.git-allowed-signers
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# QUI A LE DROIT DE SIGNER CETTE LIGNEE.
|
||||
#
|
||||
# Format natif de git (`gpg.ssh.allowedSignersFile`) : une identite, puis sa cle
|
||||
# publique. Ce fichier est VERSIONNE et couvert par les etiquettes signees : qui clone le
|
||||
# depot peut verifier les signatures sans rien demander a personne, et sans faire confiance
|
||||
# a la forge.
|
||||
#
|
||||
# Retirer une ligne ici REVOQUE le signataire pour tout ce qui vient apres. L'historique
|
||||
# deja signe reste verifiable — c'est le comportement voulu : on ne reecrit pas le passe,
|
||||
# on cesse d'accepter l'avenir.
|
||||
allaire.dan@chezlepro.ca ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILKb7XpYYfKuoBIVprPYX1VVdebRMvpiV2NcsZYaYzbP
|
||||
68
CHANGELOG.md
68
CHANGELOG.md
|
|
@ -1,5 +1,73 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-21 — L'intuition disait « blockchain » ; la réponse était déjà dans git
|
||||
|
||||
L'exploitant, en regardant la lignée s'ouvrir : *« j'ai une intuition : blockchain. »*
|
||||
L'intuition visait le bon problème — une mémoire partagée, vérifiable, sans centre — mais
|
||||
la réponse était à portée de main, et une vérification l'a montré :
|
||||
|
||||
```
|
||||
1f47e9b N 6148877 N 254268d N (N = aucune signature)
|
||||
aucune étiquette
|
||||
```
|
||||
|
||||
**Git est déjà une chaîne de hachage.** Chaque commit porte l'empreinte de son parent :
|
||||
modifier une ligne d'il y a trois mois casse toutes les empreintes suivantes. C'est un
|
||||
arbre de Merkle — la même structure qu'une blockchain, sans le reste. Ce qui manquait
|
||||
n'était pas la chaîne, mais **l'auteur** : `user.name` est déclaratif, et toute la soirée
|
||||
du 20 des commits ont porté « Daniel Allaire » sans qu'aucune preuve ne les lie à une clé.
|
||||
|
||||
### Trois manques, trois réponses mûres
|
||||
|
||||
| Manque | Posé aujourd'hui |
|
||||
|---|---|
|
||||
| **qui** a écrit | signature par clé SSH ; première étiquette signée `v2026.08.21`, vérifiée |
|
||||
| **quelles clés** ont le droit | `.git-allowed-signers`, **versionné** : qui clone vérifie sans rien demander à la forge |
|
||||
| **de quoi** on descend | `parente.yml` par écosystème, et la preuve **P40** |
|
||||
|
||||
### Ce que la fractale fait gratuitement
|
||||
|
||||
Une signature prouve l'auteur, pas que l'histoire n'a pas été **remplacée** — celui qui
|
||||
tient la forge peut réécrire et re-signer. Le seul remède est la multiplicité : si chaque
|
||||
enfant porte une copie du code dont il descend, réécrire suppose de convaincre **tous les
|
||||
descendants**.
|
||||
|
||||
C'est très exactement ce qu'une blockchain achète au prix d'une machinerie considérable, et
|
||||
que la lignée produit **comme effet secondaire de sa forme**. Une chaîne publique
|
||||
ajouterait une dépendance à un réseau extérieur ; une chaîne privée, une base de données
|
||||
distribuée exigeant plusieurs opérateurs — l'inverse de *« un humain doit pouvoir la faire
|
||||
tourner »*.
|
||||
|
||||
### Le génome
|
||||
|
||||
`make genome` nomme les **quatre dépôts** sans lesquels un écosystème ne renaît pas :
|
||||
moteur, instance, hébergeur, modèles. Ils sont **dérivés**, pas déclarés — les modèles se
|
||||
reconnaissent à leur forme (des plans en sous-dossiers, aucun à la racine).
|
||||
|
||||
Deux critères appris d'un faux positif : sans le second, le détecteur désignait le lab, qui
|
||||
porte un lien `OPS-Technolibre -> ../OPS-Technolibre` que le motif traversait. **Un lien
|
||||
vers un frère n'est pas un contenu.**
|
||||
|
||||
Patient 0 sait désormais d'où il vient : moteur `742bcbf`, étiquette `v2026.08.21`.
|
||||
|
||||
### Enseigné, pas seulement posé
|
||||
|
||||
Nouvelle unité : [Filiation, signatures & témoins](wiki/Filiation-signatures-et-témoins.md)
|
||||
— pourquoi git suffit, ce qu'une signature prouve et ce qu'elle ne prouve pas, pourquoi les
|
||||
témoins comptent plus que la longueur des clés, et quand un **journal de transparence**
|
||||
serait la vraie réponse (le jour où l'Alliance certifiera des écosystèmes).
|
||||
|
||||
Onze termes ajoutés au glossaire — génome, parenté, empreinte, Merkle, étiquette,
|
||||
signature, témoin, journal de transparence, horodatage… — et **P39 les exige désormais** :
|
||||
le vocabulaire de ce soir ne pourra pas rester non expliqué.
|
||||
|
||||
`make verifier` : **40 OK, 0 échec, 0 sauté**.
|
||||
|
||||
> **La bonne question n'était pas « quelle technologie ».** C'était : *contre qui se
|
||||
> protège-t-on, et qui, déjà, pourrait témoigner ?* Les témoins existaient — ce sont les
|
||||
> enfants. Il ne manquait qu'un nom sur les clés et un registre de filiation.
|
||||
|
||||
|
||||
## 2026-08-21 — Le glossaire définissait Set-OPS et laissait dehors tout le métier
|
||||
|
||||
Demande de l'exploitant, après une soirée passée à croiser *strophe FRR*, *VRF*, *VNet* et
|
||||
|
|
|
|||
13
Makefile
13
Makefile
|
|
@ -249,6 +249,19 @@ verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe ## R
|
|||
# `make ci` passait sur un depot nu et echouait chez un exploitant dont le symlink
|
||||
# `underlay.yml` designe une autre fabric — quatre hotes « en ecart », pour un ecart
|
||||
# fabrique par la commande elle-meme.
|
||||
# LE GENOME : les depots sans lesquels cet ecosysteme ne peut pas etre refabrique, et la
|
||||
# PARENTE qu'il inscrit (de quel moteur il descend, a quel commit, sous quelle etiquette
|
||||
# signee). Perdre les VM coute du temps ; perdre ces depots coute l'ecosysteme.
|
||||
.PHONY: genome genome-inscrire genome-verifier
|
||||
genome: ## Les depots requis a la reproduction de cet ecosysteme, et leur etat
|
||||
python3 scripts/genome.py lister
|
||||
|
||||
genome-inscrire: ## Inscrit la parente dans l'instance (instance/parente.yml)
|
||||
python3 scripts/genome.py inscrire
|
||||
|
||||
genome-verifier: ## La parente inscrite tient-elle encore ? (code de sortie)
|
||||
python3 scripts/genome.py verifier
|
||||
|
||||
.PHONY: ci
|
||||
ci: ## Verifie le depot comme la CI : modele public monte a l'ecart, sans toucher ton instance
|
||||
@set -e ; \
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Ce plan est le **pendant manuel** de `make prouver` : là où le harnais prouve
|
|||
le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (cf.
|
||||
`protocole-operateur-independant.md`).
|
||||
|
||||
**85 gestes** sur **21 unités** · **19** en « casse-répare »
|
||||
**89 gestes** sur **22 unités** · **19** en « casse-répare »
|
||||
· **5** doublés d'un garde-fou machine (colonne *Preuve auto*).
|
||||
|
||||
> **Honnêteté de couverture.** La colonne *Preuve auto* n'est remplie que lorsqu'une
|
||||
|
|
@ -72,6 +72,17 @@ le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (c
|
|||
|
||||
*Source : [DNS & résolution de noms](DNS-et-résolution) · § À toi de jouer.*
|
||||
|
||||
## Filiation, signatures et témoins
|
||||
|
||||
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|
||||
|---|---|---|---|---|
|
||||
| 1 | — | make genome — quatre dépôts ? Lequel n'a pas de *remote* ? Celui-là n'existe qu'ici. | 👁 observe | — |
|
||||
| 2 | — | git verify-tag v2026.08.21 — que se passe-t-il si tu retires ta ligne de .git-allowed-signers ? (remets-la ensuite) | 👁 observe | — |
|
||||
| 3 | — | make genome-inscrire, puis ouvre parente.yml. Dans un an, qu'est-ce que ce fichier te dira que ta mémoire ne dira plus ? | 👁 observe | — |
|
||||
| 4 | — | Demande-toi où sont les témoins aujourd'hui. Combien de copies vivantes du moteur existent, sur combien de machines distinctes ? C'est la vraie mesure de la résistance de la lignée — pas la longueur des clés. Voir aussi : Multi-instance… | 👁 observe | — |
|
||||
|
||||
*Source : [Filiation, signatures et témoins](Filiation-signatures-et-témoins) · § À toi de jouer.*
|
||||
|
||||
## Identité & SSO
|
||||
|
||||
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|
||||
|
|
|
|||
231
scripts/genome.py
Normal file
231
scripts/genome.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Le GENOME d'un ecosysteme : les depots qui le fabriquent, et sa parente.
|
||||
|
||||
Un ecosysteme Set-OPS ne se reproduit pas depuis ses machines : il se reproduit depuis
|
||||
QUATRE depots. Le moteur, le plan du tenant, le depot de l'hebergeur (sa fabric), et les
|
||||
modeles. Perdre les VM ne coute que du temps ; perdre ces quatre-la coute l'ecosysteme.
|
||||
|
||||
CE QUE CE SCRIPT REPOND, et que rien ne repondait :
|
||||
|
||||
1. de QUOI cet ecosysteme descend — quel depot, quel commit, quelle etiquette signee ;
|
||||
2. cette filiation est-elle encore VERIFIABLE — le commit existe-t-il, la signature
|
||||
tient-elle contre le registre des signataires du depot.
|
||||
|
||||
POURQUOI L'ECRIRE PLUTOT QUE LE DEDUIRE. Un enfant fabrique aujourd'hui depuis la forge
|
||||
de son parent, et laisse a l'annee suivante le soin de deviner d'ou il vient. Sans
|
||||
registre, cinq ecosystemes en un an deviennent cinq moteurs differents que plus personne
|
||||
ne sait rapprocher : c'est la mutation sans memoire. Trois lignes par depot l'evitent.
|
||||
|
||||
CE QUE CE SCRIPT NE FAIT PAS : il n'ecrit rien sur une forge et ne pousse aucun miroir.
|
||||
Il constate et inscrit. Le miroir des depots sur la forge de l'ecosysteme est le niveau
|
||||
suivant — il exige un jeton d'API, donc un choix d'exploitant.
|
||||
|
||||
python3 scripts/genome.py lister # les quatre depots, leur etat
|
||||
python3 scripts/genome.py inscrire # ecrit <instance>/parente.yml
|
||||
python3 scripts/genome.py verifier # la parente inscrite tient-elle encore ?
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
import underlay as underlay_mod # noqa: E402
|
||||
|
||||
FICHIER_PARENTE = "parente.yml"
|
||||
|
||||
|
||||
def _git(depot: Path, *args: str) -> tuple[int, str]:
|
||||
r = subprocess.run(["git", "-C", str(depot), *args],
|
||||
capture_output=True, text=True)
|
||||
return r.returncode, (r.stdout or r.stderr).strip()
|
||||
|
||||
|
||||
def _instance() -> Path:
|
||||
return Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance")).resolve()
|
||||
|
||||
|
||||
def _hebergeur() -> Path | None:
|
||||
"""Le depot qui porte `underlay.yml` — la fabric du site."""
|
||||
c = underlay_mod.chemin()
|
||||
return c.resolve().parent if c else None
|
||||
|
||||
|
||||
def _modeles() -> Path | None:
|
||||
"""Le depot des modeles, trouve par sa FORME et non par son nom.
|
||||
|
||||
Un depot de modeles contient des dossiers qui sont chacun un plan
|
||||
(`<modele>/plan/nomenclature.yml`) et n'en a PAS a sa propre racine. Chercher
|
||||
« Set-OPS-Modeles » par son nom aurait marche jusqu'au jour ou quelqu'un le renomme.
|
||||
|
||||
Deux criteres, tous deux appris d'un faux positif (2026-08-21) :
|
||||
- pas de plan a la racine, sinon on designe une INSTANCE ;
|
||||
- ne pas suivre les SYMLINKS. Le premier jet a designe le lab, qui porte un lien
|
||||
`OPS-Technolibre -> ../OPS-Technolibre` : le motif l'a traverse et a cru voir un
|
||||
modele. Un lien vers un frere n'est pas un contenu.
|
||||
"""
|
||||
for frere in sorted(RACINE.parent.iterdir()):
|
||||
if not frere.is_dir() or not (frere / ".git").exists():
|
||||
continue
|
||||
if (frere / "plan" / "nomenclature.yml").is_file():
|
||||
continue
|
||||
if any(d.is_dir() and not d.is_symlink()
|
||||
and (d / "plan" / "nomenclature.yml").is_file()
|
||||
for d in frere.iterdir()):
|
||||
return frere.resolve()
|
||||
return None
|
||||
|
||||
|
||||
def depots_requis() -> list[dict]:
|
||||
"""Les depots sans lesquels cet ecosysteme ne peut pas etre refabrique."""
|
||||
vus: dict[Path, str] = {}
|
||||
for role, chemin in (("moteur", RACINE.resolve()),
|
||||
("instance", _instance()),
|
||||
("hebergeur", _hebergeur()),
|
||||
("modeles", _modeles())):
|
||||
if chemin is None or not chemin.exists():
|
||||
continue
|
||||
# L'hebergeur et l'instance sont souvent le MEME depot (l'operateur heberge son
|
||||
# propre tenant). On garde alors les deux roles sur une seule ligne : ce sont
|
||||
# deux fonctions, pas deux copies a sauvegarder.
|
||||
if chemin in vus:
|
||||
vus[chemin] += f"+{role}"
|
||||
else:
|
||||
vus[chemin] = role
|
||||
|
||||
depots = []
|
||||
for chemin, role in vus.items():
|
||||
rc_r, remote = _git(chemin, "remote", "get-url", "origin")
|
||||
rc_c, commit = _git(chemin, "rev-parse", "HEAD")
|
||||
_, branche = _git(chemin, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
_, etiquette = _git(chemin, "describe", "--tags", "--abbrev=0")
|
||||
_, propre = _git(chemin, "status", "--porcelain")
|
||||
signee = None
|
||||
if etiquette and not etiquette.startswith("fatal"):
|
||||
rc_v, _ = _git(chemin, "verify-tag", etiquette)
|
||||
signee = rc_v == 0
|
||||
else:
|
||||
etiquette = None
|
||||
depots.append({
|
||||
"role": role,
|
||||
"nom": chemin.name,
|
||||
"remote": remote if rc_r == 0 else None,
|
||||
"commit": commit if rc_c == 0 else None,
|
||||
"branche": branche or None,
|
||||
"etiquette": etiquette,
|
||||
"etiquette_signee": signee,
|
||||
"arbre_propre": propre == "",
|
||||
})
|
||||
return depots
|
||||
|
||||
|
||||
def lister() -> int:
|
||||
depots = depots_requis()
|
||||
print(f"Genome de « {_instance().name} » — {len(depots)} depot(s) requis "
|
||||
f"a sa reproduction\n")
|
||||
print(f" {'ROLE':<20} {'DEPOT':<22} {'COMMIT':<10} {'ETIQUETTE':<16} SIGNEE")
|
||||
for d in depots:
|
||||
sig = "-" if d["etiquette_signee"] is None else ("oui" if d["etiquette_signee"] else "NON")
|
||||
print(f" {d['role']:<20} {d['nom']:<22} {(d['commit'] or '?')[:9]:<10} "
|
||||
f"{(d['etiquette'] or '(aucune)'):<16} {sig}")
|
||||
if not d["remote"]:
|
||||
print(f" {'':<20} /!\\ aucun remote : ce depot n'existe qu'ici")
|
||||
if not d["arbre_propre"]:
|
||||
print(f" {'':<20} /!\\ arbre modifie : le commit inscrit ne dira pas tout")
|
||||
return 0
|
||||
|
||||
|
||||
def inscrire() -> int:
|
||||
"""Ecrit la parente dans l'instance : d'ou elle vient, a quel etat."""
|
||||
base = _instance()
|
||||
depots = depots_requis()
|
||||
contenu = {
|
||||
"parente": {
|
||||
"inscrite_le": dt.date.today().isoformat(),
|
||||
"depots": {d["role"]: {k: d[k] for k in
|
||||
("nom", "remote", "commit", "branche", "etiquette")}
|
||||
for d in depots},
|
||||
}
|
||||
}
|
||||
entete = (
|
||||
"# PARENTE de cet ecosysteme — de quoi il descend, et a quel etat.\n"
|
||||
"#\n"
|
||||
"# GENERE par `make genome-inscrire`. Ne pas editer a la main : reinscrire.\n"
|
||||
"#\n"
|
||||
"# Sans ce fichier, un ecosysteme fabrique aujourd'hui ne sait plus, dans un an,\n"
|
||||
"# de quel moteur il descend ni a quel etat. Cinq enfants sans registre font cinq\n"
|
||||
"# moteurs differents que plus personne ne sait rapprocher.\n"
|
||||
"#\n"
|
||||
"# Il ne remplace pas les depots : il dit ou les retrouver et lequel etait le bon.\n"
|
||||
"---\n")
|
||||
cible = base / FICHIER_PARENTE
|
||||
with cible.open("w", encoding="utf-8") as f:
|
||||
f.write(entete)
|
||||
yaml.safe_dump(contenu, f, default_flow_style=False, sort_keys=False,
|
||||
allow_unicode=True)
|
||||
print(f"{cible} inscrit — {len(depots)} depot(s).")
|
||||
return 0
|
||||
|
||||
|
||||
def verifier() -> int:
|
||||
"""La parente inscrite tient-elle encore ? Code 0 si oui."""
|
||||
base = _instance()
|
||||
cible = base / FICHIER_PARENTE
|
||||
if not cible.is_file():
|
||||
print(f"Aucune parente inscrite ({cible}). La poser : make genome-inscrire",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
data = yaml.safe_load(cible.read_text(encoding="utf-8")) or {}
|
||||
inscrits = (data.get("parente") or {}).get("depots") or {}
|
||||
actuels = {d["role"]: d for d in depots_requis()}
|
||||
|
||||
fautes: list[str] = []
|
||||
for role, d in actuels.items():
|
||||
if role not in inscrits:
|
||||
fautes.append(f"{role} ({d['nom']}) absent de la parente inscrite")
|
||||
for role, ins in inscrits.items():
|
||||
d = actuels.get(role)
|
||||
if d is None:
|
||||
fautes.append(f"{role} inscrit mais introuvable aujourd'hui "
|
||||
f"({ins.get('nom')})")
|
||||
continue
|
||||
commit = ins.get("commit")
|
||||
if commit:
|
||||
chemin = RACINE.parent / ins["nom"] if ins["nom"] != RACINE.name else RACINE
|
||||
rc, _ = _git(chemin, "cat-file", "-e", f"{commit}^{{commit}}")
|
||||
if rc != 0:
|
||||
fautes.append(f"{role} : le commit inscrit {commit[:9]} n'existe plus "
|
||||
f"dans {ins['nom']} (histoire reecrite ?)")
|
||||
if not ins.get("remote"):
|
||||
fautes.append(f"{role} ({ins.get('nom')}) : aucun remote inscrit — "
|
||||
f"ce depot n'existe nulle part ailleurs qu'ici")
|
||||
|
||||
if fautes:
|
||||
for f in fautes:
|
||||
print(f"parente : {f}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Parente coherente : {len(inscrits)} depot(s), tous retrouves, "
|
||||
f"tous porteurs d'un remote.")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
sub = ap.add_subparsers(dest="commande", required=True)
|
||||
sub.add_parser("lister", help="Les depots requis a la reproduction, et leur etat.")
|
||||
sub.add_parser("inscrire", help="Ecrit <instance>/parente.yml.")
|
||||
sub.add_parser("verifier", help="La parente inscrite tient-elle encore ?")
|
||||
a = ap.parse_args()
|
||||
return {"lister": lister, "inscrire": inscrire, "verifier": verifier}[a.commande]()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -734,9 +734,42 @@ JARGON_A_ENSEIGNER = [
|
|||
"SMTP", "IMAP", "LMTP", "DKIM", "MTA",
|
||||
# etat et methode
|
||||
"restic", "devis", "preuve", "harnais", "GUI", "index", "plan", "tenant",
|
||||
# filiation — le vocabulaire arrive le 2026-08-21 avec les signatures
|
||||
"genome", "parente", "commit", "empreinte", "Merkle", "etiquette", "signature",
|
||||
"allowed-signers", "temoin", "journal de transparence", "horodatage",
|
||||
]
|
||||
|
||||
|
||||
def preuve_parente_inscrite() -> tuple[bool, str]:
|
||||
"""L'ecosysteme sait de QUOI il descend, et cette filiation tient encore.
|
||||
|
||||
POURQUOI (2026-08-21). Un ecosysteme se reproduit depuis quatre depots, pas depuis ses
|
||||
machines. Fabrique aujourd'hui depuis la forge de son parent, il ne saura plus dans un
|
||||
an de quel moteur il descend ni a quel etat — et cinq enfants sans registre font cinq
|
||||
moteurs differents que plus personne ne sait rapprocher. C'est la mutation sans
|
||||
memoire : le seul vrai risque d'une lignee.
|
||||
|
||||
CE QU'ELLE TESTE (via `scripts/genome.py verifier`) : la parente est inscrite ; chaque
|
||||
depot qu'elle nomme se retrouve ; chaque commit inscrit EXISTE encore dans son depot
|
||||
(une histoire reecrite se voit ici) ; et chacun porte un remote — un depot qui n'existe
|
||||
qu'ici n'est pas une lignee, c'est un point unique de defaillance.
|
||||
|
||||
SAUTEE quand l'instance n'est pas un depot git : c'est le cas du modele public monte
|
||||
par la CI. Un gabarit jetable n'a pas d'ascendance a declarer, et exiger un
|
||||
`parente.yml` d'une copie temporaire ferait echouer la CI pour une question qui n'a pas
|
||||
de sens dans son contexte.
|
||||
|
||||
CE QU'ELLE NE TESTE PAS : que le depot distant contienne encore ce commit. Cela exige
|
||||
de joindre la forge ; ici on ne verifie que ce qui est lisible hors ligne.
|
||||
"""
|
||||
if not (INSTANCE / ".git").exists():
|
||||
return True, "Instance sans depot git (modele jetable) : aucune ascendance a declarer."
|
||||
r = subprocess.run([sys.executable, "scripts/genome.py", "verifier"],
|
||||
cwd=RACINE, capture_output=True, text=True, timeout=120)
|
||||
sortie = (r.stdout + r.stderr).strip().splitlines()
|
||||
return r.returncode == 0, (sortie[-1] if sortie else "")
|
||||
|
||||
|
||||
def preuve_glossaire_enseigne() -> tuple[bool, str]:
|
||||
"""Tout mot que le depot emploie devant l'exploitant est explique au glossaire.
|
||||
|
||||
|
|
@ -1069,6 +1102,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_catalogue_a_jour},
|
||||
{"id": "P39", "titre": "Glossaire : tout mot employe est enseigne", "refs": [],
|
||||
"func": preuve_glossaire_enseigne},
|
||||
{"id": "P40", "titre": "Parente : l'ecosysteme sait de quoi il descend", "refs": [],
|
||||
"func": preuve_parente_inscrite},
|
||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
|
||||
]
|
||||
|
|
|
|||
139
wiki/Filiation-signatures-et-témoins.md
Normal file
139
wiki/Filiation-signatures-et-témoins.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Filiation, signatures et témoins
|
||||
|
||||
> **Unité d'apprentissage.** Moule : ① concept → ② Set-OPS → ③ transférable → ④ à toi de jouer.
|
||||
|
||||
Un écosystème Set-OPS peut en **engendrer** un autre. Dès qu'il y a une descendance, trois
|
||||
questions apparaissent, et aucune n'est technique au départ :
|
||||
|
||||
- **de qui** cet écosystème descend-il ?
|
||||
- **qui** a le droit de modifier ce dont il descend ?
|
||||
- comment le savoir **sans faire confiance** à celui qui héberge le code ?
|
||||
|
||||
---
|
||||
|
||||
## ① Le concept
|
||||
|
||||
### Git est déjà une chaîne de hachage
|
||||
|
||||
C'est la chose la plus utile à comprendre, et la moins connue. Chaque commit contient
|
||||
l'**empreinte** de son parent. Changer une ligne d'il y a trois mois change l'empreinte de
|
||||
ce commit — donc celle du suivant, et de tous les autres jusqu'à aujourd'hui.
|
||||
|
||||
```
|
||||
A ←── B ←── C ←── D modifier B casse C et D
|
||||
```
|
||||
|
||||
C'est un **arbre de Merkle** : la même structure de données qu'une blockchain. L'intégrité
|
||||
est déjà là, gratuitement, depuis 2005.
|
||||
|
||||
### Ce que la chaîne ne dit pas
|
||||
|
||||
**Qui a écrit.** `user.name` est purement déclaratif : n'importe qui peut signer « Daniel
|
||||
Allaire » et pousser. Rien ne lie ce nom à une personne.
|
||||
|
||||
C'est ce que corrigent les **signatures**. Avec une clé — la même clé SSH que pour se
|
||||
connecter — on scelle une étiquette ou un commit. Qui possède la **clé publique** peut
|
||||
vérifier ; qui ne possède pas la **clé privée** ne peut pas contrefaire.
|
||||
|
||||
Reste alors une question : *quelles clés ont le droit de signer ?* La réponse doit vivre
|
||||
**avec le code**, pas chez l'hébergeur — sinon on remplace la confiance en une personne par
|
||||
la confiance en une plateforme.
|
||||
|
||||
### Les témoins, ou pourquoi la fractale est le registre
|
||||
|
||||
Une signature prouve l'auteur, pas que l'histoire n'a pas été **remplacée**. Celui qui tient
|
||||
la forge peut réécrire et re-signer.
|
||||
|
||||
Contre ça, un seul remède : la **multiplicité**. Si chaque écosystème enfant porte une copie
|
||||
du code dont il descend, réécrire l'histoire suppose de convaincre **tous les descendants**.
|
||||
C'est exactement ce qu'une blockchain achète au prix d'une machinerie considérable — et
|
||||
qu'une lignée produit comme **effet secondaire de sa forme**.
|
||||
|
||||
> **Pourquoi pas une blockchain, alors ?** Elle résout un problème précis : *qui écrit
|
||||
> ensuite, quand personne ne fait confiance à personne et qu'il y a de l'argent en jeu.*
|
||||
> Ici : les membres sont connus, il n'y a pas d'argent dans la boucle, et une ancre de
|
||||
> confiance existe déjà. Une chaîne publique ajouterait une dépendance à un réseau
|
||||
> extérieur ; une chaîne privée, une base de données distribuée exigeant plusieurs
|
||||
> opérateurs — l'inverse de « un humain doit pouvoir la faire tourner ».
|
||||
|
||||
---
|
||||
|
||||
## ② Dans Set-OPS
|
||||
|
||||
### Les quatre dépôts du génome
|
||||
|
||||
Un écosystème ne se reproduit pas depuis ses machines. Il se reproduit depuis **quatre
|
||||
dépôts** — et `make genome` les nomme :
|
||||
|
||||
| Rôle | Ce qu'il porte |
|
||||
|---|---|
|
||||
| **moteur** | Set-OPS : les rôles, les preuves, le Makefile |
|
||||
| **instance** | le plan du tenant, sa voûte chiffrée |
|
||||
| **hébergeur** | l'`underlay.yml` du site : sa fabric physique |
|
||||
| **modèles** | les offres dont les instances sont tirées |
|
||||
|
||||
Perdre les VM coûte du temps. Perdre ces quatre-là coûte l'écosystème.
|
||||
|
||||
### Le registre des signataires
|
||||
|
||||
`.git-allowed-signers`, **versionné dans le dépôt** :
|
||||
|
||||
```
|
||||
allaire.dan@chezlepro.ca ssh-ed25519 AAAAC3NzaC1lZDI1...
|
||||
```
|
||||
|
||||
Qui clone peut vérifier sans rien demander à la forge. Retirer une ligne **révoque** le
|
||||
signataire pour la suite ; ce qui est déjà signé reste vérifiable — on ne réécrit pas le
|
||||
passé, on cesse d'accepter l'avenir.
|
||||
|
||||
### La parenté, inscrite
|
||||
|
||||
`make genome-inscrire` écrit `parente.yml` dans l'instance : de quel moteur elle descend,
|
||||
à quel **commit**, sous quelle **étiquette signée**.
|
||||
|
||||
```yaml
|
||||
moteur:
|
||||
remote: ssh://…/Set-OPS-Public.git
|
||||
commit: 742bcbf4b0e1322bfa5e0dee14c94bcad6898c53
|
||||
etiquette: v2026.08.21
|
||||
```
|
||||
|
||||
Sans ce fichier, un écosystème fabriqué aujourd'hui ne sait plus, dans un an, d'où il
|
||||
vient. Cinq enfants sans registre font cinq moteurs différents que plus personne ne sait
|
||||
rapprocher. **C'est la mutation sans mémoire, et c'est le seul vrai risque d'une lignée.**
|
||||
|
||||
La preuve **P40** vérifie que la parenté est inscrite, que chaque dépôt nommé se retrouve,
|
||||
que chaque commit inscrit **existe encore** — une histoire réécrite se voit là — et que
|
||||
chacun porte un *remote* : un dépôt qui n'existe qu'ici n'est pas une lignée, c'est un
|
||||
point unique de défaillance.
|
||||
|
||||
---
|
||||
|
||||
## ③ Transférable
|
||||
|
||||
Rien ici n'appartient à Set-OPS :
|
||||
|
||||
- **signature par clé SSH** : git 2.34+, partout, sans infrastructure ;
|
||||
- **`allowed_signers`** : le mécanisme natif d'OpenSSH ;
|
||||
- **journal de transparence** (Certificate Transparency, Sigstore) : la réponse mûre quand
|
||||
plusieurs organisations doivent constater les mêmes faits sans se faire confiance ;
|
||||
- **horodatage RFC 3161** : prouver qu'un document existait à une date.
|
||||
|
||||
Le jour où l'Alliance certifiera des écosystèmes, c'est un **journal de transparence**
|
||||
qu'il faudra — un registre append-only, auditable par un tiers — et non une blockchain.
|
||||
|
||||
---
|
||||
|
||||
## ④ À toi de jouer
|
||||
|
||||
1. `make genome` — quatre dépôts ? Lequel n'a pas de *remote* ? Celui-là n'existe qu'ici.
|
||||
2. `git verify-tag v2026.08.21` — que se passe-t-il si tu retires ta ligne de
|
||||
`.git-allowed-signers` ? (remets-la ensuite)
|
||||
3. `make genome-inscrire`, puis ouvre `parente.yml`. Dans un an, qu'est-ce que ce fichier
|
||||
te dira que ta mémoire ne dira plus ?
|
||||
4. Demande-toi où sont les **témoins** aujourd'hui. Combien de copies vivantes du moteur
|
||||
existent, sur combien de machines distinctes ? C'est la vraie mesure de la résistance de
|
||||
la lignée — pas la longueur des clés.
|
||||
|
||||
**Voir aussi** : [Multi-instance & fédération](Multi-instance-et-fédération),
|
||||
[La preuve](La-preuve), [Sauvegardes](Sauvegardes).
|
||||
|
|
@ -306,3 +306,42 @@ quoi** il a porté.
|
|||
[Le GUI](Le-GUI-console-d-exploitation).
|
||||
|
||||
**CI** — La vérification automatique à chaque poussée (`make ci` la rejoue à l'identique).
|
||||
|
||||
---
|
||||
|
||||
## La filiation
|
||||
|
||||
→ Unité complète : [Filiation, signatures & témoins](Filiation-signatures-et-témoins)
|
||||
|
||||
**Génome** — Les **quatre dépôts** sans lesquels un écosystème ne peut pas être refabriqué :
|
||||
le *moteur*, l'*instance*, le dépôt de l'hébergeur, les *modèles*. `make genome` les nomme.
|
||||
|
||||
**Parenté** — Le fichier `parente.yml` d'une instance : de quel moteur elle descend, à quel
|
||||
*commit*, sous quelle *étiquette signée*. Sans lui, un écosystème ne sait plus d'où il vient.
|
||||
|
||||
**Commit** — Un instantané du dépôt, qui porte l'**empreinte** du précédent. Modifier le
|
||||
passé change toutes les empreintes suivantes : c'est ce qui rend une histoire vérifiable.
|
||||
|
||||
**Empreinte (hachage)** — Une courte signature calculée d'un contenu. Deux contenus
|
||||
différents n'ont pas la même — c'est ce qui permet de détecter une modification.
|
||||
|
||||
**Arbre de Merkle** — La structure où chaque élément porte l'empreinte du précédent. Git en
|
||||
est un ; une blockchain aussi. L'intégrité vient de là, pas d'une chaîne de blocs.
|
||||
|
||||
**Étiquette (tag)** — Un nom posé sur un état précis du dépôt (`v2026.08.21`). Signée, elle
|
||||
devient un point de repère **vérifiable** : « c'est de cet état-là que je descends ».
|
||||
|
||||
**Signature** — Le scellé posé avec une clé privée. Qui a la clé **publique** peut vérifier ;
|
||||
qui n'a pas la privée ne peut pas contrefaire. Sans elle, un nom d'auteur est déclaratif.
|
||||
|
||||
**`.git-allowed-signers`** — Le registre, **versionné**, des clés autorisées à signer cette
|
||||
lignée. Retirer une ligne révoque pour la suite ; le passé signé reste vérifiable.
|
||||
|
||||
**Témoin** — Une copie vivante et indépendante du *génome*. Réécrire l'histoire suppose de
|
||||
convaincre tous les témoins : c'est la lignée elle-même qui protège la lignée.
|
||||
|
||||
**Journal de transparence** — Un registre append-only, auditable par un tiers (Certificate
|
||||
Transparency, Sigstore). La réponse mûre quand plusieurs organisations doivent constater les
|
||||
mêmes faits sans se faire confiance — et l'alternative sobre à une blockchain.
|
||||
|
||||
**Horodatage (RFC 3161)** — Prouver qu'un document **existait** à une date, par un tiers.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
*Flotte & preuve*
|
||||
- [Multi-instance & fédération](Multi-instance-et-fédération)
|
||||
- [La preuve](La-preuve)
|
||||
- [Filiation, signatures & témoins](Filiation-signatures-et-témoins)
|
||||
- [Vérifier le déployé](Vérifier-le-déployé)
|
||||
|
||||
**Opérations**
|
||||
|
|
|
|||
Loading…
Reference in a new issue