# Règles d'intégration Git — à respecter systématiquement

Définies par Enguerran le 06/08/2026, en accord avec l'équipe de développement.
**Applicables sans exception, sauf contre-ordre explicite de sa part.**

---

## La chaîne

```
1. Test en local
        ↓
2. Commit — 1 issue + ses tests unitaires et de non-régression = 1 branche = 1 PR
        ↓
3. Merge sur `development` (= staging), PR par PR, au fil de l'eau
   (le pipeline se déclenche au push : contrôle + message d'erreur)
        ↓
4. Test sur le staging, issue par issue
        ↓
5. Merge sur `main` (= production) — groupé par sprint, sauf contre-ordre
```

> Maintenance des tests dans le temps (régression vs obsolescence) :
> voir [`17_DOCTRINE_TESTS.md`](./17_DOCTRINE_TESTS.md).

### Branches empilées : le piège du 08/08 (à ne pas reproduire)

Quand une PR B est basée sur la branche d'une PR A (« PR empilée »), **merger A ne
re-cible PAS automatiquement B** : GitHub ne le fait que si la branche de A est
supprimée au merge. Si on conserve les branches (`--delete-branch=false`), merger B
l'envoie **dans la branche de A** — pas dans `development`. La PR affiche « merged »,
mais son contenu n'atteint jamais staging.

**Vécu le 08/08** : 7 lots de code (EA-012/004/016/017/028/018/031) affichés comme
mergés étaient restés dans leurs branches parentes ; détecté en comparant le nombre de
migrations sur `development` (79 au lieu de 82 attendues).

**Règle** : avant de merger une pile, **re-cibler chaque PR empilée sur `development`**
(`gh pr edit <n> --base development`). Puis **vérifier le contenu réellement présent** sur
`development` (un fichier témoin par lot), jamais le seul statut « merged » de la PR.

⚠️ **Correction du 13/08 — « au fur et à mesure » ne marche PAS.** Ce document laissait le
choix entre re-cibler d'emblée ou au fil des merges. **La seconde branche de l'alternative
est fausse, vérifiée à nos dépens** : au merge de FRONT#147 avec `--delete-branch`, GitHub
n'a pas re-ciblé la PR enfant #150 — **il l'a FERMÉE**. Et une PR close est un cul-de-sac :

```
GraphQL: Cannot change the base branch of a closed pull request. (updatePullRequest)
```

