# Plan — le modèle `release/**` et l'environnement manquant avant la recette

> Instruit le 24/08 à la demande d'Enguerran, à partir du runbook
> ([`14_RUNBOOK_BASCULE_PROD.md`](./14_RUNBOOK_BASCULE_PROD.md) §0.3) : *« il manque un
> environnement avant la recette »*. Ce document est le plan d'exécution soumis à son go.
> **Rien n'est lancé tant que le go n'est pas donné.**
>
> Préalables satisfaits au 24/08 soir, vérifiés : train n°4 passé (porte de liaison en
> service sur `main` des deux dépôts, **éprouvée en réel dans les deux sens** — un run
> `issues` success sur le cas nominal, un run `issues` **failure** sur une référence
> inexistante) ; #653 (le cap structuration) et #655 (erratum staging) sur `development`.

## 1. Le problème, et ce que le runbook a déjà tranché

`development` sert à la fois de branche de travail et de cible de recette : dès qu'un fil
merge, ce que les testeurs testent change sous eux (constat d'Enguerran, 23/08). La cible du
runbook §0.3 :

| branche | rôle | déploie vers |
|---|---|---|
| `development` | les fils y mergent en continu | **serveur de dev — N'EXISTE PAS** |
| `release/AAAA-MM` | **figée** à la coupe ; seuls les correctifs de recette y entrent | **staging** = recette |
| `main` | la production | prod |

⚠️ **Ce que le runbook ne dit pas** : le serveur de dev n'est pas un confort. La charte
(`FIL_DE_CHANTIER.md` §3) exige de chaque fil une **recette connectée sur données réelles** —
jouée aujourd'hui sur staging. Une fois staging réservé à `release/**`, un merge dans
`development` n'a **plus d'écran** : c'est l'environnement manquant.

### Branche et environnement — la distinction qui a été demandée

**Les environnements sont fixes** (dev, staging, prod : URL, base, serveur inchangés). **Les
branches disent quel contenu les alimente.** À chaque cycle : `git checkout -b release/2026-10
development && git push` — le motif `release/**` du workflow attrape la nouvelle branche,
**zéro modification de workflow, zéro renommage, zéro geste serveur**. Les branches release
s'accumulent (~12/an) : c'est l'archive — chaque recette et chaque mise en prod a un nom, et
`git diff release/2026-09..main` répond à « qu'est-ce qui a changé depuis la validation ? ».

**Pourquoi pas une branche `staging` permanente** : pour la re-figer à chaque cycle il faut
soit un force-push (interdit par la protection, dangereux), soit y merger `development` (elle
n'est alors plus figée). La branche datée évite les deux.

## 2. ⚠️ Le risque identifié — et le garde-fou qui le ferme

**Le motif `release/**` seul est dangereux.** Trois scénarios mesurables :

1. `release/2026-10` est en recette ; un correctif de prod est poussé sur `release/2026-09`
   → **staging est écrasé par l'ancienne release, silencieusement** — les testeurs regardent
   un écran qui a reculé d'un mois ;
2. une branche `release/typo` créée par erreur → déployée, silencieusement ;
3. deux pushes rapprochés sur deux branches release → le dernier gagne, ordre non contrôlé.

**Le garde-fou : une variable de dépôt `RELEASE_COURANTE`** (valeur : `release/2026-09`), et
le workflow refuse **bruyamment** toute branche release qui n'est pas elle.

| scénario | comportement |
|---|---|
| push sur la release courante | déploie |
| correctif sur l'ancienne release | **refus bruyant** — staging intact |
| `release/typo` | **refus bruyant** |
| variable oubliée au nouveau cycle | les pushes de la nouvelle release **rougissent** — visible, jamais silencieux |

**Chaque erreur possible produit du rouge, jamais un mauvais déploiement silencieux.** C'est
le critère qui écarte l'alternative « déployer la release la plus récente par tri » : un tri
peut se tromper sans le dire. S'y ajoutent un groupe de concurrence (pas de déploiements
entrelacés) et la branche + le commit déployés écrits au journal du run.

⚠️ **Au passage : le modèle ACTUEL porte la même classe de risque, en pire** — tout push sur
`development` déploie staging sans aucune garde. La bascule rend le dispositif plus strict
qu'aujourd'hui.

## 2 ter. ✅ V3 du 25/08 (après-midi) — TRANCHÉ : ce document devient un PLAN D'EXÉCUTION

> **Le modèle B est arbitré par Enguerran** (25/08, dans la session structure). La
> comparaison du §2 bis reste comme trace d'instruction ; à partir d'ici, plus de
> débat — un plan. Source consignée de la proposition et de l'analyse :
> [le commentaire sur #658](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/issues/658#issuecomment-5407798846)
> — *le dépôt fait foi, pas la messagerie qui l'a transportée.*

### Les décisions actées (25/08)

1. **Trois environnements** : serveur de **DEV** ← `development` en continu ·
   **STAGING** ← branche fixe `staging` (recréée par cérémonie scriptée à chaque
   campagne) · **PROD** ← `main`. ⚠️ **`main` reste au runbook MANUEL jusqu'après la
   bascule** — décision ferme, aucune CD vers la production.
2. L'arrivée du serveur de dev **dissout le « trou de recette temporaire »** du 24/08.
3. **Reprise des devs L4 dès que le serveur de dev tourne** — sans attendre la fin de
   l'alignement CI/CD (zones disjointes). **En tête de reprise : le correctif
   certificat**, vérifié sur staging, livré en prod AVEC la bascule.
4. **Règle immédiate : AUCUNE issue nouvelle sans jalon** — `L5 (avant bascule)` ou
   `V2 (post-bascule)`, posé À LA CRÉATION. Le verrou déterministe (la porte de
   gabarit vérifie le jalon) est instruit ci-dessous.
5. La **typologie d'issues** (dev / bugs recette / doc / corrections run) passe en
   tâche de fond derrière les environnements — instruction seulement, aucune bascule
   d'outillage pendant la recette (le cap du 24/08 tient).

### La séquence d'Enguerran — 12 points, dans son ordre

1. suivi de la recette L4, **sans nouveaux développements** pendant les phases infra ;
2. mise en place des **trois environnements** ;
3. **alignement CI/CD** sur cette structure ;
4. **reprise des devs L4** sur l'environnement de dev — conversation dédiée ;
5. en parallèle : **planification L5** dans la nouvelle structure ;
6. ouverture des serveurs + noms de domaine — ⚠️ **staging GARDE son domaine actuel
   tant que la recette vit** ;
7. suspension **limitée aux chantiers touchant workflows/serveurs** ;
8. branchements des environnements — **jamais sous les pieds de Lilian** ;
9. tests de branchements ;
10. CI/CD alignée **hors `main`** ;
11. finalisation L4 ;
12. lancement **L5 nouveau schéma** — conversation dédiée.

### Le raccordement à la cérémonie livrée (PR #662) — qui fait quoi

La cérémonie `scripts/recreer_staging.sh` est livrée (session bascule, CI verte,
autotest 5/5, contre-épreuves des deux verrous). Sa liste de raccordement côté
structure, point par point :

| demande de #662 | réponse | état |
|---|---|---|
| protection `staging` : « autoriser le force-push » | ⚠️ **NON en permanence — fenêtre scriptée.** Mesuré avant de régler : **SCHOTTL et Abakan (équipe externe) ont `push=true` sur les deux dépôts** — un force-push permanent leur permettrait de réécrire la branche de recette en silence, hors cérémonie. L'enveloppe `scripts/ouvrir_campagne_staging.sh` ouvre la fenêtre, joue la cérémonie, **referme dans tous les cas** (trap) et **vérifie** l'état final. Éprouvée en la faisant échouer. C'était la condition n°1 de l'arbitrage. | ✅ livrée (PR #659) |
| contrôle requis sur `staging` | posé : `Suite — vert ou rouge (MySQL 8)` (BACK) / `tests` (FRONT), `enforce_admins`, identique à `development` — relu | ✅ fait |
| `deploy-staging.yml` figé sur `push: [staging]`, sans garde | fait dans les PR #659/#363 — avec le **correctif du pull serveur** (il tirait `development` en dur) et `report-release` recentré | ✅ dans le train |
| documentation + runbook §0.3 | ce document (v3) + amendement §0.3 ci-joint | ✅ ce commit |
| invariant du report réaffirmé | porté par `report-release` en continu ET par le verrou bloquant de la cérémonie | ✅ |

⚠️ **La cérémonie échoue tant que la fenêtre est fermée — c'est voulu** : jouée sans
l'enveloppe, elle refuse au force-push. L'entrée d'une campagne est donc
**l'enveloppe**, pas la cérémonie nue. À écrire dans la procédure d'ouverture.

### La porte-jalon (verrou de la règle n°4) — instruite, faisable

Proposition du lotissement (commentaire sur #649), confirmée réalisable : la porte
`conformite-issues.yml` se déclenche déjà sur `opened`/`edited` et son payload porte
`github.event.issue.milestone` — **le contrôle est un test de nullité dans le
workflow, PAS une règle du vérificateur hors-ligne** (le jalon n'est pas dans le
corps ; le vérificateur resterait inchangé, donc son corpus aussi). Refus →
`gabarit:non-conforme` + commentaire nommant les deux jalons admis (L5/V2). À
éprouver en le faisant échouer (issue-test sans jalon). **Exécution : avec le volet
GitHub des environnements, fichiers jumeaux dans les deux dépôts.**

### Ce qui reste hors de ce plan

Les serveurs eux-mêmes (session bascule — Enguerran commande les machines) · la
CI/CD complète (conversation dédiée, phase 3 de la séquence — l'alerte d'origine est
maintenue) · la cérémonie (livrée, PR #662) · le lotissement (jalons, gardien de la
règle n°4).

## 2 bis. ⚠️ V2 du 25/08 — la contre-proposition d'Enguerran, instruite

> Enguerran a contre-proposé (via la session bascule, routage chapeau) : **pas de
> branches datées, une branche `staging` à NOM FIXE** sur laquelle la CD joue en
> permanence — « plus de doute : la seule branche sur laquelle la CD joue est
> staging » — recréée depuis `development` à chaque campagne par une **cérémonie
> scriptée**. Les trois PR de la phase A sont GELÉES le temps de cet arbitrage.

### Les deux modèles, honnêtement

| | **A — `release/AAAA-MM` + `RELEASE_COURANTE`** (construit, PR gelées) | **B — `staging` fixe + cérémonie scriptée** (contre-proposition) |
|---|---|---|
| lisibilité pour un humain | ❌ une indirection : il faut savoir QUELLE release est courante (variable) | ✅ **« la CD joue sur staging », point.** Zéro doute, zéro variable |
| panne « variable/cérémonie oubliée » | rouge en CI… mais **staging sert du périmé sans le dire aux testeurs** (classe #603) | pas de variable ; la cérémonie est UN geste atomique, scripté |
| gestes par cycle | 2 (couper la branche, mettre à jour la variable) | 1 (lancer la cérémonie) |
| garde `RELEASE_COURANTE` | nécessaire (fermer le motif `release/**`) | **inutile — complexité en moins** |
| force-push | jamais, nulle part | ⚠️ requis à la cérémonie (les merges de correctifs sur `staging` ont des SHA hors `development` : pas d'avance rapide possible) |
| archive des campagnes | branches datées, gratuites | tags `recette-AAAA-MM-fin` posés par la cérémonie — équivalent |
| correctif sur la version EN PROD pendant la campagne suivante | ✅ la branche release de la prod existe encore | ⚠️ plus de branche : passer par une branche depuis le tag → PR vers `main` + report — cas rare, à documenter |
| `report-release` (rouge tant qu'un correctif n'est pas reporté) | ✅ | ✅ **conservé tel quel, adapté à `staging`** — il est indépendant du modèle, et la cérémonie y ajoute son verrou bloquant |
| protection de branche | posée (`release/**`) | à poser (`staging`), mêmes contrôles |

### ⚠️ La mesure qui change le poids du force-push (25/08)

`allowsForcePushes` **se bascule par API sur une règle existante** — testé dans les
deux sens sur la règle `release/**`, remis à `false`. La cérémonie peut donc :
vérifier le verrou de report → poser le tag → **ouvrir le force-push** →
force-pousser `staging` → **refermer** → vérifier. **La fenêtre de force-push dure
quelques secondes et vit dans un script audité** — elle n'est plus un trou permanent.
⚠️ Hors cérémonie, un force-push sur `staging` reste **impossible** (protection).

### Les deux verrous de la cérémonie (proposition bascule, confirmés utiles)

1. **tag `recette-AAAA-MM-fin`** sur la pointe AVANT tout reset — perte impossible,
   archive gratuite ;
2. **refus si un correctif de `staging` n'est pas reporté dans `development`** — le
   contrôle qui manquait à FRONT#154, en version BLOQUANTE ; `report-release` donne le
   même signal en continu entre deux cérémonies.

### Ma recommandation — et elle rejoint celle de la bascule

**Le modèle B (`staging` fixe, cérémonie scriptée), avec trois conditions :**

1. la fenêtre de force-push est **ouverte et refermée PAR le script**, et l'état
   `allowsForcePushes=false` est **vérifié en fin de cérémonie** (mesuré possible) ;
2. **`report-release` est conservé** (adapté à `staging`) — le signal continu entre
   deux cérémonies, en plus du verrou bloquant de la cérémonie ;
3. la cérémonie est **éprouvée en la faisant échouer** avant toute mise en service :
   un correctif non reporté doit la BLOQUER, et un force-push hors cérémonie doit
   être REFUSÉ.

Pourquoi B : l'argument décisif n'est pas technique, il est d'exploitation. C'est
Enguerran qui vivra avec ce modèle pendant des années, et « la CD joue sur staging,
point » supprime l'indirection qui, dans A, finit un jour en écran périmé silencieux.
Les avantages propres de A (pas de force-push, hotfix parallèle) sont soit neutralisés
par la mesure ci-dessus, soit rares et documentables. Ce que A garde d'unique — la
branche datée comme archive — est couvert par les tags.

**Classement des risques (bascule, que je confirme)** : `staging` fixe scripté <
`release/**` + garde < `staging` fixe SANS script — la cérémonie scriptée n'est pas
un confort, c'est la condition du modèle.

### Ce qu'il y aurait à défaire / réutiliser selon l'option

| geste déjà posé | si A (construit) | si B (recommandé) |
|---|---|---|
| variables `RELEASE_COURANTE` (2 dépôts) | restent | **à supprimer** (2 commandes) |
| protection `release/**` (2 dépôts) | reste | **à remplacer** par une règle `staging` (mêmes contrôles) |
| PR #659 / FRONT#363 | partent telles quelles | **à retravailler** : déclencheur `staging`, garde supprimée, **le correctif du pull serveur et le journal restent** (valables tels quels), `report-release` adapté |
| PR #657 (ce plan) | v2 = trace de l'arbitrage | idem |
| issues #658 / FRONT#362 | ferment avec les PR | **CA à amender** (commentaire, pas réécriture) |
| cérémonie scriptée | sans objet | **à écrire par la session bascule** (routage chapeau, acté) |

## 3. Phase A — côté GitHub (session structure, prête à exécuter au go)

> ⚠️ **Section v1, GELÉE le 25/08** dans l'attente de l'arbitrage A/B ci-dessus. Si B
> est retenu, les étapes 2 et 4 changent (garde supprimée, déclencheur `staging`) et
> la coupe devient la première cérémonie.

Ordre d'exécution, chaque étape éprouvée avant la suivante :

1. **Protection de `release/**`** dans les deux dépôts — mêmes contrôles requis que
   `development` (la protection classique accepte les motifs). Éprouvée : une PR de test vers
   une branche release doit exiger le contrôle.
2. **Garde `RELEASE_COURANTE`** dans `deploy-staging.yml` des deux dépôts + variable posée.
   ⚠️ Ne touche PAS aux étapes de migration du workflow — l'arbitrage #575/#626 reste entier.
3. **Coupe de `release/2026-09`** depuis `development`, **les deux dépôts, même référence**,
   à un moment où la feuille de recette de Lilian est au propre (coordination chapeau ↔
   session recette).
4. **Bascule du déclencheur** : `push: branches: [development]` → `['release/**']`.
   ⚠️ **Ordre impératif runbook : couper la branche AVANT de basculer le workflow** — ainsi
   le contenu de staging ne change pas d'un octet, impact nul sur la recette en cours.
5. **Épreuve d'échec, avant toute annonce** (doctrine : un garde-fou qu'on n'a pas vu échouer
   n'en est pas un) : pousser une branche `release/zz-epreuve` → le déploiement doit
   **ROUGIR** avec un message qui nomme la branche et la release attendue ; puis pousser un
   commit vide sur `release/2026-09` → staging doit se déployer. Nettoyage des branches
   d'épreuve, comptes avant/après.
6. **Garde-fou de report** (nouveau, ferme le motif FRONT#154 côté release) : un contrôle
   déterministe qui **rougit si un commit de `release/` n'est pas contenu dans
   `development`** — la règle « correctif sur release, PUIS report sur development » cesse de
   reposer sur la vigilance. Éprouvé en le faisant échouer.
7. **Annonce à toutes les sessions LE JOUR MÊME** (règle 2 du runbook §0.3) : un merge dans
   `development` n'arrive plus sur staging ; les correctifs de recette vont sur `release/`
   d'abord. Relais chapeau → session recette + session bascule.

## 4. Phase B — le serveur de dev (session bascule, PAS cette session)

Créer l'environnement qui recevra `development` en continu. Arbitrages à instruire là-bas :
second vhost + base dédiée sur la machine de staging (rapide, zéro machine en plus) ou
machine dédiée ; dimensionnement dans le même mouvement que le serveur cible (runbook §0.4).
⚠️ Aucune session n'atteint un serveur (runbook §0.5) : l'exécution est humaine.

## 5. Les arbitrages — état au 24/08 soir

> **Mise à jour du 24/08 (soir) — TOUS les arbitrages sont rendus par Enguerran** :
> GO phase A complet · trou de recette temporaire **accepté** (CI entière conservée,
> annonces avec mention honnête) · #575/#626 tranché par le **compromis #626 d'abord**
> (afficher les migrations en attente, exécution humaine ; #575 différée à la
> conversation CI/CD — tracé sur les deux issues) · les deux gestes par cycle (couper
> la release, mettre à jour `RELEASE_COURANTE`) sont tenus par **le pilote Claude à la
> demande d'Enguerran**.
>
> Exécution en cours : issues BACK#658 / FRONT#362 (liées par dépendance GitHub posée
> par la porte de liaison — première utilisation réelle), PR #659 / FRONT#363,
> variables `RELEASE_COURANTE=release/2026-09` posées, protection `release/**` posée
> et relue sur les deux dépôts (mêmes contrôles que `development`, `enforce_admins`).
>
> ⚠️ Conséquence de la protection, à connaître : les contrôles requis sur `release/**`
> font que **les correctifs de recette passent par PR vers la release**, pas par push
> direct — plus strict, et cohérent avec « 1 issue = 1 branche = 1 PR ». L'épreuve
> nominale post-coupe s'ajuste en conséquence (PR de test ou `workflow_dispatch`
> depuis la release, pas un push direct).

| arbitrage | état |
|---|---|
| séquencement après le train n°4 | ✅ **rendu par Enguerran**, et le n°4 est passé |
| coller phase A au train n°4 vs attendre | ✅ sans objet — le n°4 est passé, la phase A part au go |
| **trou de recette temporaire** : entre la bascule du workflow et la création du serveur de dev, les fils gardent la CI complète sur `development` mais perdent la recette connectée ; leurs annonces déclarent honnêtement « recette connectée impossible, pas d'environnement » (recevable, charte §« Rendre la main ») et la recette se rattrape à la création du serveur de dev ou à la coupe suivante | ⚠️ **exposé à Enguerran (question CI posée et répondue), en attente de son OUI explicite — c'est une dégradation temporaire du niveau de preuve** |
| #575 contre #626 (le déploiement exécute les migrations, ou alerte seulement ?) | ⚠️ **non tranché, et la phase A ne le tranche pas.** Compromis éclairé proposé : #626 d'abord (compter et AFFICHER les migrations en attente — le silence est le défaut), #575 (exécution auto) reste à la conversation CI/CD |
| qui coupe la release au cycle suivant, et qui met à jour `RELEASE_COURANTE` | ⚠️ **à fixer au go** — deux gestes par cycle, dont l'oubli du second est bruyant par construction |

## 6. Séquencement inter-sessions (cadrage chapeau du 24/08)

- **la phase A passe en premier sur `deploy-staging.yml`** — aucun autre chantier ne touche ce
  fichier avant sa fusion (notifié par le chapeau à la session bascule) ;
- la coupe est coordonnée avec la session recette (feuille au propre) ;
- le besoin du serveur de dev est signalé tôt à la session bascule pour dimensionnement
  groupé ;
- le plan complet passe par le chapeau pour présentation à Enguerran — circuit des
  arbitrages en vigueur.
