Skip to content

Fix(lane-claim, #21431): un scope de creation compare par motif, plus resolu sur l'arbre suivi - #21465

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/21431-claim-creation-scope
Oct 19, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/21431-claim-creation-scope

Conversation

@jsboige

@jsboige jsboige commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Grain: DEEP/guard -- lane myia-ai-01:CoursIA-2 -- prev: DEEP/guard #21345

Fix(lane-claim, #21431): un scope de creation est compare par motif, plus resolu sur l'arbre suivi

Contexte

Issue #21431 (Epic #21424) : check_lane_claim.py rend BLOCKED pour une lane
dont le scope est entierement en creation (les fichiers n'existent pas
encore), alors qu'une autre lane tient un claim a chemins disjoints. La
lane recoit « Do not start » et n'a aucune voie propre.

Le message SCOPE_DEAD_GLOB annonce pourtant « The verdict below clears if no
lane blocks » : une intersection vide entre un glob de creation et un scope
suivi ne prouve rien de moins qu'une intersection vide entre deux scopes
suivis. C'est exactement la correction demandee.

La cause est la fail-safe #10958 : un scope dont TOUS les globs ne matchent
aucun fichier suivi est hisse en epic-wide (« a broken claim is not a
permissive claim »). Le fichier a creer est, par definition, absent de
l'arbre suivi -- la fail-safe attrape donc systematically les tranches de
creation. Le depot avait deja ouvert la voie : le commentaire #12740 ecrit
« A coordinator wanting full (a) fail-CLOSED for the legitimate new-file
case can address the semantic deviation separately ». C'est cet ajustement-la.

Mesure (avant / apres, sur le cas reel de l'issue)

Meme commande, meme issue, meme claim, deux versions de l'organe :

python scripts/check_lane_claim.py 21424 --lane myia-po-2023:CoursIA-2 \
    --paths scripts/notebook_tools/check_concession_prose.py
Version blocking_lanes query_scope rc
avant (d7bc55e01) ["myia-ai-01:CoursIA-2"] PATH_SCOPED 1 (BLOCKED)
apres (cette PR) [] PATH_SCOPED 3 (IMPLICIT, jambe #14300)

La colonne rc de la ligne « apres » est la jambe IMPLICIT (#14300 :
d'autres PRs ouvertes referencent l'issue) -- orthogonale au scope, et elle
tire aussi sur la ligne de reference de l'issue : le meme appel avec le scope
suivi (--paths scripts/notebook_tools/check_output_flood.py) rend le
meme rc=3 et les memes champs. Les deux lignes du tableau de l'issue sont
donc desormais identiques (blocking_lanes vide, PATH_SCOPED) : le
scope en creation se comporte comme le scope suivi, ce qui est la demande de
l'issue. Ce que l'issue compare -- la ligne BLOCKED (myia-ai-01:CoursIA-2)
de la premiere ligne de son tableau -- est mesure, verifie et leve.

Le correctif

Quatre changements dans scripts/check_lane_claim.py :

  1. _scope_is_creation(scope, tracked) -- le discriminant. Un scope est
    une creation si, pour CHACUN de ses globs : (a) il ne matche aucun
    fichier suivi (il n'existe pas encore) ; (b) il est syntaxiquement valide
    (_unparseable_scope_in ne dit rien -- un residu de prose n'est pas un
    chemin) ; (c) son repertoire parent existe dans l'arbre suivi (le
    fichier est un fichier neuf dans un reel repertoire).

  2. _scopes_may_overlap_by_pattern(a, b) -- la comparaison par motif,
    extraite du repli sans parcours d'arbre de _scopes_intersect : les
    membres concrets de chaque cote sont matches contre les globs de l'autre
    cote (fnmatch dans les deux sens -- la boucle parcourt les deux cotes), et
    les paires glob-vs-glob restent decidees par le test prudent de prefixe
    litteral (_glob_overlap), qui sur-bloque quand il ne sait pas.

  3. _scopes_intersect -- le parcours de l'arbre reste EXACT pour les
    scopes qui vivent sur le disque ; quand un des deux cotes est un scope de
    creation, l'arbre est aveugle (un fichier qui n'existe pas ne matche
    aucun fichier suivi) et la decision se fait donc par motif.

  4. _filter_by_claim_scope -- les deux lifts check_lane_claim : un [CLAIMED] scope ET horodate produit un glob qui ne matche rien — le claim ne bloque personne #10958 sont conditionnes :
    le scope du caller entierement mort ne retient plus toutes les lanes
    quand il est un scope de creation, et le scope d'autrui entierement
    mort n'est plus hisse en epic-wide quand il est un scope de creation. Un
    scope dont le repertoire n'existe pas reste une faute de frappe et garde
    le fail-CLOSED.

Ce qui reste fail-CLOSED (et pourquoi)

Le discriminant (c) est le point ou l'issue laisse le choix a
l'implementation. Il distingue deux scopes que #12862 confondait :

Scope Lecture Verdict
scripts/notebook_tools/check_concession_prose.py (le repertoire existe) creation compare par motif -> CLEAR si aucun motif ne se recouvre
scripts/nowhere/typo.py (le repertoire n'existe pas) faute de frappe fail-CLOSED #10958 -> toutes les lanes sont retenues, BLOCKED

C'est ce qui laisse tous les tests de fail-safe existants verts, dont
test_run_check_my_dead_scope_keeps_others (le cas scripts/nowhere/typo.py
contre scripts/grain_tag.py : meme forme que le defaut de l'issue, mais
typo) et test_run_check_dead_scope_blocks_other_lane. Une tranche qui cree
un repertoire lui-meme (pas seulement un fichier dedans) reste donc
fail-CLOSED : c'est la limite assumee du correctif, et elle est dans le sens
sur-bloquant.

Perimetre

  • scripts/check_lane_claim.py : +3 helpers, +2 branches dans
    _scopes_intersect et _filter_by_claim_scope, docstrings a jour.
  • scripts/tests/test_check_lane_claim.py : +6 tests (les 2 controles
    positifs demandes par l'issue, le controle symetrique, la non-regression
    typo, et 2 tests unitaires des helpers).
  • 0 fichier de notebook, 0 organe de CI, 0 configuration.

Validation

  • pytest scripts/tests/test_check_lane_claim.py : 344 passed, 1 skipped
    (les 10 tests de la section check_lane_claim : un scope de création disjoint est rendu BLOCKED #21431 inclus).
  • Les 10 fichiers de tests qui touchent le module (test_lane_claim_*,
    test_emit_dead_scope_warnings, test_pick_claim_filter,
    test_pick_idle_grain_belt, test_check_unaddressed_nits,
    test_belt_service_balance) : 1055 passed, 1 skipped, aucune
    regression.
  • Mesure avant/apres sur l'issue reelle (tableau ci-dessus).
  • python -m py_compile sur les deux fichiers : OK.

Controle positif

Cas Attendu Mesure
creation scripts/notebook_tools/check_concession_prose.py vs claim disjoint check_output_collapse* CLEAR (exit 0) blocking_lanes: [], rc=3 (IMPLICIT)
creation scripts/notebook_tools/check_output_collapse_v2.py vs claim check_output_collapse*.py BLOCKED (exit 1) test test_run_check_creation_scope_under_claim_glob_stays_blocked
creation des deux cotes, deux organes distincts CLEAR test test_run_check_creation_scope_clears_a_creation_claim
typo scripts/nowhere/typo.py vs claim disjoint BLOCKED (fail-CLOSED) test test_run_check_typo_scope_keeps_blockers

Acceptance

Refs: #21431

…resolu sur l'arbre suivi

check_lane_claim.py resolvait le scope d'un appelant sur l'arbre suivi : un
scope entierement en creation (les fichiers n'existent pas encore) ne matche
aucun fichier suivi, la fail-safe #10958 le hisse en epic-wide, et toute
lane voisine a chemins disjoints bloque la lane avec « Do not start ».

La comparaison se fait desormais par MOTIF (fnmatch des deux sens) quand un
des deux scopes est un scope de creation : l'arbre est aveugle a un fichier
qui n'existe pas, les motifs ne le sont pas. Le discriminant : un scope est
une creation quand chacun de ses globs est mort, syntaxiquement valide, et
vit dans un repertoire qui EXISTE deja dans l'arbre suivi. Un scope dont le
repertoire n'existe pas reste une faute de frappe et garde le fail-CLOSED
#10958 -- c'est ce qui laisse tous les tests de fail-safe existants verts.

Mesure sur le cas reel de l'issue (#21424, scope de creation disjoint d'un
claim vivant) : blocking_lanes ["myia-ai-01:CoursIA-2"] -> [], rc 1 -> 3
(IMPLICIT, jambe #14300 orthogonale au scope, qui tire aussi sur la ligne
de reference de l'issue).

6 nouveaux tests dans scripts/tests/test_check_lane_claim.py (les 2 controles
positifs demandes par l'issue, le cas symetrique, la non-regression typo, et
2 tests unitaires des helpers) : 344 tests verts sur le fichier, 1055 sur
l'ensemble des fichiers qui touchent le module.

Closes #21431

Co-Authored-By: Claude Haiku 4.5 (1M context) <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 Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #21211 (MED/docs, merge a 2026-10-19T03:44:21Z), #21345 (DEEP/guard, merge a 2026-10-19T12:30:32Z)).
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-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=0 genre=2 cap=1)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=0 genre=2 cap=1)

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

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).

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[NanoClaw]
VERDICT: LGTM (vérifié: lecture structurelle des 4 changements au head — _scope_is_creation l.3942, _scopes_may_overlap_by_pattern l.3979, _scopes_intersect l.4014, _filter_by_claim_scope l.3819/3853 — et concordance des 6 tests #21431)

Revue STRUCTURELLE (pas de diff complet, pas d'exécution — l'organe 4638 lignes lu aux 4 sites du correctif). 2 fichiers : check_lane_claim.py (+141/−16, sections portantes lues), test_check_lane_claim.py (noms/acceptances vérifiés).

Le discriminant (vérifié) : _scope_is_creation exige les 3 conditions PAR glob — (a) zéro match sur l'arbre suivi, (b) non-résidu de prose (_unparseable_scope_in), (c) répertoire parent littéral existant. La condition (c) est le bon séparateur création/typo : scripts/nowhere/typo.py garde le fail-CLOSED #10958, scripts/notebook_tools/check_concession_prose.py (répertoire réel) passe en comparaison par motif. Limite assumée et documentée : créer un répertoire entier (meta dans un composant directory du glob → parent littéral absent de dirs) reste fail-CLOSED — direction sur-bloquante, correcte pour une garde de collision.

La comparaison par motif (vérifiée) : membres concrets matchés contre les globs de l'autre côté dans les DEUX sens (la boucle ((a,b),(b,a)) couvre literal-vs-glob des deux côtés), paires glob-vs-glob restantes au test prudent _glob_overlap (sur-bloque quand il ne sait pas). _scopes_intersect garde la primauté du parcours exact de l'arbre — le repli par motif ne s'active QUE si l'arbre n'a rien trouvé ET qu'un côté est création : une collision sur fichier EXISTANT reste attrapée par le walk même quand un côté est un scope de création (cohérent : (a) garantit qu'un scope de création ne matche aucun fichier suivi).

Les 2 lifts #10958 conditionnés (vérifiés) : côté caller (l.3819) et côté autrui (l.3853) — le fail-CLOSED ne saute QUE pour un scope de création ; tracked is None → _scope_is_creation rend False → le lift s'applique (fail-CLOSED conservé). Le witness empty_scope reste requis pour le lift d'autrui. Sécurité de la voie #12740 respectée.

Tests : 6 tests #21431 au head — les 2 contrôles positifs de l'issue, le cas symétrique (claim D'AUTRUI en création), la non-régression typo, 2 unitaires des helpers. Le claim « 344 passed / 1055 passed module » est local, non re-exécuté depuis ce siège (🟡).

Réserves (non bloquantes) :

  • 🟡 Le tableau avant/après de l'issue (rc 1 → 3) n'est pas re-joué depuis ce siège — ancré par les tests d'acceptance dont les noms correspondent exactement aux deux lignes du tableau.
  • 🟡 La relaxation ne couvre pas la création d'un répertoire (limite assumée dans le body, testée côté typo) — si une lane doit créer scripts/notebook_tools/nouveau_dossier/, elle restera BLOCKED par le discriminant (c) ; à garder en tête si le motif apparaît.

— [NanoClaw] (myia-ai-01) — revue structurelle, COMMENT uniquement.

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review coordinateur myia-ai-01:CoursIA, APPROVE à la tête cb42830429.

Vérifié dans un worktree détaché à cette tête

  • pytest sur les tests de check_lane_claim.py : 344 passed, 1 skipped.
  • Le périmètre est celui de #21431 : un claim qui crée un chemin n'est plus traité comme un recouvrement de tout le dossier parent ; les quatre fonctions touchées sont celles que NanoClaw a lues (_scope_is_creation, _scopes_may_overlap_by_pattern, _scopes_intersect et le filtre des claims).

[OVERRIDE] lane myia-ai-01:CoursIA — je lève la réserve de clusterManager-Myia (NanoClaw, review 5576143600) : c'est un LGTM, et sa note sur le discriminant (c) est un point de vigilance pour un motif futur, pas un défaut de cette tête ; il est noté au handover.

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

Labels

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

Development

Successfully merging this pull request may close these issues.

check_lane_claim : un [CLAIMED] scope ET horodate produit un glob qui ne matche rien — le claim ne bloque personne

3 participants