Compare commits
No commits in common. "79461fdc38288b8ad65b1b8c1ed5f5522b0dce29" and "1f47e9bca85bf010aada0fd05abae634bb223605" have entirely different histories.
79461fdc38
...
1f47e9bca8
9 changed files with 1 additions and 549 deletions
|
|
@ -1,11 +0,0 @@
|
||||||
# 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,73 +1,5 @@
|
||||||
# CHANGELOG — Set-OPS
|
# 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
|
## 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
|
Demande de l'exploitant, après une soirée passée à croiser *strophe FRR*, *VRF*, *VNet* et
|
||||||
|
|
|
||||||
13
Makefile
13
Makefile
|
|
@ -249,19 +249,6 @@ 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
|
# `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
|
# `underlay.yml` designe une autre fabric — quatre hotes « en ecart », pour un ecart
|
||||||
# fabrique par la commande elle-meme.
|
# 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
|
.PHONY: ci
|
||||||
ci: ## Verifie le depot comme la CI : modele public monte a l'ecart, sans toucher ton instance
|
ci: ## Verifie le depot comme la CI : modele public monte a l'ecart, sans toucher ton instance
|
||||||
@set -e ; \
|
@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.
|
le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (cf.
|
||||||
`protocole-operateur-independant.md`).
|
`protocole-operateur-independant.md`).
|
||||||
|
|
||||||
**89 gestes** sur **22 unités** · **19** en « casse-répare »
|
**85 gestes** sur **21 unités** · **19** en « casse-répare »
|
||||||
· **5** doublés d'un garde-fou machine (colonne *Preuve auto*).
|
· **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
|
> **Honnêteté de couverture.** La colonne *Preuve auto* n'est remplie que lorsqu'une
|
||||||
|
|
@ -72,17 +72,6 @@ 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.*
|
*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
|
## Identité & SSO
|
||||||
|
|
||||||
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|
||||||
|
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
#!/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,42 +734,9 @@ JARGON_A_ENSEIGNER = [
|
||||||
"SMTP", "IMAP", "LMTP", "DKIM", "MTA",
|
"SMTP", "IMAP", "LMTP", "DKIM", "MTA",
|
||||||
# etat et methode
|
# etat et methode
|
||||||
"restic", "devis", "preuve", "harnais", "GUI", "index", "plan", "tenant",
|
"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]:
|
def preuve_glossaire_enseigne() -> tuple[bool, str]:
|
||||||
"""Tout mot que le depot emploie devant l'exploitant est explique au glossaire.
|
"""Tout mot que le depot emploie devant l'exploitant est explique au glossaire.
|
||||||
|
|
||||||
|
|
@ -1102,8 +1069,6 @@ PREUVES: list[dict] = [
|
||||||
"func": preuve_catalogue_a_jour},
|
"func": preuve_catalogue_a_jour},
|
||||||
{"id": "P39", "titre": "Glossaire : tout mot employe est enseigne", "refs": [],
|
{"id": "P39", "titre": "Glossaire : tout mot employe est enseigne", "refs": [],
|
||||||
"func": preuve_glossaire_enseigne},
|
"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": [],
|
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||||
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
|
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
# 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,42 +306,3 @@ quoi** il a porté.
|
||||||
[Le GUI](Le-GUI-console-d-exploitation).
|
[Le GUI](Le-GUI-console-d-exploitation).
|
||||||
|
|
||||||
**CI** — La vérification automatique à chaque poussée (`make ci` la rejoue à l'identique).
|
**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,7 +37,6 @@
|
||||||
*Flotte & preuve*
|
*Flotte & preuve*
|
||||||
- [Multi-instance & fédération](Multi-instance-et-fédération)
|
- [Multi-instance & fédération](Multi-instance-et-fédération)
|
||||||
- [La preuve](La-preuve)
|
- [La preuve](La-preuve)
|
||||||
- [Filiation, signatures & témoins](Filiation-signatures-et-témoins)
|
|
||||||
- [Vérifier le déployé](Vérifier-le-déployé)
|
- [Vérifier le déployé](Vérifier-le-déployé)
|
||||||
|
|
||||||
**Opérations**
|
**Opérations**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue