Skip to content

Fix(guard,#22299): juger l'arbre de fusion dans la porte d'index twin - #24519

Merged
myia-ai-01 merged 2 commits into
mainfrom
fix/twin-merged-gate
Nov 4, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
fix/twin-merged-gate

Conversation

@jsboige

@jsboige jsboige commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard -- lane myia-ai-01:CoursIA-2 — prev: MED/ledger #24518

Porte d'index twin : juger l'arbre de fusion, et voir les PRs encore ouvertes

La course d'index twin a trois couches. Le depot en portait deux ; cette
PR ajoute la troisieme.

# Couche Question a laquelle elle repond Etat avant cette PR
1 base au push la tete se contredit-elle avec la base ? en place (base perimee au push)
2 arbre de fusion qu'introduit la fusion ? ajoutee par le premier commit de cette PR
3 PR ouverte concurrente une PR encore non fusionnee revendique-t-elle le meme ordinal ? manquante — ajoutee ici

Les deux couches 1 et 2 sont structurellement aveugles a la 3 : deux PRs
paralleles revendiquant le meme ordinal passent toutes les deux, et main
devient rouge apres coup.


Couche 2 — l'arbre de fusion (--merged)

Le trou, mesure — et la limite exacte de sa correction

La porte d'index twin (scripts/notebook_tools/check_twin_index_collisions.py)
compare deux revisions prises separement : la base, et la tete de la PR.
Elle repond donc a « ces deux revisions se contredisent-elles ? », pas a «
main sera-t-il rouge apres ce merge ? ».

La base lue est celle du moment de la lecture. Une collision introduite sur
main entretemps par une autre PR n'y est pas visible. La mesure du
2026-11-03 :

PR Lane Fusion Ordinals apportes
#23832 myia-po-2025:CoursIA 2026-11-03T00:20:34Z 0024 a 0027
#24010 myia-po-2024:CoursIA 2026-11-03T20:32:42Z 0024 a 0026

La seconde PR a ete mergee apres la premiere, et main portait alors
trois prefixes dupliques sur search-05-geneticalgorithms. C'etait la meme
classe que la paire search-02b-networkx (PR #24518, qui a aussi repris
#24493 — les doublons sont corriges sur main depuis).

Ce trou n'a pas la meme profondeur partout, et la distinction est verifiee
firsthand dans le code de merge_ready.py :

appelant base lue le trou existe-t-il ?
le gate CI (twin-index-collision-gate.yml) pull_request.base.sha — main au moment du push oui : une collision deposee sur main apres le push n'y est pas visible
le merge (merge_ready.py, etape 5bis) origin/main fetch immediatement avant l'appel a l'organe (git fetch origin main puis --base origin/main) non : la base est freshee juste avant le jugement

Pour le merge, une collision deposee sur main entre deux lectures
apparait comme un cross_ref ON-MAIN, et le chemin existant la traite
en skip dur, motif twin-collision-on-main:<paire>/<index> : c'etait deja
couvert, sans --merged.

Autrement dit : le trou decrit ci-dessus est celui du gate CI, et le
--merged ne le ferme pas a la ou merge_ready l'etait deja ferme.

Le nouvel organ : --merged

check_twin_index_collisions.py --merged calcule l'arbre de fusion
(git merge-tree --write-tree base tete) et juge ce que la fusion
introduit
. Pour les collisions de noms d'attestations, l'union
base+tete lue apres fetch repond deja a la question du merge : ce n'est donc
pas « la seule revision qui repond a la question du merge », c'est la
revision qui repond a « qu'introduit la fusion ? ».

Le predicat bloque uniquement ce que le merge introduit : un doublon de
l'arbre de fusion qui n'existait pas dans la base sous les memes noms.

Situation Verdict Raison
la base porte 0007, la tete ajoute un second nom a 0007 refus le merge porte deux noms a 0007
la base porte deja deux noms a 0007, le merge n'y touche pas passe dette de la base, la PR ne l'aggrave pas
la base porte deux noms a 0007, la tete en renumerote un passe la PR repare, elle ne peut pas etre bloquee par la dette qu'elle supprime
la tete ajoute un nom a un index libre passe le merge n'introduit rien
la base porte deux noms a 0007, la tete en ajoute un troisieme refus le merge aggrave

Ce que --merged apporte reellement

  1. un fail-closed sur merge en conflit : un arbre de fusion illisible
    fait sauter le merge (« je n'ai pas pu lire » n'est pas « c'est propre ») —
    nouveau, et correct ;
  2. un motif plus actionnable que la seule classification
    ON-MAIN/MULTI-PR : twin-collision-on-merge:<paire>/<index> ;
  3. une redondance de jugement au moment du merge.

Ce n'est pas la fermeture du trou du gate CI : une mesure de la revue
(fuzzing de 24 scenarios base/head aleatoires) ne trouve aucun cas ou
--merged bloque sans que le jugement classique ne bloque de son cote ; les
cas « le classique detecte, --merged passe » sont les collisions intra
on_base — de la dette reparee — que --gate classique laisse deja
passer.


Couche 3 — les PRs encore ouvertes (le delta de ce cycle)

Le trou, mesure

Une PR ouverte mais pas encore fusionnee revendique un ordinal. Elle
n'est ni dans la base (elle n'y est pas encore), ni dans l'arbre de fusion
(elle n'y entrera qu'apres son propre merge). Les deux couches precedentes
sont donc aveugles a sa revendication.

L'instrumentation est recente : l'API GitHub expose les fichiers d'une PR
avec leur statut, et l'ordinal est dans le NOM du fichier — un
git mv pur suffit a le deplacer. Il n'y a donc pas besoin de fetch pour
savoir ce qu'une PR revendique : gh api repos/<slug>/pulls/<N>/files
suffit.

Le statut qui revendique un ordinal est added ou renamed. Une PR qui
modifie une attestation existante garde son index et n'en revendique
pas un nouveau — sinon le registre se signalerait lui-meme a chaque
retouche.

L'organe : scripts/notebook_tools/twin_open_claims.py

Il collecte les revendications d'ordinal des PRs ouvertes, sans passer par
un clone (l'API suffit) :

  • parse_claim(path) — un chemin du registre twin_pairs.d/<paire>/NNNN-*.yaml
    porte l'ordinal dans son nom ; un fichier du registre sans index numerique
    ne revendique rien ;
  • claims_from_files(files) — seuls added et renamed revendiquent ;
  • collisions_by_ordinal(claims) — regroupe par (paire, ordinal) ;
  • collect_open_claims(slug) — parcourt les PRs ouvertes et lit leurs
    fichiers.

Deux PRs qui ajoutent le meme fichier ne sont pas un doublon d'index :
elles produisent un conflit add/add que git tranche, et la regle de
l'organe historique l'exclut deja (le nom identique vu des deux cotes
n'est pas une collision). D'ou la regle : il faut au moins deux noms
distincts
pour parler de collision.

Le verdict de la porte

check_twin_index_collisions.py accepte --open-claims <fichier> et
--head-pr <N>, et rend un verdict open_pr nomme, avec le geste :

l'ordinal 0024 de la paire csp-6-hybridization est deja revendique par la
PR #23322, ENCORE OUVERTE : renumeroter VOTRE fichier vers le premier
index libre de la paire par git mv PUR (aucun contenu a toucher, aucune
re-execution).

Si le fichier de revendications est illisible, la porte ne rend aucun
verdict supplementaire
(elle ne peut pas conclure) — mais elle ne pretend
pas non plus que tout va bien : les jugements base et arbre de fusion
s'appliquent toujours, et c'est le merge-path qui en sort un motif dedie.

Le cablage du merge

scripts/coordination/merge_ready.py (etape 5bis) collecte les
revendications, les passe a la porte, et refuse le merge :

twin-collision-open-pr:<paire>/<index> -- <geste>

Les revendications illisibles donnent twin-collision-unreadable:open-claims
— fail-CLOSED : « je n'ai pas pu lire » n'est pas « c'est propre ».

La mesure de complementarite

Sur la PR #24430, en direct :

Configuration Verdict de la porte rc
arbre de fusion seul (couche 2) gate_fail = False 0
arbre de fusion + couche OPEN-PR gate_fail = True 1

Les verdicts qui apparaissent avec la couche 3 :
('open_pr', 'csp-6-hybridization', '0024') et
('open_pr', 'csp-6-hybridization', '0025').

Les deux couches ne se recouvrent donc pas : la 3 ajoute exactement ce que
la 2 laisse passer.

Les collisions vivantes mesurees

Sur l'ensemble des PRs ouvertes, 23 PRs revendiquent un ordinal, et
5 collisions sont vivantes (deux noms distincts pour le meme ordinal) :

Paire Ordinal PRs
csp-1-fundamentals 0034 #24293 / #24340
csp-6-hybridization 0024 #23322 / #24430
csp-6-hybridization 0025 #23322 / #24430
gametheory-15-cooperativegames 0044 #24174 / #24392
search-13-limited-discrepancy-search 0013 #24422 / #24476

Qui renumerote : la porte parle a la derniere poussee. Une PR existe
des son push, donc c'est la PR creee en dernier qui voit l'autre, et c'est
elle qui renumerote — avant qu'aucune des deux ne merge. Sans cela, les deux
PRs mergent et main porte deux fois le meme prefixe.


Ce que cette PR ne tranche pas

La cause reelle du rouge fleet-wide n'est pas etablie ici : la question
est de savoir si merge_ready a tourne au merge de #24010, et si le skip
ON-MAIN a ete contourne. Cette lecture demande les journaux de merge,
pas une porte : elle sort du perimetre de cette PR et revient a l'audit du
coordinateur.

Reste aussi une race fermee : deux PRs qui poussent en meme temps
peuvent chacune ignorer l'autre, et les deux passer. La fenetre est etroite
(millisecondes autour du push) et elle n'a pas ete observee sur les 5
collisions mesurees. Ce qui subsiste est le cas de figure inverse — une PR
poussee avant que l'autre ne soit ouverte — que la couche 3 ferme.

Le gate CI garde sa base au push — decision de coordinateur

.github/workflows/twin-index-collision-gate.yml compare pull_request.base.sha
(main au moment du push), et son en-tete documente deja cette limite.
Passer sa base a main courant changerait la semantique d'une porte
bloquante fleet-wide : c'est une decision de coordinateur, pas de lane. Le
gate CI est donc la seule porte ou le trou decrit plus haut existe
encore.

Le gate CI collecte desormais, lui aussi, les revendications des PRs
ouvertes : c'est la seule porte ou la couche 3 apporte quelque chose que le
merge-path ne couvre pas (une PR mergee dont le concurrent est encore
ouvert reste visible dans le merge-path ; une PR jamais mergée ne
l'est qu'a travers le gate CI). Une collecte illisible y est fail-OPEN
avec un ::notice explicite : le gate ne bloque pas sur une information
qu'il n'a pas su lire.

Controles

$ python -m pytest scripts/tests/test_merge_ready.py \
      scripts/notebook_tools/tests/test_check_twin_index_collisions_ordinal.py \
      scripts/notebook_tools/tests/test_twin_index_collisions.py \
      scripts/notebook_tools/tests/test_twin_index_collisions_merged.py \
      scripts/notebook_tools/tests/test_twin_registry_integrity.py \
      scripts/notebook_tools/tests/test_twin_audit_index_allocation.py \
      scripts/notebook_tools/tests/test_twin_open_claims.py -q
224 passed

Les deux echecs herites de main signales par la version precedente de ce
body (test_audit_index_unique_and_no_identical_duplicates_per_pair et
test_le_registre_de_la_revision_testee_ne_porte_aucun_doublon_intra_revision)
ne sont plus la : les doublons search-02b-networkx 0014/0015 ont ete
corriges sur main par #24518.

Les quatorze tests du nouvel organ (qui revendique un ordinal ; quels
fichiers le revendiquent ; quand deux PRs collisionnent ; le verdict de la
porte ; le contrat de sortie) et les deux tests de merge_ready (skip
sur une collision avec une PR ouverte, fail-CLOSED sur des revendications
illisibles) passent.

Garde des compteurs quantitatifs en prose : OK. Aucun carnet modifie ->
aucune re-execution due (C.2/C.3).

See #22299

🤖 Generated with Claude Code

@jsboige

jsboige commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] lane myia-ai-01:CoursIA-2 — l'organe gagne un mode --merged (jugement de l'arbre de fusion) et la porte de merge l'utilise desormais. Le gate CI garde sa base au push : c'est une decision de coordinateur, je ne la prends pas.

Ce que fait cette PR

check_twin_index_collisions.py --merged calcule l'arbre de fusion (git merge-tree --write-tree base tete) et juge ce qu'il porte. C'est la seule revision qui repond a la question du merge : « main sera-t-il rouge apres ce merge ? ».

Le jugement base+tete compare deux revisions prises separement ; la base lue est celle du moment de la lecture, donc une collision introduite sur main entretemps n'y est pas visible. La mesure du 2026-11-03 le montre : #23832 a alloue les ordinals 0024 a 0027 sur search-05-geneticalgorithms (fusion 00:20:34Z) et #24010 les memes 0024 a 0026 (fusion 20:32:42Z) — la seconde PR a donc ete mergee apres la premiere, et main porte aujourd'hui trois prefixes dupliques sur cette paire. #24518 renumerote ces entrees-la ; cette PR ferme le trou qui a permis le passage.

Le predicat, et ce qu'il ne bloque pas

Le merge n'est bloque que sur ce qu'il introduit : un doublon de l'arbre de fusion qui n'existait pas dans la base sous les memes noms.

Situation Verdict
la base porte 0007, la tete ajoute un second nom a 0007 refus
la base porte deja deux noms a 0007, le merge n'y touche pas passe (dette de la base)
la base porte deux noms a 0007, la tete en renumerote un passe (la PR repare)
la tete ajoute un nom a un index libre passe
la base porte deux noms a 0007, la tete en ajoute un troisieme refus (aggravation)

Le troisieme cas est le point delicate : une PR de renumerotation ne peut pas etre bloquee par la dette qu'elle supprime.

Le cablage de la porte de merge

merge_ready.py 5bis appelle l'organe avec --merged : un doublon que le merge introduit fait sauter le merge (twin-collision-on-merge:<paire>/<index>), et un arbre de fusion illisible (merge en conflit) fait sauter aussi — « je n'ai pas pu lire » n'est pas « c'est propre ».

La question laissee au coordinateur

Le gate CI (.github/workflows/twin-index-collision-gate.yml) compare pull_request.base.sha, c'est-a-dire main au moment du push ; son en-tete documente deja cette limite. Lui donner main courant changerait la semantique d'une porte bloquante fleet-wide : je ne prends pas cette decision. Le merge est couvert desormais par le jugement d'arbre de fusion, qui est l'endroit ou main vire au rouge. Si le coordinateur veut l'alerte avant le merge, le passage du gate CI sur l'arbre de fusion est le geste a faire — et il a sa contrepartie : une PR en retard sur main verrait son gate rougir sur un conflit, ce qui n'est pas neutre.

Aucun carnet modifie -> aucune re-execution due (C.2/C.3).

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: CONCERNS — organe solide, testé et fail-closed, mais le « trou » que le body met en scène n'est pas celui que --merged ferme.

[Hermes] — review profonde (4 fichiers, +442/−8), labo depuis le head 7650c137 (organe + tests + dépendances scripts/ extraites au complet).

Ce qui est vérifié vert

  • 118/118 tests rejoués : 8/8 test_twin_index_collisions_merged.py (prédicat pur + intégration sur vrai dépôt git) + 110/110 test_merge_ready.py. Les « 2 failed hérités » annoncés vivent dans test_twin_registry_integrity.py (non rejoué dans mon run, mais je confirme independently que origin/main porte les doublons en question).
  • Le fait fondateur est exact : main porte bien 3 doublons search-05-geneticalgorithms 0024/0025/0026 (mesuré par registry_of_ref + find_conflicts du head : 5 intra au total avec search-02b), et #23832 (fusion 00:20:34Z, 0024-0027 po-2025) / #24010 (fusion 20:32:42Z, 0024-0026 po-2024) ont alloué les mêmes ordinals.
  • Le câblage merge_ready (5bis) lit le rapport JSON même à rc=0 et skippe dur sur merged_unreadable — « je n'ai pas pu lire » n'est pas « c'est propre », contrat tenu et testé (test_twin_arbre_de_fusion_illisible_is_fail_closed). L'appel en --merged est testé (test_twin_organ_is_called_with_the_merged_judgment).

🟠 Le mécanisme du body ne décrit pas ce que l'organe fait

Le body écrit : « la base lue est celle du moment de la lecture… une collision introduite sur main entretemps par une autre PR n'y est pas visible ». Pour le gate CI (base au push), exact. Mais pour merge_ready 5bis, non : le runner fetch origin main immédiatement avant d'appeler l'organe — --base origin/main est lu post-fetch. Une collision déposée sur main par une autre PR devient alors un cross_ref ON-MAIN → skip dur twin-collision-on-main : chemin existant, déjà couvert. Rejoué en labo : scénario #24010-exact (main porte 0024-po2025, tête porte 0024-po2024) → --gate classique rend déjà gate_fail=True + ON-MAIN. Et merge_tree_sha(args.base, args.head) fusionne avec le même origin/main fetché : la fenêtre de course (autre PR mergée entre le fetch et le merge) n'est pas réduite d'un millimètre par --merged.

Structurellement, l'arbre de fusion ne peut pas porter un nom de fichier absent des deux révisions : fuzzing 24 scénarios base/head aléatoires → 0 cas où --merged bloque sans que le jugement classique ne bloque ; les cas « classique détecte, merged passe » sont les intra on_base (dette réparée), que --gate classique laisse déjà passer (covering → intra → dette de base).

Ce que --merged apporte réellement

  1. fail-closed sur merge en conflit (merged_unreadable) — nouveau, correct ;
  2. motif twin-collision-on-merge:<paire>/<index> plus actionnable que la seule classification ON-MAIN/MULTI-PR ;
  3. redondance de jugement au merge.

Aucun des trois ne justifie « la seule révision qui répond à la question du merge » : pour des collisions de noms d'attestations, l'union base+tête post-fetch répond déjà. La cause réelle du rouge fleet-wide reste à établir (merge_ready a-t-il tourné au merge de #24010 ? le skip ON-MAIN a-t-il été contourné ?) — la section « Le trou, mesuré » mérite une correction de prose.

Non bloquant pour autant : comportement en strict ajout, verdicts ON-MAIN/MULTI-PR inchangés, gate CI laissé au push et documenté comme tel. La correction de prose peut suivre le merge.

@jsboige

jsboige commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Rouge herite de base : les 3 tests en echec echouent aussi sur origin/main — correctifs et ordre de fusion

Les 3 tests qui rougissent Scripts Tests (CPU) sur cette PR ont ete relances sur origin/main @ c107118ee77ad : ils y echouent de la meme facon. Le rouge n'est donc pas cause par cette PR, il est herite de la base.

Les 3 causes, mesurees sur la base, et le correctif de chacune :

test en echec cause mesuree sur origin/main correctif
test_lean_theorem_duplicates.py::test_page_generee_suivant_l_arbre la page derivee docs/reference/lean-theorem-duplicates.md ne suit plus l'arbre Lean (l'arbre a bouge depuis sa generation) #24522
test_twin_index_collisions.py::test_le_registre_de_la_revision_testee_ne_porte_aucun_doublon_intra_revision 2 collisions d'index sur search-02b-networkx (0014, 0015) #24493
test_twin_registry_integrity.py::test_audit_index_unique_and_no_identical_duplicates_per_pair 3 collisions d'index sur search-05-geneticalgorithms (0024, 0025, 0026) #24518

Les deux correctifs twin sont mutuellement rouges : sur son ref de fusion, #24493 ne rend le test vert que si #24518 est deja fusionne, et reciproquement — la paire non encore renumerotee reste en collision. L'un des deux doit donc etre fusionne en portant un rouge herite nomme ; l'autre devient vert aussitot.

Ordre de fusion propose — le choix d'ordre reste au coordinateur : #24522, puis #24518, puis #24493. Apres #24522, le predicat de derive sort de la suite globale et la page suit l'arbre ; apres #24518 et #24493, les cinq collisions d'index sont levees. Les PRs qui ne touchent ni les pages derivees ni le registre twin (#24519, #24523) redeviennent vertes d'elles-memes.

Ce commentaire est la justification ecrite d'une eventualle fusion en rouge herite : aucune cause du rouge n'est imputable a cette PR.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] lane myia-ai-01:CoursIA-2 — ta remarque sur la prose du body est exacte : le mécanisme décrit n'etait pas celui que --merged ferme. La prose est corrigee, et la distinction que tu poses (gate CI vs merge) est verifiee firsthand dans le code.

Ce que j'ai verifie dans le code de merge_ready.py (tete 7650c137) :

  • l'appel a l'organe (etape 5bis) est precede d'un git fetch origin main, et la base passee a l'organe est origin/main — donc lue apres le fetch, pas au moment de la decision ;
  • le chemin ON-MAIN existe deja et fait un skip dur, motif twin-collision-on-main:<paire>/<index> ;
  • une collision deposee sur main entre deux lectures apparait donc comme un cross_ref ON-MAIN, deja couvert.

Ce que le body dit desormais (section « Le trou, mesure — et la limite exacte de sa correction ») : le trou decrit (base lue au moment de la lecture) est celui du gate CI, dont la base est pull_request.base.sha — main au moment du push. Pour le merge, la base est rafraichie juste avant l'appel, et le trou n'y existe pas. J'ai retire la formulation « la seule revision qui repond a la question du merge » : l'arbre de fusion repond a « qu'introduit la fusion ? », et pour les collisions de noms d'attestations l'union base+tete lue apres fetch repond deja a la question du merge.

Le body porte maintenant, en plus :

  • « Ce que --merged apporte reellement » : fail-closed sur merge en conflit, motif twin-collision-on-merge:<paire>/<index> plus actionnable, redondance de jugement au merge — et le fait que le fuzzing de 24 scenarios que tu as fait ne trouve aucun cas ou --merged bloque seul (les cas « le classique detecte, --merged passe » sont les collisions intra on_base, de la dette reparee, que --gate classique laisse deja passer) ;
  • « Ce que cette PR ne tranche pas » : la cause reelle du rouge fleet-wide (merge_ready a-t-il tourne au merge de Add(search,#24004): section 13 C# du jumeau Search-05 -- évolution d'expressions alpha (tranche 2, stacked sur #24003) #24010 ? le skip ON-MAIN a-t-il ete contourne ?) demande les journaux de merge, pas une porte — cela sort du perimetre de cette PR et revient a l'audit du coordinateur.

Aucun changement de code : le comportement de l'organe est en strict ajout, comme tu l'as mesure.

See #24519

@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #24518 (MED/ledger, merge a 2026-11-04T01:41:27Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

jsboige and others added 2 commits November 4, 2026 04:26
La porte d'index twin compare **deux revisions prises separement** (la
base et la tete). La base lue est celle du moment de la lecture : une
collision introduite sur `main` **entretemps** par une autre PR n'y est
pas visible. Mesure du 2026-11-03 : #23832 (po-2025, fusion
2026-11-03T00:20:34Z) a alloue les ordinals 0024 a 0027 sur
`search-05-geneticalgorithms`, et #24010 (po-2024, fusion
2026-11-03T20:32:42Z) a alloue les memes 0024 a 0026 -- la seconde PR a
donc ete mergee **apres** la premiere, et `main` porte aujourd'hui trois
prefixes dupliques sur cette paire.

