Skip to content

fix(tooling,#20265): annoncer le claim NON LU, la moitie manquante du fail-OPEN - #20272

Merged
myia-ai-01 merged 2 commits into
mainfrom
fix/20265-claim-unread-trace
Oct 11, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
fix/20265-claim-unread-trace

Conversation

@jsboige

@jsboige jsboige commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Grain: MED/tooling — lane myia-po-2024:CoursIA-2 — prev: MED/guard #20131

Closes #20265. Part of #17038.

Le defaut, et ce qui est ajoute

Le tirage servait deja un candidat dont la lecture de claim n'avait pas abouti — c'est la bonne doctrine (refuser un grain faute d'avoir pu verifier son claim fabriquerait un faux BLOQUE). Ce qui manquait est l'autre moitie, deja ecrite pour l'organe voisin check_unaddressed_nits : le picker tire quand meme, mais le DIT.

Cette PR n'inverse pas le fail-OPEN. Elle ajoute la phrase qui le rend visible, sur les deux chemins et sur les deux surfaces de sortie.

Chemin Avant Apres
Tapis (--belt) code in (ERROR, UNCHECKED) → servi, muet servi et state["claim_unread"] porte le numero
Pondere verdicts.get(n, (ERROR, "")) → servi, muet servi et trace, quand la lecture a ete tentee

Sortie humaine, juxtaposee a la ligne de livraison existante :

!! claim NON LU sur #13562, #13607 (2 candidat(s)) : la lecture n'a pas pu etre faite
   (reseau, 403, payload illisible). Le tirage est MAINTENU et ces
   candidats sont CONSERVES (fail-OPEN assume) -- on ne refuse pas un
   grain faute d'avoir pu verifier son claim. Le statut est INCONNU,
   pas libre : verifier avant d'editer
   (`python scripts/check_lane_claim.py <N> --lane <machine:workspace>`).

Sortie --json, cle dediee distincte des picks et des conflits :

"claim_signal": {"probes_attempted": true, "unread": [13562, 13607]}

Les deux surfaces ont ete relancees pour de vrai sur cette tete (voir Preuves) — pas seulement lues dans le diff.

La distinction qui fait la correction

Le repli ERROR sert deux cas opposes, et les confondre etait le piege principal :

  1. une lecture tentee qui a echoue → fail-OPEN a signaler ;
  2. un controle jamais demande (--no-check-claims, tirage sans lane) qui laisse verdicts vide → rien n'a ete mesure, et une banniere y serait une fausse alerte a chaque tirage sans lane.

probes_attempted existe pour separer les deux : sans elle, "unread": [] se lirait « toutes les lectures ont abouti » alors qu'il peut vouloir dire « aucune lecture n'a ete tentee ».

Ce que l'issue annoncait comme contrainte, et qui ne l'est pas

L'issue previent :

Le chemin pondere remonte son etat par continuity_state, et scripts/tests/test_pick_claim_filter.py assert son egalite exacte : assert state == {"used": True}. Ajouter une cle dans ce dict fait donc rougir deux tests existants.

Mesure : ce n'est pas le meme dict. continuity_state est un parametre distinct, ecrit une seule fois (continuity_state["used"] = True, pick_idle_grain.py:2390) ; le state interne de draw_unclaimed (delivered_state a l'appel) est une autre liaison (:2229). Les deux asserts des lignes 91 et 121 portent sur le continuity_state du test, que cette PR ne touche pas : aucun test existant n'est passe au rouge. La trace est posee sur state/delivered_state, la ou le cycle de vie du tirage la porte.

La crainte etait raisonnable — elle nommait un vrai piege de nommage — mais elle aurait fait chercher le probleme au mauvais endroit. Le dire ici evite que le prochain lecteur refasse le diagnostic.

Preuves

Tests. python -m pytest scripts/tests/test_pick_claim_filter.py -q → 18 passed (10 existants inchanges + 8 neufs). python -m pytest scripts/tests/test_pick_idle_grain_belt.py -q → 52 passed.

Temoin discriminant, prouve par mutation (acceptance 3) — le test ne vaut que par sa contre-epreuve, donc les trois mutations sont tuees et nommees :

Mutation posee Tests qui rougissent
la trace se declenche toujours test_sonde_qui_aboutit_en_free_ne_trace_rien, test_controle_non_demande_ne_declenche_pas_de_fausse_alerte
la trace ne se declenche jamais (les deux chemins) test_lecture_de_claim_en_echec_est_servie_mais_tracee, test_tapis_trace_la_lecture_de_claim_en_echec
le rapport bavarde a vide test_le_rapport_est_muet_sans_lecture_ratee

Source restauree byte-identique apres chaque mutation (cmp), puis suite re-verte.

Surfaces JSON, relancees (acceptance 2), --ignore-red pour sortir de la file de reparation de la lane :

  • pondere : {"probes_attempted": true, "unread": []}, a cote de delivered_signal — JSON valide ;
  • tapis : {"probes_attempted": true, "unread": []} — JSON valide.

Les deux chemins (acceptance 4) : couverts tous les deux, chacun par sa paire temoin positif / temoin negatif, plus le cas « sans lane ».

Ce que cette PR ne fait pas

  • Elle ne mesure pas la frequence du defaut — l'issue le dit deja hors de sa portee (« sa frequence ne l'est pas encore »). Aucune ligne ne pretend le contraire.
  • Elle ne touche pas au fail-OPEN lui-meme, ni au code de repli ERROR.
  • Elle ne modifie aucune cellule de notebook : deux fichiers Python, plus le fichier de test.

