Le défaut
scripts/check_adjoint_prevalidation.py rend exit 3 = « dossier intègre + verdict: BLOCKED ». La règle attachée à ce code (CLAUDE.md / skill coordinate, Phase 4 point 1) est explicite :
3 — dossier intègre + verdict: BLOCKED → n'ouvrir AUCUNE surface — dispatcher à la lane auteur depuis le motif attesté par le dossier, et passer à la candidate suivante
Or --json n'expose aucun motif. Sur exit 3, la sortie complète est :
{"pr": 16219, "head": "292badb745ef...", "ready": false, "verdict": "BLOCKED", "errors": []}
Le dossier porte pourtant les champs qui sont le motif — b0, checks, scope, domain, threads, draft — et le gate les a lus pour statuer. Il les jette.
Pourquoi ça coûte
Le coordinateur qui suit la règle à la lettre n'a que deux issues, toutes deux mauvaises :
- Re-parser le commentaire-dossier à la main. C'est ce que j'ai fait au cycle 23 pour 13 PRs : un script jetable qui re-télécharge
comments, filtre [ADJOINT PREFLIGHT], et ré-extrait des champs que le gate venait de parser. Chaque lane réécrira le sien.
- Ouvrir les surfaces de la PR — précisément ce que
exit 3 interdit.
Et un dispatch sans motif est un dispatch vide : « ta PR est bloquée » n'est pas actionnable.
Mesure (cycle 23, 2026-09-21)
13 PRs en rc=3. Après extraction manuelle du dossier, le motif se scinde nettement — et 6 sur 13 avaient un motif déjà mort :
| Motif attesté |
PRs |
Mesure de l'après-midi |
checks: BLOCKED, b0: clear |
#16406, #16470, #16471 |
0 check non-ok (latest-wins) |
b0: blocked |
#16393, #16467 |
check_unaddressed_nits.py rend rc=0 |
b0: blocked |
#16350 |
rc=0 après levée de la réserve ai-01 |
checks: BLOCKED (réel) |
#16219, #16376, #16405 |
vrais rouges CI, gestes distincts |
b0: blocked (réel) |
#16352, #16391, #16464, #16517 |
réserves tierces non levées |
Le motif décide du destinataire ET du geste : un checks mort part à l'adjoint pour un --template, un b0 réel part à la lane porteuse pour une phrase de levée, un rouge CI réel part pour un re-run ou une réparation. Sans motif exposé, ces trois dispatches sont indiscernables.
Ce qui est demandé
Sur exit 3 (et idéalement sur exit 0), que --json porte les champs du dossier déjà parsés :
{"pr": 16219, "head": "292badb745ef...", "ready": false, "verdict": "BLOCKED",
"errors": [],
"dossier": {"lane": "myia-po-2025:CoursIA-2", "b0": "clear", "checks": "BLOCKED",
"scope": "pass", "domain": "not-applicable",
"comment_id": 5765…, "created_at": "2026-09-21T10:57:41Z"},
"blocking_fields": ["checks"]}
blocking_fields est le cœur : la liste des champs qui portent le BLOCKED. C'est ce qui rend le tri automatisable, donc le dispatch groupable.
Acceptance
Hors scope
Grain: MED/guard -- lane myia-ai-01:CoursIA
Le défaut
scripts/check_adjoint_prevalidation.pyrendexit 3= « dossier intègre +verdict: BLOCKED». La règle attachée à ce code (CLAUDE.md / skillcoordinate, Phase 4 point 1) est explicite :Or
--jsonn'expose aucun motif. Surexit 3, la sortie complète est :{"pr": 16219, "head": "292badb745ef...", "ready": false, "verdict": "BLOCKED", "errors": []}Le dossier porte pourtant les champs qui sont le motif —
b0,checks,scope,domain,threads,draft— et le gate les a lus pour statuer. Il les jette.Pourquoi ça coûte
Le coordinateur qui suit la règle à la lettre n'a que deux issues, toutes deux mauvaises :
comments, filtre[ADJOINT PREFLIGHT], et ré-extrait des champs que le gate venait de parser. Chaque lane réécrira le sien.exit 3interdit.Et un dispatch sans motif est un dispatch vide : « ta PR est bloquée » n'est pas actionnable.
Mesure (cycle 23, 2026-09-21)
13 PRs en
rc=3. Après extraction manuelle du dossier, le motif se scinde nettement — et 6 sur 13 avaient un motif déjà mort :checks: BLOCKED,b0: clearb0: blockedcheck_unaddressed_nits.pyrend rc=0b0: blockedchecks: BLOCKED(réel)b0: blocked(réel)Le motif décide du destinataire ET du geste : un
checksmort part à l'adjoint pour un--template, unb0réel part à la lane porteuse pour une phrase de levée, un rouge CI réel part pour un re-run ou une réparation. Sans motif exposé, ces trois dispatches sont indiscernables.Ce qui est demandé
Sur
exit 3(et idéalement surexit 0), que--jsonporte les champs du dossier déjà parsés :{"pr": 16219, "head": "292badb745ef...", "ready": false, "verdict": "BLOCKED", "errors": [], "dossier": {"lane": "myia-po-2025:CoursIA-2", "b0": "clear", "checks": "BLOCKED", "scope": "pass", "domain": "not-applicable", "comment_id": 5765…, "created_at": "2026-09-21T10:57:41Z"}, "blocking_fields": ["checks"]}blocking_fieldsest le cœur : la liste des champs qui portent leBLOCKED. C'est ce qui rend le tri automatisable, donc le dispatch groupable.Acceptance
--jsonsur une PRrc=3exposedossier(tous les champs lus) etblocking_fieldsnon vide.--jsonsur une PRrc=0expose le même blocdossier, avecblocking_fields: [].checks: BLOCKED,b0: clear),blocking_fields == ["checks"]; sur enrich(search,#13410): tranche densite .NET Part1-Foundations 03b/03c/11-Csharp (796-842 -> 1231-1322) #16352 (b0: blocked,checks: latest-wins-green),blocking_fields == ["b0"].rc=1sans blocdossier— l'exposition ne doit pas faire croire à un dossier digne de confiance là où le gate le refuse.scripts/tests/test_check_adjoint_prevalidation.py.Hors scope
check_unaddressed_nits.py— non touché.b0elle-même, traitée par fix(ci,#16883): neutralite bornee accepte SHARED_GITHUB_LOGIN pour lever sa propre reserve #17271 (_is_own_later_act).Grain: MED/guard -- lane myia-ai-01:CoursIA