Skip to content

fix(guards,#15864): extract_lane refuse la lane fantome « X. Enonce » — point final suivi de prose - #15866

Merged
myia-ai-01 merged 3 commits into
mainfrom
fix/grain-tag-lane-sentence-period
Sep 15, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
fix/grain-tag-lane-sentence-period

Conversation

@jsboige

@jsboige jsboige commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2023:CoursIA — prev: MED/tooling #15845

Summary

grain_tag.extract_lane — le lecteur unique de la lane (#9485) — lisait le token myia-po-2023:CoursIA. Énoncé là où le marqueur déclarait myia-po-2023:CoursIA, suivi d'une phrase. Le gate lane_claim bloque alors la lane déclarante sur son propre claim, en accusant une lane qui n'existe pas. Deux correctifs antérieurs se combinent : le . admis dans le token (#12719) et la majuscule accentuée admise en tête de mot de continuation (#13830).

Cause

extract_lane admettait :

  • #12719 → le . dans le token (hostnames), avec rstrip(".") pour le point final ;
  • #13830 → (?-i:[A-Z0-9À-ÖØ-ÞĀ-ſ]) comme initiale de continuation, pour la vraie lane myia-ai-01:LivresAgités Épisode.

Combinés sur lane myia-po-2023:CoursIA. Énoncé réécrit : CoursIA. porte un . admis, puis Énoncé est une continuation légitime. Le point n'est donc plus final, rstrip(".") ne le voit pas, et la lane lue devient myia-po-2023:CoursIA. Énoncé.

Le déclencheur est le protocole lui-même : un [DELIVERED] re-marque le claim tant que sa PR est ouverte (#12320), donc son texte est réduit comme un claim actif — et il est suivi de prose française qui commence naturellement par un mot à majuscule accentuée. 4ᵉ variante de la classe d'auto-blocage (#12145, #12719, #13830).

Le check-run ne le dit pas : Always-on guards -- 13 organes, 1 checkout est muet (output.title = null, summary vide) ; seul le log du job nomme Organes bloquants en echec : lane_claim.

Le correctif

Un . suivi d'un blanc (ou de la fin du texte) est de la ponctuation de prose, jamais une partie de <machine>:<workspace> ; un . à l'intérieur du token reste admis (A.B, host.example:Baz — suivis d'un non-blanc). Un (?<!\.) devant la clause de continuation fait donc finir le token au point, et le strip de #12719 s'applique comme avant.

Appliqué aux deux jumeaux (_LANE_RE et _LANE_FALLBACK_RE — discipline #12145 : ne corriger qu'une moitié laisse le défaut entier dans la copie). Le témoin lane_marker_residues continue de déclarer la forme malformée (trailing-period:), sans bloquer.

Preuve

Garde réel rejoué, entrées vivantes identiques (body de #15733, --pr-closing-refs 15674), seul le lecteur change :

avant : {"guard_pass": false, "blocking_lane": "myia-po-2023:CoursIA. Énoncé", ...}
apres : {"guard_pass": true,  "blocking_lane": null, "reason": "no other lane holds a fresh active claim ..."}

Rouge-avant (garde retiré des deux jumeaux, restauration par cp) :

7 failed, 2 passed
FAILED test_lane_sentence_period_stops_the_token_primary
FAILED test_lane_sentence_period_stops_the_token_fallback
FAILED test_lane_sentence_period_parse_grain_tag_same_lane
FAILED test_lane_sentence_period_residue_witnessed
FAILED test_lane_period_is_the_discriminator
FAILED test_lane_period_after_accented_workspace
FAILED test_delivered_marker_sentence_period_is_not_a_phantom_lane

Le test bout-en-bout échoue en montrant le token fantôme dans le verdict JSON du garde : "myia-po-2023:CoursIA. Énoncé" — le message CI reproduit, pas une assertion hors-sol.

$ python -m pytest scripts/tests/test_grain_tag.py scripts/tests/test_check_lane_claim.py -q
403 passed, 1 skipped

Non-régressions couvertes : LivresAgités Épisode (continuation accentuée sans point) intacte ; A.B et host.example:Baz intacts ; #12719 (point final, PR #12530) intact ; les 5 marqueurs fondateurs de #12719 et la table #13830 inchangés.

Perimetre

3 fichiers :

Fichier Δ
scripts/grain_tag.py +30 / −3 — les deux jumeaux, docstrings extract_lane + lane_marker_residues
scripts/tests/test_grain_tag.py +70 — 6 tests (les deux jumeaux, parse_grain_tag, témoin de résidu, paire discriminante, . interne)
scripts/tests/test_check_lane_claim.py +32 — 1 test bout-en-bout sur _run_check

Classe voisine signalée dans l'issue, non modifiée ici (hors scope PR) : check_unaddressed_nits.py _OVERRIDE_LANE refuse le blanc, donc n'avale pas de prose — mais laisserait un point final dans la lane capturée. Aucun cas observé.

Closes #15864.

🤖 Generated with Claude Code

… — point final suivi de prose

Le lecteur unique de la lane (grain_tag.extract_lane, #9485) admet le `.` DANS
le token (#12719, hostnames) et une majuscule ACCENTUEE en tete de mot de
continuation (#13830, la vraie lane `myia-ai-01:LivresAgités Épisode`).
Combines, ils avalent la prose : `lane myia-po-2023:CoursIA. Énoncé réécrit`
se lisait `myia-po-2023:CoursIA. Énoncé` — le point n'est plus FINAL, donc le
`rstrip(".")` de #12719 ne le voit pas.

Consequence mesuree : `lane_claim_required.py` bloque la lane declarante sur
son PROPRE claim, en accusant une lane qui n'existe pas (le gate ne bloque que
sur une lane != pr_lane). Declencheur : un `[DELIVERED]` re-marque le claim
tant que sa PR est ouverte (#12320), et il est suivi de prose francaise qui
commence naturellement par une majuscule accentuee. 4e variante de la classe
d'auto-blocage (#12145, #12719, #13830).

Le `.` suivi d'un blanc (ou de la fin du texte) est de la ponctuation : un
`(?<!\.)` devant la clause de continuation fait finir le token au point, et le
strip de #12719 s'applique comme avant. Un `.` a l'interieur du token
(`A.B`, `host.example:Baz`) est suivi d'un non-blanc et survit.
Applique aux DEUX jumeaux (`_LANE_RE` et `_LANE_FALLBACK_RE`, discipline
#12145) ; le temoin `lane_marker_residues` continue de declarer la forme
malformee (`trailing-period:`).

Verification : 7 tests neufs, rouge-avant mesure (7 echecs sans le garde, le
verdict JSON du garde portant le token fantome), 403 passed / 1 skipped avec.

Closes #15864.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@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 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2023:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #15624 (MED/guard, merge a 2026-09-12T00:58:00Z), #15587 (MED/docs, merge a 2026-09-12T03:10:43Z), #15679 (MED/docs, merge a 2026-09-12T08:08:33Z), #15649 (MED/docs, merge a 2026-09-12T10:09:21Z), #15593 (LIGHT/docs, merge a 2026-09-12T17:28:47Z), #15753 (MED/guard, merge a 2026-09-12T18:44:02Z), #15765 (MED/guard, merge a 2026-09-12T22:38:42Z), #15804 (MED/guard, merge a 2026-09-12T22:38:48Z), #15771 (MED/guard, merge a 2026-09-12T22:38:55Z), #15784 (LIGHT/docs, merge a 2026-09-12T22:39:02Z), #15789 (LIGHT/docs, merge a 2026-09-12T22:39:24Z), #15792 (LIGHT/docs, merge a 2026-09-12T22:40:50Z)).
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 added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) labels Sep 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=8 genre=12 cap=12)
  • GENRE-RUN : run consecutif d'un genre LIGHT (voir signals.runs dans le log du job)

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 added variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) and removed variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) labels Sep 13, 2026
@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #15866 (fix(guards,#15864): extract_lane refuse la lane fantome « X. Enonce » — point final suivi de prose) 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.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@jsboige

jsboige commented Sep 13, 2026

Copy link
Copy Markdown
Owner Author

Instance reelle mesuree aujourd'hui — cette classe n'est pas theorique.

Le piege a bloque un PR ce matin. #15733 est reste rouge sur l'organe lane_claim avec ce verdict (helper du depot, scripts/ci/lane_claim_required.py) :

{"guard_pass": false, "pr_lane": "myia-po-2023:CoursIA",
 "blocking_lane": "myia-po-2023:CoursIA. Énoncé", "blocking_issue": 15674, ...}

myia-po-2023:CoursIA. Énoncé n'existe pas : c'est mon propre [DELIVERED] sur #15674 qui ecrivait lane myia-po-2023:CoursIA. suivi d'un mot accentue. Le gate en a conclu que ma lane fermait une issue detenue par une AUTRE lane — la mienne, deformee.

La forme du defaut n'est pas liee a l'accent. extract_lane au head de ma branche (base main + un notebook, donc le code non corrige) :

'lane myia-po-2023:CoursIA. Énoncé réécrit'   -> 'myia-po-2023:CoursIA. Énoncé'
'lane myia-po-2023:CoursIA. Next sentence'    -> 'myia-po-2023:CoursIA. Next'
'lane myia-po-2023:CoursIA — énoncé réécrit'  -> 'myia-po-2023:CoursIA'

Deuxieme ligne : . + n'importe quel mot suffit (ASCII compris). Si le test de ce correctif ne couvre que la variante accentuee, il laissera passer la forme ASCII — qui est la plus probable en prose anglaise.

Reparation a la source (utile a connaitre, elle ne demande aucun code) : editer le commentaire fautif au tiret cadratin — la forme que son [CLAIMED] voisin utilisait deja et qui parse correctement. Helper rejoue apres l'edition : {"guard_pass": true, "blocking_lane": null, ...} EXIT=0, et l'organe est reparti au vert.

Scan borne du perimetre qui bloque (PRs ouvertes -> issues visees par un closing-ref -> commentaires de claim -> extract_lane) : 93 PRs ouvertes, 24 issues visees, 0 lane fantome restante apres la reparation de la mienne. Donc rien d'autre n'est bloque par ce mecanisme a cet instant — ce correctif retire la classe de mine, il ne debloque pas un PR en attente.

Semantic conflict #15864 x #15918 resolved in scripts/grain_tag.py.

Textual state: extract_lane auto-merged to main's #15918 structure (bare
token strictly before the keyworded match on the marker line wins; ties
keep "the primary"), while _LANE_RE kept the #15864 sentence-period stop.
scripts/tests/test_grain_tag.py conflicted (both sides append test
blocks) -- resolved by keeping BOTH families.

The hidden regression: #15918's fallthrough keeps the BODY match m as
the tie primary, but #10395 Variante 1 calls extract_lane with a silent
body and the lane only on the marker line ("lane X. Enonce ..." -- the
#15864 phantom). Tie -> `if m:` with m=None -> None, so acceptance
#15864-2 failed on the merged tree (1 failed / 111 passed).

Fix: after the #15918 bare-precedence gate, return the marker-line
keyworded match m_kw when the body primary is absent. Both real callers
(check_lane_claim.py:617/619) pass the marker line as part of the body,
so m is non-None whenever m_kw is and the new branch stays dead for
them; #15918's precedence and tie tests pass unchanged.

Merged tree: test_grain_tag.py 112 passed, test_check_lane_claim.py
302 passed / 1 skipped, 10 other grain_tag consumers 379 passed.

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

jsboige commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

Résolution du conflit avec main — merge 2aa67d2190 ( Repair propre PR, pattern merge-main-into-branch, #16072 ).

État textuel. Un seul conflit marker : scripts/tests/test_grain_tag.py (les deux côtés ajoutent des blocs de tests à la même zone — #15864 côté branche, #15918 côté main). Résolu en gardant les deux familles.

Conflit sémantique caché (le vrai sujet). scripts/grain_tag.py a auto-mergé silencieusement vers la structure #15918 : le fallback marker_line ne retourne plus que le token bare quand il précède strictement le match keyworded ; l'égalité de position retombe sur if m: — le match du body. Or l'acceptance #15864-2 appelle extract_lane avec un body muet et la lane uniquement sur la marker line (#10395 Variante 1) : égalité → m=None → retour None. La PR de réparation de ses propres PRs était elle-même bloquée par la régression — test test_lane_sentence_period_stops_the_token_fallback en échec sur l'arbre mergé (1 failed / 111 passed).

Fix délibéré. Après le gate de précédence #15918, retour du match keyworded de la marker line quand le primaire du body est absent (grain_tag.py:589-599). Les deux callers réels (check_lane_claim.py:617/619) passent la marker line comme partie du body → m non-None dès que m_kw l'est → la nouvelle branche reste morte pour eux ; les tests de précédence et d'égalité #15918 passent inchangés.

Validation sur l'arbre mergé :

Branche pushée (9b802ab69d..2aa67d2190), mergeable: MERGEABLE — plus de conflit avec main. BLOCKED restant = plancher DWELL 120 min re-armé par le push, le sweep horaire le lève (règle : jamais de push pour "corriger" un dwell).

🤖 Generated with Claude Code

jsboige added a commit that referenced this pull request Sep 14, 2026
Sole conflict: scripts/tests/test_pr_gate_sweep_select.py -- both sides
append test blocks at the same site (branch #15976 advisory-exemption
family vs main #15775 cancelled-constituent 5-field family). Resolved by
keeping BOTH families concatenated.

Unlike #15866, no hidden semantic conflict: the selector auto-merge
carries both features and the full file passes (35 passed), plus
sibling pr-gate suites (192 passed) and the workflow YAML validates --
main's workflow pin test (5-field consumption) and the branch's
advisory-exemption tests coexist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@myia-ai-01

Copy link
Copy Markdown
Collaborator

Review ai-01 — je suis le premier lecteur de cette PR (surface reviews[] vide, mesuree).

Le rc=0 de l'organe ne dit pas ici "approuvee" : il dit "personne n'a rien ecrit". Je l'ai donc lue integralement plutot que de merger sur un vert d'absence.

Le correctif est le bon invariant. Le lookbehind (?<!\.) refuse la continuation quand le token est suivi d'un point PUIS d'une espace -- ponctuation de phrase -- tandis qu'un point INTERNE a un hostname est suivi d'un non-espace et survit. C'est la distinction juste, et elle est posee sur les deux jumeaux _LANE_RE et _LANE_FALLBACK_RE. Ce dernier point est decisif : la classe de bug #12145 s'est deja rouverte une fois sur le seul fallback parce qu'un correctif n'avait bouge qu'une des deux moities. Ici les deux bougent ensemble, et le commentaire le dit explicitement.

Complementarite verifiee : le lookbehind arrete le token au point, le rstrip(".") de #12719 retire ce point -- les deux moities se repondent au lieu de se doubler.

Verifie aussi : 3 fichiers, +142/-3, scope conforme au titre ; tests ajoutes des deux cotes (+68 test_grain_tag, +32 test_check_lane_claim) ; 14 organes success / 1 skipped a la tete 2aa67d2190 ; 0 thread inline (mesure GraphQL) ; base == main.

Un point signale, non bloquant : le label lane-claim-absent est pose et l'issue #15864 ne porte aucun [CLAIMED]. Le garde est advisory et la PR est livree, donc je ne bloque pas -- mais aucun verrou de lane n'a protege ce grain pendant son developpement. A poser au prochain grain de cette famille plutot qu'a rattraper ici.

Les signaux G-VAR portes en label datent du 09-12 ; je les re-mesure au jour du merge, pas sur leur cliche.

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

Labels

lane-claim-absent Closing issue carries no claim at all (#10223) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

2 participants