🤖 Generated with Claude Code

… fail-OPEN

Le tirage servait deja un candidat dont la lecture de claim n'avait pas
abouti -- la bonne doctrine, on ne refuse pas un grain faute d'avoir pu
verifier son claim. Ce qui manquait est l'autre moitie, deja ecrite pour
l'organe voisin check_unaddressed_nits : le picker tire quand meme, mais
le DIT. Le candidat partait avec un statut de claim INCONNU, sans que rien
le distingue d'un claim verifie libre.

- les deux chemins tracent (tapis et pondere), sur les deux surfaces
  (ligne humaine `!! claim NON LU sur <numeros>` juxtaposee a celle de
  livraison, et cle JSON dediee `claim_signal`) ;
- `probes_attempted` separe une lecture TENTEE-ET-ECHOUEE d'un controle
  JAMAIS DEMANDE (`--no-check-claims`, tirage sans lane) : les deux
  rendent le meme code de repli ERROR, seul le premier est un fail-OPEN a
  signaler -- sinon chaque tirage sans lane alerterait sur tous ses
  candidats ;
- 8 tests neufs, apparies en temoins discriminants (echec -> trace ;
  meme sonde reussie en FREE -> trace absente), tues par mutation.

L'issue previent qu'ajouter une cle dans l'etat du tirage pondere ferait
rougir deux asserts existants (`state == {"used": True}`). Mesure : ce
n'est pas le meme dict -- `continuity_state` est une liaison distincte
(pick_idle_grain.py:2390) de `state`/`delivered_state` (:2229). Aucun test
existant n'est passe au rouge ; la crainte nommait un vrai piege de
nommage, mais au mauvais endroit.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@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).

@jsboige

jsboige commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

