Skip to content

Fix(tooling,#13906): l'organe de roulement ecrit son rapport en UTF-8 - #24664

Closed
jsboige wants to merge 1 commit into
mainfrom
fix/epic-body-staleness-utf8-stdout
Closed

jsboige wants to merge 1 commit into
mainfrom
fix/epic-body-staleness-utf8-stdout

Conversation

@jsboige

@jsboige jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Grain: MED/tooling — lane myia-po-2027:CoursIA — prev: MED/tooling #24067

Le défaut, reproduit avant fix

_cli sérialisait sur sys.stdout via json.dump(..., ensure_ascii=False). Sous une console Windows cp1252, un seul titre d'EPIC portant un caractère hors cp1252 (U+21C4, U+2192, emoji) lève UnicodeEncodeError à la ligne 462, et le rapport entier est perdu — y compris les entrées dont le titre était encodable.

Mesure du 2026-11-04, lane myia-po-2027:CoursIA, sur le corpus réel (95 EPICs × 800 PRs) :

Run Sortie Findings visibles
sans PYTHONIOENCODING 1980 octets, UnicodeEncodeError sur \x97 2 sur 59
avec PYTHONIOENCODING=utf-8 16040 octets, rc=0 59 sur 59

Le second run est celui qui a servi la tranche de roulement du jour. Il n'a été obtenu qu'en corrigeant à la main la console de la lane — or #13906 est un protocole de flotte : toute lane qui l'exécute doit obtenir le même rapport, sans connaître cette variable.

Ce que fait le correctif

build_payload est sérialisé en mémoire, puis écrit en octets sur sys.stdout.buffer, avec repli sur sys.stdout.write quand la sortie est un objet sans buffer (capture de test). Le rapport ne dépend plus de l'encodage de la console.

text = json.dumps(build_payload(epics, merged_prs), ensure_ascii=False, indent=...)
_write_stdout_utf8(text + "\n")

Preuves

Point Résultat
python -m pytest scripts/tests/test_epic_body_staleness.py -q 39 passed (38 avant, +1 test de régression)
Contrôle négatif — le nouveau test lancé sans le correctif JSONDecodeError: Expecting value: line 1 column 1 (char 0) sur une sortie vide : la perte totale est reproduite, le test mesure bien le défaut
Bout-en-bout sans PYTHONIOENCODING rc=0, JSON valide, 95 EPICs × 120 PRs, 19 findings, un titre hors cp1252 présent dans la charge

Le test de régression simule la console cp1252 par un objet dont .write encode en cp1252 (donc lève) et dont .buffer est un BytesIO : il échoue sur l'ancien code, passe sur le nouveau.

Portée

  • Aucun changement de sémantique : le payload, le corpus, les verdicts et le contrat de lecture sont identiques ; seule la couche d'écriture change.
  • Le mode --json/--pretty et le contrat checks: latest-wins-green ne sont pas touchés.
  • Pas de nouveau détecteur, pas d'élargissement de filet : c'est une réparation de transport, pas une règle.

Ce que ce correctif ne fait pas

Il ne change pas le fait que l'organe ne distingue pas une livraison d'axe d'une citation incidente — cette limite est déclarée par l'organe lui-même et reste à la charge de la lecture humaine (cf. la tranche du jour sur #23553, 3 livraisons d'axe sur 4 PRs citées).

See #13906

🤖 Generated with Claude Code

`_cli` serialisait sur `sys.stdout` via `json.dump(..., ensure_ascii=False)`.
Sous une console Windows cp1252, un seul titre d'EPIC portant un caractere
hors cp1252 (U+21C4, U+2192, emoji) levait `UnicodeEncodeError` et le rapport
entier etait perdu, y compris les entrees encodables.

Mesure du 2026-11-04 (lane myia-po-2027): premier run tronque a 1980 octets
sur 59 findings, soit 2 findings visibles sur 59. Le run n'a pu etre obtenu
qu'en imposant `PYTHONIOENCODING=utf-8`, c'est-a-dire en corrigeant a la main
la console de chaque lane qui mesure — or le roulement #13906 est un protocole
de flotte : chaque lane qui l'execute doit obtenir le meme rapport.

Le correctif ecrit les octets directement sur `sys.stdout.buffer`, avec repli
sur `sys.stdout.write` quand la sortie est un objet sans buffer (capture de
test). Le rapport ne depend plus de l'encodage de la console.

Preuves:
- `python -m pytest scripts/tests/test_epic_body_staleness.py -q` -> 39 passed
  (38 avant, +1 test de regression qui rejoue le defaut).
- Controle negatif: le nouveau test, lance sans le correctif, leve
  `JSONDecodeError: Expecting value: line 1 column 1 (char 0)` sur une sortie
  vide — la perte totale est reproduite.
- Bout-en-bout sans `PYTHONIOENCODING`: rc=0, JSON valide, 95 EPICs x 120 PRs,
  19 findings, un titre hors cp1252 present dans la charge.

See #13906

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[RETRAIT] doublon de #24622 — retire par sa lane, pas de contenu perdu

Je ferme cette PR : elle double #24622 (fix/epic-staleness-cp1252, d9e0e80d43, mergeState=CLEAN), livree plus tot dans la journee par ma propre lane et qui repare le meme defaut sur les deux memes fichiers.

La cause du doublon, mesuree : j'ai lu le body de #23553, l'organe, puis code le correctif, sans relire le registre des verifications en attente de la lane — qui porte la ligne c491 nommant #24622 et sa tete. La recherche de plateau (L898) a ete faite pour le grain de roulement, pas pour ce correctif. Cout : une branche et un run de tests inutiles, aucun contenu perdu.

Ce que cette PR apporte de plus, et qui peut servir a #24622 : un controle negatif executable — le test rejoue le defaut en simulant une console cp1252 (objet dont .write encode en cp1252, donc leve, et dont .buffer est un BytesIO). Lance sans correctif, il rend JSONDecodeError: Expecting value: line 1 column 1 (char 0) sur une sortie vide : la perte totale du rapport est reproduite, pas seulement supposee. Si #24622 veut cette forme, elle est sur la branche fix/epic-body-staleness-utf8-stdout, commit 054f6412fe.

Aucune action attendue ici. La suite du correctif est sur #24622.

@jsboige jsboige closed this Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2027:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-11-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.

@github-actions

github-actions Bot commented Nov 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).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant