Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/rules/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ GitHub auto-closes issues on `Refs #N`, `Fixes #N`, `Closes #N`. Use safe syntax
| **Branche manipulée par plusieurs agents de front** | **INTERDIT** | un `[CLAIMED]` d'une autre lane sur l'issue vaut « plusieurs agents » → [lane-claim-protocol.md](lane-claim-protocol.md) |

- **L'alternative merge d'abord, quand elle existe** : `git merge origin/main`, `gh pr update-branch`, cherry-pick, revert, nouveaux commits. Le force-push est le dernier recours, jamais le réflexe de rebase par défaut.
- **`gh pr update-branch` ne remet PAS le plancher DWELL à zéro quand la fusion est sans conflit** (#16149, durci par la CR du 2026-09-16 ; corrige ce que #15859 avait laissé croire) : le plancher de merge (120 min, `scripts/ci/merge_dwell.py`) se mesure par `last_authoritative_committed_at`, qui remonte la chaîne first-parent **au-delà** des fusions de rafraîchissement de base **prouvées content-free** — trois conditions conjointes : deux parents, le second **ancêtre de la base**, et l'arbre du merge **identique à l'auto-merge** des parents (`git merge-tree --write-tree`). C'est exactement la forme d'un `update-branch` sans conflit. **Rafraîchir une branche propre pour récupérer un fix de `main` est donc gratuit.** Fail-closed partout ailleurs : une résolution manuelle de conflit (arbre ≠ auto-merge), un rebase, ou une preuve d'équivalence indisponible **re-arment** le plancher — c'est du contenu d'auteur, et il se mesure. Un commit de code ordinaire le re-arme évidemment aussi. Ce rouge n'est **pas un défaut de la PR** : le gate le nomme `DWELL -- ... leve au premier balayage suivant <ts>` et le balayage (`pr-gate-stale-sweep.yml`) lève seul — **sa cadence réelle n'est pas horaire** malgré son cron `7 * * * *` : le workflow mesure lui-même **178-341 min** entre deux services (5 tirs en 24 h, #15197), donc l'échéance annoncée est une borne, pas une promesse. Sur une PR dont le **seul** rouge est `DWELL`, `update-branch` n'est pas nuisible, il est **inutile** (rafale CI pour rien sur un pool de runners partagé) ; le geste **gratuit** est de rejouer la jambe (`gh run rerun <run_id> --job <job_id>`) : aucun commit, donc aucun ré-armement.
- **`gh pr update-branch` ne remet PAS le plancher DWELL à zéro quand la fusion est sans conflit** (#16149) : `last_authoritative_committed_at` saute les fusions de rafraîchissement **prouvées content-free**, et fail-close sur tout le reste (résolution manuelle de conflit, rebase, preuve indisponible = **ré-armé**). **Rafraîchir une branche propre pour récupérer un fix de `main` est donc gratuit.** Ce rouge n'est pas un défaut de la PR — le gate le nomme et le balayage le lève seul ; sur une PR dont le **seul** rouge est `DWELL`, le geste **gratuit** est de rejouer la jambe (`gh run rerun <run_id> --job <job_id>`) : aucun commit, donc aucun ré-armement. Prédicat conjonctif, cadence réelle du balayage (178-341 min, #15197) et récit de la correction (#16962) : [git-workflow-detail.md §4](../../docs/reference/git-workflow-detail.md#4-dwell--pourquoi-update-branch-ne-re-arme-plus-le-plancher-16962).
- **Ce que chaque geste fait au plancher** :
- `gh pr update-branch` **sans conflit** : **inchangé** — fusion content-free, sautée.
- `gh pr update-branch` **avec résolution de conflit** : **ré-armé** — l'arbre diffère de l'auto-merge.
- rebase : **ré-armé** — fail-closed.
- commit de contenu ordinaire : **ré-armé** — la tête change de date.
- **`update-branch` tue AUSSI le dossier de prévalidation, et c'est la moitié qu'on oublie** : il change la tête, donc le contrat exact-head (`[ADJOINT PREFLIGHT]`, `surfaces-sha256`, comptes `diff-files`/`additions`) est **périmé à la seconde**, et `check_adjoint_prevalidation.py` rend `head is stale` / `discussion surfaces changed`. La boucle se referme sur elle-même : `main` rouge → la lane doit `update-branch` (bon geste) → **le dossier périt** → l'adjoint ne peut plus attester `checks: latest-wins-green` sans mentir, donc il retient le dossier (à juste titre) → `exit 1` → pas de merge → la PR vieillit → il faut re-`update-branch`. **Correction du 2026-09-20 (#16962)** : cette boucle avait d'abord été décrite avec un second effet — « et le DWELL se ré-arme ». C'est **faux** pour un `update-branch` sans conflit (`last_authoritative_committed_at` saute les fusions prouvées content-free), et l'erreur rendait la boucle plus serrée qu'elle ne l'est. Le seul effet réel est la **péremption du dossier**, et il suffit à produire la boucle. Mesure du 2026-09-19 : **17 candidates sur 17** refusées par le gate pour ce seul motif, aucune pour un défaut de PR.
- **`update-branch` tue AUSSI le dossier de prévalidation, et c'est la moitié qu'on oublie** : il change la tête, donc le contrat exact-head (`[ADJOINT PREFLIGHT]`, empreinte `surfaces-sha256`, comptes `diff-files`/`additions`) est **périmé à la seconde**, et `check_adjoint_prevalidation.py` rend `head is stale` / `discussion surfaces changed`. C'est cette péremption, et non le DWELL, qui referme la boucle — mesure et mécanisme vérifié à la source : [prevalidation-dossier-order-detail.md §1-§3](../../docs/reference/prevalidation-dossier-order-detail.md).

**L'ordre qui sort de la boucle — le dossier vient APRÈS la stabilisation de la branche, jamais avant :**

1. la lane `update-branch` si elle doit récupérer `main` ;
2. on laisse les checks se ré-agréger à la nouvelle tête, puis **on rejoue** le job si besoin — personne ne re-pousse. Le plancher DWELL, lui, n'a **pas** été ré-armé par un `update-branch` sans conflit : il continue de se mesurer depuis le dernier commit qui modifie le côté PR — la date de committer que remonte `last_authoritative_committed_at`, pas celle de la fusion de rafraîchissement. Il ne se ré-arme que si le rafraîchissement a exigé une **résolution manuelle de conflit** (arbre ≠ auto-merge des parents), cas où l'attente de 120 min redevient réelle ;
2. on laisse les checks se ré-agréger à la nouvelle tête, puis **on rejoue** le job si besoin — personne ne re-pousse. Le plancher DWELL n'a **pas** été ré-armé par un `update-branch` sans conflit ; il ne l'est que par une **résolution manuelle de conflit**, cas où l'attente de 120 min redevient réelle ;
3. **alors** l'adjoint écrit le dossier, à la tête exacte ;
4. le coordinateur merge **immédiatement**, et **la branche est gelée entre 3 et 4**.

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/prevalidation-dossier-order-detail.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Distinction à tenir : le **fait** « 17/17, aucun défaut de PR » est un témo

`scripts/check_adjoint_prevalidation.py` épingle dans le dossier des **surfaces mesurées à l'instant T**, puis refuse si elles ont bougé.

Surfaces comparées — `diff-files`, `diff-additions`, `diff-deletions` :
Surfaces comparées — `diff-files`, `diff-additions`, `diff-deletions` — plus l'empreinte `surfaces-sha256` des corps de discussion, qui atteste que les surfaces lues par B.0 n'ont pas bougé :

```python
# validate_dossier() : surfaces comptees relevees a l'instant T
Expand Down
Loading