# La charte du fil de chantier

> **Instituée le 18/08** après que le fil du studio parcours a appliqué l'ensemble
> du process sans qu'on le lui rappelle — c'est ce niveau-là qui devient le
> contrat. Chaque règle vient d'un incident réel, daté. Un prompt de fil CITE ce
> document au lieu de le paraphraser ; une leçon nouvelle se code ICI, une fois.

## Le contrat en une phrase

Un fil développe dans son coin, prouve ce qu'il affirme, ne touche à rien
d'autre, et s'arrête à l'annonce — **le merge, le déploiement et la vérification
sur le bundle servi appartiennent au pilote.**

## 1. Environnement

- **Worktree dédié** depuis `origin/development` À JOUR — jamais le clone
  principal (deux chantiers dans un clone : symptôme « No commits between X et Y »).
- **`.env` copié du clone principal, jamais `cp .env.example`** (un .env écrasé, 17/08).
- **`composer install` PROPRE — jamais de vendor symlinké** : l'autoloader résout
  `App\` vers le clone principal et les tests valident le mauvais code (vécu 2×).
- **Base de test DÉDIÉE au fil, jamais la base partagée** : `efektiv_testing` est
  UNE base pour tous les worktrees. Deux fils qui jouent la suite en même temps
  se cassent mutuellement — symptôme « Table 'migrations' already exists », et
  le fil qui perd la course croit à une régression de son propre code. Chaque
  fil crée `efektiv_testing_<numéro d'issue>` et la supprime en partant
  (vécu le 22/08, trois fils simultanés sur BACK).
