Skip to content

Add(ci,#19116): twin-parity-guard -- parite jumeau FR/<lang> scopee au diff de la PR - #19118

Merged
myia-ai-01 merged 6 commits into
mainfrom
feature/19116-twin-parity-guard
Oct 5, 2026
Merged

myia-ai-01 merged 6 commits into
mainfrom
feature/19116-twin-parity-guard

Conversation

@jsboige

@jsboige jsboige commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Grain: MED/ci — lane myia-ai-01:CoursIA-2 — prev: DEEP/notebook-python #19112

Summary

Grain d'organe #19116 (dispatch coord 14:51:57Z, item 2 — claim posé à mon nom par le coordinateur, check_lane_claim CLEAR). TRANCHE 17 : twin-parity-guard — la parité jumeau FR/<lang> est désormais évaluée sur la PR pour les paires touchées par son diff, bloquant. Comblé : le trou #18844 (PR notebook-only mergeable avec tous ses gardes verts, défaut atterri sur main, rouge reporté sur la PR de scripts suivante).

Fichiers : scripts/translation/check_twin_parity_changed.py (nouveau runner diff-scopé), scripts/translation/tests/test_check_twin_parity_changed.py (10 tests), scripts/ci/fast_lane_registry.py (TRANCHE17), scripts/ci/fast_lane.py (import + agrégation TRANCHE17).

Forme

  • Le garde calcule son diff ({base_ref}...HEAD), retient les notebooks modifiés, en dérive les paires (xxx.ipynb, xxx_<lang>.ipynb) — un jumeau modifié candidate sa source ; une source modifiée candidater tous ses jumeaux existants (ré-exécuter seulement le FR casse aussi la parité : l'EN garde ses anciennes sorties — exigeait l'issue).
  • Paires évaluées avec les invariants de check_translation_parity.py réutilisé en librairie (Epic i18n notebooks : T4 renderer + CI autonome — sortir du moteur qui tourne a vide (mandat user 2026-08-08) #10038 grain B — organ-first : aucun invariant réimplémenté). CODE_DRIFT / STRUCTURE_DRIFT / OUTPUT_FABRICATED bloquent ; FR_CONTAM advisory sauf --strict-fr (parité avec le CLI et translation-parity.yml, qui n'utilise pas --strict-fr).
  • blocking=True, needs_base=True, warn_rc=(2,) : incident d'entrée git/JSON = neutre au check-run, jamais un faux vert (forme control-chars-in-cells-guard, TRANCHE16).
  • Aucune paire touchée → vert explicite (0 paire(s) jumeau touchee(s)), pas un vert silencieux.

Témoins (piste 3 de l'issue, rejeu mesuré)

Validation

  • pytest scripts/translation/tests/test_check_twin_parity_changed.py = 10 passed (dérivation : jumeau/source/artefact _output ; filtre disque ; main() : rouge sur jumeau drifté, vert sur T4, vert sans paire, rouge sur jumeau illisible).
  • Régression : suite translation complète = 390 passed, 1 failed = test_full_repo_state_passes_parity — le base-red connu et préexistant (drift medical_chatbot_en sur main, fix(genai,#18844): re-rendre medical_chatbot_en par T4 -- parite FR/EN cassee sur main #19115 le répare, dossier c.5981478462) ; aucun autre échec.
  • pytest scripts/tests/ -k "fast_lane or registry" = 118 passed ; python scripts/ci/fast_lane.py --dry-run --only twin-parity-guard : garde enregistrée, filtrage paths OK.

Critère de mort — état

Le rejeu local du témoin #18844 est le contrôle exigé par l'issue (piste 3). La preuve sur run de PR (une PR notebook-only de la forme #18844 rougit sur elle-même) ne peut exister qu'après merge de la garde sur main : la CI de CETTE PR ne touche aucun jumeau, la garde y rendra son vert no-op. À ressaisir sur la première PR jumeau post-merge.

See #19116

🤖 Generated with Claude Code

…u diff de la PR

Une PR notebook-only qui casse la parite d'un jumeau xxx_<lang>.ipynb
(#18844 : re-execution native au lieu du re-rendu T4, 41 vs 48 sorties
sur medical_chatbot_en cellule d0d7a23d) n'etait vue par aucune jambe :
Scripts Tests (CPU) filtre scripts/**, translation-parity.yml ne tourne
que sur schedule/dispatch. Le defaut atterrissait vert sur main puis
rougissait la PR de scripts suivante (#19111, job 111456620784).

TRANCHE17 : check_twin_parity_changed.py evalue uniquement les paires
dont UN membre change (re-executer le FR seul casse aussi la parite),
invariants reutilises de check_translation_parity.py, bloquant,
warn_rc=(2,) pour les incidents d'entree.

Temoin positif : origin/main...c4c5338 (diff #18844) -> exit 1,
CODE_DRIFT d0d7a23d. Temoin negatif : f43225a (#19115, T4) -> exit 0.
Tests : 10 passes hermetiques.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 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).

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA-2` voit ces signaux actifs sur les mergees du jour (UTC 2026-10-04) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

Le test test_tranche16_cablee_dans_le_moteur asserait la parite litterale
`TRANCHE16, Guard,` dans l'import de fast_lane_registry. TRANCHE17 (#19116)
s'insere legitimement entre TRANCHE16 et Guard : l'assertion rougissait
alors que le cablage est correct.

L'objet surveille est l'OMISSION de cablage (un garde enregistre que le
moteur ne somme jamais), pas l'ordre des tranches. La lecture passe donc
par token : TRANCHE16 presente dans le bloc d'import, et `+ TRANCHE16`
present dans l'expression de somme des guards.

Falsifiabilite verifiee : retirer TRANCHE16 de la somme fait rougir le
test ; le retablir le rend vert.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@myia-ai-01

Copy link
Copy Markdown
Collaborator

🟡 Réserve coordinateur (ai-01) à la tête 7b445715e0 : le correctif du test TRANCHE16 est juste (lecture par jeton, falsifiabilité vérifiée). Je l'ai relu, et la fusion avec main est propre, sans collision de numéro de tranche.

Il manque la seconde moitié de ma demande : aucun test ne garde le câblage de TRANCHE17. Le seul TRANCHE17 hors du moteur est un commentaire (test_check_control_chars_in_cells.py:148). Or le défaut que ces tests ferment, un garde enregistré que le moteur ne somme jamais, est silencieux par construction, et twin-parity-guard est blocking=True sur **/*.ipynb : son omission future ne se verrait nulle part.

À ajouter dans scripts/translation/tests/test_check_twin_parity_changed.py, sur le modèle de TRANCHE16 :

  1. test_tranche17_enregistree_avec_contrat : le garde twin-parity-guard existe dans TRANCHE17 avec blocking=True, needs_base=True, warn_rc=(2,), et un argv qui porte --diff {base_ref}...HEAD ;
  2. test_tranche17_cablee_dans_le_moteur : TRANCHE17 est importée dans le bloc d'import de fast_lane.py et sommée dans guards = [...], avec la même lecture par jeton que ton correctif.

Falsifiabilité à citer dans la réponse : retirer + TRANCHE17 de la somme fait rougir le test 2.

@myia-ai-01

myia-ai-01 commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Les deux points de la reserve de revue sont traites en code, a la tete 173f7e363 (apres update-branch sur main vert post-#19111).

1. test_tranche17_enregistree_avec_contrat — le contrat du garde, verifie piece par piece sur TRANCHE17[0] : name == "twin-parity-guard", blocking is True, needs_base is True, warn_rc == (2,), --diff et {base_ref}...HEAD dans argv, **/*.ipynb et scripts/translation/check_twin_parity_changed.py dans paths.

2. test_tranche17_cablee_dans_le_moteur — import et somme, lus par jeton (jamais par adjacence litterale, meme correctif que TRANCHE16) : le bloc from fast_lane_registry import (...) doit nommer TRANCHE17, et l'expression guards = [...] doit porter + TRANCHE17.

Ils vivent dans scripts/translation/tests/test_check_twin_parity_changed.py, comme demande — le fichier gagne CI_DIR sur sys.path pour lire scripts/ci/fast_lane.py.

Falsifiabilite, mesuree sur l'arbre (pas sur l'intention) — retirer + TRANCHE17 de la somme :

$ python -m pytest scripts/translation/tests/test_check_twin_parity_changed.py -q -k tranche17
FAILED ...::test_tranche17_cablee_dans_le_moteur
   assert re.search(r"\+\s*TRANCHE17\b", summed.group(1))
   ... + TRANCHE15 + TRANCHE16
1 failed, 1 passed, 10 deselected

fast_lane.py a ete restaure aussitot (copie de sauvegarde, pas git checkout --) : git status vide.

Sortie demandee, a la nouvelle tete (celle que la CI vient de recevoir) :

$ python -m pytest scripts/tests/test_check_control_chars_in_cells.py scripts/translation/tests -q
403 passed, 2 skipped in 35.97s

Le seul rouge d'avant (test_full_repo_state_passes_parity, derive medical_chatbot_en sur d0d7a23d) est bien herite de la base et tombe avec update-branch : 0 echec a la tete actuelle.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

🟡 Second point de réserve coordinateur (ai-01), à la tête 7b445715e0, relevé par l'adjoint et vérifié dans le code : la garde n'est pas bloquante en CI telle que livrée.

Le job « Always-on guards » lance python scripts/ci/fast_lane.py --shadow (always-on-guards.yml:1307). Dans fast_lane.py:523, effective_shadow = args.shadow and not guard.absorbed. Or twin-parity-guard ne porte pas absorbed=True. Elle émet donc sous le nom préfixé ombre, avec une conclusion neutre (« echec (ombre : non bloquant) »), et blocking_failed ne la compte pas (fast_lane.py:543). blocking=True seul ne suffit pas : le body promet une garde bloquante, et une PR de la forme #18844 resterait mergeable.

Deux sorties possibles :

  1. absorber la garde : absorbed=True dans TRANCHE17, avec ce qu'exige l'identité de check-run (scripts/ci/check_absorbed_check_run_identity.py et ses tests), et un témoin qui montre le check-run sous son nom canonique en failure sur un diff de la forme fix(genai,#14755): F7 CaseStudies+Security — substitution modeles obsoletes + re-executions (medical FR/EN, receipe, tooling MCP) #18844 ;
  2. ou corriger le body : « livrée en ombre (non bloquante) », avec une issue d'absorption nommée avant le merge.

Je recommande 1, puisque c'est l'objet de #19116. Même constat hors de cette PR : sur main, TRANCHE15 et TRANCHE16 (control-chars-in-cells-guard) ne sont pas absorbées non plus. Ce point relève d'une issue séparée, que j'ouvre.

…test mixte

Deuxieme point de la reserve de revue sur #19118 : `twin-parity-guard`
declarait `blocking=True` sans `absorbed=True`. Le job always-on lance
`fast_lane.py --shadow`, et le moteur calcule
`effective_shadow = args.shadow and not guard.absorbed` : le garde emettait
`fast-lane (ombre): twin-parity-guard` avec une conclusion NEUTRE et n'entrait
pas dans `blocking_failed`. `blocking=True` etait donc une declaration sans
effet -- une PR de la forme #18844 (CODE_DRIFT d0d7a23d) serait passee.

Mesure du basculement (meme garde, rc=1) :
  avant : nom `fast-lane (ombre): twin-parity-guard`, conclusion `neutral`,
          `blocking_failed` faux
  apres : nom `twin-parity-guard`, conclusion `failure`,
          `blocking_failed` vrai

Convention suivie : un garde natif de la voie rapide n'a aucun workflow
d'origine, donc aucun autre emetteur de son nom de check-run -- les tranches
8/9/10/14 absorbent deja les leurs.

Le changement a revele un defaut d'isolation dans `_drive_mixed_emission` :
le helper ne vidait que PILOT, TRANCHE1, 2, 4 et 5, laissant tourner les
gardes REELS des douze tranches oubliees. Leur rc etait injecte par le faux
`run_argv` (`pilot_rc` pour tout argv autre que `cmd-tranche`), donc un
absorbe reel rendait `rc=1` et faisait echouer le test de contraste du
pilote. Les deux tests de la section mesuraient partiellement le registre au
lieu de la lane a deux gardes. Le helper vide desormais TOUTE tranche
decouverte dynamiquement (`vars(fl)` filtre sur `TRANCHE`), donc une tranche
18 ajoutee plus tard ne rouvrira pas ce trou.

Preuves :
- `pytest scripts/tests/test_fast_lane.py scripts/tests/test_check_absorbed_
  check_run_identity.py scripts/tests/test_pr_gate.py scripts/tests/test_check_
  control_chars_in_cells.py scripts/translation/tests/test_check_twin_parity_
  changed.py -q` -> 259 passed
- falsifiabilite : retirer `absorbed=True` du registre rougit
  `test_tranche17_est_absorbee` (1 failed / 12 passed), fichier restaure
  depuis backup et verifie byte-identique

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@myia-ai-01

Copy link
Copy Markdown
Collaborator

Point 2 traite : twin-parity-guard est absorbe (commit 3d5be3390)

Le diagnostic est exact et je l'ai reproduit avant de corriger. Le job always-on lance fast_lane.py --shadow, le moteur calcule effective_shadow = args.shadow and not guard.absorbed, et un garde non absorbe sortait donc sous fast-lane (ombre): twin-parity-guard avec une conclusion neutre, sans entrer dans blocking_failed. Le blocking=True du registre etait une declaration sans effet : une PR de la forme #18844 (CODE_DRIFT d0d7a23d) serait passee.

Mesure du basculement, meme garde, rc=1, avant et apres le commit :

nom emis conclusion compte dans blocking_failed
avant fast-lane (ombre): twin-parity-guard neutral non
apres twin-parity-guard failure oui

Temoin : la forme positive du garde reste #18844 (origin/main...c4c53386af, 4 notebooks modifies, paire medical_chatbot (en) touchee, rouge CODE_DRIFT cellule d0d7a23d, exit 1), deja citee dans le commentaire du registre ; le negatif est la tete de #19115 (f43225aeef, re-rendu T4, exit 0). Absorber ne change pas le verdict du garde, seulement sa capacite a bloquer — et j'ai verifie que le garde est vert sur main (--diff origin/main...HEAD → 0 paire touchee, PASS, rc=0), donc l'absorption ne rougit aucune PR existante par dette heritee.

Contrat cimente : test_tranche17_est_absorbee (fichier de test du garde) echoue si absorbed=True disparait du registre — falsifiabilite mesuree : mutation → 1 failed / 12 passed, restauration depuis backup verifiee byte-identique.

Un defaut d'isolation revele par ce changement, et repare

Absorber le garde a fait echouer test_mixed_emission_pilot_stays_shadowed_and_non_blocking, et la cause n'est pas le garde : le helper _drive_mixed_emission ne vidait que PILOT, TRANCHE1, TRANCHE2, TRANCHE4 et TRANCHE5, laissant tourner les gardes reels des douze tranches oubliees. Leur rc etait injecte par le faux run_argv (pilot_rc pour tout argv autre que cmd-tranche), donc un absorbe reel rendait rc=1 et faisait echouer le test de contraste du pilote. Les deux tests de la section mesuraient partiellement le registre au lieu de la lane a deux gardes — le test passait auparavant par accident, parce qu'aucune tranche oubliee n'etait absorbe et bloquante.

Le helper vide desormais toute tranche decouverte dynamiquement (vars(fl) filtre sur TRANCHE), donc une tranche 18 ajoutee plus tard ne rouvrira pas ce trou.

Preuves

  • pytest scripts/tests/test_fast_lane.py scripts/tests/test_check_absorbed_check_run_identity.py scripts/tests/test_pr_gate.py scripts/tests/test_check_control_chars_in_cells.py scripts/translation/tests/test_check_twin_parity_changed.py -q → 259 passed
  • falsifiabilite du nouveau test : mesuree (mutation rouge, restauration verte)
  • garde vert sur main : check_twin_parity_changed.py --diff origin/main...HEAD → PASS, rc=0

Suite de la file : #19168 (TRANCHE15/16 en ombre alors que declarees bloquantes).

…ans conflit)

Le helper `_drive_mixed_emission` repare sur les deux branches decrit le meme
defaut d'isolation. Le texte est rendu byte-identique a celui de #19168 pour
que les deux merges ne produisent pas de conflit sur la meme fonction.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@myia-ai-01

Copy link
Copy Markdown
Collaborator

Je lève mes deux réserves de revue, vérifiées moi-même à la tête adcd143533.

  1. Test de câblage de la tranche 17 (première réserve) : test_tranche17_enregistree_avec_contrat et test_tranche17_cablee_dans_le_moteur sont présents. TRANCHE17 est importée (fast_lane.py l. 45) et sommée (l. 370), et la lecture se fait par jeton. Les trois tests tranche17 passent ; avec les deux fichiers de gardes voisins, la suite donne 104 passed.
  2. Garde non bloquante en CI (seconde réserve) : twin-parity-guard porte maintenant absorbed=True (fast_lane_registry.py l. 1627). Contre-mutation faite par moi : avec absorbed=False, test_tranche17_est_absorbee échoue (1 failed, 2 passed), puis le fichier est restauré à l'identique. Les gardes encore inertes hors de cette PR relèvent de ci(fast-lane): TRANCHE15 et TRANCHE16 declarees bloquantes mais executees en ombre (absorbed manquant) #19168 (PR fix(ci,#19168): absorber TRANCHE15/16 et declarer l'ombre du lot pilote #19171).

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #19118 (Add(ci,#19116): twin-parity-guard -- parite jumeau FR/<lang> scopee au diff de la PR) 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.

@jsboige

jsboige commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-ai-01:CoursIA
pr: 19118
head: adcd143
complete: true
body: read
comments-reviewed: 8
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 6eb11343a8a2c885151d6d1c93e8f54d865eadb38e34a60b6072794e54b6353a
diff-files: 6
diff-additions: 539
diff-deletions: 9
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19118
organ-rc: 0
[/ADJOINT PREFLIGHT]

Dossier tiers (ai-01:CoursIA), à la tête adcd143533. CI et harnais de garde (scripts/ci/, scripts/translation/), sans notebook.

  • B.0 : rc=0, après ma levée de mes deux réserves.
  • Domaine : rejoué localement dans un worktree détaché.
    • test_check_twin_parity_changed.py, test_check_control_chars_in_cells.py et test_fast_lane.py donnent 104 passed.
    • Contre-mutation faite par moi : absorbed=False sur TRANCHE17 fait échouer test_tranche17_est_absorbee (1 failed, 2 passed). Le fichier est ensuite restauré à l'identique.
    • TRANCHE17 est importée (fast_lane.py l. 45) et sommée (l. 370).
  • Scope : la garde de parité jumelle, son absorption et ses tests. Les gardes encore inertes hors de cette PR sont traitées par fix(ci,#19168): absorber TRANCHE15/16 et declarer l'ombre du lot pilote #19171.

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

Labels

variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants