docs: add pro runbooks (deploy/edge/public_site) + annotations spec + start-here v2
All checks were successful
CI / build-and-anchors (push) Successful in 1m45s
SMOKE / smoke (push) Successful in 11s

This commit is contained in:
2026-02-21 15:34:47 +01:00
parent fe7810671d
commit 7444eeb532
5 changed files with 966 additions and 0 deletions

View File

@@ -0,0 +1,327 @@
# SPEC — Annotations éditoriales (YAML v1) + merge + anti-doublon
> Objectif : permettre aux tickets (Gitea) de déposer “Références / Médias / Commentaires” dans `src/annotations/**`,
> de façon univoque, stable, et sans régression.
## 0) Contexte et intention
Le site est statique. Lédition collaborative se fait via :
- un mode “proposition” (UI / modal)
- un ticket Gitea (issue) standardisé
- un script dapplication côté éditeur (`apply-ticket.mjs` ou équivalent)
- génération dun YAML dannotations versionné dans Git
La donnée dannotation doit être :
- **audit-able** (Git)
- **merge-able** (sans tout casser)
- **stable** (IDs paragraphes / liens / médias)
- **scalable** (éviter YAML monstrueux à long terme)
## 1) Arborescence canonique
### 1.1 Un workKey par “ouvrage / section du site”
On veut une univocité entre :
- SiteNav (Méthode, Essai-thèse, Traité, Cas IA, Glossaire, Atlas)
et
- larborescence annotations
Proposition canonique (workKey = route racine) :
- `methode`
- `archicrat-ia` (Essai-thèse ArchiCraT-IA)
- `traite`
- `ia`
- `glossaire`
- `atlas`
### 1.2 Règle de stockage “v1”
**Par page**, un YAML unique :
src/annotations/<workKey>/<slugSansWorkKey>.yml
Exemples :
- Page : `/archicrat-ia/prologue/`
- slug content = `archicrat-ia/prologue`
- fichier : `src/annotations/archicrat-ia/prologue.yml`
- Page : `/traite/00-demarrage/`
- fichier : `src/annotations/traite/00-demarrage.yml`
> Note : “slugSansWorkKey” = la partie après `<workKey>/`.
> Sil y a des sous-dossiers (chapitres), le chemin reflète la structure : `chapitre-1/section-a.yml` si on choisit du sharding.
## 2) Question “gros YAML” : page unique vs sharding par paragraphe
### 2.1 Option A (v1 recommandée) : 1 YAML par page
Avantages :
- simple
- peu de fichiers
- diff lisible si volume modéré
- cohérent avec un modèle “annotations par page”
Inconvénients :
- YAML peut grossir si milliers dannotations
### 2.2 Option B (v2 future) : sharding par paragraphe
src/annotations/<workKey>/<slugSansWorkKey>/<paraId>.yml
Avantages :
- fichiers petits
- merges moins conflictuels
Inconvénients :
- plus de fichiers
- tooling plus complexe (indexation + merge multi-fichiers)
### 2.3 Recommandation de mission (sans casser lexistant)
- On démarre en **Option A**.
- On se garde une migration future (v2) quand le volume réel le justifie.
- On impose dès v1 : **clé unique + merge déterministe + anti-doublon**, ce qui rend la migration future possible.
## 3) Format YAML v1 (schéma complet)
### 3.1 Top-level
en yaml :
schema: 1
# Optionnel mais recommandé (doit matcher la page)
page: "<workKey>/<slugSansWorkKey>"
meta:
title: "Titre de la page (optionnel)"
updatedAt: "2026-02-21T12:34:56Z" # ISO8601
updatedBy: "username" # compte editor
source:
kind: "ticket"
id: 123
url: "https://gitea.../issues/123"
paras:
"<paraId>":
references: []
media: []
comments: []
### 3.2 paras : clé = paraId (ex: p-0-d7974f88)
Chaque paragraphe peut porter 3 types déléments :
references
media
comments
Règle : si une section est vide, elle peut être [] ou absente.
Mais pour simplifier les merges, on recommande de garder la forme canonique avec [].
## 4) Formats des items + clés uniques
### 4.1 References
#### 4.1.1 Format
references:
- id: "ref:doi:10.1234/abcd.efgh" # clé stable (voir 4.1.2)
kind: "doi" # doi | url | isbn | arxiv | hal | other
label: "Titre court"
target: "https://doi.org/10.1234/abcd.efgh"
note: "Pourquoi cest pertinent (optionnel)"
addedAt: "2026-02-21T12:34:56Z"
addedBy: "username"
#### 4.1.2 Règle de clé unique (anti-doublon)
id doit être stable et déterministe :
doi → ref:doi:<doi>
isbn → ref:isbn:<isbn>
url → ref:url:<normalizedUrl>
Normalisation URL (v1) : au minimum
trim
lowercase scheme/host
retirer trailing slash si non significatif
conserver query si importante
#### 4.1.3 Merge / précédence
Quand on merge deux listes references :
union par id (clé unique)
si même id existe des deux côtés :
conserver kind/target de litem le plus “riche” (target non vide gagne)
concat/merge note :
si notes différentes : garder les deux en les séparant (ex: noteA + "\n---\n" + noteB)
addedAt : conserver le plus ancien
addedBy : conserver le premier (ou liste si on veut, mais v1 simple : first)
### 4.2 Media
#### 4.2.1 Format
media:
- id: "media:image:sha256:abcd..." # clé stable (voir 4.2.2)
type: "image" # image | video | audio | file
src: "/public/media/<workKey>/<slugSansWorkKey>/<paraId>/<filename>"
caption: "Légende (optionnel)"
credit: "Auteur/source (optionnel)"
license: "CC-BY (optionnel)"
addedAt: "2026-02-21T12:34:56Z"
addedBy: "username"
#### 4.2.2 Règle de clé unique
id déterministe :
idéal : hash du fichier (sha256)
sinon : hash de type + src
v1 (si on ne calcule pas de hash fichier) :
media:<type>:<src>
#### 4.2.3 Merge / précédence
union par id
si collision :
garder src identique (sinon cest un bug)
fusionner caption/credit/license selon “non vide gagne”
addedAt : plus ancien
### 4.3 Comments
#### 4.3.1 Format
comments:
- id: "cmt:20260221T123456Z:username:0001"
kind: "comment" # comment | question | objection | todo | validation
text: "Texte du commentaire"
status: "open" # open | resolved
addedAt: "2026-02-21T12:34:56Z"
addedBy: "username"
source:
kind: "ticket"
id: 123
#### 4.3.2 Clé unique
Les commentaires sont “append-only” → id peut être générée (timestamp + user + compteur)
Anti-doublon : si on ré-applique un ticket, on refuse de dupliquer un id existant.
#### 4.3.3 Merge / précédence
union par id
collisions rares, mais si elles arrivent :
si textes différents → garder les deux (on renomme lid du second)
## 5) Règles globales de merge (résumé)
Quand on applique un ticket sur un YAML existant :
vérifier schema == 1
vérifier page si présent :
doit matcher <workKey>/<slugSansWorkKey>
paras :
créer paras[paraId] si absent
pour chaque liste (references/media/comments) :
merge par id (anti-doublon)
appliquer règles de précédence (non vide gagne / concat note / append-only comments)
## 6) Table de correspondance “UI ticket → YAML”
Cette table permet à un successeur IA dimplémenter apply-ticket.mjs sans ambiguïté.
### 6.1 Champs UI minimaux
workKey (sélection implicite via page)
pagePath (ex: /archicrat-ia/prologue/)
pageSlug (ex: archicrat-ia/prologue)
paraId (ex: p-0-d7974f88)
kind :
reference
media
comment
### 6.2 Mapping exact
| UI kind | UI champs | YAML cible |
| --------- | ----------------------------------------------------------- | ---------------------------- |
| reference | kind(doi/url/isbn), target, label, note | `paras[paraId].references[]` |
| media | type(image/video/audio/file), src, caption, credit, license | `paras[paraId].media[]` |
| comment | kind(comment/question/objection/todo/validation), text | `paras[paraId].comments[]` |
### 6.3 Règles de génération dID (implémentation)
reference.id :
doi : ref:doi:${doi}
isbn : ref:isbn:${isbn}
url : ref:url:${normalize(url)}
media.id :
media:${type}:${src}
comment.id :
cmt:${timestamp}:${user}:${counter}
## 7) Validation YAML (sanity)
Avant commit (et en CI) :
YAML parse OK
schema OK
page si présent cohérent
paras est un mapping
paraId match pattern : ^p-\d+-[a-f0-9]{8}$ (existant)
src media pointe dans /public/media/... (ou /media/... si on choisit un alias, mais v1 canon : /public/media/...)
## 8) Notes de compatibilité
Les routes “Essai-thèse” ont été migrées vers /archicrat-ia/*.
Les anciennes routes /archicratie/archicrat-ia/* peuvent exister en legacy, mais la donnée canonique dannotation doit suivre le workKey final (archicrat-ia).
## 9) Ce que létape 9 devra implémenter
pipeline : ticket → YAML (apply-ticket)
index : build-annotations-index + check-annotations
tooling : détection médias orphelins / liens cassés
éventuellement : migration vers sharding par paragraphe (v2) si volume réel le justifie