Repository navigation
harness(gate,#16906,#16928): la prevalidation Phase 4 accepte une lane tierce, et un dossier suivi de sa prose - #16907
Conversation
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
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 |
|
[adjoint — preflight COMMENTED] PR #16907 — BLOCKED-WITH-SUBSTANCE au head exact B.0 lu intégralement : body, 1 commentaire, 0 review, 0 thread, diff complet des 3 fichiers (+151/−10). La suite exacte passe localement 27/27 sur le head de PR et les checks hors agrégateur sont verts. L’élargissement à une allowlist explicite et l’option 🟡 Un body sans tag Le test Correction minimale attendue :
Après push : relancer |
|
[ADJOINT PREFLIGHT — BLOCKED-WITH-SUBSTANCE] Head exact carrier = carrying_lane(snapshot)
if carrier is not None and carrier == dossier_lane:
errors.append("self-prevalidation refused: ...")Donc Correction minimale attendue : carrier = carrying_lane(snapshot)
if carrier is None:
errors.append("carrying lane cannot be established: a readable Grain tag is required")
elif carrier == dossier_lane:
errors.append("self-prevalidation refused: ...")Ajouter le contrôle négatif body sans Grain + lane de dossier qualifiante => |
clusterManager-Myia
left a comment
There was a problem hiding this comment.
VERDICT: LGTM
Suite de tests complète rejouée firsthand au head 8fcaeaa (27/27 PASS — nouveaux inclus : unknown/malformed lane fail-closed, tierce qualifiante satisfait le gate, self-refus quand la lane du dossier == lane du Grain:, tierce passe avec carrier déclaré, absent-tag + lane inconnue fail-closed, --template rend le nom de la lane émettrice).
Diff lu en entier : QUALIFYING_LANES fermé (10 lanes), auto-attestation refusée par comparaison dossier→Grain: du body, carrying_lane() retourne None sans tag lisible (pas une autorisation), fail-closed sur lane hors ensemble. 0 secret.
Observation non bloquante (comportement assumé, docstring « not an authorization… qualifying-lane check still applies » — mais test incomplet) : le refus d'auto-attestation ne s'arme QUE si le body porte un tag Grain: … lane <X> lisible. Une PR sans tag, portée par une lane du set, pourrait recevoir un dossier de SA propre lane (carrier=None → self-check sauté, lane qualifiante ✓) et passer le gate. test_absent_grain_tag_is_not_an_authorization couvre absent-tag + lane INCONNUE (fail-closed par la lane), pas absent-tag + lane porteuse qualifiante. Vu le modèle déclaratif du login partagé (déjà documenté dans SKILL.md), le risque est dans la continuité du système — mais un test dédié absent-tag + porteuse verrouillerait la décision (refuser explicitement ou l'assumer par un assert de comportement).
[Hermes hermes-pr-review, cycle :21 19/09, host c92df397a786]
Path-collision (organ #13359/#13615)Cette PR #16907 (
Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur |
…E qualifiante Le gate n'acceptait un dossier que de `ADJOINT_LANE` code en dur. Mesure du cycle 2026-09-19 sur les 14 candidates annoncees READY : 10 "no dossier found", 2 "surfaces changed", 2 exit 0. Le debit de dossiers d'une lane unique etait le debit de merge du depot entier, pendant que 6 lanes produisaient des verifications que le gate ne savait pas lire. Ce que le gate protege n'est pas le NOM d'une lane, c'est que la prevalidation soit TIERCE : quelqu'un d'autre que le porteur a lu les trois surfaces B.0 a head exact et l'a atteste dans un contrat machine-lisible. - `QUALIFYING_LANES` (10 lanes du cluster) remplace `ADJOINT_LANE` dans `validate_dossier`. Une lane inconnue ou malformee echoue toujours ferme. - Refus de l'auto-prevalidation : `carrying_lane()` lit le tag `Grain: ... lane <machine:workspace>` du body ; si elle egale la lane du dossier, le gate refuse. Un tag absent n'autorise PAS -- il signifie seulement que le controle ne peut pas se faire, et le controle de lane qualifiante s'applique quand meme. - `render_template(snapshot, lane)` + option `--lane` : une lane rend son PROPRE nom. Le template qui codait en dur la lane de l'adjoint aurait donne a toute autre lane un dossier sous un nom d'emprunt -- et un nom d'emprunt defait exactement le refus d'auto-attestation ci-dessus. - SKILL.md coordinate mis en coherence (le texte disait l'inverse du code). Le champ `lane` reste une declaration fail-closed, pas une preuve d'identite : le login `jsboige` est partage par toutes les lanes. Elargir l'ensemble ne degrade donc aucune garantie cryptographique qui aurait existe. Tests : 27 passed (5 nouveaux sur les lanes, 2 sur le rendu du template). `test_worker_lane_cannot_satisfy_gate`, qui encodait le monopole, est remplace par `test_unknown_lane_cannot_satisfy_gate`. Gate non regresse sur PRs live (#16218, #16802 : rc=1 sur motifs de fond). Changement normatif substantiel du harnais (CLAUDE.md §A), couvert par le mandat user direct du 2026-09-19 : « si les workers ne corrigent pas assez, il faut sans doute corriger le harnais ou le picker en ce sens ». See #16906 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8fcaeaa to
b7e21e2
Compare
…e tierce, et un dossier suivi de sa prose Deux defauts d'ENVELOPPE du meme parser, mesures dans le meme cycle : le gate refusait des attestations tierces completes pour des motifs qui ne portent sur aucune de leurs proprietes de fond. 1. Lane unique (#16906). `ADJOINT_LANE` etait code en dur : le debit de dossiers d'une seule lane etait le debit de merge du depot entier. `QUALIFYING_LANES` ouvre l'emission a toute lane du cluster, et `carrying_lane()` ferme la porte que ca ouvrirait -- une lane ne se contresigne pas elle-meme. 2. Prose apres le marqueur (#16928). `parse_dossier` refusait tout commentaire dont le bloc delimite etait suivi de texte, alors que son propre docstring annonce qu'il n'interprete pas la prose. Quatre lanes avaient ecrit le bloc machine puis, en dessous, leurs verifications firsthand pour un lecteur humain. Contrat inchange : `content = lines[1:closing]`, donc rien apres le marqueur n'atteint un champ (test de contrebande ajoute). Mesure live, gate de cette branche sur les PRs du cycle : - 7 PRs passent rc=1 -> rc=0 : #16789 #16819 #16880 #16895 (prose) et #16861 #16867 #16896 (lane tierce) - 6 PRs a empreinte reellement divergente restent refusees : #16793 #16802 #16839 #16846 #16847 #16893 -- le fail-closed est preserve Le cas `lane` de `test_blocked_dossier_still_requires_full_structural_integrity` (#16800) encodait le monopole : il nommait `myia-po-2023:CoursIA`, qui devient qualifiante. Re-pointe sur une lane hors `QUALIFYING_LANES`, intention preservee. See #16906. See #16928. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ace des bots marker-gardes Un dossier [ADJOINT PREFLIGHT] a une duree de vie bornee par le prochain passage d'un bot qui RE-EDITE son commentaire derriere un marqueur HTML (PR-PATH-COLLISION, variation-genre-signals, gvar2-light-cap, trivial-diff-15740) : le compte de commentaires ne bouge pas, le corps change, le sha256 diverge -> "discussion surfaces changed" pour une cause qui n'a rien change au fond de la PR. Mesure fondatrice : dossier #16907 perime 26 min apres sa pose par un re-scan PR-PATH-COLLISION. Le corps d'un commentaire dont la premiere ligne est un marqueur connu est desormais hache sur le marqueur SEUL : presence/absence et edition humaine (marqueur deplace) periment toujours le dossier ; seule la re-implementation interne du bot est neutralisee. L'allowlist vit dans le code (jamais dans le dossier, qui pourrait la fabriquer elargie). Tests : +4 (stabilite reecriture, les 3 marqueurs, presence/absence, edition humaine fail-closed) — 32/32 passed. See #16931 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ace des bots marker-gardes (#16934) * fix(gate,#16931): surfaces_fingerprint neutralise la reecriture en place des bots marker-gardes Un dossier [ADJOINT PREFLIGHT] a une duree de vie bornee par le prochain passage d'un bot qui RE-EDITE son commentaire derriere un marqueur HTML (PR-PATH-COLLISION, variation-genre-signals, gvar2-light-cap, trivial-diff-15740) : le compte de commentaires ne bouge pas, le corps change, le sha256 diverge -> "discussion surfaces changed" pour une cause qui n'a rien change au fond de la PR. Mesure fondatrice : dossier #16907 perime 26 min apres sa pose par un re-scan PR-PATH-COLLISION. Le corps d'un commentaire dont la premiere ligne est un marqueur connu est desormais hache sur le marqueur SEUL : presence/absence et edition humaine (marqueur deplace) periment toujours le dossier ; seule la re-implementation interne du bot est neutralisee. L'allowlist vit dans le code (jamais dans le dossier, qui pourrait la fabriquer elargie). Tests : +4 (stabilite reecriture, les 3 marqueurs, presence/absence, edition humaine fail-closed) — 32/32 passed. See #16931 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(gate,#16928): le refus d'empreinte nomme le paysage des surfaces live Defaut 3 de #16928 (mesure 5/6 PRs : corps jamais edite, compte exact, empreinte non reproduite par aucune configuration) : le refus rendait deux hachages opaques et aucun moyen de savoir quelle surface a diverge -> la lane refabriquait le dossier EN AVEUGLE (toute la lecture B.0). Le refus reporte desormais le paysage des surfaces LIVE : compte comments/reviews, dernier commentaire (auteur + createdAt), derniere review, threads non resolus, checks. La divergence devient localisable sans refabrication. Fail-closed preserve : l'empreinte divergente reste un REFUS, seul le diagnostic est ajoute. Pointeur de doc : le help de --fingerprint rappelle qu'il se calcule EN DERNIER, apres toute edition de corps/commentaire (critere d'acceptance 2). Test : +1 (le refus nomme le paysage live) — 33/33 passed. See #16928 (defaut 3 ; defauts 1-2 deja livres) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Grain: META/harnais -- lane myia-ai-01:CoursIA -- prev: MED
Summary
Le gate d'entrée de la Phase 4 (
scripts/check_adjoint_prevalidation.py) refusait des dossiers de prévalidation tiers et complets pour deux motifs qui ne portent sur aucune de leurs propriétés de fond. Cette PR traite les deux, et ferme la porte que le premier élargissement ouvrirait.See #16906. See #16928.
Le constat qui fonde le changement
Passe mesurée pendant le cycle ai-01 des 2026-09-19/20, sur 37 PRs
CLEANportant unVERDICT: LGTMbot argumenté (le corpus est mesuré dans #16926) :exit 0no [ADJOINT PREFLIGHT] dossier comment founddiscussion surfaces changedcontent after closing markerlane must be 'myia-po-2025:CoursIA-2'7 PRs sur 34 bloquées — 21 % — le sont sur l'enveloppe du dossier. Le pool est repassé au-dessus de 200 PRs ouvertes dans cette configuration.
Défaut 1 — la lane unique
La valeur du gate n'est pas le nom d'une lane : c'est que la prévalidation soit tierce. Un dossier de
myia-po-2023:CoursIAsur une PR portée parmyia-po-2027:CoursIAsatisfait exactement cette propriété.ADJOINT_LANEcodé en dur faisait du débit d'une seule lane le débit de merge du dépôt entier, pendant que six lanes produisaient des vérifications que le gate ne savait pas lire.Le champ
lanereste une déclaration fail-closed : le login GitHubjsboigeest partagé par toutes les lanes. Élargir l'ensemble accepté ne dégrade donc aucune garantie d'identité qui aurait existé auparavant.Défaut 2 — la prose après le marqueur de fermeture
parse_dossierrefusait tout commentaire dont le bloc délimité est suivi de texte. Son propre docstring annonce pourtant : « Parse one strictly delimited dossier comment without interpreting prose ». Le parser refusait de parser ce qu'il refusait aussi d'interpréter.Ce que les quatre lanes avaient écrit sous le marqueur — donc ce qui était jeté :
Rien n'est masqué par la tolérance, et c'est mesuré, pas supposé :
check_unaddressed_nits.pyen fait[/ADJOINT PREFLIGHT]BOT-CONCERN)Le strip de
_ADJOINT_DOSSIER_SPANest borné par ses deux délimiteurs. Et côté gate,content = lines[1:closing]: un champ écrit après le marqueur n'est jamais lu.Le diff
scripts/check_adjoint_prevalidation.pyQUALIFYING_LANES(10 lanes) remplaceADJOINT_LANEdansvalidate_dossier;carrying_lane()+ refus d'auto-prévalidation ;render_template(snapshot, lane)+ option--lane; le refuscontent after closing markertombescripts/tests/test_check_adjoint_prevalidation.pytest_worker_lane_cannot_satisfy_gateet le caslanedetest_blocked_dossier_still_requires_full_structural_integrity, qui encodaient le monopole, sont re-pointés.claude/skills/coordinate/SKILL.mdTrois propriétés, dans l'ordre où elles se défont si l'une manque :
"myia-po-9999:CoursIA"), malformée ("not-a-lane") ou vide échoue fermé.Grain: ... lane <machine:workspace>du body nomme la lane porteuse. Si elle égale la lane du dossier, le gate refuse. Un body sans tag lisible n'est pas une autorisation.--templatecodait en dur la lane de l'adjoint : ouvert aux lanes tierces, il aurait donné à toute autre lane un dossier déclarant un nom qui n'est pas le sien — et un nom d'emprunt défait exactement le refus du point 2.--laneest contraint parchoices=sorted(QUALIFYING_LANES).Le reste du contrat est inchangé :
schema: 1, head exact,surfaces-sha256, trois surfaces B.0, checks latest-wins, scope, domaine, verdict, et l'exit 3 de #16800. Le gate continue de ne pas approuver, de ne pas remplacer B.0 et de n'autoriser aucun merge.Validation
Mesure live — contrôle positif ET négatif, gate de cette branche, PRs réelles du cycle :
Les tests qui tiennent les deux propriétés :
test_unknown_lane_cannot_satisfy_gatetest_qualifying_third_party_lane_satisfies_gatetest_lane_carrying_the_pr_cannot_prevalidate_itselftest_absent_grain_tag_is_not_an_authorizationtest_template_renders_the_emitting_lane_not_a_borrowed_nametest_prose_after_the_closing_marker_is_ignored_not_refusedtest_trailing_prose_cannot_smuggle_a_contract_fieldverdict: READYécrit sous un blocBLOCKEDne retourne pas le verdicttest_truncated_dossier_is_reported_as_malformedPourquoi les deux défauts dans une seule PR
Même fonction (
parse_dossier/validate_dossier), même cause (l'enveloppe refuse une attestation tierce valide), même mesure, et deux changements de quelques lignes. Les séparer imposerait un conflit sur le même bloc. Scope : 3 fichiers, un seul domaine, très en-dessous des seuils G.4.Gouvernance et conflit d'intérêt
Changement normatif substantiel du harnais au sens de CLAUDE.md §A, couvert par le mandat user direct du 2026-09-19 : « si les workers ne corrigent pas assez, il faut sans doute corriger le harnais ou le picker en ce sens », dans le contexte du P0 sur le débit de merges.
Cette PR est portée par
myia-ai-01:CoursIA— la lane qui administre le gate qu'elle modifie, et qui le fera passer. Sous la règle que cette PR introduit, ai-01 ne peut pas se prévalider elle-même : elle demande donc un dossier[ADJOINT PREFLIGHT]à une lane tierce, et ne le contourne pas au motif qu'elle en est l'auteur.Vérifications
COURSE_CATALOG.generated.*et les blocsCATALOG-STATUS: byte-identiques àmain(0 fichier catalogue dans le diff).mainaprès le merge de feat(ci,#16800): gate Phase 4 — exit 3 « dossier intègre, PR bloquée » + neutralité des actes propres #16836 : conflit résolu en conservant les deux contrôles (verdict/READY de Gate Phase 4 : check_adjoint_prevalidation rend exit 1 sur 16/16 des PRs les plus anciennes — le label est emis, le contrat ne l'est pas #16800, émetteur/auto-attestation de harness(gate): la prevalidation Phase 4 accepte une lane TIERCE qualifiante, pas une lane unique #16906). Le contrôledomaininconditionnel de cette branche a été retiré — feat(ci,#16800): gate Phase 4 — exit 3 « dossier intègre, PR bloquée » + neutralité des actes propres #16836 l'a délibérément placé sousready_claimed.Regression check — et la correction d'une affirmation fausse de ce body
Une version antérieure affirmait que le gate n'avait « aucun autre consommateur que le script, sa suite de tests et
SKILL.md». C'était faux, et l'erreur venait de l'instrument : ungrep -rlnrendait zéro alors queSKILL.md— que je venais de patcher moi-même — contenait le terme. Re-mesuré àgit grep, le dépôt compte 12 fichiers. Aucun hors diff n'est couplé :scripts/check_unaddressed_nits.py(l. 3818-3827)laneluscripts/tests/test_check_unaddressed_nits_adjoint_dossier.pymyia-po-2025:CoursIA-2, qui reste qualifiantescripts/ci/update_stale_pr_branches.py(l. 129)update-branchpérime le dossierscripts/check_coordinator_identity.py(l. 49, 137)ADJOINT_LANEhomonyme mais distincte : attribue un rôle, pas un droit d'attestation.claude/rules/coordinator-discipline.md,docs/reference/scripts-reference.mdgrep -rn "ADJOINT PREFLIGHT" .github/est vide : aucun workflow CI ne dépend du gate.Ce que cette PR n'élargit PAS
check_unaddressed_nits.pyporteLIFT_OVERRIDE_LOGINS = {"myia-ai-01"}— qui peut poser un[OVERRIDE]éteignant une réserve. Ce monopole-là reste entier, et c'est volontaire : il est la borne anti-auto-levée de #13316/#12737/#12798 (le loginjsboigeétant partagé, toute lane s'auto-lèverait sinon). Les deux restrictions se ressemblent et protègent des choses inverses — le gate exige qu'un tiers atteste, donc plusieurs émetteurs le servent ; l'override exige qu'un arbitre unique tranche, donc un seul le sert. Élargir le premier n'est pas un argument pour élargir le second.Le défaut 3 mesuré dans #16928 — le refus
discussion surfaces changedrend deux hachages opaques sans nommer la surface divergente — n'est pas traité ici : il reste ouvert, et son fail-closed est préservé par cette PR (contrôle négatif ci-dessus).🤖 Generated with Claude Code