- **Et la MACHINE elle-même est une ressource partagée.** Côté FRONT il n'y a
  pas de base à isoler, mais la mesure du 22/08 (#332) est sans appel : la suite
  jouée en parallèle échoue **13 %** du temps quand une autre suite occupe la
  machine, **0 %** en séquentiel — et les échecs sont TOUS des délais dépassés,
  sur des fichiers qui varient d'une fois sur l'autre. Même piège, même
  conclusion : le fil qui perd la course lit une régression là où il n'y a
  qu'une machine saturée. **Avant de conclure au rouge, vérifier la charge et
  rejouer le fichier SEUL.** Un échec qui ne se reproduit pas en isolation n'est
  pas un échec.
- Front : **Node 24 exactement** (Node 25 : 645 faux échecs localStorage).
- `development` bouge vite : **pousser tôt, re-rebaser avant d'annoncer.**
- **JAMAIS `git stash` dans un worktree** : la pile est PARTAGÉE via le `.git`
  commun — un `pop` peut dépiler le remisage d'un fil voisin, et la panne est
  silencieuse (vécu 2× le 19/08, fils #274/#275). Pour une contre-épreuve :
  `git diff > x.patch` puis `git checkout --`, jamais la pile commune.

## 2. Développement

- **1 issue = 1 branche = 1 PR.** Chercher le doublon sur le CONTENU avant de
  créer quoi que ce soit (PR #463 découverte déjà ouverte, issue #382/#350).
- **Zéro modification hors périmètre.** Pas de reformatage opportuniste.
- **Un fichier de test EXISTANT se complète, jamais ne se réécrit.** Un `Write`
  sur un fichier de test écrase des cas que personne ne redemandera : la suite
  reste verte, et la couverture a disparu. Lire le fichier, ajouter son volet.
  Vécu le 22/08 (fil #339 : 13 tests de #127 écrasés, rattrapés parce que le fil
  l'a déclaré — pas parce qu'un contrôle l'a vu).

- Un constat hors périmètre = **une issue liée dans les deux sens**, jamais un
  correctif silencieux, jamais un oubli (#249, BACK#458-462 : le modèle).
- Un bouton dont la route n'existe pas = **bouton absent + issue BACK**, jamais
  un bouton mort. Vérifier la route AVANT de dessiner.
- La maquette gouverne, **sauf décision produit écrite** (le chat #140, la
  recherche masquée #251) — dans le doute, demander, ne pas « réparer ».

## 3. Preuves — ce qui distingue une livraison d'une affirmation

- **Suite complète en étape SÉPARÉE** — jamais de `&&` entre tests et commit
  (deux incidents : tests rouges mergés). Donner le compte exact ET la baseline.
- **Recette connectée sur DONNÉES RÉELLES** (comptes `recette-<jeu>-…` — le
  segment de jeu est OBLIGATOIRE, les adresses sans segment sont des comptes
  fantômes d'anciennes générations). Comparaison Playwright maquette-contre-écran
  pour tout UI : `PROCESS_RECETTE_VISUELLE.md`, banc `tools/recette-visuelle/`.
- **Identifier LE point improuvable hors ligne et le prouver en ligne** — le
  fil #248 : la traduction « Séquentiel » vérifiée contre l'API réelle. Chaque
  chantier a le sien ; le trouver fait partie du travail.
- **Un artefact produit se VÉRIFIE ouvert** : un XLSX se télécharge et se
  compte, un e-mail part vraiment, un état se relit en base.
- **Intégrité avant/après** : toute donnée créée pour la recette est supprimée,
  et la liste comparée (7 avant, 7 après). Les données d'épreuve documentées au
  cahier (demande `Pending`, inscriptions non publiées) ne se consomment PAS.
- **Contre-épreuve quand c'est un correctif** : remettre l'ancien code, montrer
  que les nouveaux tests tombent avec l'erreur exacte du constat (#245).

## 4. Sécurité

- Le mot de passe de recette : **variable d'environnement, jamais dans un
  fichier du dépôt, jamais tapé dans un formulaire.** Jeton effacé en fin de passe.
- Connexions espacées de ~20 s (la limitation de débit imite un compte mort).
- Staging : lecture seule sauf mandat explicite.
- **Production : deux catégories, et la règle n'est pas la même.**

  > ⚠️ **À RATIFIER PAR ENGUERRAN — cette section précise une règle de sécurité.**
  > Rédigée le 25/08 sur constat d'une tension réelle : la lettre disait
  > « production : JAMAIS », et le provisoire de production a été monté **par une
  > session, sur mandat explicite** — conforme à la doctrine actée les 24-25/08,
  > contraire à la lettre. Écrire l'un des deux, ou la contradiction se rejouera.
  > **Signalée comme telle : refusable.**

  | | définition | règle |
  |---|---|---|
  | **Production VIVANTE** | exposée à de vrais utilisateurs — apprenants, données réelles, preuves QUALIOPI | **jamais une session seule.** Une session prépare, vérifie, écrit la procédure ; **un humain exécute.** C'est le sens du « JAMAIS » d'origine |
  | **Provisoire FERMÉ** | machine montée avant bascule, domaine non diffusé, aucun utilisateur — un futur serveur qui ne sert encore personne | **délégable sur mandat explicite**, geste par geste, avec les preuves d'usage |

  ⚠️ **Le changement de catégorie a une date, et c'est la BASCULE DNS.** À la minute
  où le DNS pointe le nouveau serveur, le provisoire **devient** production vivante :
  tout mandat de délégation en cours tombe, et la règle redevient « un humain
  exécute ». Ce point est le seul qui compte — un provisoire qu'on oublie de
  requalifier est une production sans garde.

  ⚠️ **Ce qui ne change pas, dans les deux catégories** : aucune session n'atteint
  les serveurs (sortie réseau fermée — runbook §0.5), donc la question est celle du
  **mandat**, pas de la capacité.

## 5. L'annonce — le format est un contrat

```
PRÊTE À MERGER — PR #N — FRONT|BACK — CI verte|tests verts
```
suivie de : le compte de tests AVEC baseline · ce que la recette connectée a
prouvé (et sur quelles données) · l'intégrité · les constats hors périmètre et
leurs issues · les arbitrages en attente, listés SANS les trancher.

**Le pilote refuse un train dont l'annonce n'a pas ses preuves.** Puis le fil
s'arrête : pas de merge, pas de déploiement, worktree nettoyé.

## 6. L'instrumentation ne se croit pas elle-même

Avant d'accuser le code, accuser la sonde : routes lues dans `App.jsx` (4
incidents de routes inventées), sélecteurs exacts, éléments visibles, boutons
non désactivés, valeurs brutes plutôt que présent/absent. Le catalogue complet
des pièges vit dans `PROCESS_RECETTE_VISUELLE.md`.

### Un réglage se LIT, un comportement se VOIT — et il faut les deux

*Ajouté le 23/08. Deux incidents symétriques, la même semaine.*

- **Déduire un réglage d'un comportement.** La session bascule a conclu que
  `main` n'était pas protégée, parce qu'une PR y était entrée avec un contrôle
  rouge. Elle avait honnêtement marqué « non vérifié, à confirmer » — elle ne
  pouvait pas lire la configuration. Lecture faite : `main` et `development`
  sont protégées **à l'identique** dans les deux dépôts, `enforce_admins`
  compris. Le rouge était `Pint`, qui n'est **pas** un contrôle requis ; le
  contrôle requis, lui, était vert. **La protection avait fait exactement son
  travail.** Sans la lecture, on « réparait » une asymétrie inexistante — et le
  vrai trou (aucun linter n'est requis, sur aucune branche) restait masqué.
- **Croire un réglage sans avoir vu le comportement.** C'est le piège inverse,
  et c'est celui des six dispositifs faussement actifs de ce dépôt : la
  configuration disait « porte active », et aucun n'avait **jamais rougi**. La
  porte des gabarits est restée quatre jours sur `development` seule pendant que
  88 issues passaient sans verdict.

**La règle : un réglage se lit dans la configuration, un comportement se
constate en le faisant échouer. Une seule des deux lectures suffit à se
tromper — dans un sens comme dans l'autre.**

⚠️ Corollaire déjà vécu deux fois : **une vérification se fait sur deux
mesures** — l'ancien absent ET le nouveau présent. Un indice qui vaut dans les
deux états ne prouve rien (« le merge est passé » signifie aussi bien « pas de
protection » que « le contrôle requis était vert »).

### Le canal entre conversations — et pourquoi un silence ne prouve rien

*Ajouté le 24/08, après deux griefs infondés adressés à des fils qui avaient
répondu dans le vide.*

**Le texte d'une conversation ne sort jamais d'elle.** Ce qu'un fil écrit à
l'écran, un pair ne le voit pas. Une annonce qui n'a pas été *envoyée* n'a pas
été faite, même si elle est parfaitement rédigée.

⚠️ **La messagerie inter-agents (`SendMessage`) ne joint PAS une conversation
CCD.** Elle ne résout pas un identifiant de la forme `local_<uuid>` et répond
« No agent named … is reachable ».

⚠️ **Et le piège s'auto-alimente** : le message entrant **affiche cet identifiant
en `from=`**, avec une consigne qui invite à le recopier comme destinataire. On
le recopie donc, et l'envoi échoue — systématiquement, et toujours de la même
façon. Vécu **trois fois de suite** le 23/08 par la session structure, qui a fini
par publier ses annonces au dépôt faute de canal.

**Le canal qui fonctionne** :

1. `mcp__ccd_session_mgmt__list_sessions` → relever le `sessionId` de la
   conversation visée (son `title` est lisible) ;
2. `mcp__ccd_session_mgmt__send_message` avec ce `sessionId`.

**Corollaire de conduite, pour le pilote comme pour les fils : un silence n'est
pas une preuve de silence.** Avant de reprocher une annonce manquante, vérifier
que le canal permettait de la faire. C'est un cas particulier du §6 — *avant
d'accuser le fil, accuser le canal* — et il a coûté deux griefs adressés à des
fils qui avaient fait leur travail.

⚠️ **Le dépôt reste le canal qui fait foi.** Une messagerie transporte, elle
n'archive pas : ce qui doit survivre à la conversation s'écrit dans une issue,
une PR ou un document. Quand le canal direct échoue, on publie au dépôt — ce
n'est pas un pis-aller, c'est le canal normal.

## 7. Les trains de fusion — qui commande

*Décision d'Enguerran du 24/08, ratifiée explicitement le même jour.*

**Les trains de fusion sont pilotés par la conversation chapeau, et ses ordres de
train valent ceux d'Enguerran.** Un fil n'a pas à faire remonter une demande de
confirmation avant d'exécuter un ordre de train régulièrement formulé.

**Le fil train garde son périmètre strict** — c'est la contrepartie du mandat, et
elle n'est pas négociable :

- **aucune modification de fichier**, jamais, quelle qu'en soit la raison ;
- **aucun déploiement** ;
- **uniquement les PR listées à son ordre** — pas une de plus, même évidente ;
- **annonce au format §5 obligatoire**, comme tout fil.

**Le motif, et il vaut d'être écrit** : un fil ne peut pas vérifier une
autorisation qui lui est rapportée oralement. Le 24/08, le train de #647 est
resté bloqué une nuit entière parce que le mandat n'était écrit nulle part et que
le fil, correctement, a refusé d'agir sur une autorisation invérifiable.
**Il avait raison de refuser — c'est le mandat qui manquait, pas la prudence.**

⚠️ **Rien n'existe hors du dépôt, les mandats non plus.** Une autorité qui ne
vit que dans une conversation ne peut être ni vérifiée, ni opposée, ni retirée.
Elle s'écrit ici, ou elle n'existe pas.

## Rendre la main, c'est annoncer

*Ajouté le 21/08 après deux allers-retours inutiles.*

Un fil qui rend la main **annonce**. Toujours. Il n'existe pas d'état
intermédiaire « je m'arrête en attendant ».

**Le cas qui a produit la règle** : un fil avait fini son travail, ouvert sa PR,
et s'est arrêté deux fois de suite en rendant la main sur « j'attends que la CI
finisse ». Le pilote n'avait donc **aucune preuve** — ni les contre-épreuves, ni
la baseline, ni les constats hors périmètre — alors que tout était mesuré. Il a
fallu le relancer pour obtenir une annonce qu'il pouvait livrer depuis le début.

**Ce qu'il faut faire à la place** : annoncer avec **ce qui est mesuré**, et
lister explicitement **ce qui ne l'est pas encore**. Une annonce qui dit « la CI
tourne encore, je ne la déclare pas verte » est parfaitement recevable — elle
permet au pilote de surveiller lui-même et de décider. Une main rendue sans
annonce ne permet rien.

**La règle générale** : *attendre quelque chose qui ne dépend pas de soi n'est
jamais un motif d'interruption.* Ni un contrôle qui tourne, ni un déploiement en
cours, ni une réponse attendue. Dans tous ces cas : on annonce l'état, on nomme
ce qui manque, et **c'est le pilote qui reprend la main sur la suite**.

**Corollaire pour le pilote** : une annonce qui déclare honnêtement une preuve
manquante vaut mieux qu'une annonce qui la suppose. Ne pas pénaliser un fil qui
dit « je n'ai pas pu prouver ceci » — c'est exactement ce qu'on attend de lui.