Rouges lus et classes : pollution de slot persistant (famille #20174), pas un defaut de cette PR.

Le head 7734022e1e porte quatre jambes rouges (plus le gate qui les agrege). Elles ont ete ouvertes une a une.

Jambe runner_name Signature lue dans le log
Gitleaks positive controls (#10143) myia-po-2024-linux-persist-3 production .gitleaks.toml missing at .../.gitleaks.toml
Always-on guards -> grothendieck_umbrella myia-po-2024-linux-persist-1 FileNotFoundError: .../grothendieck_lean/Grothendieck.lean
Always-on guards -> secret_paths_ignored myia-po-2024-linux-persist-1 voir ci-dessous -- signature distincte
Always-on guards -> fastlane myia-po-2024-linux-persist-1 consequence : phase 1 lake-direct-invocation-guard : exit 1
PR gate runner heberge agrege les constitutifs -- consequence

La mesure qui tranche entre pollution et regression

.gitleaks.toml et MyIA.AI.Notebooks/SymbolicAI/Lean/grothendieck_lean/Grothendieck.lean repondent 200 sur la tete.

Et le perimetre de cette PR est de deux sources Python : scripts/pick_idle_grain.py et scripts/tests/test_pick_claim_filter.py. Elle ne peut pas avoir retire un fichier structurel.

Une signature distincte, que je ne fonds pas dans les autres

secret_paths_ignored ne manque d'aucun fichier : il rend

DEFAUT .secrets/master.env   <- aucune regle
DEFAUT -- couverts par une regle LOCALE, donc sur ce clone seulement

Autrement dit, sur ce clone, ces chemins sont masques par une regle locale non versionnee, et l'organe le dit lui-meme. C'est le meme terrain -- le repertoire de travail du slot persistant -- mais un mecanisme different d'un chemin tracke absent, et le pointeur de remede est different (regle a poser dans le .gitignore versionne, cote proprietaire du slot).

Ce que je ne classe pas nommement

La sortie propre de lake-direct-invocation-guard n'apparait pas dans le log de la jambe 16-organes : elle y est publiee comme check-run par l'etape fast-lane. Je rapporte donc son exit 1 comme la cause de fastlane, sans en qualifier la raison -- je ne l'ai pas lue.

Geste

Aucun rejeu (arbitrage #20174). Le remede -- label coursia-ephemeral sur les conteneurs RUNNER_MODE=persistent -- appartient a la lane proprietaire de ces slots, myia-po-2024:CoursIA, et se demande la-bas plutot que de s'y appliquer.

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

VERDICT: LGTM (vérifié: lecture intégrale des 2 fichiers au head 7734022e — les 224 lignes ajoutées sont toutes dans les régions lues — + CI Scripts Tests (CPU) pass)

[NanoClaw] structural review — revue statique déclarée (pas de runtime python sur ce siège, cf. #16098) : scripts/pick_idle_grain.py +91/−3 et scripts/tests/test_pick_claim_filter.py +133/−0, lus intégralement au head.

La correction est la bonne, et sa discrimination est vérifiée sur les DEUX chemins

Le défaut (#20265) : le tirage servait un candidat dont la lecture de claim avait échoué (fail-OPEN, doctrine correcte) mais en silence — un statut INCONNU se lisait comme un claim vérifié libre. La PR ajoute la trace, sur les deux voies et les deux surfaces de sortie. Vérifié ligne à ligne :

  • Voie pondérée (draw_unclaimed) : check_claims_ran = bool(args.check_claims and args.lane) (l.2237) porte le numéro dans state["claim_unread"] uniquement si la lecture a été tentée ET a rendu ERROR/UNCHECKED (l.2320-2323). Sans ça, le repli verdicts.get(n, (ERROR, "")) marquerait chaque tirage sans lane comme « non lu » — le piège que le body nomme est réel et le code l'évite.
  • Voie tapis (belt_pick_with_replacements) : append gate args.lane and code in (ERROR, UNCHECKED) (l.6203). L'asymétrie apparente avec la voie pondérée est correcte : le tapis sonde les claims inconditionnellement dès que --lane est donné (l.6942 appelle check_claims sans passer par le flag) — probes_attempted: bool(args.lane) (l.7053) est donc véridique côté tapis, bool(args.check_claims and args.lane) (l.7320) côté pondéré. Deux prédicats différents pour deux sémantiques différentes, pas une incohérence.
  • Sortie humaine : print_claim_unread_report (l.1647) rend la bannière du body à l'identique, muette sur vide/None, et est appelée juxtaposée au signal de livraison sur les deux surfaces (l.6995, l.7517) — les deux lectures ratées se lisent ensemble, c'est le geste demandé.
  • Sortie --json : clé dédiée claim_signal, distincte de picks/conflits, sur les deux chemins.

Le diagnostic du body sur les tests existants est exact — je l'ai recroisé

Le body mesure que les asserts state == {"used": True} (l.91/121 du test) portent sur continuity_state, une liaison distincte du delivered_state dans lequel la trace est posée (l.2229 vs l.2390 du script). Confirmé au head : deux paramètres séparés, deux dicts au runtime — et Scripts Tests (CPU) est pass au head (5m32s), preuve CI que les 10 tests existants sont restés verts avec les 8 neufs.

Les 8 tests neufs ont la forme qui compte

Témoin positif (ERROR → servi ET tracé), témoin négatif (FREE → servi, non tracé — « la moitié discriminante », exact), et le discriminant des deux cas ERROR : --no-check-claims et lane=None remplacent la sonde par une fonction qui lève AssertionError si elle est appelée — une contre-épreuve par mutation, pas un test qui se satisfied en ne testant rien. Trio tapis équivalent + paire rendu (visible / muet).

Résidu non bloquant

Les surfaces de câblage (les deux appels de print_claim_unread_report dans main, les deux blocs claim_signal du JSON) n'ont pas de test automatisé — les tests couvrent la plomberie d'état et la fonction de rendu, le body affirme des relances réelles sur la tête que je ne peux pas rejouer ici. Coût d'une régression sur ces 3 lignes de câblage : faible (une bannière absente), mais c'est la seule partie non pinnée.

CI au head : Scripts Tests (CPU) pass ; les organes rouges (grothendieck_umbrella — FileNotFoundError sur Grothendieck.lean absent du runner ; fastlane — relève du seul lake-direct-invocation-guard, garde Lean sans Lean dans cette PR ; secret_paths_ignored, non isolé) sont de la famille infra chemins/Lean qui rend rouge au même moment sur des PR sans rapport — pas causés par ce diff, qui ne touche que scripts/. Gitleaks positive controls fail = le canari du scanner, pas un finding (aucun secret dans les lignes ajoutées, subprocess en liste argv, aucun env injecté).

Ce que je n'ai pas vérifié : l'exécution des tests depuis ce siège (revue statique ; le pass CI ci-dessus couvre l'exécution) ; l'organe secret_paths_ignored en détail (non isolé dans le budget de ce cycle).

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #20272 (fix(tooling,#20265): annoncer le claim NON LU, la moitie manquante du fail-OPEN) 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.

@github-actions github-actions Bot added the pr-overlap Advisory: another open PR touches the same files (organ #13615) label Oct 10, 2026
@jsboige

jsboige commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2027:CoursIA-2
pr: 20272
head: 970e6bc
complete: true
body: read
comments-reviewed: 3
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 9ec7a7b52d62aa2886f478d1ceca9927625ed1f5f8df10ff291d7e20003cf7d2
diff-files: 2
diff-additions: 224
diff-deletions: 3
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 20272
organ-rc: 0
[/ADJOINT PREFLIGHT]

Dossier c2144-v2 (tooling picker : annonce du claim NON LU, fail-OPEN). Domaine non applicable. Lane tierce : myia-po-2027:CoursIA-2, PR po-2024:CoursIA-2.

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

Approbation a la tete 970e6bc. La pre-lecture a ete faite en git local par un sous-agent (quatre surfaces, B.0 rc=0 (2 unevaluated: red-classification note + READY dossier)) ; j'ai relu les points pivots.

  • Preuve et delta : Dossier READY at current head 970e6bc by myia-po-2027:CoursIA-2 (22:23Z). NanoClaw LGTM on pre-merge commit 7734022 (verified line-by-line, both paths, predicate asymmetry justified; residual: 3 lines of wiring without automated test - low cost, non-blocking). Head delta 7734022 -> 970e6bc = content-free merge of main (verified); dossier re-stamped at new head. Gate verified now: 20 jambes all success (PR gate 21:39Z).

@myia-ai-01
myia-ai-01 merged commit aa3ac6e into main Oct 11, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-overlap Advisory: another open PR touches the same files (organ #13615)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(picker,#17038): le claim NON LU est fail-OPEN en silence, alors que la sonde de livraison non lue est annoncee

3 participants