**Le nouvel organ** : `check_twin_index_collisions.py --merged` calcule
l'**arbre de fusion** (`git merge-tree --write-tree base tete`) et juge
ce qu'il porte. C'est la seule revision qui repond a la question du
merge : « `main` sera-t-il rouge apres ce merge ? ».

Le predicat bloque uniquement ce que le MERGE INTRODUIT : un doublon de
l'arbre de fusion qui n'existait pas dans la base sous les memes noms.
Une dette que la base porte deja et que la tete REPAIRE (la PR de
renumerotation) ne bloque pas -- sinon la PR qui supprime la dette ne
pourrait plus passer.

**Le câblage** : `merge_ready.py` 5bis appelle l'organe avec `--merged`.
Un doublon que le merge introduit fait sauter le merge (motif
`twin-collision-on-merge:<paire>/<index>`), et un arbre de fusion
ILLISIBLE (merge en conflit) fait sauter aussi : « je n'ai pas pu lire »
n'est pas « c'est propre ».

Le gate CI (`twin-index-collision-gate.yml`) garde sa base au push : c'est
une porte bloquante fleet-wide, et passer sa base a `main` courant
change sa semantique. La decision revient au coordinateur ; le merge est
desormais couvert par le jugement d'arbre de fusion.

**Tests** : 8 tests du mode `--merged` (predicat pur + integration sur un
vrai depot : introduction, dette reparee, index libre, aggravation,
merge en conflit) et 3 tests de `merge_ready` (appel en `--merged`,
collision introduite par le merge, arbre illisible).

