From f655e99497795ef72624cb17e869d5d00d9f30c9 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Sat, 19 Sep 2026 06:29:40 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=93=9D=20docs(release):=20corrige=20q?= =?UTF-8?q?uatre=20passages=20d=C3=A9mentis=20par=20le=20cut=20de=20la=20v?= =?UTF-8?q?1.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Linked issue s'applique aux PR vers dev aussi (validate-pr.yml n'a aucune condition sur la base) ; Refs ne passe pas ; éditer le corps relance le check, un rerun rejoue l'ancien corps. - Étape 4 : main exige une approbation en plus des checks ; les promotions passent en --admin (enforce_admins à false). - Étape 6 : /api/health ne distingue pas l'image du tag de celle du filet deploy.yml ; la release se vérifie sur son run et gh release view. - « Revenir en arrière » affirmait qu'un tag rejoué reconstruit l'image et recrée la release : release.yml saute le build et fait un edit. L'étape 5 disait déjà l'inverse, le fichier se contredisait. Plus le rattrapage d'un Deploy prod en échec (#42) et une référence morte (« cf. contrainte 2 »). closes #43 Claude-Session: https://claude.ai/code/session_01P5DUscqftnE27pbmuUqL5J --- docs/RELEASE.md | 54 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index e711b4c..5fbf2ea 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -39,11 +39,19 @@ les deux ne se réveillent jamais ensemble. Elle n'est pas dans le protocole générique `me:release` et bloque le premier cut si on la découvre en route. -### Toute PR vers `main` doit fermer une issue +### Toute PR doit fermer une issue — vers `main` comme vers `dev` -Le check `Linked issue` de `validate-pr.yml` exige un `Closes #N` dans le corps -de la PR. **Chaque cut de version a donc besoin de sa propre issue de -release** — sans elle, la PR `dev → main` reste rouge. +Le check `Linked issue` de `validate-pr.yml` tourne sur les PR vers `main` +**et** vers `dev`, sans condition sur la base, et exige `Closes #N` (ou +`Fixes` / `Resolves`) dans le corps — `Refs #N` ne passe pas. **Chaque cut de +version a donc besoin de sa propre issue de release**, citée par les deux PR du +cut : celle qui prépare les notes vers `dev`, puis `dev → main`. Vers `dev`, +qui n'est pas la branche par défaut, `Closes` ne ferme rien au merge — c'est la +PR vers `main` qui fermera l'issue. + +Corps oublié : l'éditer relance le check (type `edited`). Un `gh run rerun` de +l'ancien run, lui, échoue encore — il rejoue la charge utile de l'événement +d'origine, donc l'ancien corps. Le check `Branch convention`, lui, autorise déjà `dev → main` et `hotfix/* → main`, il ne bloque pas. @@ -100,15 +108,24 @@ repart avec un `[Unreleased]` vide et gagne sa ligne sous `## Past releases`. ### 4. `dev` → `main` -Ouvrir une PR (obligatoire, cf. contrainte 2), corps contenant `Closes #`, titre au format conventionnel : +Ouvrir une PR (obligatoire, cf. la contrainte ci-dessus), corps contenant +`Closes #`, titre au format conventionnel : ``` 🔖 chore(release): v1.1.0 ``` Attendre les 4 checks verts, puis merger en **merge commit** — jamais squash, -il écraserait les commits atomiques. `required_linear_history` a été désactivé +il écraserait les commits atomiques. Les checks ne suffisent pas : `main` exige +aussi **une approbation**, et la PR reste `BLOCKED` sans elle. Faute de second +reviewer, le merge passe en admin (`enforce_admins` est à `false`) : + +```bash +gh pr merge --merge --admin +``` + +Toutes les promotions l'ont fait (#29, #32, #41) — c'est un contournement +assumé de la règle d'approbation, pas des checks. `required_linear_history` a été désactivé sur `main` pour ça, comme sur `gwm-cli` et `kbrdn-docs`. ### 5. Tag, depuis `main`, APRÈS le merge @@ -137,6 +154,11 @@ GitHub Actions ne garde qu'un job en attente par groupe de concurrence, donc trois déploiements qui se chevauchent sur un même environnement peuvent en évincer un. Relancer le tag est sans effet de bord. +Même rattrapage si `Deploy prod` **échoue** (vu sur la v1.0.1 : `ssh-keyscan` +sans réponse, #42) : `gh run rerun --failed` rejoue le déploiement puis la +release GitHub, sans reconstruire l'image. Établir la cause avant de relancer — +un job qui échoue sur le code échouera pareil. + Le push du tag suffit — `release.yml` prend la suite : image `:v1.1.0`, deploy prod, vérification `/api/health`, puis release GitHub avec `--notes-file changelogs/1.1.0.md` et pour titre `v1.1.0` seul. @@ -154,6 +176,19 @@ Le workflow le fait déjà et échoue avant de publier la release si le compte n est pas — cette commande sert à contrôler après coup, ou à répondre à « quelle version est live ». +⚠️ **Elle ne prouve pas que la release a tourné.** Le merge sur `main` a déjà +déployé la prod par `deploy.yml`, et pour une stable l'image du filet porte la +même version que celle du tag (`APP_VERSION` vide hors release → repli sur +`package.json`, cf. `nuxt.config.ts`) ; le `sha` aussi est le même. Sur la +v1.0.1, `/api/health` renvoyait `1.0.1` en prod alors que `Deploy prod` avait +échoué. La release se vérifie sur le run du tag et sur la release elle-même : + +```bash +gh run list --workflow release.yml --limit 1 # le run du tag : vert +gh release view vX.Y.Z --json author,isDraft -q '.author.login + " " + (.isDraft|tostring)' +# github-actions[bot] false +``` + ## Revenir en arrière Deux mécanismes, à ne pas confondre : @@ -172,7 +207,10 @@ Le second est le seul qui traverse plusieurs versions et le seul qui survive à une perte des conteneurs. Relancer `release.yml` en `workflow_dispatch` sur un ancien tag fonctionne -aussi, mais reconstruit l'image et tente de recréer une release qui existe déjà. +aussi, sans effet de bord : l'image existe déjà dans le registre, donc le build +est sauté (`Image already published?`) et l'artefact d'origine est redéployé ; +la release existe déjà, donc ses notes sont mises à jour (`gh release edit`) au +lieu d'échouer sur un `create`. ## Notes From fe0bf8541440bcd2dbec496b70ea4b58cf0a8d32 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Sat, 19 Sep 2026 06:37:57 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20docs(release):=20--admin=20c?= =?UTF-8?q?ontourne=20aussi=20les=20checks,=20et=20le=20rollback=20rejoue?= =?UTF-8?q?=20les=20notes=20du=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le bloc sur l'approbation coupait la phrase sur required_linear_history, dont le « pour ça » renvoyait alors au merge admin ; il passe après, la phrase d'origine redevient intacte. `--admin` ignore aussi les checks requis : seule l'attente des checks verts protège. Un tag rejoué remet les notes de l'arbre du tag, pas une correction faite depuis. « Toutes les promotions » devient la liste vérifiée (#29, #32, #41). closes #43 Claude-Session: https://claude.ai/code/session_01P5DUscqftnE27pbmuUqL5J --- docs/RELEASE.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 5fbf2ea..e681aa1 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -116,17 +116,21 @@ Ouvrir une PR (obligatoire, cf. la contrainte ci-dessus), corps contenant ``` Attendre les 4 checks verts, puis merger en **merge commit** — jamais squash, -il écraserait les commits atomiques. Les checks ne suffisent pas : `main` exige -aussi **une approbation**, et la PR reste `BLOCKED` sans elle. Faute de second -reviewer, le merge passe en admin (`enforce_admins` est à `false`) : +il écraserait les commits atomiques. `required_linear_history` a été désactivé +sur `main` pour ça, comme sur `gwm-cli` et `kbrdn-docs`. + +Les checks ne suffisent pas : `main` exige aussi **une approbation**, et la PR +reste `BLOCKED` sans elle. Faute de second reviewer, le merge passe en admin +(`enforce_admins` est à `false`), comme pour #29, #32 et #41 : ```bash gh pr merge --merge --admin ``` -Toutes les promotions l'ont fait (#29, #32, #41) — c'est un contournement -assumé de la règle d'approbation, pas des checks. `required_linear_history` a été désactivé -sur `main` pour ça, comme sur `gwm-cli` et `kbrdn-docs`. +⚠️ `--admin` contourne **toutes** les règles de protection, checks requis +compris : il mergerait une PR rouge ou en attente, et `deploy.yml` enverrait ce +code en prod. Seule l'attente des 4 checks verts, juste au-dessus, protège — +jamais de `--admin` avant. ### 5. Tag, depuis `main`, APRÈS le merge @@ -207,10 +211,12 @@ Le second est le seul qui traverse plusieurs versions et le seul qui survive à une perte des conteneurs. Relancer `release.yml` en `workflow_dispatch` sur un ancien tag fonctionne -aussi, sans effet de bord : l'image existe déjà dans le registre, donc le build -est sauté (`Image already published?`) et l'artefact d'origine est redéployé ; -la release existe déjà, donc ses notes sont mises à jour (`gh release edit`) au -lieu d'échouer sur un `create`. +aussi : l'image existe déjà dans le registre, donc le build est sauté +(`Image already published?`) et l'artefact d'origine est redéployé ; la release +existe déjà, donc `gh release edit` remplace ses notes au lieu d'échouer sur un +`create`. ⚠️ Ces notes viennent de l'arbre **du tag** : si +`changelogs/X.Y.Z.md` a été corrigé depuis (cf. Notes), le rollback remet la +version d'origine — refaire le `gh release edit` après. ## Notes From 2de85f2753443cdd7d231b59a994b3c69dfe9245 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Sat, 19 Sep 2026 06:47:04 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9D=20docs(release):=20l=C3=A8ve?= =?UTF-8?q?=20la=20contradiction=20sur=20le=20tag=20rejou=C3=A9=20et=20cib?= =?UTF-8?q?le=20le=20run=20du=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'étape 5 disait « sans effet de bord » là où « Revenir en arrière » dit que les notes sont réécrites depuis le tag : l'étape 5 renvoie désormais à ce paragraphe. `gh run list --branch vX.Y.Z` isole le run du tag (un dispatch s'affiche sous main). L'ordre filet / tag dépend du verrou, et un cut avec candidat a plus de deux PR : les deux formulations sont corrigées. L'avertissement --admin tient en une phrase. closes #43 Claude-Session: https://claude.ai/code/session_01P5DUscqftnE27pbmuUqL5J --- docs/RELEASE.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index e681aa1..ccf5ceb 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -44,8 +44,8 @@ cut si on la découvre en route. Le check `Linked issue` de `validate-pr.yml` tourne sur les PR vers `main` **et** vers `dev`, sans condition sur la base, et exige `Closes #N` (ou `Fixes` / `Resolves`) dans le corps — `Refs #N` ne passe pas. **Chaque cut de -version a donc besoin de sa propre issue de release**, citée par les deux PR du -cut : celle qui prépare les notes vers `dev`, puis `dev → main`. Vers `dev`, +version a donc besoin de sa propre issue de release**, citée par chaque PR du +cut, vers `dev` comme `dev → main`. Vers `dev`, qui n'est pas la branche par défaut, `Closes` ne ferme rien au merge — c'est la PR vers `main` qui fermera l'issue. @@ -127,10 +127,8 @@ reste `BLOCKED` sans elle. Faute de second reviewer, le merge passe en admin gh pr merge --merge --admin ``` -⚠️ `--admin` contourne **toutes** les règles de protection, checks requis -compris : il mergerait une PR rouge ou en attente, et `deploy.yml` enverrait ce -code en prod. Seule l'attente des 4 checks verts, juste au-dessus, protège — -jamais de `--admin` avant. +⚠️ `--admin` contourne aussi les checks requis : il mergerait une PR rouge, +que `deploy.yml` enverrait en prod. Il ne vient qu'après les 4 checks verts. ### 5. Tag, depuis `main`, APRÈS le merge @@ -156,7 +154,8 @@ le tag de registre est mutable, mais l'artefact d'origine est conservé et redéployé tel quel. C'est aussi le rattrapage si un run de release est annulé : GitHub Actions ne garde qu'un job en attente par groupe de concurrence, donc trois déploiements qui se chevauchent sur un même environnement peuvent en -évincer un. Relancer le tag est sans effet de bord. +évincer un. Relancer le tag ne reconstruit rien ; seules les notes de la +release sont réécrites depuis l'arbre du tag (cf. « Revenir en arrière »). Même rattrapage si `Deploy prod` **échoue** (vu sur la v1.0.1 : `ssh-keyscan` sans réponse, #42) : `gh run rerun --failed` rejoue le déploiement puis la @@ -180,15 +179,16 @@ Le workflow le fait déjà et échoue avant de publier la release si le compte n est pas — cette commande sert à contrôler après coup, ou à répondre à « quelle version est live ». -⚠️ **Elle ne prouve pas que la release a tourné.** Le merge sur `main` a déjà -déployé la prod par `deploy.yml`, et pour une stable l'image du filet porte la +⚠️ **Elle ne prouve pas que la release a tourné.** Le merge sur `main` déploie +aussi la prod par `deploy.yml` — avant ou après le job du tag, selon qui prend +le verrou `vps-deploy-prod` —, et pour une stable l'image du filet porte la même version que celle du tag (`APP_VERSION` vide hors release → repli sur `package.json`, cf. `nuxt.config.ts`) ; le `sha` aussi est le même. Sur la v1.0.1, `/api/health` renvoyait `1.0.1` en prod alors que `Deploy prod` avait échoué. La release se vérifie sur le run du tag et sur la release elle-même : ```bash -gh run list --workflow release.yml --limit 1 # le run du tag : vert +gh run list --workflow release.yml --branch vX.Y.Z # le run du tag : vert gh release view vX.Y.Z --json author,isDraft -q '.author.login + " " + (.isDraft|tostring)' # github-actions[bot] false ``` From 7f20ad1b76a0b62af1ff8aa35aac369803d3af86 Mon Sep 17 00:00:00 2001 From: Kylian Bardini Date: Sat, 19 Sep 2026 07:26:46 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20docs(release):=20la=20preuve?= =?UTF-8?q?=20d'une=20release,=20c'est=20la=20release,=20pas=20le=20run=20?= =?UTF-8?q?du=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gh run list --branch vX.Y.Z` ne voit pas un rattrapage en workflow_dispatch (rangé sous main) : sur la v1.0.0 il ne montre que le run en échec alors que la release est publiée. Le job release dépend de deploy, qui fait le contrôle /api/health : `gh release view` suffit. Le titre de la contrainte dit ce que le check exige (un mot-clé de fermeture), pas une fermeture. closes #43 Claude-Session: https://claude.ai/code/session_01P5DUscqftnE27pbmuUqL5J --- docs/RELEASE.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index ccf5ceb..6d0ee55 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -39,7 +39,7 @@ les deux ne se réveillent jamais ensemble. Elle n'est pas dans le protocole générique `me:release` et bloque le premier cut si on la découvre en route. -### Toute PR doit fermer une issue — vers `main` comme vers `dev` +### Toute PR doit porter `Closes #N` — vers `main` comme vers `dev` Le check `Linked issue` de `validate-pr.yml` tourne sur les PR vers `main` **et** vers `dev`, sans condition sur la base, et exige `Closes #N` (ou @@ -185,10 +185,13 @@ le verrou `vps-deploy-prod` —, et pour une stable l'image du filet porte la même version que celle du tag (`APP_VERSION` vide hors release → repli sur `package.json`, cf. `nuxt.config.ts`) ; le `sha` aussi est le même. Sur la v1.0.1, `/api/health` renvoyait `1.0.1` en prod alors que `Deploy prod` avait -échoué. La release se vérifie sur le run du tag et sur la release elle-même : +échoué. La preuve, c'est la release elle-même : le job `release` dépend de +`deploy` (`needs: [resolve, deploy]`), qui fait le contrôle `/api/health` — une +release publiée par le bot n'existe donc que si le déploiement a réussi, qu'il +vienne du push du tag, d'un `rerun --failed` ou d'un `workflow_dispatch` (rangé +sous `main` dans `gh run list`, d'où l'inutilité d'y chercher le run du tag) : ```bash -gh run list --workflow release.yml --branch vX.Y.Z # le run du tag : vert gh release view vX.Y.Z --json author,isDraft -q '.author.login + " " + (.isDraft|tostring)' # github-actions[bot] false ```