La rouvrir ne restaure pas sa base. Il a fallu créer une PR neuve (#151) depuis la même
branche — le travail était intact, seul l'objet PR avait été détruit, mais l'historique de
relecture et les commentaires sont perdus.

**La seule séquence sûre, dans cet ordre :**

```bash
gh pr edit <enfant> --base development          # 1. RE-CIBLER, toujours en premier
gh pr merge <parent> --merge --delete-branch    # 2. merger le parent ensuite
gh pr merge <enfant> --merge --delete-branch    # 3. puis l'enfant
```

**Et la vraie leçon** : une pile à deux niveaux se paie déjà. Préférer merger le parent
avant même d'ouvrir l'enfant — une branche fille partie d'un `development` à jour ne pose
aucun de ces problèmes.

## Ce que cela implique concrètement

| Règle | Conséquence pratique |
|---|---|
| **1 issue = 1 branche = 1 PR** | Jamais de PR groupant plusieurs issues. Une PR qui corrige deux choses doit être scindée. |
| **Tests dans la même PR** | Les tests unitaires et de non-régression accompagnent le correctif, dans la même branche. Pas de PR de code sans ses tests. |
| **Merge au fil de l'eau sur `development`** | Pas d'attente : dès qu'une issue est prête et validée, elle part sur le staging. |
| **Test issue par issue sur le staging** | La recette se fait sur `development` déployé, avant tout passage en production. |
| **`main` par sprint** | La production reçoit un lot cohérent en fin de sprint, pas des correctifs isolés. |

**Correspondance des branches :** `development` → staging · `main` → production.

---

## Deux écarts constatés au 06/08 — à traiter

### 1. Les deux PR ouvertes ne respectent pas « 1 issue = 1 PR »

Elles ont été construites avant l'énoncé de la règle :

| PR | Issues couvertes | Écart |
|---|---|---|
| [#11](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/11) | #8, #9, #10 | 3 issues dans une PR |
| [#95](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/95) | #18, #23, #24, #25, + #12 partiellement | 4 issues et demie dans une PR |

Trois options, à trancher par Enguerran :

- **Les scinder** en 7 PR d'une issue chacune — conforme, mais représente du travail de découpage et repousse le déploiement de correctifs de sécurité actifs.
- **Les traiter en exception explicite**, en le notant dans les PR (« antérieures à la règle du 06/08 »).
- **Garder #11 groupée** — les trois correctifs de sécurité forment un lot cohérent qu'on voudra déployer d'un bloc — et **scinder #95**, qui mélange environnement et compteurs.

*Recommandation : la troisième option. Elle respecte l'esprit de la règle là où c'est utile, sans retarder la fermeture de failles actives.*

### 2. Le pipeline annoncé n'existe pas encore

La règle mentionne « le pipeline se lance au moment du push avec contrôle + message d'erreur ».
Vérification faite : **aucun workflow GitHub Actions** n'existe, sur aucune branche, dans aucun
des deux dépôts. Zéro exécution enregistrée.

Il faut donc soit le créer, soit savoir qu'il tourne ailleurs (ce que l'équipe pourra préciser).
Sans lui, rien ne vérifie automatiquement qu'une PR ne casse pas les tests — le contrôle
reposerait entièrement sur l'exécution manuelle en local.

**Ce que ce pipeline devrait faire, au minimum**, pour le back :

```
composer install → php artisan test → vendor/bin/pint --test
```

Avec la référence connue : **5 échecs pré-existants** liés au champ `cta_url`. Tant qu'ils ne sont
pas corrigés, le pipeline ne peut pas exiger le vert absolu — il doit soit les traiter, soit les
marquer comme attendus.

### 3. Rappel : `main` n'alimente pas encore la production

La production est aujourd'hui déployée **à la main**, depuis un dépôt tiers
(`Rigictech/e-learning-api`), et non depuis `main`. L'étape 5 de la chaîne décrit donc l'état
cible, pas l'état actuel. Le raccordement est à faire — procédure dans
`12_PROCEDURE_DEPLOIEMENT.md`.

---

## Application à partir de maintenant

À partir du lot L1, chaque issue donne lieu à :

1. une branche nommée d'après l'issue — convention proposée : `feat/EA-011-lesson-views`,
   `fix/EA-014-pagination` ;
2. un commit (ou plusieurs) contenant le code **et** ses tests ;
3. une PR unique référençant l'issue (`Ferme #NN`) ;
4. un merge sur `development` dès validation ;
5. une recette sur le staging, issue par issue ;
6. un merge groupé sur `main` en fin de lot.

---

## Deux formats, une seule source (règle du 11/08)

Les documents destinés aux **humains** existent en **Word** (`.docx`), plus lisibles pour
les non-développeurs. Les `.md` restent la **source de travail** : c'est eux qu'on édite,
et `scripts/doc_word.sh` régénère les `.docx`. Jamais l'inverse — le `.md` fait foi, le
`.docx` est une vue. Après toute modification d'un document clé, relancer le script.

## Où tracer une anomalie (règle du 11/08)

Deux nuits de recette L4 ont produit le même incident trois fois : une anomalie trouvée en
cours de développement, corrigée, et documentée **au mauvais endroit** — un commentaire
d'issue ou un corps de PR. Un commentaire se noie dans le fil ; une PR ne se relit pas au
moment du passage en production. Résultat : l'information existait, et personne ne pouvait
la retrouver au moment où elle comptait.

La règle, appliquée systématiquement :

| Situation | Où tracer |
|---|---|
| Anomalie **corrigée dans le périmètre** d'une issue en cours | Dans le **CA (corps) de cette issue**, section « Défauts trouvés et corrigés » — avec le lien de la PR |
| Anomalie **reportée ou hors périmètre** | **Issue dédiée**, rattachée à un **jalon dès sa création**, référence croisée dans le corps |
| Décision prise en cours de route (arbitrage, écart maquette) | Dans le **CA de l'issue concernée**, datée et attribuée — jamais seulement en commentaire |

Corollaires :

- **La référence croisée entre les deux dépôts vit dans le corps de l'issue**, pas en
  commentaire : `scripts/audit_sprints.py` la lit, un commentaire lui est invisible.
- **Une issue sans jalon n'existe pour personne.** Elle ne figure dans aucun sprint et
  dérive jusqu'à sa redécouverte par accident. Le jalon se pose à la création, pas plus tard.
- Le cas vécu qui a fixé la règle : quatre écrans de L4 jamais développés, invisibles
  précisément parce que rien de tout cela n'était appliqué (cf. `13_PLAN_ACTUALISE_10_AOUT.md`).

## Le merge passe par `scripts/merge_train.sh` — plus par la main

**Deux fois le même incident** : 07/08, puis 09/08 sur le lot L3 alors qu'il était **déjà
documenté ci-dessus**. Une procédure écrite en prose ne tient pas au moment où l'on est pressé
de finir. Elle est donc devenue exécutable.

### Le piège, en une phrase

Une PR dont la base est **une autre branche de fonctionnalité** fusionne dans cette branche, pas
dans `development`. GitHub ne re-cible la base que si la branche de base est **supprimée** — ce
qui n'arrive pas avec `--delete-branch=false`. Le merge « réussit », les issues passent en
recette, et le code n'est nulle part.

### Ce que le script impose

```bash
# Avant de merger — refuse toute PR dont la base n'est pas la cible
scripts/merge_train.sh --base development --dry-run 170 171 172

# Merge + vérification que CHAQUE commit est bien dans la cible
scripts/merge_train.sh --base development 170 171 172

# Cas d'une pile : le sommet contient tout, une seule PR suffit
scripts/merge_train.sh --base development --top 174
```

Trois garanties :

1. **Refus** de toute PR dont la base n'est pas la cible — avec la commande de correction affichée.
2. **Vérification post-merge** : chaque commit de chaque PR doit être un ancêtre de la cible.
   C'est le contrôle qui manquait, et qu'aucune relecture humaine ne fait de façon fiable.
3. Sur `--base main`, le script rappelle les **migrations en attente** et impose la vérification
   d'un **lien profond** après déploiement (le `.htaccess` de routage SPA est la victime classique
   du `rsync --delete`, cf. `12_PROCEDURE_DEPLOIEMENT.md`).

### Règle de fond : ne pas empiler quand on peut éviter

Une pile de PR est utile pour **relire** (chaque PR reste petite), jamais pour **merger**. Au
moment du merge, deux options seulement :

- **re-cibler toutes les PR** sur `development` (`gh pr edit <n> --base development`), puis merger
  dans l'ordre ; ou
- **ne merger que le sommet** de la pile — il contient l'ensemble.

### Pourquoi pas une protection de branche ?

Elle est indisponible sur dépôt privé en plan gratuit (cf. `git_cicd_target.md`). Tant que c'est
le cas, **l'outil de merge est le seul point d'application** — d'où le choix d'un script plutôt
que d'un contrôle CI, qui ne pourrait rien bloquer.


---

## Où lire ce qui est DÉVELOPPÉ (et où ne pas le lire)

Ajouté le 13/08 après une erreur de lecture : conclure « le lot L3 n'a pas
commencé » à partir de dates de jalon et d'un compteur d'issues fermées, alors
que les développements L0→L4.5 étaient terminés.

**Ces deux indicateurs ne mesurent pas l'avancement :**

| Indicateur | Ce qu'il dit vraiment |
|---|---|
| Dates du jalon | le **plan initial**, jamais révisé quand la réalité a bougé |
| Issues fermées | la cadence de **contre-recette d'Enguerran**, qui clôture par périmètre cible et non au fil de l'eau |

**La réponse est le label `statut:`**, et lui seul :

| Label | Sens |
|---|---|
| `statut: à faire` | ouverte, non traitée |
| `statut: en dev` | branche en cours |
| `statut: PR ouverte` | développée, PR en attente de revue/merge |
| `statut: en recette` | **mergée sur `development`**, en test via le cahier de recette |

**Le label est posé automatiquement par `merge_train.sh`** dès qu'un merge est
vérifié — donc il ne dépend plus de la discipline de qui merge. C'était le point
faible : le 13/08, sept issues mergées portaient encore `statut: à faire`.

Les issues visées par l'automatisme sont celles que la PR **désigne
explicitement** : le préfixe numérique de la branche (convention « 1 issue = 1
branche »), ou une ligne `Issue : #N` dans le corps. Volontairement pas tous les
`#N` du corps — une PR cite couramment ses voisines, et étiqueter une voisine
« en recette » serait le mensonge qu'on cherche à supprimer.

**En cas de doute, la seule preuve est le dépôt** :

```bash
git log origin/development --oneline --grep="#<numéro>"
```

Et il faut chercher **dans les deux dépôts** : une issue portée par BACK peut
être livrée par un commit de FRONT.

### « Partiellement livrée » n'existe pas

**Décision d'Enguerran, 13/08** : *« Pour moi partiellement livrée n'est pas
livrée. Sinon il faut créer une issue en lien avec l'issue livrée. Avec la
logique 1 issue = 1 PR on ne devrait pas avoir ce cas normalement. »*

Pas de label supplémentaire, donc. Une issue est **livrée ou elle ne l'est
pas** : tant que son CA n'est pas entièrement couvert, elle reste `à faire`.

Et le diagnostic compte plus que la règle : **si le cas se présente, c'est un
défaut de découpage, pas un défaut d'étiquetage.** Une issue qu'une seule PR ne
peut pas couvrir était trop grosse au départ. Cas réel — #180 demande de migrer
**25 écrans** : ce n'est pas une issue, c'est un chantier.

**Que faire quand ça arrive** :

1. la **tranche livrée devient sa propre issue**, rattachée à la première, et
   passe `en recette` ;
2. l'issue d'origine **garde le reste du CA** et reste `à faire` ;
3. si le reste est lui-même trop gros pour une PR, il se découpe aussi.

**Comment le déclarer dans la PR** :

| Ligne dans le corps de la PR | Effet au merge |
|---|---|
| `Issue : #N` | la PR **achève** l'issue → `statut: en recette` |
| `Issue (partielle) : #N` | la PR n'en fait qu'une tranche → **aucun étiquetage**, et le train rappelle de découper |

Le second cas doit rester rare. S'il se répète, ce sont les issues qu'il faut
écrire plus petites, pas la convention qu'il faut étendre.
