Files
Beaujol-Bus-Lab/docs/superpowers/plans/2026-07-09-bus-axer-workspace.md
T
2026-07-09 10:57:31 +02:00

1172 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bus Axer Workspace + Blog Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Créer le workspace git complet du projet d'aménagement du bus Irisbus Axer (atelier mobile, VASP ≤9 places) + un blog de chantier Hugo « hackerspace » servi sur Tower à `bus.saillant.cc`.
**Architecture:** Repo documentaire structuré par domaines numérotés (00→80), documents seed réels dans chaque domaine, hébergé sur Gitea `git.saillant.cc`. Blog Hugo dans `blog/` avec thème terminal custom vendored (zéro dépendance upstream), publié en statique via nginx dockerisé sur Tower, exposé par l'infra `*.saillant.cc` existante (cloudflared ou Traefik selon investigation).
**Tech Stack:** git, Gitea API, Hugo (extended non requis), nginx:alpine (Docker, pinné par digest), rsync, cloudflared **ou** Traefik 3.
## Global Constraints
- Docs de contenu en **français** ; commits en **anglais**, sujet ≤ 50 chars, corps ≤ 72 cols, **pas d'attribution IA**, pas de `--no-verify`.
- Remote Gitea en **SSH** : `git@git.saillant.cc:electron-rare/bus-axer.git` (SSH :2222 **réparé le 2026-07-09**, alias `~/.ssh/config` → Tower `100.78.6.122:2222`, validé e2e). Fallback HTTPS+token : `https://electron@git.saillant.cc/electron-rare/bus-axer.git` (le `electron@` échappe à la règle globale `insteadOf`).
- **MAJ infra 2026-07-09** : la forge Gitea tourne sur **Tower** (container `gitea`, HTTP `100.78.6.122:3001`) ; `git.saillant.cc` est routé **tunnel CF (ES) → Traefik ES** via `dynamic/git-cutover.yml` sur electron-server.
- Politique supply-chain : pas de thème Hugo en submodule/upstream flottant (thème custom écrit dans le repo) ; image Docker pinnée par digest.
- Répertoire de travail : `/Users/electron/Documents/BUS` (git déjà initialisé, branche `main`, `.gitignore` + spec déjà committés).
- Hôtes : Tower = `clems@192.168.0.120` (Tailscale `100.78.6.122`) ; electron-server = `electron@100.78.191.52`. SSH sans mot de passe.
---
### Task 1: Documents racine (README, CLAUDE.md, ROADMAP, DECISIONS)
**Files:**
- Create: `README.md`
- Create: `CLAUDE.md`
- Create: `ROADMAP.md`
- Create: `DECISIONS.md`
**Interfaces:**
- Produces: conventions du repo (CLAUDE.md) que toutes les tâches suivantes respectent ; la structure de dossiers annoncée dans README est celle créée par les Tasks 24 et 6.
- [ ] **Step 1: Écrire `README.md`**
```markdown
# BUS — Irisbus Axer → atelier mobile
Rénovation et aménagement d'un bus **Irisbus Axer** en **atelier / labo
mobile** : électronique & prototypage + fabrication numérique (imprimantes
3D, découpe laser, petite CNC), avec **1 cabine de douche** et **9 places
assises** homologuées.
- **Objectif réglementaire** : reclassification **VASP atelier ≤ 9 places**
(permis C, passage DREAL/UTAC).
- **Horizon** : 5 ans — voir [ROADMAP.md](ROADMAP.md).
- **Blog de chantier** : https://bus.saillant.cc (sources dans `blog/`).
## État
- [x] Bus acquis, intérieur vidé
- [ ] Conception (plans, poids, énergie)
- [ ] Dossier réglementaire VASP
- [ ] Chantier
## Structure
| Dossier | Contenu |
|---------|---------|
| `00-vehicule/` | Fiche technique Axer, état initial, photos de départ |
| `10-reglementaire/` | Parcours VASP, DREAL/UTAC, checklists, contacts |
| `20-conception/` | Implantation, zoning, **suivi de poids par essieu** |
| `30-energie-elec/` | Bilan de puissance, architecture élec, schémas KiCad |
| `40-fluides/` | Eau/douche, chauffage, ventilation/extraction |
| `50-amenagement/` | Isolation, mobilier, 9 sièges homologués |
| `60-machines/` | Parc machines, établis, arrimage en roulage |
| `70-chantier/` | Journal de chantier, photos datées |
| `80-budget/` | Budget prévisionnel, dépenses, devis |
| `blog/` | Site Hugo → bus.saillant.cc |
Les décisions structurantes sont tracées dans [DECISIONS.md](DECISIONS.md).
```
- [ ] **Step 2: Écrire `CLAUDE.md`**
```markdown
# CLAUDE.md — projet BUS (Irisbus Axer → atelier mobile)
Workspace documentaire du chantier d'aménagement. **Pas un projet de
code** : l'essentiel est du markdown, plus un site Hugo (`blog/`) et des
schémas KiCad (`30-energie-elec/kicad/`).
## Conventions
- **Docs en français** (réglementaire FR, artisans, fournisseurs).
Commits en anglais, sujet ≤ 50 chars, pas d'attribution IA.
- Toute décision structurante → entrée datée dans `DECISIONS.md`.
- Tout ajout de masse significatif (> 5 kg) → mise à jour de
`20-conception/suivi-poids.md`. C'est le point dur n°1 du VASP.
- Journal de chantier : un fichier par session,
`70-chantier/journal/AAAA-MM-JJ-sujet.md`.
- Photos compressées (< 500 ko) dans `70-chantier/photos/AAAA-MM-JJ/` ;
**vidéos hors repo**.
- Dépenses : une ligne par achat dans `80-budget/depenses.csv`.
## Blog
- Sources Hugo dans `blog/`, thème terminal custom vendored
(`blog/themes/terminal-bus/`) — ne pas ajouter de thème upstream.
- Publier : `blog/deploy.sh` (build + rsync vers Tower, nginx `:8090`,
public sur https://bus.saillant.cc).
## Git
- Remote : `https://electron@git.saillant.cc/electron-rare/bus-axer.git`
(HTTPS + token ; garder le `electron@` dans l'URL — il échappe à la
règle globale `insteadOf` qui réécrirait vers le SSH :2222 cassé).
```
- [ ] **Step 3: Écrire `ROADMAP.md`**
```markdown
# Roadmap 5 ans
| Année | Jalon principal | Statut |
|-------|-----------------|--------|
| An 1 (2026-27) | Conception complète (plans, suivi poids, bilan énergie) + contact DREAL/RTI + isolation | en cours |
| An 2 (2027-28) | Énergie & électricité (solaire, batteries, distribution) | à venir |
| An 3 (2028-29) | Fluides (douche, chauffage, ventilation) + aménagement structurel | à venir |
| An 4 (2029-30) | Mobilier atelier, sièges homologués, installation machines | à venir |
| An 5 (2030-31) | Finitions, dossier VASP, passage DREAL/UTAC, mise en service | à venir |
## An 1 — détail
- [ ] Compléter `00-vehicule/fiche-technique.md` (peser le bus : PV réel !)
- [ ] Relevé coté de l'intérieur vidé → `20-conception/implantation.md`
- [ ] Premier contact DREAL / centre RTI → `10-reglementaire/contacts.md`
- [ ] Bilan de puissance machines → `30-energie-elec/bilan-puissance.md`
- [ ] Zoning validé (atelier / douche / 9 places / circulation)
- [ ] Choix isolation + pose
- [ ] Blog lancé (premier billet publié)
Le blog raconte le chantier dès l'An 1.
```
- [ ] **Step 4: Écrire `DECISIONS.md`**
```markdown
# Registre des décisions
Format : une entrée par décision structurante, la plus récente en haut.
## 2026-07-09 — Blog de chantier Hugo « hackerspace » sur Tower
**Décision** : blog statique Hugo (`blog/`), thème terminal custom
vendored, servi par nginx dockerisé sur Tower `:8090`, exposé sur
https://bus.saillant.cc via l'infra `*.saillant.cc` existante.
**Pourquoi** : markdown cohérent avec le repo, zéro dépendance upstream
(politique supply-chain), Tower déjà allumé en continu.
**Alternatives écartées** : Zola/Astro (écosystème moins connu ici),
thème upstream en submodule (flottant, non audité).
## 2026-07-09 — VASP atelier ≤ 9 places
**Décision** : reclassification en VASP atelier avec 9 places assises max.
**Pourquoi** : conduite au permis C, sortie du régime transport en commun
(fin des contrôles semestriels), tout en gardant la capacité de déplacer
une équipe.
**Conséquences** : suivi de poids par essieu obligatoire dès la
conception ; sièges et ancrages homologués ; exigences DREAL à intégrer
en amont (`10-reglementaire/`).
## 2026-07-09 — Structure du workspace par domaines
**Décision** : dossiers numérotés par domaine technique (00→80) plutôt
que par phase chronologique.
**Pourquoi** : sur 5 ans, chaque domaine vit à son rythme et traverse
plusieurs phases ; la roadmap transversale vit dans `ROADMAP.md`.
```
- [ ] **Step 5: Vérifier et committer**
Run: `cd /Users/electron/Documents/BUS && ls README.md CLAUDE.md ROADMAP.md DECISIONS.md && git add README.md CLAUDE.md ROADMAP.md DECISIONS.md && git commit -m "docs: add project root docs"`
Expected: les 4 fichiers listés, commit créé sans erreur de hook.
---
### Task 2: Domaines 00-vehicule, 10-reglementaire, 20-conception
**Files:**
- Create: `00-vehicule/fiche-technique.md`
- Create: `00-vehicule/etat-initial.md`
- Create: `00-vehicule/photos/.gitkeep`
- Create: `10-reglementaire/parcours-vasp.md`
- Create: `10-reglementaire/checklist-conformite.md`
- Create: `10-reglementaire/contacts.md`
- Create: `20-conception/implantation.md`
- Create: `20-conception/suivi-poids.md`
**Interfaces:**
- Consumes: conventions de `CLAUDE.md` (Task 1).
- Produces: `20-conception/suivi-poids.md` référencé par CLAUDE.md et par les domaines 3060 (chaque équipement ajouté y inscrit sa masse).
- [ ] **Step 1: Écrire `00-vehicule/fiche-technique.md`**
```markdown
# Fiche technique — Irisbus Axer
À compléter depuis la carte grise et une pesée réelle.
| Champ | Valeur |
|-------|--------|
| Modèle exact / année | _à relever_ |
| Immatriculation | _à relever_ |
| Genre CG actuel (J.1) | TCP |
| Moteur / puissance | _à relever_ |
| Kilométrage | _à relever_ |
| Longueur / largeur / hauteur | _à mesurer_ |
| Empattement | _à relever_ |
| PTAC (F.2) | _à relever_ |
| PV constructeur | _à relever_ |
| **PV réel pesé (vide, après vidage)** | _peser en station — donnée d'entrée du suivi de poids_ |
| Charge utile disponible | PTAC PV pesé |
| Masses max par essieu (F.2.1 / plaques) | _à relever_ |
## Notes
- La pesée réelle (total + par essieu si possible) est le point zéro de
`20-conception/suivi-poids.md` — à faire avant tout aménagement.
```
- [ ] **Step 2: Écrire `00-vehicule/etat-initial.md`**
```markdown
# État initial (après vidage)
Inventaire au démarrage du projet — 2026-07.
## Ce qui a été déposé
- Sièges passagers d'origine : _préciser combien, conservés/vendus ?_
## Ce qui reste à bord
- _à inventorier : poste de conduite, trappes, gaines, câblages,
chauffage d'origine, éclairage, portes/issues de secours…_
## Points de vigilance relevés
- _corrosion, étanchéité (joints de baies, lanterneaux), plancher…_
Photos de départ : `photos/` (compressées, datées).
```
- [ ] **Step 3: Écrire `10-reglementaire/parcours-vasp.md`**
```markdown
# Parcours VASP atelier ≤ 9 places
Objectif : carte grise genre **VASP**, carrosserie **atelier** (« ATEL »),
9 places assises max → conduite au permis C, sortie du régime transport
en commun.
## Étapes (à confirmer auprès de la DREAL)
1. **Pesée** du véhicule vidé (point zéro poids).
2. **Conception conforme d'abord** : ancrages sièges, installation
électrique, gaz éventuel, issues, extincteurs — connaître les
exigences AVANT de construire.
3. **Aménagement** documenté (photos, factures, schémas — ce repo).
4. **Dossier RTI** (réception à titre isolé) : notice descriptive,
plans, attestations (élec si requis), certificat de carrossage le
cas échéant.
5. Passage **DREAL** (ou UTAC selon région/cas) → procès-verbal RTI.
6. Préfecture / ANTS : nouvelle carte grise VASP ATEL.
## Questions ouvertes
- [ ] Liste exacte des pièces exigées par la DREAL locale ?
- [ ] Attestation électrique requise (Qualifelec / organisme) ?
- [ ] Exigences sur la douche (eaux grises) en VASP atelier ?
- [ ] Sièges : homologation R14/R17 des ancrages rapportés ?
Réponses et contacts → `contacts.md` ; état → `checklist-conformite.md`.
```
- [ ] **Step 4: Écrire `10-reglementaire/checklist-conformite.md`**
```markdown
# Checklist conformité VASP atelier
À valider au fil du chantier — chaque ligne cochée cite sa source
(échange DREAL, texte, contrôleur).
## Masses
- [ ] PTAC respecté (marge ≥ 10 %) — cf. `../20-conception/suivi-poids.md`
- [ ] Masses par essieu respectées
## Places assises
- [ ] ≤ 9 places (conducteur inclus)
- [ ] Sièges et ancrages homologués, ceintures
## Électricité
- [ ] Circuit cellule séparé du circuit véhicule
- [ ] Protections (fusibles/disjoncteurs) dimensionnées et documentées
- [ ] Coupe-batterie cellule accessible
## Incendie / issues
- [ ] Extincteur(s) fixé(s) et accessible(s)
- [ ] Issues de secours conservées et dégagées
- [ ] Détecteur de fumée / CO
## Machines (spécifique atelier)
- [ ] Arrimage machines conforme en roulage (cf. `../60-machines/fixations.md`)
- [ ] Extraction fumées laser vers l'extérieur
```
- [ ] **Step 5: Écrire `10-reglementaire/contacts.md`**
```markdown
# Contacts réglementaires
| Qui | Rôle | Contact | Dernier échange |
|-----|------|---------|-----------------|
| DREAL _région à préciser_ | RTI / réception VASP | _à trouver_ | — |
| Centre de pesée poids lourds | Pesée point zéro | _à trouver_ | — |
| Contrôleur technique PL | CT pendant travaux | _à trouver_ | — |
Journal des échanges (mail/tél) : noter date, interlocuteur, réponse.
```
- [ ] **Step 6: Écrire `20-conception/implantation.md`**
```markdown
# Implantation / zoning
Contraintes d'entrée :
- 9 places assises (conducteur inclus) — position à définir (avant ?).
- 1 cabine de douche (proche réservoirs → minimiser les longueurs d'eau).
- Zone électronique/proto : établi, lumière, assis.
- Zone fab numérique : imprimantes 3D, laser (extraction vers baie ou
lanterneau dédié), petite CNC (poussières → zone séparable).
- Circulation : accès à tout en roulage arrêté, issues dégagées.
- Répartition des masses : lourd (batteries, eau, machines) centré et
bas, équilibré gauche/droite — voir `suivi-poids.md`.
## Plan
_Relevé coté de l'intérieur vidé à faire, puis plan (papier scanné ou
CAD) déposé ici._
## Versions de zoning
| Version | Date | Description | Verdict |
|---------|------|-------------|---------|
| v0 | __ | _premier jet_ | __ |
```
- [ ] **Step 7: Écrire `20-conception/suivi-poids.md`**
```markdown
# Suivi de poids — LE document critique du VASP
Règle (cf. CLAUDE.md) : tout ajout > 5 kg s'inscrit ici.
## Point zéro
| Donnée | Valeur | Source |
|--------|--------|--------|
| PTAC | _à relever (CG F.2)_ | carte grise |
| PV pesé à vide | _pesée à faire_ | ticket de pesée |
| Budget masse aménagement | PTAC PV marge 10 % PTAC | calcul |
## Registre des masses
| Date | Poste | Domaine | Masse (kg) | Position (av/ar, G/D) | Cumul (kg) |
|------|-------|---------|-----------:|----------------------|-----------:|
| — | — | — | — | — | 0 |
## Estimations à venir (avant achat)
| Poste | Estimation (kg) |
|-------|----------------:|
| Isolation + habillage | __ |
| Parc batteries | __ |
| Eau (pleins) | 1 kg/L × volume |
| Machines (cf. `../60-machines/inventaire.md`) | __ |
| Mobilier + établis | __ |
| 9 sièges + ancrages | __ |
```
- [ ] **Step 8: Créer le dossier photos et committer**
Run: `cd /Users/electron/Documents/BUS && mkdir -p 00-vehicule/photos && touch 00-vehicule/photos/.gitkeep && git add 00-vehicule 10-reglementaire 20-conception && git commit -m "docs: seed vehicle, regulatory, design domains"`
Expected: commit avec 8 fichiers créés.
---
### Task 3: Domaines 30-energie-elec (+ KiCad), 40-fluides
**Files:**
- Create: `30-energie-elec/bilan-puissance.md`
- Create: `30-energie-elec/architecture.md`
- Create: `30-energie-elec/kicad/bus-elec/bus-elec.kicad_pro`
- Create: `30-energie-elec/kicad/bus-elec/bus-elec.kicad_sch`
- Create: `40-fluides/eau.md`
- Create: `40-fluides/chauffage.md`
- Create: `40-fluides/ventilation.md`
**Interfaces:**
- Consumes: `20-conception/suivi-poids.md` (Task 2) — les masses batteries/eau y sont reportées.
- Produces: projet KiCad `30-energie-elec/kicad/bus-elec/` (schémas de distribution à venir).
- [ ] **Step 1: Écrire `30-energie-elec/bilan-puissance.md`**
```markdown
# Bilan de puissance
Les machines de fab numérique dimensionnent le système — les remplir en
premier. Puissances à relever sur plaques/manuels, usage réaliste.
## Consommateurs 230 V
| Équipement | P max (W) | P moyenne (W) | h/jour type | Wh/jour |
|------------|----------:|--------------:|------------:|--------:|
| Découpe laser | __ | __ | __ | __ |
| Imprimante(s) 3D | __ | __ | __ | __ |
| Petite CNC | __ | __ | __ | __ |
| Stations de soudage | __ | __ | __ | __ |
| PC / écrans | __ | __ | __ | __ |
| **Total** | | | | __ |
## Consommateurs basse tension (12/48 V)
| Équipement | P (W) | h/jour | Wh/jour |
|------------|------:|-------:|--------:|
| Éclairage LED | __ | __ | __ |
| Ventilation / extraction | __ | __ | __ |
| Pompe à eau (douche) | __ | __ | __ |
| Chauffage (électronique de commande) | __ | __ | __ |
| **Total** | | | __ |
## Scénarios
- **Autonome** (batterie + solaire) : dimensionne parc + PV.
- **Raccordé** (P17/16 A sur site) : la batterie ne couvre que l'appoint.
- Arbitrage documenté dans `architecture.md` + `DECISIONS.md`.
```
- [ ] **Step 2: Écrire `30-energie-elec/architecture.md`**
```markdown
# Architecture électrique (draft)
Découpage prévu — chaque bloc aura son schéma KiCad (`kicad/bus-elec/`) :
1. **Entrée secteur** : prise extérieure P17, disjoncteur d'entrée,
inverseur de source (secteur / convertisseur).
2. **Parc batterie cellule** (chimie et tension bus DC à décider :
12 V vs 48 V — 48 V favorable vu les puissances atelier).
3. **Charge** : chargeur secteur + MPPT solaire (+ éventuel DC-DC depuis
l'alternateur, isolé du circuit véhicule).
4. **Conversion** : convertisseur/chargeur 230 V (dimensionné par le
bilan de puissance, pics laser/CNC compris).
5. **Distribution BT** : tableau fusibles, coupe-batterie, bus barre.
6. **Distribution 230 V** : tableau avec différentiel 30 mA + disjoncteurs
par zone (établi élec / machines / douche-zones humides).
7. **Monitoring** : shunt + BMS, affichage (SOC, courants) — terrain de
jeu maison (cf. KXKM Parallelator).
Règles VASP : circuit cellule séparé du véhicule, protections
documentées, coupe-batterie accessible (cf. `../10-reglementaire/`).
Chaque choix (chimie, tension, marques) → `DECISIONS.md` + masses →
`../20-conception/suivi-poids.md`.
```
- [ ] **Step 3: Créer le squelette KiCad**
Créer `30-energie-elec/kicad/bus-elec/bus-elec.kicad_pro` :
```json
{
"meta": {
"filename": "bus-elec.kicad_pro",
"version": 3
},
"sheets": [
[
"e63e39d7-6ac0-4ffd-8aa3-1841a4541b55",
"Root"
]
]
}
```
Créer `30-energie-elec/kicad/bus-elec/bus-elec.kicad_sch` :
```
(kicad_sch
(version 20250114)
(generator "eeschema")
(generator_version "9.0")
(uuid "e63e39d7-6ac0-4ffd-8aa3-1841a4541b55")
(paper "A3")
(title_block
(title "Bus Axer - distribution electrique cellule")
(company "L'Electron Rare")
(comment 1 "Draft - architecture voir architecture.md")
)
)
```
- [ ] **Step 4: Écrire `40-fluides/eau.md`**
```markdown
# Eau — douche
Un seul point d'eau (cabine de douche) → circuit simple.
| Élément | Choix | Masse pleine | Statut |
|---------|-------|-------------:|--------|
| Réservoir eau propre | _volume à décider (douche seule : 80120 L ?)_ | 1 kg/L | à décider |
| Réservoir eaux grises | ≥ volume utile douche | idem | à décider |
| Pompe + accumulateur | __ | — | à décider |
| Chauffe-eau | _électrique ? échangeur chauffage ?_ | — | à décider |
| Antigel / vidange | vannes point bas | — | à prévoir |
Questions réglementaires (eaux grises en VASP atelier) →
`../10-reglementaire/parcours-vasp.md`. Masses → suivi-poids.
```
- [ ] **Step 5: Écrire `40-fluides/chauffage.md`**
```markdown
# Chauffage
Besoin : hors-gel permanent (machines, électronique, eau) + confort en
séance de travail. Options à instruire :
| Option | + | |
|--------|---|---|
| Air pulsé gasoil (type Airtronic/chinois) | autonome, réservoir véhicule | bruit, conso élec au démarrage |
| Poêle (bois/pellet) | ambiance, hors réseau | homologation VASP délicate, place |
| Électrique (secteur uniquement) | simple, propre | inutilisable en autonome |
Piste probable : gasoil pour la base + appoint électrique si raccordé.
Décision → `DECISIONS.md`, conso → bilan-puissance, masse → suivi-poids.
```
- [ ] **Step 6: Écrire `40-fluides/ventilation.md`**
```markdown
# Ventilation / extraction — sécurité prioritaire
## Extraction process (obligatoire)
- **Laser** : extraction dédiée vers l'extérieur (fumées toxiques),
gaine rigide, filtration éventuelle, asservie à la machine.
- **Impression 3D** : caisson ventilé ou extraction (UFP/COV).
- **CNC / poussières** : aspiration copeaux localisée, zone séparable.
## Renouvellement d'air général
- VMC ou lanterneau extracteur + entrées d'air basses.
- Douche : extraction humidité.
## Points d'attention
- Percements carrosserie = points d'étanchéité (à tracer dans le
journal de chantier avec photos).
- Débits et conso élec → `../30-energie-elec/bilan-puissance.md`.
```
- [ ] **Step 7: Committer**
Run: `cd /Users/electron/Documents/BUS && git add 30-energie-elec 40-fluides && git commit -m "docs: seed energy and fluids domains"`
Expected: commit avec 7 fichiers.
---
### Task 4: Domaines 50-amenagement, 60-machines, 70-chantier, 80-budget
**Files:**
- Create: `50-amenagement/isolation.md`
- Create: `50-amenagement/mobilier.md`
- Create: `50-amenagement/sieges.md`
- Create: `60-machines/inventaire.md`
- Create: `60-machines/fixations.md`
- Create: `70-chantier/journal/2026-07-09-lancement-projet.md`
- Create: `70-chantier/photos/.gitkeep`
- Create: `80-budget/budget-previsionnel.md`
- Create: `80-budget/depenses.csv`
- Create: `80-budget/devis/.gitkeep`
**Interfaces:**
- Consumes: `suivi-poids.md` (Task 2), `bilan-puissance.md` (Task 3).
- Produces: `70-chantier/journal/` — source de contenu du blog (Task 6) ; `80-budget/depenses.csv` avec l'en-tête exact `date,domaine,fournisseur,description,montant_eur,facture`.
- [ ] **Step 1: Écrire `50-amenagement/isolation.md`**
```markdown
# Isolation
Objectif : atelier utilisable été/hiver, condensation maîtrisée
(électronique + machines + douche = humidité).
| Zone | Matériau envisagé | Épaisseur | Statut |
|------|-------------------|-----------|--------|
| Parois | _laine de bois ? liège ? Armaflex ?_ | __ | à décider |
| Plafond | __ | __ | à décider |
| Plancher | __ | __ | à décider |
| Baies (surface vitrée énorme sur un Axer) | _conserver ? obturer ? survitrage ?_ | __ | à décider |
Points durs : ponts thermiques structure, pare-vapeur (douche !),
masse totale → suivi-poids. Traitement anticorrosion avant pose.
```
- [ ] **Step 2: Écrire `50-amenagement/mobilier.md`**
```markdown
# Mobilier
Principes : léger (poids !), modulaire, tout arrimé en roulage.
| Meuble | Zone | Masse est. | Statut |
|--------|------|-----------:|--------|
| Établi électronique | zone proto | __ | à concevoir |
| Rangement composants (tiroirs) | zone proto | __ | à concevoir |
| Support machines | zone fab | __ | à concevoir |
| Vestiaire / rangement EPI | entrée | __ | à concevoir |
Matériaux envisagés : contreplaqué peuplier/bouleau, profilé alu
(2020/3030 — cohérent avec l'outillage maison).
```
- [ ] **Step 3: Écrire `50-amenagement/sieges.md`**
```markdown
# 9 places assises homologuées
Point réglementaire majeur (cf. `../10-reglementaire/`).
- Conducteur inclus dans les 9.
- Options : conserver des sièges d'origine (ancrages existants =
simplicité) vs sièges rapportés (ancrages à faire homologuer, R14).
- Ceintures obligatoires selon configuration.
- Position dans le zoning : `../20-conception/implantation.md`.
| # | Siège | Origine/rapporté | Ancrage | Ceinture | Statut |
|---|-------|------------------|---------|----------|--------|
| 1 (conducteur) | d'origine | origine | origine | origine | OK |
| 29 | __ | __ | __ | __ | à décider |
```
- [ ] **Step 4: Écrire `60-machines/inventaire.md`**
```markdown
# Parc machines
Chaque machine : masse (→ suivi-poids), puissance (→ bilan-puissance),
emplacement (→ implantation), arrimage (→ fixations.md).
| Machine | Masse (kg) | P max (W) | Emplacement prévu | Extraction | Statut |
|---------|-----------:|----------:|-------------------|------------|--------|
| Découpe laser | __ | __ | __ | obligatoire | à définir |
| Imprimante 3D #1 | __ | __ | __ | caisson | à définir |
| Petite CNC | __ | __ | __ | aspiration | à définir |
| Établi élec (stations, alim, oscillo) | __ | __ | zone proto | — | à définir |
```
- [ ] **Step 5: Écrire `60-machines/fixations.md`**
```markdown
# Arrimage en roulage
Exigence sécurité + conformité VASP : rien ne bouge, freinage d'urgence
compris (ordre de grandeur : retenue ≥ 0,8 g vers l'avant).
Principes :
- Machines lourdes : fixation traversante sur structure plancher
(pas sur le seul habillage), platines réparties.
- Petit matériel : bacs fermés, sangles, tiroirs verrouillés.
- Check-list « départ » à afficher dans le bus (à rédiger ici quand
l'aménagement se précise).
```
- [ ] **Step 6: Écrire le premier journal de chantier `70-chantier/journal/2026-07-09-lancement-projet.md`**
```markdown
# 2026-07-09 — Lancement du projet
Création du workspace : structure documentaire, roadmap 5 ans, registre
de décisions, blog de chantier (https://bus.saillant.cc).
État du bus : acquis, intérieur vidé. Prochaines actions concrètes :
1. Pesée du bus vidé (point zéro du suivi de poids).
2. Relevé coté de l'intérieur.
3. Premier contact DREAL sur le parcours VASP atelier ≤ 9 places.
```
- [ ] **Step 7: Écrire `80-budget/budget-previsionnel.md`**
```markdown
# Budget prévisionnel (par domaine)
Horizon 5 ans, à affiner à mesure que la conception avance. Les montants
réels vont dans `depenses.csv`.
| Domaine | Enveloppe estimée (€) | Notes |
|---------|----------------------:|-------|
| Réglementaire (pesée, RTI, CT) | __ | |
| Isolation + habillage | __ | |
| Énergie/élec (batteries, solaire, convertisseur, tableau) | __ | poste lourd |
| Fluides (eau, chauffage, ventilation) | __ | |
| Mobilier / aménagement | __ | |
| Sièges + ancrages | __ | |
| Machines (si achats dédiés) | __ | |
| Imprévus (15 %) | __ | |
| **Total** | __ | |
```
- [ ] **Step 8: Créer `80-budget/depenses.csv`**
```csv
date,domaine,fournisseur,description,montant_eur,facture
```
- [ ] **Step 9: Créer les dossiers et committer**
Run: `cd /Users/electron/Documents/BUS && mkdir -p 70-chantier/photos 80-budget/devis && touch 70-chantier/photos/.gitkeep 80-budget/devis/.gitkeep && git add 50-amenagement 60-machines 70-chantier 80-budget && git commit -m "docs: seed fitout, machines, log, budget domains"`
Expected: commit avec 10 fichiers.
---
### Task 5: Remote Gitea `electron-rare/bus-axer` + push
**Files:**
- Modify: `.git/config` (ajout remote, via commande git)
**Interfaces:**
- Consumes: token Gitea dans `~/.git-credentials` (helper `store` global déjà configuré).
- Produces: remote `origin` = `https://electron@git.saillant.cc/electron-rare/bus-axer.git`, branche `main` poussée et trackée.
- [ ] **Step 1: Extraire le token et créer le repo via l'API Gitea**
```bash
TOKEN=$(grep 'git.saillant.cc' ~/.git-credentials | head -1 | sed -E 's#https://[^:]+:([^@]+)@.*#\1#')
curl -sf -X POST "https://git.saillant.cc/api/v1/orgs/electron-rare/repos" \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"bus-axer","description":"Irisbus Axer - atelier mobile (VASP, blog bus.saillant.cc)","private":false}' | head -c 300
```
Expected: JSON avec `"full_name":"electron-rare/bus-axer"`. Si HTTP 409, le repo existe déjà — vérifier avec `curl -sf -H "Authorization: token $TOKEN" https://git.saillant.cc/api/v1/repos/electron-rare/bus-axer` qu'il est vide avant de continuer ; s'il n'est pas vide, STOP et demander à l'utilisateur.
- [ ] **Step 2: Ajouter le remote et pousser**
```bash
cd /Users/electron/Documents/BUS
git remote add origin git@git.saillant.cc:electron-rare/bus-axer.git
git push -u origin main
```
Expected: push accepté (SSH :2222 réparé 2026-07-09, alias `git.saillant.cc` → Tower). Si l'auth SSH échoue malgré tout, fallback : `git remote set-url origin https://electron@git.saillant.cc/electron-rare/bus-axer.git` puis re-push.
- [ ] **Step 3: Mettre à jour la section Git de `CLAUDE.md`**
Remplacer dans `CLAUDE.md` (racine du repo) la section `## Git` par :
```markdown
## Git
- Remote : `git@git.saillant.cc:electron-rare/bus-axer.git` (SSH :2222,
forge Gitea sur Tower). Fallback HTTPS + token :
`https://electron@git.saillant.cc/electron-rare/bus-axer.git`
(garder le `electron@` — il échappe à la règle globale `insteadOf`).
```
Puis : `git add CLAUDE.md && git commit -m "docs: update remote URL to repaired SSH"`
- [ ] **Step 4: Vérifier**
Run: `git ls-remote origin main && git push`
Expected: une ligne avec le SHA du dernier commit local (`git rev-parse main` identique) après le push final.
---
### Task 6: Blog Hugo + thème terminal-bus vendored
**Files:**
- Create: `blog/hugo.toml`
- Create: `blog/themes/terminal-bus/theme.toml`
- Create: `blog/themes/terminal-bus/layouts/baseof.html`
- Create: `blog/themes/terminal-bus/layouts/home.html`
- Create: `blog/themes/terminal-bus/layouts/page.html`
- Create: `blog/themes/terminal-bus/layouts/section.html`
- Create: `blog/themes/terminal-bus/static/css/terminal.css`
- Create: `blog/content/posts/2026-07-09-le-projet.md`
- Create: `blog/content/a-propos.md`
**Interfaces:**
- Consumes: matière du journal `70-chantier/journal/` (Task 4).
- Produces: site buildable par `hugo --source blog``blog/public/` (consommé par `deploy.sh`, Task 8). `blog/public/` et `blog/resources/` sont déjà dans `.gitignore`.
- [ ] **Step 1: Vérifier/installer Hugo**
Run: `command -v hugo || brew install hugo; hugo version`
Expected: version Hugo ≥ 0.146 (layouts « new style » : `layouts/baseof.html` sans sous-dossier `_default`). Si la version installée est plus ancienne, placer les layouts dans `blog/themes/terminal-bus/layouts/_default/` avec les noms classiques (`baseof.html`, `single.html`, `list.html`, `index.html` à la racine de `layouts/`).
- [ ] **Step 2: Écrire `blog/hugo.toml`**
```toml
baseURL = "https://bus.saillant.cc/"
languageCode = "fr-fr"
title = "bus.saillant.cc"
theme = "terminal-bus"
timeZone = "Europe/Paris"
[params]
description = "Chantier d'un Irisbus Axer transformé en atelier/labo mobile — électronique, fab numérique, VASP."
prompt = "electron@bus"
[permalinks]
posts = "/:year/:month/:slug/"
[markup.goldmark.renderer]
unsafe = true
```
- [ ] **Step 3: Écrire `blog/themes/terminal-bus/theme.toml`**
```toml
name = "terminal-bus"
license = "MIT"
description = "Thème terminal/hackerspace minimal, vendored, zéro dépendance."
min_version = "0.146.0"
```
- [ ] **Step 4: Écrire `blog/themes/terminal-bus/layouts/baseof.html`**
```html
<!DOCTYPE html>
<html lang="{{ site.Language.LanguageCode }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ if not .IsHome }}{{ .Title }} — {{ end }}{{ site.Title }}</title>
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ site.Params.description }}{{ end }}">
<link rel="stylesheet" href="{{ "css/terminal.css" | relURL }}">
</head>
<body>
<header>
<pre class="banner">
____ _ _ ____
| __ )| | | / ___| bus.saillant.cc
| _ \| | | \___ \ Irisbus Axer -&gt; atelier mobile
| |_) | |_| |___) | electronique . fab numerique . VASP
|____/ \___/|____/
</pre>
<nav>
<span class="prompt">{{ site.Params.prompt }}:~$</span>
<a href="{{ "/" | relURL }}">ls posts/</a>
<a href="{{ "/a-propos/" | relURL }}">cat a-propos.md</a>
</nav>
</header>
<main>
{{ block "main" . }}{{ end }}
</main>
<footer>
<span class="prompt">{{ site.Params.prompt }}:~$</span> uptime
— chantier démarré en 2026, horizon 5 ans. Généré par Hugo.
</footer>
</body>
</html>
```
- [ ] **Step 5: Écrire `blog/themes/terminal-bus/layouts/home.html`**
```html
{{ define "main" }}
<p class="intro">{{ site.Params.description }}</p>
<h2><span class="prompt">$</span> ls -lt posts/</h2>
<ul class="post-list">
{{ range where site.RegularPages "Section" "posts" }}
<li>
<span class="date">{{ .Date.Format "2006-01-02" }}</span>
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ end }}
```
- [ ] **Step 6: Écrire `blog/themes/terminal-bus/layouts/page.html`**
```html
{{ define "main" }}
<article>
<h1><span class="prompt">$</span> cat {{ path.Base .File.Path }}</h1>
{{ with .Date }}<p class="date"># {{ .Format "2006-01-02" }}</p>{{ end }}
{{ .Content }}
</article>
{{ end }}
```
- [ ] **Step 7: Écrire `blog/themes/terminal-bus/layouts/section.html`**
```html
{{ define "main" }}
<h1><span class="prompt">$</span> ls {{ .Section }}/</h1>
<ul class="post-list">
{{ range .Pages }}
<li>
<span class="date">{{ .Date.Format "2006-01-02" }}</span>
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ end }}
```
- [ ] **Step 8: Écrire `blog/themes/terminal-bus/static/css/terminal.css`**
```css
:root {
--bg: #0d1117;
--fg: #c9d1d9;
--green: #3fb950;
--amber: #d29922;
--dim: #8b949e;
--border: #30363d;
}
* { box-sizing: border-box; }
body {
background: var(--bg);
color: var(--fg);
font-family: "JetBrains Mono", "Fira Code", ui-monospace, "SF Mono",
Menlo, monospace;
font-size: 15px;
line-height: 1.6;
max-width: 80ch;
margin: 0 auto;
padding: 1.5rem 1rem 4rem;
}
.banner { color: var(--green); font-size: 12px; line-height: 1.2;
overflow-x: auto; }
.prompt { color: var(--green); font-weight: bold; }
nav a, footer a { margin-right: 1.5ch; }
a { color: var(--amber); text-decoration: none; }
a:hover { text-decoration: underline; background: var(--amber);
color: var(--bg); }
h1, h2, h3 { color: var(--green); font-size: 1.1em; margin-top: 2em; }
h1 { font-size: 1.25em; }
.date { color: var(--dim); margin-right: 2ch; }
.post-list { list-style: none; padding: 0; }
.post-list li { padding: 0.2em 0; }
img { max-width: 100%; border: 1px solid var(--border); }
pre, code { background: #161b22; }
pre { padding: 1em; overflow-x: auto; border: 1px solid var(--border); }
code { padding: 0.1em 0.4ch; }
pre code { padding: 0; }
blockquote { border-left: 3px solid var(--green); margin-left: 0;
padding-left: 1.5ch; color: var(--dim); }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid var(--border); padding: 0.3em 1ch;
text-align: left; }
hr { border: 0; border-top: 1px dashed var(--border); }
footer { margin-top: 4rem; color: var(--dim); font-size: 0.85em; }
```
- [ ] **Step 9: Écrire le premier billet `blog/content/posts/2026-07-09-le-projet.md`**
```markdown
---
title: "Le projet : un Irisbus Axer devient atelier mobile"
date: 2026-07-09
slug: "le-projet"
---
Un bus. Un vrai, un Irisbus Axer, déjà vidé de ses sièges. Destination :
**atelier / labo mobile** — électronique et prototypage d'un côté,
fabrication numérique de l'autre (imprimantes 3D, découpe laser, petite
CNC). Plus une cabine de douche et 9 places assises, parce qu'un atelier
qui ne peut pas emmener son équipe ne sert à rien.
Côté paperasse, l'objectif est une reclassification **VASP atelier
≤ 9 places** : conduite au permis C, sortie du régime transport en
commun. Ça impose de penser réglementaire *avant* de visser quoi que ce
soit — ancrages de sièges, circuit électrique séparé, suivi de poids par
essieu tenu depuis le premier jour.
L'horizon est assumé : **5 ans**. Année 1 : conception, pesée, isolation.
Ensuite l'énergie, les fluides, l'aménagement, les machines, et le
passage DREAL en bout de course.
Ce blog racontera le chantier — les plans, les ratés, les pesées, les
schémas. Tout le projet est versionné dans un dépôt git, y compris ce
billet.
Prochaine étape : peser le bus vide. Point zéro.
```
- [ ] **Step 10: Écrire `blog/content/a-propos.md`**
```markdown
---
title: "À propos"
slug: "a-propos"
---
Chantier de transformation d'un Irisbus Axer en atelier/labo mobile,
mené par L'Electron Rare. Électronique, fabrication numérique, une
douche, 9 places, et un objectif VASP au bout de 5 ans.
Contact : clement@saillant.cc
```
- [ ] **Step 11: Builder et vérifier**
Run: `cd /Users/electron/Documents/BUS && hugo --source blog --minify && ls blog/public/index.html blog/public/2026/07/le-projet/index.html`
Expected: build sans erreur, les deux fichiers HTML existent. Ouvrir un aperçu local si souhaité : `hugo server --source blog` → http://localhost:1313.
- [ ] **Step 12: Committer**
Run: `cd /Users/electron/Documents/BUS && git add blog && git commit -m "feat: add Hugo blog with terminal-bus theme"`
Expected: commit créé, `blog/public/` absent du commit (gitignoré).
---
### Task 7: Hébergement Tower + route bus.saillant.cc
**Files:**
- Create (sur Tower): `/home/clems/bus-blog/public/` (répertoire servi)
- Modify (selon investigation) : ingress cloudflared **ou** config Traefik edge sur electron-server
**Interfaces:**
- Consumes: `blog/public/` buildé (Task 6).
- Produces: nginx `bus-blog` sur Tower `:8090` servant `/home/clems/bus-blog/public`, atteignable depuis electron-server ; `https://bus.saillant.cc` publique.
- [ ] **Step 1: Investiguer l'infra de routage `*.saillant.cc`**
Prior connu (MAJ 2026-07-09) : `git.saillant.cc` passe par **tunnel CF sur electron-server → Traefik ES** (fichier dynamique `dynamic/git-cutover.yml` dans `/home/electron/edge/`) → backend Tower. Le chemin probable pour `bus.saillant.cc` est donc : entrée DNS/ingress CF → Traefik ES → fichier dynamique `dynamic/bus-blog.yml` → Tower `:8090`.
```bash
# Mémoires de référence (routage actuel, tunnel cloudflared, état edge)
ls ~/.claude/projects/-Users-electron/memory/ | grep -Ei 'cloudflared|git_saillant|gitea_migrated'
cat ~/.claude/projects/-Users-electron/memory/reference_cloudflared_tunnel_saillant.md
# État réel sur electron-server (Traefik edge + cloudflared ?)
ssh electron@100.78.191.52 'docker ps --format "table {{.Names}}\t{{.Ports}}" | grep -Ei "traefik|cloudflared" ; ls /home/electron/edge/ /home/electron/edge/dynamic/ 2>/dev/null ; cat /home/electron/edge/dynamic/git-cutover.yml 2>/dev/null'
```
Expected: confirmer le chemin réel (tunnel CF → Traefik ES → Tower) et récupérer le pattern exact du routeur `git.saillant.cc` (`git-cutover.yml`) à répliquer. Noter le mécanisme retenu dans `DECISIONS.md` (entrée « Exposition bus.saillant.cc »).
- [ ] **Step 2: Vérifier Docker et le port 8090 sur Tower**
```bash
ssh clems@192.168.0.120 'docker --version && (ss -tlnp | grep -w 8090 || echo PORT_8090_LIBRE)'
```
Expected: Docker présent, `PORT_8090_LIBRE`. Si le port est pris, choisir 8091+ et reporter le choix partout (nginx, route, deploy.sh, CLAUDE.md).
- [ ] **Step 3: Lancer nginx statique sur Tower (image pinnée par digest)**
```bash
ssh clems@192.168.0.120 '
mkdir -p /home/clems/bus-blog/public &&
docker pull nginx:1.27-alpine &&
DIGEST=$(docker inspect --format "{{index .RepoDigests 0}}" nginx:1.27-alpine) &&
echo "Digest: $DIGEST" &&
docker run -d --name bus-blog --restart unless-stopped \
-p 8090:80 \
-v /home/clems/bus-blog/public:/usr/share/nginx/html:ro \
"$DIGEST"
'
```
Expected: container `bus-blog` démarré. Consigner le digest dans `DECISIONS.md` (traçabilité supply-chain).
- [ ] **Step 4: Smoke test local Tower**
```bash
ssh clems@192.168.0.120 'echo "<h1>bus-blog ok</h1>" > /home/clems/bus-blog/public/index.html && curl -s http://localhost:8090/ | head -1'
```
Expected: `<h1>bus-blog ok</h1>`. Vérifier aussi depuis electron-server (chemin qu'empruntera la route) : `ssh electron@100.78.191.52 'curl -s http://100.78.6.122:8090/ | head -1'`.
- [ ] **Step 5: Ajouter la route publique `bus.saillant.cc` (branche selon Step 1)**
**Branche A — cloudflared** : suivre `reference_cloudflared_tunnel_saillant.md` — ajouter au tunnel `2c6b04a3…` une règle ingress `bus.saillant.cc → http://100.78.6.122:8090` via l'API CF (endpoint `cfd_tunnel/<id>/configurations`, PUT du tableau ingress complet avec la nouvelle entrée AVANT le catch-all 404) + créer le DNS CNAME `bus``<tunnel-id>.cfargotunnel.com` (proxied).
**Branche B — Traefik edge** : sur electron-server, ajouter au compose `/home/electron/edge/docker-compose.yml` (ou à sa config dynamique si un dossier `dynamic/` existe) un routeur+service :
```yaml
# fragment config dynamique Traefik (fichier ou labels selon l'existant)
http:
routers:
bus-blog:
rule: "Host(`bus.saillant.cc`)"
entryPoints: ["websecure"]
tls:
certResolver: letsencrypt
service: bus-blog
services:
bus-blog:
loadBalancer:
servers:
- url: "http://100.78.6.122:8090"
```
puis créer l'enregistrement DNS `bus.saillant.cc` → même cible que `git.saillant.cc` (A/CNAME identique), et recharger Traefik (`docker compose -f /home/electron/edge/docker-compose.yml up -d`). **Adapter le fragment à la structure réellement trouvée** (resolver name, entrypoint) en copiant le pattern du routeur `git.saillant.cc` existant.
- [ ] **Step 6: Vérifier la route publique**
Run: `curl -sI https://bus.saillant.cc/ | head -5`
Expected: `HTTP/2 200` avec le contenu du smoke test. La propagation DNS peut prendre quelques minutes — réessayer avant de conclure à un échec.
---
### Task 8: deploy.sh + premier déploiement end-to-end
**Files:**
- Create: `blog/deploy.sh` (exécutable)
**Interfaces:**
- Consumes: site Hugo (Task 6), nginx Tower + route (Task 7).
- Produces: commande unique de publication `blog/deploy.sh`.
- [ ] **Step 1: Écrire `blog/deploy.sh`**
```bash
#!/usr/bin/env bash
# Publish the blog to Tower (nginx :8090 -> https://bus.saillant.cc)
set -euo pipefail
cd "$(dirname "$0")"
TOWER="clems@192.168.0.120"
DEST="/home/clems/bus-blog/public/"
echo "==> hugo build"
hugo --minify --cleanDestinationDir
echo "==> rsync to $TOWER:$DEST"
rsync -az --delete public/ "$TOWER:$DEST"
echo "==> smoke test"
curl -sfI https://bus.saillant.cc/ >/dev/null \
&& echo "OK: https://bus.saillant.cc" \
|| { echo "WARN: public URL not reachable (DNS/route?)"; exit 1; }
```
- [ ] **Step 2: Rendre exécutable et déployer**
Run: `chmod +x /Users/electron/Documents/BUS/blog/deploy.sh && /Users/electron/Documents/BUS/blog/deploy.sh`
Expected: build OK, rsync OK, `OK: https://bus.saillant.cc`.
- [ ] **Step 3: Vérifier le contenu publié**
Run: `curl -s https://bus.saillant.cc/ | grep -o "le-projet" | head -1 && curl -sI https://bus.saillant.cc/css/terminal.css | head -1`
Expected: `le-projet` trouvé sur la home, CSS servi en 200.
- [ ] **Step 4: Committer et pousser**
```bash
cd /Users/electron/Documents/BUS
git add blog/deploy.sh
git commit -m "feat: add blog deploy script"
git push
```
Expected: push OK sur `electron-rare/bus-axer`.
- [ ] **Step 5: Mettre à jour DECISIONS.md avec les choix d'infra réels**
Ajouter en haut de `DECISIONS.md` une entrée « 2026-07-09 — Exposition bus.saillant.cc » documentant : mécanisme retenu (cloudflared ou Traefik), port Tower réel, digest nginx pinné. Puis :
```bash
git add DECISIONS.md && git commit -m "docs: record blog infra decisions" && git push
```
Expected: push OK.