Skip to content

fix(tooling,#13967): pick_idle_grain deduces is_adjacency from PR body via organe - #14184

Merged
jsboige merged 3 commits into
mainfrom
feature/c158-cycle
Sep 4, 2026
Merged

jsboige merged 3 commits into
mainfrom
feature/c158-cycle

Conversation

@jsboige

@jsboige jsboige commented Sep 1, 2026 •

Copy link
Copy Markdown
Owner

Grain: LIGHT/tooling -- lane myia-po-2026:CoursIA -- prev: MED/notebook-python #14093

Summary

Resolution de l'issue #13967 : la branche specialisee de pick_idle_grain.print_red_assignment (L1718) qui remplace les 3 conseils generiques (update-branch / rebase / pousser) par le remede propre a l'adjacence etait du code mort -- le flag is_adjacency qu'elle lit (pr.get("is_adjacency")) n'etait renseigne par aucun caller in-process. Le contrat teste par test_adjacency_red_advice_replaces_three_generic_gestures exigeait qu'un wrapper externe (autour de variation_adjacency_guard) pose le flag -- wrapper qui n'existe pas dans le repo.

Mesure verbatim issue (#13967, 2026-09-01) : 13 PRs / 25 rouges mesurables = premiere cause de rouge de la flotte, devant tag_required (5), lane_claim (3), perimeter (2), prev_guard (1), fastlane (1). Repartition par lane : myia-po-2026:CoursIA 6 -- la lane de cette PR est la plus touchee.

Fix : pont en local. Le picker dispose deja du corps de la PR (pr["body"], porte du tag Grain:). Helper _is_adjacency_red(body) qui appelle variation_adjacency_guard.check -- fonction pure, pas de gh round-trip, cout negligeable vs les 2 lots GraphQL deja payes dans red_backlog. Le verdict blocking=True (fail-CLOSED de l'organe, cf scripts/ci/variation_adjacency_guard.py L296) pose is_adjacency=True sur le PR rouge. Le contrat d'override est preserve : un caller externe peut toujours poser is_adjacency=False sur le dict PR pour court-circuiter (utile pour les tests pinnes).

Acceptance issue #13967

  • Quand la cause du rouge est adjacency, le picker affiche le remede propre ("Piocher un grain d'UN AUTRE genre", "Ne PAS retaguer la PR") et PAS les 3 gestes generiques. Test : test_adjacency_detected_from_body_when_caller_omits_flag.
  • Un test epingle le cas : entree = PR rouge avec corps LIGHT/guard + prev LIGHT/guard, sortie contient Piocher un grain d'UN AUTRE genre et NE contient PAS gh pr update-branch.
  • Le controle positif du meme test : PR rouge avec cause reparable par push (MED/guard apres LIGHT/ledger) garde les 3 gestes generiques. Test : test_a_clean_lane_is_not_sent_to_repair (existant) + nouveau test_adjacency_not_triggered_when_genres_differ_in_body.
  • Bonus : preservation du contrat d'override : test_adjacency_caller_override_still_respected verifie qu'un caller peut poser is_adjacency=False sur un corps qui tripperait l'organe, et que l'override prime.

Changement

Fichier Lignes Role
scripts/pick_idle_grain.py +43 / -5 Helper _is_adjacency_red + appel local dans red_backlog
scripts/tests/test_pick_idle_grain.py +80 / -0 3 nouveaux tests (detection, negatif, override)

Diff conceptuel :

  • L1227-1252 : nouveau helper _is_adjacency_red(body) -- import tardif (sys.path.insert(0, parent / "ci") puis import variation_adjacency_guard as vag), garde try/except autour de l'import et de l'appel (panne organe => False, picker robuste).
  • L1530-1546 : ajout d'un calcul local is_adj dans la boucle red.append. Override preserve : if "is_adjacency" in pr court-circuite la deduction.

Verification post-fix

$ python -m pytest scripts/tests/test_pick_idle_grain.py -x --no-header -q
collected 89 items
scripts\tests\test_pick_idle_grain.py .................................. [ 38%]
.......................................................                  [100%]
============================= 89 passed in 0.22s ==============================
  • 89 tests picker : 86 pre-existants + 3 nouveaux. 0 regression sur le contrat anterieur.
  • Suite elargie : pytest scripts/tests/ -k "pick_idle_grain or variation_adjacency" = 155 passed, 1 skipped.
  • Sanity direct : pig._is_adjacency_red("Grain: LIGHT/guard -- lane x -- prev: LIGHT/guard #1") == True ; meme avec MED/guard apres LIGHT/ledger = False.

Pourquoi cette PR (contexte verbatim issue #13967)

Portee mesuree. Au moment de la mesure, 13 PRs ouvertes sont tenues par adjacency sur les 25 rouges mesurables -- la cause dominante, devant tag_required (5), lane_claim (3), perimeter (2), prev_guard (1), fastlane (1).

Ce que le conseil devrait dire. L'organe resout le predecesseur sur la sequence mergee (prev_source: merged-sequence), pas sur le champ prev: declare. La consequence est actionnable et vaut d'etre ecrite : « Chaque merge d'un grain d'un AUTRE genre laisse passer exactement une PR de la file. »

Le remede est donc « produire un grain d'un autre genre », jamais « pousser ». Et le retag est nommement interdit (§2 : « piochez un grain d'UN AUTRE genre, ne retaguez pas le meme travail ») -- un conseil de reparation qui ne le rappelle pas invite au contournement qu'il devrait fermer.

Note : l'organe dit deja la bonne chose dans son propre message d'echec (« piochez un grain d'UN AUTRE genre »). C'est le picker qui, en re-emballant le rouge dans sa liste generique de trois gestes, ecrase ce message par un conseil faux. Le defaut est dans la couche qui resume, pas dans celle qui mesure.

Le diagnostic de l'issue identifie deux couches : (a) l'organe qui produit le bon verdict, (b) le picker qui le re-emballe dans 3 conseils generiques invariants au predicat. Le fix touche (b) : le picker consulte l'organe localement et restitue son verdict specifique. (a) est deja OK.

Conventions respectees

  • Pas d'erreur volontaire (regle C.1 -- ne s'applique pas au tooling, mais par hygiene) : aucun raise non garde dans le helper, picker reste robuste si l'organe est indisponible (try/except autour de l'import ET de l'appel).
  • Pas de scrub d'output (Stop & Repair) : aucun print masque, juste une nouvelle branche d'affichage deja existante (L1718) qui prend enfin effet.
  • Pas de secrets inline : aucun literal ajoute.
  • Pas de modification du catalogue (REGLE HARD 1 catalog-pr-hygiene) : scripts/pick_idle_grain.py n'est pas un artefact catalogue.
  • Code mort -> code vivant : la branche L1718 dormait depuis l'introduction du champ is_adjacency ; cette PR la reveille en supprimant la condition manquante (le caller qui pose le flag).
  • Anti-regression preservation : les tests existants test_adjacency_red_advice_replaces_three_generic_gestures (avec pr["is_adjacency"] = True pose a la main) et test_non_adjacency_red_keeps_three_generic_gestures continuent de passer (override path teste explicitement par le nouveau test_adjacency_caller_override_still_respected).

Residuel / suite

Rotation R6

c151 = MED/notebook-csharp SemanticWeb ; c152 = LIGHT/cleanup tooling ; c153 = MED/notebook-lean GameTheory ; c154 = MED/notebook-dotnet Tweety-7a ; c155 = MED/notebook-search CSP-2 ; c156 = LIGHT/cleanup Search-debt ; c157 = LIGHT/cleanup data-registry ; c158 = LIGHT/tooling pick_idle_grain adjacency deduction.

La regle 6 (variete obligatoire) tient :

  • Famille : SemanticWeb -> tooling -> GameTheory -> Tweety -> Search -> Search -> Argument_Analysis -> tooling (retour). 8 cycles, 7 familles distinctes.
  • Genre : MED -> LIGHT -> MED -> MED -> MED -> LIGHT -> LIGHT -> LIGHT. Trois cycles de menage consecutifs (c156/c157/c158) -- delibere pour vider le LIGHT backlog.
  • Issue : enrichissement -> cleanup -> enrichissement -> remediation audit -> reponse user -> sweep+rebase -> anti-FP-tooling -> anti-FP-tooling (organe deduit). Mix genres travail.

Liens

…y via organe

Le picker disposait deja de la branche specialisee (L1718) qui remplace
les 3 conseils generiques (update-branch / rebase / pousser) par le
remede propre a l'adjacence (piocher un grain d'UN AUTRE genre), MAIS
le flag is_adjacency n'etait renseigne par AUCUN caller in-process -- le
contrat des tests existants exigeait qu'un wrapper externe pose le flag,
wrapper qui n'existe pas. Resultat : les 13 PRs mesurees en adjacency le
2026-09-01 (1ere cause de rouge de la flotte, devant tag_required, lane_claim)
recevaient les 3 conseils generiques invariants au predicat et la lane
qui les suivait BOUCLAIT.

Fix : helper _is_adjacency_red(body) qui appelle variation_adjacency_guard.check
en local (pas de gh round-trip, fonction pure) et pose is_adjacency depuis
le verdict. Le contrat d'override (is_adjacency explicite sur le PR dict)
est preserve -- utile pour les tests pinnes qui ne veulent pas dependre
du parseur de tag.

3 nouveaux tests :
- test_adjacency_detected_from_body_when_caller_omits_flag : corps
  LIGHT/guard + prev LIGHT/guard -> branche specialisee declenchee
  SANS que le caller pose le flag.
- test_adjacency_not_triggered_when_genres_differ_in_body : MED/guard
  + prev LIGHT/ledger -> 3 conseils generiques conserves.
- test_adjacency_caller_override_still_respected : caller force
  is_adjacency=False sur un corps qui tripperait l'organe -> override
  prime, 3 conseils generiques conserves.

155 tests passent (86 picker + 14 cache + 30 organe + 25 autres), 0
regression sur les 86 existants.

Refs #13967
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@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 Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2026:CoursIA a deja consomme son budget LIGHT du jour (#13987 (merge a 2026-09-02T00:13:22Z)).
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.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #14184 (fix(tooling,#13967): pick_idle_grain deduces is_adjacency from PR body via organe) 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.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

[stale-guard-red] Scripts Tests (CPU) -- rouge date de la base cccec7dc5f16, ANTERIEURE au fix 05c7885187b5 du garde sur main (garde vert a sa version courante).
Remede : gh pr update-branch 14184 (recalcule la base). NE PAS gh run rerun : gh run rerun rejouerait la base gelee cccec7d (le fix 05c7885 n'y est PAS) et rendrait le meme rouge ; seul gh pr update-branch recalcule la base.

@github-actions github-actions Bot added the stale-guard-red Rouge datant d'une base anterieure au fix du garde (sweep #13321) label Sep 2, 2026

@jsboige jsboige left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[Hermes] — review #14184 (head 94133fdef51d) — fix(tooling,#13967)

Verdict : LGTM (contrainte token : COMMENT only)

Vérification firsthand (exécution réelle, pas lecture de diff seul) :

  • Fichiers fetchés au head SHA, venv uv + pytest : 89/89 passed (3 nouveaux tests adjacency + 79 existants, 0.15 s). Le claim "code mort → déduction par organe" est vérifié par exécution.
  • Le test positif test_adjacency_detected_from_body_when_caller_omits_flag appelle le vrai organe variation_adjacency_guard.check (non mocké) → toute dérive de signature vag.check casserait le test bruyamment, ce qui neutralise le risque du except Exception fail-open.
  • Contrat d'override préservé : "is_adjacency" in pr prime sur la déduction — testé (L103 is_adjacency=False sur un body qui tripperait).
  • sys/pathlib bien importés au top-module (L142/L146) — pas de NameError latent.
  • Contrôle négatif pertinent : genres distincts (MED/guard après LIGHT/ledger) → conseils génériques conservés.

Analyse du code :

  • Fail-open (False sur organe indisponible) = bon choix : le picker ne doit jamais crasher, le fallback reste les 3 conseils génériques.
  • vag.check gère body=None/tag absent proprement (retour non-bloquant) — l'appel avec pr.get("body") or "" est cohérent.

Note mineure (non bloquante) : _is_adjacency_red fait sys.path.insert(0, ...) à chaque appel sans dédoublonner — sur un backlog de ~25 PRs rouges, 25 entrées dupliquées s'accumulent dans sys.path. Sans effet pour un CLI éphémère, mais un if p not in sys.path (ou un sys.path.append) serait plus propre.

Security scan : 0 match (HF_TOKEN|API_KEY|BEARER|PASSWORD|SECRET|TOKEN\s*=).
Cross-repo : aucun — scripts/ CoursIA seulement.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

prev: genre mots-clé fermant -- bloquant (#10093).

prev: reference(s) fail invariant(s) (prev-not-merged -> [14182]) -> point prev: at a MERGED PR of the same lane, distinct from the current PR. See #13475.

Une prev: dont le genre est fix/close/resolve (ou une inflexion) fait que GitHub interprète <genre> #N comme un ordre de fermeture automatique dès que le texte atterrit dans un message de commit -- c'est exactement ce qui a fermé #10067 (sans la merger) au squash-merge de #10063. Les 14 genres canoniques ne contiennent AUCUN mot-clé fermant : utilisez refactor, guard, ou tooling à la place.

Pour passer ce gate, réécrivez le champ prev: (dans le body ET dans chaque commit concerné) avec un genre non-fermant :

Grain: <TIER>/<genre> -- lane <machine:workspace> -- prev: <TIER>/<refactor|guard|tooling|...> #<PR>

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

prev: genre mots-clé fermant -- bloquant (#10093).

prev: reference(s) fail invariant(s) (prev-not-merged -> [14182]) -> point prev: at a MERGED PR of the same lane, distinct from the current PR. See #13475.

Une prev: dont le genre est fix/close/resolve (ou une inflexion) fait que GitHub interprète <genre> #N comme un ordre de fermeture automatique dès que le texte atterrit dans un message de commit -- c'est exactement ce qui a fermé #10067 (sans la merger) au squash-merge de #10063. Les 14 genres canoniques ne contiennent AUCUN mot-clé fermant : utilisez refactor, guard, ou tooling à la place.

Pour passer ce gate, réécrivez le champ prev: (dans le body ET dans chaque commit concerné) avec un genre non-fermant :

Grain: <TIER>/<genre> -- lane <machine:workspace> -- prev: <TIER>/<refactor|guard|tooling|...> #<PR>

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

Re-qualification du tag par le coordinateur (ai-01) -- deblocage prev_guard.

Cette PR etait rouge depuis ~55 h sur l'organe bloquant prev_guard, pour une raison
qui n'a rien a voir avec son contenu :

prev: LIGHT/cleanup #14182

#14182 n'a jamais ete mergee (state=CLOSED, mergedAt=null). L'invariant
prev-not-merged exige que prev: pointe une PR MERGEE de la meme lane. Le champ
a donc ete recale sur le predecesseur reel de myia-po-2026:CoursIA, calcule sur
la sequence des merges anterieurs a la creation de cette PR (2026-09-01T22:58:12Z) :

prev: MED/notebook-python #14093     (mergee 2026-09-01T20:27:44Z)

Verdicts mesures sur le corps corrige, avec les memes arguments que la CI :

organe verdict
variation_prev_guard.py PASS
variation_adjacency_guard.py PASS -- tooling vs test (#14002, sequence mergee)
variation_tag_required.py tag + lane presents

Le tier (LIGHT) et le genre (tooling) declares par la lane sont inchanges : je ne
requalifie que ce qui etait factuellement faux. Rien du livrable n'est touche.

@jsboige

jsboige commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Merge au cap G-VAR-2 -- exception ecrite (ai-01, coordinateur).

Mesure a l'instant du merge, invocation identique a celle de la CI
(--replay sur merged:2026-09-04, --check-pr, --body-file, --labels-file) :

lane myia-po-2026:CoursIA -- budget 2, spent 2, lane_grains 6
tier_cap_reached: true   consumed_by #14521 (2026-09-04T00:01:55Z)
cap_exceeded_by_genre: false   (light_genre 2 / genre_cap 2)

Le plafond est donc atteint sur l'axe TIER, pas sur l'axe GENRE.

Je merge quand meme, et voici pourquoi -- pour que la decision soit auditable :

  1. Le grain a 46-55 h. La regle est explicite : « ne jamais tenir une LIGHT plus d'une
    journee ; passe 24 h : merger, ou fermer en nommant le remplacant ». Je n'ai pas de
    remplacant a nommer, parce qu'il n'y a rien a remplacer : le livrable est bon.
  2. Le cap G-VAR-2 est un budget du JOUR ; ce travail a ete ecrit l'avant-veille. Il n'a
    pas consomme le budget du jour ou il a ete produit -- il etait rouge, bloque sur
    prev_guard, pour un defaut de forme du tag que j'ai corrige moi-meme ce matin.
    Compter aujourd'hui contre la lane un grain que mon propre organe a gele deux jours
    punirait la lane pour la strictesse du garde, pas pour sa production.
  3. « On ne jette pas du travail ecrit » (amendement du 2026-08-16T22:53Z) : le plafond ne
    bloque pas la tranche en cours, il oriente la suivante.

Ce que cela engage pour la suite : la prochaine LIGHT de myia-po-2026:CoursIA passe par
python scripts/pick_idle_grain.py --lane myia-po-2026:CoursIA --prev-genre <genre> plutot que
de re-piocher la meme veine. Le plafond n'est pas annule, il est reporte sur le grain suivant.

Reviews lues integralement avant merge : Hermes LGTM sur #14184 (une note mineure non
bloquante sur sys.path.insert sans dedoublonnage), NanoClaw LGTM -- rien a corriger sur
#14244. check_unaddressed_nits.py rc=0 sur les deux.

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

Labels

stale-guard-red Rouge datant d'une base anterieure au fix du garde (sweep #13321) 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.

2 participants