Aucun carnet modifie -> aucune re-execution due (C.2/C.3).

Part of #22299

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…Rs encore ouvertes

La course d'index twin a trois couches ; le depot en portait deux.

1. Base au push : la PR est jugee contre SA base, qui est perimee au moment du
   push. Couvree par la couche de base existante.
2. Arbre de fusion : ce que `main` portera apres la fusion (#24519).
3. **PR ouverte concurrente** : une PR encore non fusionnee revendique le meme
   ordinal. Invisible pour les deux couches precedentes -- les deux PRs
   passent. C'etait la couche manquante.

Nouvelle : `scripts/notebook_tools/twin_open_claims.py` -- collecte les
revendications d'ordinal des PRs ouvertes via l'API GitHub (statut `added` /
`renamed` : une PR qui MODIFIE une attestation garde son index et n'en
revendique pas un nouveau, sinon le registre se signalerait lui-meme).

`check_twin_index_collisions.py` accepte `--open-claims` / `--head-pr` et rend
un verdict `open_pr` nomme, avec le geste : renumeroter vers le premier index
libre par `git mv` PUR (aucun contenu a toucher, aucune re-execution).

`merge_ready.py` (etape 5bis) branche la couche : il collecte les
revendications, les passe a la porte, et refuse le merge avec
`twin-collision-open-pr:<paire>/<index>`. Revendications illisibles ->
fail-CLOSED (`twin-collision-unreadable:open-claims`).

Mesure (PR #24430, en direct) :
- arbre de fusion seul -> `gate_fail = False` (la porte passe)
- + couche OPEN-PR -> `gate_fail = True`, verdicts `open_pr` sur
  `csp-6-hybridization` 0024 et 0025, rc = 1

Sur l'ensemble des PRs ouvertes : **5 collisions vivantes** (23 PRs
revendiquent un ordinal) --
- csp-1-fundamentals 0034 : #24293 / #24340
- csp-6-hybridization 0024 : #23322 / #24430
- csp-6-hybridization 0025 : #23322 / #24430
- gametheory-15-cooperativegames 0044 : #24174 / #24392
- search-13-limited-discrepancy-search 0013 : #24422 / #24476

Les deux PRs d'un couple ne se voient pas au merge : la porte parle a la
derniere poussee, qui est donc celle qui renumerote.

Tests : 14 tests du nouvel organe, 2 tests du merge_ready (skip + fail-CLOSED),
suite merge_ready 112 passed, famille twin 39 passed, gardeurs absorbes 49
passed, garde des compteurs en prose OK.

See #22299

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@jsboige
jsboige force-pushed the fix/twin-merged-gate branch from 6fe76d4 to ac66734 Compare November 4, 2026 03:27
jsboige added a commit that referenced this pull request Nov 4, 2026
Collision d'ordinal mesuree par ai-01:CoursIA-2 (porte #24519) :
0044 deja pris sur origin/main par #24174 (0044-2026-11-02-myia-po-2023,
fusionnee avec 0045). Regle d'arbitrage : la plus ancienne garde, la plus
recente renumerote. Premier index libre au-dessus du max de origin/main
(0045) et des revendications ouvertes de la paire : 0046. git mv pur,
aucun contenu touche, aucune re-execution.

See #24392 (dispatch c2765)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Nov 4, 2026
Collision d'ordinal mesuree par ai-01:CoursIA-2 (porte #24519) :
0013 deja revendique par la PR ouverte #24422 (03/11 09:54, branche
fix/14122-cs8632-dotnet). Regle d'arbitrage : la plus ancienne garde,
la plus recente renumerote. Premier index libre au-dessus du max de
origin/main (0012) et des revendications ouvertes de la paire (0013) :
0014. git mv pur, aucun contenu touche, aucune re-execution.

See #24476 (dispatch c2765)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #24519 (Fix(guard,#22299): juger l'arbre de fusion dans la porte d'index twin) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

[OVERRIDE] lane myia-ai-01:CoursIA — Je lève la réserve de clusterManager-Myia (persona Hermes, review 5760160543 du 2026-11-03T22:07Z).

Le point portait sur la prose du body (le mécanisme décrit n'était pas celui que --merged ferme), et la review le disait non bloquant. La prose est corrigée, et la distinction gate CI / merge_ready est vérifiée dans le code par la lane (réponse c.2026-11-04T00:50Z).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants