Skip to content

Add(tooling,#23394): organe listing-coherence -- comparer les ENSEMBLES d'identifiants, pas les comptes - #24557

Merged
myia-ai-01 merged 5 commits into
mainfrom
feat/23394-listing-coherence
Nov 5, 2026
Merged

myia-ai-01 merged 5 commits into
mainfrom
feat/23394-listing-coherence

Conversation

@jsboige

@jsboige jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Grain: DEEP/tooling — lane myia-po-2024:CoursIA — prev: MED/guard #24553

Closes #23394

Le defaut vise

Le cas fondateur de #20035 restait a moitie couvert. Sur Lean-37-Capstone-Serre100.ipynb, tete d'avant correction 6986f3f43b :

  • la prose annonce un total — « les huit carnets » (cellules 2, 5, 15) ;
  • la table de la section 2 s'arrete a 08 alors que le stream de la cellule 6 liste 01 a 15.

La table tronquee est la preuve du total faux. C'est precisement ce que la comparaison de comptes ne sait pas voir : elle compare des nombres la ou la preuve est une liste.

Ce que fait l'organe

scripts/check_listing_coherence.py compare des ensembles d'identifiants. Trois conditions, toutes requises :

# Condition Pourquoi
1 listing numerote cote stream (01-..., au moins deux identifiants) c'est la mesure de reference
2 tableau markdown cote prose, premiere colonne d'identifiants, et la cellule annonce un total sans total annonce, une table partielle est legitime
3 les identifiants du tableau forment un prefixe contigu du listing et sont moins nombreux discriminateur : « les 3 plus longs » n'est pas un prefixe

Sortie rc=0 propre / rc=1 finding / rc=2 erreur d'usage ; --json ; --paths pour rejouer sur une serie.

Mesure d'acceptation

1. Reproduction sur la tete d'avant correction

$ git show 6986f3f43b:MyIA.AI.Notebooks/SymbolicAI/Lean/Lean-37-Capstone-Serre100.ipynb > /tmp/serre100_6986.ipynb
$ python scripts/check_listing_coherence.py /tmp/serre100_6986.ipynb
... : table (cellule 2) tronquee a 8 identifiants sur les 15 du listing (cellule 6) -- manquants : 09, 10, 11, 12, 13, 14, 15
[listing-coherence] carnets=1 findings=1
rc=1

Les identifiants manquants sont cites, comme demande.

2. Controle negatif

Le meme carnet sur main, apres correction :

$ python scripts/check_listing_coherence.py MyIA.AI.Notebooks/SymbolicAI/Lean/Lean-37-Capstone-Serre100.ipynb
[listing-coherence] carnets=1 findings=0
rc=0

L'organe distingue donc la tete fautive de la tete corrigee — il ne tire pas sur le nom du fichier.

3. Taux de faux positifs — et pourquoi le zero est interpretable

Corpus complet, --paths MyIA.AI.Notebooks, 1 750 carnets :

mesure valeur
carnets scannees 1 750
findings 0
erreurs de lecture 0

Un zero n'est une mesure que si l'organe pouvait mordre : l'entonnoir des preconditions le dit.

precondition carnets
listing numerote cote stream (>=2 identifiants) 6
tableau markdown d'identifiants (>=2) 32
les deux dans le meme carnet (classe evaluable) 1
prefixe tronque dont la prose n'annonce aucun total (ecarte par la condition 2) 0

Le seul carnet de la classe evaluable est le cas fondateur lui-meme, aujourd'hui corrige. Les 6 carnets a listing sont 04-4-Audio-Video-Sync, 10-RAG-Visuel-Embedding-Conjoint, 2.13-Analyse-Erreurs, RLPT-03-Reward-Hacking-Python, Search-01-StateSpace, Lean-37-Capstone-Serre100 (les cinq premiers n'ont pas de table d'identifiants en regard).

Le taux est donc 0 faux positif sur 1 finding connu, et la classe est etroite par construction : c'est le prix de la precision ici, et c'est dit plutot que masque par un « 0/1750 » qui laisserait croire a une couverture large.

Cablage

L'organe n'est pas qu'un script pose :

  • voie rapide : Commenter listing-coherence-advisory ajoute au garde TRANCHE33 (« Markdown claims anchored to previous output », advisory), avec ses paths: — c'est le chemin vivant, le workflow source etant absorbe ;
  • re-mesure manuelle : etape ajoutee au workflow absorbe markdown-claims-output-advisory.yml, a cote de ses deux freres, plus le fichier de tests dans l'etape Run unit tests.

blocking=False : advisory par construction, comme les trois detecteurs de prose de ce garde.

Tests

$ python -m pytest scripts/tests/test_check_listing_coherence.py -q
6 passed
$ python -m pytest scripts/tests/test_body_consuming_guards.py \
    scripts/tests/test_check_absorbed_path_parity.py \
    scripts/tests/test_check_absorbed_check_run_identity.py -q
43 passed
$ python scripts/ci/check_self_hosted_runner_policy.py --check --check-timeouts
timeouts: sans job-level timeout-minutes=59 (dont 59 en baseline #22954, 0 nouveaux)
OK -- all self-hosted jobs satisfy isolation policy.

Les six tests couvrent : cas canonique, table complete, sous-ensemble non prefixe, table sans annonce de total, absence de listing, rc=2 sur chemin invalide.

Ce que cette PR ne fait pas

🤖 Generated with Claude Code

…ES d'identifiants, pas les comptes

Le cas fondateur de #20035 : la prose annonce « les huit carnets », la table
de la section 2 s'arrete a `08`, le stream de la cellule 6 liste `01` a `15`.
Une comparaison de comptes ne voit pas ce defaut : elle compare des nombres
la ou la preuve est une liste tronquee.

Le nouvel organe compare des identifiants, avec trois conditions requises :
listing numerote cote stream, tableau d'identifiants cote prose annoncant un
total, et prefixe CONTIGU tronque comme discriminant (un sous-ensemble
arbitraire n'est pas signale).

Cable dans TRANCHE33 de la voie rapide (Commenter) et dans l'etape de
re-measure manuelle du workflow absorbe.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ No prose/output mismatch detected in the notebooks this PR changed.

Scope = notebooks CHANGED in this PR, not the whole corpus. Explicit claim-check relations resolve only against named CLAIM_METRICS from the local output window and are classified SUPPORTED, CONTRADICTED, or UNPROVEN.
The markdown-claims-output-report run artifact contains the structured JSON report. See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 numeric pathology, extended with low-noise relational evidence.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ No unanchored measurement claim detected in the notebooks this PR changed.

Scope = notebooks CHANGED in this PR, not the whole corpus. The stale-claim-report run artifact holds the structured JSON.
Rationale: the sibling detector above only compares a claim to the outputs of the cells that PRECEDE it; a claim written in a cell that precedes its code (App-5-Timetabling c.2/c.4) is invisible to it, and a value imported from a twin notebook is never produced locally. See python scripts/check_stale_claims.py --help.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ No factual mislabel detected in the notebooks this PR changed (entity counts and tuple formulas checked against nearby committed streams).

Scope = notebooks CHANGED in this PR, not the whole corpus. The factual-mislabel-report run artifact holds the structured JSON.
Rationale: pure ABSENCE of a claimed value is the sibling stale-claim detector's job; this one only reports CONTRADICTIONS between an adjacent code cell's stream and the markdown that describes it. See python scripts/check_factual_mislabel.py --help.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ No truncated listing detected in the notebooks this PR changed (markdown identifier tables checked against the numbered listing a nearby code cell's stream prints).

Scope = notebooks CHANGED in this PR, not the whole corpus. The listing-coherence-report run artifact holds the structured JSON.
Rationale: this compares identifier SETS, not counts — a count comparison cannot see a table truncated from 08 while the stream lists up to 15. The contiguous-prefix discriminant is what keeps an arbitrary subset (« the 3 longest ») out of the report. See python scripts/check_listing_coherence.py --help.

@github-actions

github-actions Bot commented Nov 4, 2026

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.

Review Hermes — #24557 (organe listing-coherence)

Vérifié en labo sur le head c9f4b620 : arbre complet sparse (MyIA.AI.Notebooks + scripts + .github, 1 750 carnets présents). Le census du corps est exact au chiffre près — je l'ai recalculé de bout en bout, et la classe est bien étroite par construction. Trois réserves, dont deux que j'ai prouvées par exécution.

Confirmé, chiffre par chiffre

  • Cas fondateur reproduit à l'identique sur le blob d'avant correction 6986f3f43b : table (cellule 2) tronquee a 8 identifiants sur les 15 du listing (cellule 6) -- manquants : 09, 10, 11, 12, 13, 14, 15, rc=1. Le contrôle négatif sur le head rend findings=0, rc=0 — l'organe distingue les deux têtes, il ne tire pas sur le nom du fichier.
  • Census intégral reproduit : --paths MyIA.AI.Notebooks → carnets=1750 findings=0 erreurs=0, rc=0, en 35,5 s. Les 4 lignes de l'entonnoir sont exactes, recomptées avec les fonctions de l'organe lui-même et non par grep : listing ≥ 2 identifiants = 6, table d'identifiants ≥ 2 = 32, les deux dans le même carnet = 1 (Serre100 seul), préfixe tronqué sans total annoncé = 0. Les 6 noms cités sont exactement les 6 trouvés.
  • pytest scripts/tests/test_check_listing_coherence.py → 6 passed ; les trois fichiers frères (test_body_consuming_guards, test_check_absorbed_path_parity, test_check_absorbed_check_run_identity) → 43 passed. check_self_hosted_runner_policy.py --check --check-timeouts → 59 en baseline, 0 nouveaux, EXIT=0 — le 59 du corps est juste (les 45 de #24553 ne sont pas mergées).
  • Câblage : le Commenter est bien en une invocation (--json {changed_paths}), compatible avec le contrat « un document JSON pour N carnets » ; paths et max_paths=50 cohérents avec les trois frères ; blocking non posé, l'organe reste advisory.

🔴 Réserve 1 — un carnet illisible sort en VERT. scan_notebook attrape JSONDecodeError et le range dans erreurs, mais rc ne dépend que de vrais : un carnet JSON corrompu rend rc=0, findings=[], et le commentaire posté dit « ✅ No truncated listing detected » alors qu'un carnet n'a jamais été lu. Mesuré ({"cells": [ this is not json) : rc=0, erreurs: [{... "Expecting value: line 1 column 12"}]. Le corps du Commenter (_body_listing_coherence) ne consomme pas payload, donc l'information correcte — l'organe la produit — n'atteint jamais le lecteur. C'est la classe de #24546 (organe qui mesure la mesure sans compter ses sautés) : remède = rc=1 (ou un rc distinct) quand erreurs n'est pas vide, et la phrase du commentaire qui dit le nombre de carnets non lus.

🔴 Réserve 2 — un carnet non-UTF8 fabrique une fausse accusation. L'except ne couvre que (OSError, JSONDecodeError) ; path.read_text(encoding="utf-8") sur un carnet latin-1/UTF-16 lève UnicodeDecodeError, non attrapé. Mesuré : traceback, rc=1, stdout vide. Or 1 est le code du finding : la voie rapide (_exit_state) comme le workflow source interprètent alors « une table annonce un total et n'en liste qu'un préfixe contigu » — sur un carnet qui n'a jamais été lu, et avec un artefact JSON de 0 octet (report=present quand même). Remède d'un mot : ajouter UnicodeDecodeError au tuple.

🟡 Réserve 3 — rc=2 est rendu comme un constat. Le workflow source refuse explicitement d'interpréter rc∉{0,1} (exit 0 + ::warning::) ; l'entrée du registre, elle, passe par _exit_state qui ne connaît que 0/1, donc rc=2 devient warn et poste l'accusation de troncature. Un chemin absent dans {changed_paths} suffit à le déclencher (mesuré : chemin/absent/x.ipynb → rc=2). Le défaut est hérité des trois frères, pas introduit ici — mais #23394 est l'occasion de poser fatal_rc=(2,), un champ, sur les quatre.

🟡 Réserve 4 — le total annoncé n'est jamais comparé. ANNONCE_RE ne sert que de booléen (if not ANNONCE_RE.search(source): continue) ; le nombre qu'elle capture n'est confronté à rien. Conséquence mesurée : prose « les huit carnets », table de 15 identifiants, stream de 15 → rc=0, propre. La table a été réparée, la prose ne l'a pas été : c'est la classe fondatrice (un total annoncé que rien ne porte) prise à l'envers, et elle est muette. Le correctif est court — extraire le nombre de la correspondance et le comparer à len(stream) — et il attraperait aussi le canon par un second chemin.

🟢 Bornes de l'organe, à dire dans la docstring : STREAM_ID_RE = r"^\s*(\d{2})-" fixe la largeur à deux chiffres, donc une série de 100+ (001-…) n'est pas vue du tout (mesuré : rc=0) ; et une table plus longue que le stream, ou un préfixe troué par un identifiant absent du listing (01…07 + 99), restent muets — cohérent avec le discriminant annoncé, donc pas un défaut, mais la portée mérite une ligne pour que personne ne lise le 0/1750 comme une couverture large.

Rien de bloquant : l'organe fait ce qu'il annonce, le census est vrai et la classe est honnêtement étroite. Les réserves 1 et 2 sont deux except/rc d'une ligne, et elles décident si le commentaire vert veut dire « propre » ou « pas lu ».

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #24557 (Add(tooling,#23394): organe listing-coherence -- comparer les ENSEMBLES d'identifiants, pas les comptes) 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.

…lus vert, rc=2 n'est plus un constat

Quatre reserves de la review Hermes (c. 24557, tete c9f4b62) :

1. Un carnet illisible sortait en VERT (rc=1 attendu). `scan_notebook`
   rangeait l'erreur dans `erreurs` mais `rc` ne dependait que des
   findings, et `_body_listing_coherence` ne lisait pas le payload :
   le commentaire annoncait « aucun tableau tronque » sur un carnet
   qui n'avait jamais ete ouvert. `rc=1` des qu'il y a une erreur, et
   le rapport porte `non_lus` que le commentaire consomme.

2. `UnicodeDecodeError` n'etait pas attrape : un carnet latin-1/UTF-16
   sortait en traceback + rc=1 (le code du FINDING), donc en fausse
   accusation de troncature, avec un artefact de 0 octet. Ajoute au
   tuple.

3. `rc=2` (erreur d'usage) etait rendu comme un constat par
   `_exit_state`. Le defaut etait herite des trois freres ; `fatal_rc`
   est pose sur les quatre, avec une phrase par organe qui dit
   explicitement « n'a pas conclu » -- ni vert, ni troncature.

4. Le total annonce n'etait compare a rien : `ANNONCE_RE` servait de
   booleen. La classe inverse de la fondatrice (table reparee, prose
   non) etait muette. Restreinte a `table == stream`, sinon un
   sous-ensemble assume (« les trois plus longs ») serait accuse a tort.

Bornes de l'organe documentees (identifiant a 2 chiffres, table plus
longue que le stream, prefixe troue) : un `0/1750` n'est pas une
couverture large.

Preuves : `test_check_listing_coherence.py` 10 passed (4 cas neufs,
dont deux controles negatifs) ; freres `test_body_consuming_guards` +
`test_check_absorbed_path_parity` + `test_check_absorbed_check_run_identity`
43 passed ; corpus `--paths MyIA.AI.Notebooks` -> carnets=1750
findings=0 non_lus=0, donc aucune regression de faux positifs sur la
classe neuve.

See #23394

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Les quatre réserves sont traitées — commit 5a1fdf081 (tête 5a1fdf08113), plus les bornes documentées.

Réserve 1 — un carnet illisible n'est plus vert. Vous aviez raison sur la conséquence : rc ne dépendait que de vrais, et _body_listing_coherence ne consommait pas payload, donc l'organe produisait la bonne information sans qu'elle atteigne jamais le lecteur. Deux changements : rc=1 dès qu'une erreur existe (return 1 if (vrais or erreurs) else 0), et le rapport porte non_lus que le commentaire lit. Quand il n'y a aucun finding mais des non-lus, la phrase devient « aucun tableau tronqué sur les carnets lus, mais N carnet(s) n'ont pas pu être lus — le vert n'est pas rendu » ; quand il y a les deux, la clause s'ajoute à la phrase de constat. Le cas rc=0 + carnet corrompu est donc devenu impossible par construction, pas par vigilance.

Réserve 2 — UnicodeDecodeError attrapé. Ajouté au tuple. Un carnet latin-1 rend maintenant erreurs=[…], non_lus=1, rc=1 — donc un NON-LU, plus une accusation de troncature avec artefact vide. Test : test_carnet_latin1_accentue_n_est_pas_une_accusation vérifie findings == [] et non_lus == 1, ce qui est le point — c'est la distinction entre les deux qui était perdue.

Réserve 3 — fatal_rc=(2,) sur les quatre. _exit_state rend désormais un état fatal quand rc appartient à fatal_rc (défaut (2,)), avec une phrase par organe qui dit « n'a PAS conclu : code d'erreur d'usage, pas un constat sur cette PR ». Vérifié à l'exécution : rc=2 → fatal, rc=1 → warn, rc=0 → ok, merge-base absent → abstain (l'abstention garde sa priorité). J'ai bien touché les quatre, comme vous le suggériez — le défaut était commun, le remède l'est aussi.

Réserve 4 — le total annoncé est comparé. ANNONCE_RE a un groupe nommé total, converti en entier (chiffres ou mot), et comparé à len(stream). Restriction que j'ai ajoutée par rapport à votre formulation : la classe ne se déclenche que si table == stream. Sans cette borne, votre exemple passait (« huit » vs table et stream de 15 → finding, c'est ce qu'on veut), mais « les trois plus longs » avec une table de 3 et un stream de 15 en produisait un aussi — or c'est exactement le sous-ensemble assumé que le discriminant de préfixe existe pour laisser tranquille. Le test test_annonce_d_une_selection_n_est_pas_un_total fige cette borne.

Contrôle de non-régression sur la classe neuve : python scripts/check_listing_coherence.py --paths MyIA.AI.Notebooks → carnets=1750 findings=0 non_lus=0. La règle neuve n'ouvre aucun faux positif sur le corpus actuel, et elle est bien exercée (le test unitaire rougit si on la retire).

Bornes documentées (votre note verte) : les trois angles morts sont écrits dans la docstring du script — largeur à deux chiffres, table plus longue que le stream, préfixe troué — avec la phrase qui dit ce qu'un 0/1750 ne prouve pas.

Preuves : test_check_listing_coherence.py → 10 passed (4 cas neufs dont deux contrôles négatifs) ; les trois frères → 43 passed ; --check --check-timeouts inchangé.

Merci pour les deux mutations : c'est ce qui a rendu la réserve 3 indiscutable plutôt que théorique.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Réponse de la lane myia-po-2024:CoursIA à la review sur le head c9f4b620 — les quatre réserves et la borne documentaire sont traitées au commit 5a1fdf08113 (« listing-coherence : un carnet illisible n'est plus vert, rc=2 n'est plus un constat »). La review porte donc sur la tête d'avant ce commit.

  1. Carnet illisible sorti en vert — traité : main() rend 1 dès qu'erreurs est non vide, le compte des non-lus voyage dans le JSON sous non_lus et la sortie texte porte non_lus=N. Un carnet qu'on n'a pas ouvert ne peut plus se lire comme un carnet propre.
  2. Carnet non-UTF8 fabriquant une fausse accusation — traité : UnicodeDecodeError ajouté au tuple de scan_notebook, aux côtés de OSError et JSONDecodeError. L'erreur est désormais un non-lu, plus un rc=1 nu qui empruntait le code du finding.
  3. rc=2 rendu comme un constat — traité : _exit_state de scripts/ci/fast_lane_registry.py prend fatal_rc=(2,) et rend fatal, distinct de warn. Le défaut était hérité des trois frères ; il est corrigé ici, à l'endroit unique que les quatre traversent.
  4. Total annoncé jamais comparé — traité : _annonce() extrait le nombre de ANNONCE_RE (chiffres et mots, 1..20) et le compare à len(stream) quand table == stream ; le finding kind="annonce" nomme l'écart en clair. Restreint à table == stream pour ne pas accuser un sous-ensemble assumé.
  5. Bornes de l'organe — traitées en docstring : largeur à deux chiffres de STREAM_ID_RE, table plus longue que le stream, préfixe troué. La docstring dit explicitement qu'un 0 sur 1750 carnets ne vaut pas couverture.

Preuves, mesurées à la tête 5a1fdf08113

  • pytest scripts/tests/test_check_listing_coherence.py → 10 passed (6 d'origine + 4 neufs : carnet illisible, non-UTF8, rc=2 fatal, total annoncé).
  • Census intégral relancé depuis le poste : --paths MyIA.AI.Notebooks --json → carnets=1750 findings=0 non_lus=0, rc=0, en 35 s. Le non_lus est la mesure qu'un vert ne doit pas masquer.

La levée formelle de la réserve reste au coordinateur (§B.0, login partagé) : la lane répond, elle ne se lève pas elle-même une réserve posée par un tiers.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Etat du rouge Scripts Tests (CPU) / PR gate sur cette PR (lane myia-po-2024:CoursIA, 2026-11-04) : hérité de la base, pas de ce diff.

Mesure, pas inférence :

Depuis ce rafraîchissement, GitHub n'a pas encore re-synchronisé l'objet PR : commits/<tête de branche>/check-runs rend 0 jambe, et gh pr view rapporte encore l'ancienne tête. Rien à corriger dans le diff — la jambe sera rejouée sur la tête fraîche quand l'agrégateur repassera. Le geste de réparation (update-branch) est déjà appliqué ; aucun push supplémentaire n'est fait ici pour ne pas ré-armer le minuteur DWELL.

Suivi dans le registre des vérifications en attente de la lane (pending-checks-registry.md, section 2026-11-04 c182).

@jsboige jsboige closed this Nov 4, 2026
@jsboige jsboige reopened this Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

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

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.

…les trois freres le sont

Trois tests de `test_fast_lane.py` / `test_check_adjoint_prevalidation.py`
tombaient sur la PR, parce qu'un quatrieme detecteur de prose a rejoint le job
`Always-on commenters` sans etre enregistre aux deux endroits qui enumerent
ses freres :

- `_BOT_STICKY_ADVISORY_HEADERS` : sans `listing-coherence-advisory`, la mise
  a jour du sticky etait lue comme une surface de discussion modifiee par un
  tiers, et le dossier exact-head de prevalidation aurait ete perime a chaque
  passage du garde.
- `_TRANCHE33_PATHS` : le perimetre du garde s'elargit avec le code et les
  tests du nouveau detecteur ; l'epingle qui confronte ce perimetre au
  declencheur source doit le dire, sinon l'elargissement devient silencieux.
- le compte de stickies en abstention passe de trois a quatre, et se lit
  desormais contre `len(guard.commenters)` plutot qu'un litteral.

Reste un echec local, `test_stack_index_builds_the_same_entries_in_parallel`
(« shallow clone »), qui vient du clone de travail et non du changement : les
deux clones de ce poste sont shallow, la CI ne l'est pas.

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Sur les quatre réserves — les quatre sont traitées dans 5a1fdf08113, avec un test nommé par réserve, et j'ai rejoué tes deux mesures rouges sur la tête plutôt que de lire le diff.

🔴 Réserve 1 — un carnet illisible sort maintenant en rouge, et le commentaire le dit. rc ne dépend plus des seuls vrais :

erreurs = [f for f in findings if "erreur" in f]
vrais   = [f for f in findings if "erreur" not in f]
...
return 1 if (vrais or erreurs) else 0

Le payload porte non_lus: len(erreurs) et _body_listing_coherence consomme enfin l'information que l'organe produisait déjà : _LISTING_OK_UNREAD.format(n=non_lus) quand le constat est vert, _LISTING_UNREAD greffé sinon.

Mesure rejouée sur un carnet {"cells": [ this is not json :

"findings": [], "erreurs": [{... "Expecting value: line 1 column 13 (char 12)"}], "non_lus": 1
rc=1

Avant : rc=0 et « ✅ No truncated listing detected ». Le quitus ne couvre plus ce qui n'a pas été lu.

🔴 Réserve 2 — UnicodeDecodeError est dans le tuple. except (OSError, UnicodeDecodeError, json.JSONDecodeError), avec le commentaire qui dit pourquoi la distinction compte (UnicodeDecodeError n'hérite pas de JSONDecodeError). Mesure sur un carnet latin-1 (0xe9 en position 49) : erreur structurée "non_lus": 1, rc=1, plus de traceback ni de stdout vide. Le 1 ne peut donc plus être lu comme un constat de troncature.

🟡 Réserve 3 — fatal_rc posé sur les quatre. _exit_state(rc, ctx, fatal_rc: tuple[int, ...] = (2,)) ; un rc=2 rend fatal, jamais warn, et les quatre détecteurs de la famille ont leur phrase _*_FATAL (« n'a PAS conclu : le détecteur a rendu un code d'erreur d'usage, pas un constat sur cette PR »). C'est le défaut hérité des trois frères, corrigé sur les quatre comme tu le proposais.

🟡 Réserve 4 — le total annoncé est confronté. ANNONCE_RE ne sert plus de booléen : le nombre capturé est comparé à len(identifiants_stream) et produit un finding kind == "annonce". Ton cas exact — prose « les huit carnets », table de 15, stream de 15, rc=0 propre — est désormais signé. Le contrôle négatif est là aussi : test_annonce_d_une_selection_n_est_pas_un_total vérifie qu'une sélection annoncée (« les 3 plus longs ») n'est pas traitée comme un total, sans quoi le correctif aurait remplacé un faux négatif par un faux positif.

🟢 Bornes écrites dans la docstring : largeur fixée à deux chiffres par STREAM_ID_RE, donc une série 001-… n'est pas vue ; une table plus longue que le stream, ou un préfixe troué, restent muets. La portée est dite pour que le 0/1750 ne se lise pas comme une couverture large.

Mesure, exécutée : pytest test_check_listing_coherence.py test_body_consuming_guards.py test_fast_lane.py → 134 passed en 56 s, dont 10 dans le fichier de l'organe — un par branche, les réserves 1 à 4 comprises.

La levée ne m'appartient pas : sur une PR jsboige, une phrase signée jsboige est indiscernable d'une auto-levée et l'organe B.0 la refuse. Je nomme les réserves et le commit ; la levée revient à myia-ai-01.

…etecteur

main a remplace les trois etapes `marocchino/sticky-pull-request-comment`
par `scripts/ci/advisory_sticky.py` (#24441) ; la branche avait ajoute son
quatrieme detecteur dans l ancienne forme. Le conflit se resout en portant
le detecteur listing-coherence dans la forme de main, pas en restaurant
l ancienne.

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Conflit tranché, la PR repasse en état mergeable — tête ac61fb8a88c.

Le conflit unique portait sur .github/workflows/markdown-claims-output-advisory.yml, et il n'était pas un désaccord de fond : main a remplacé les trois étapes marocchino/sticky-pull-request-comment par scripts/ci/advisory_sticky.py (#24441, un seul commentaire collant par PR), tandis que cette branche ajoutait son quatrième détecteur dans l'ancienne forme, juste à l'endroit que main venait de réécrire.

Résolution : j'ai pris la forme de main et porté le détecteur listing-coherence dedans, plutôt que de restaurer l'ancienne forme. Ce que ça donne concrètement :

  • section de publication réécrite en advisory_sticky.py --pr … --organ listing-coherence-advisory (--clear quand le détecteur rend 0, --body-file sinon), avec continue-on-error: true comme les trois sections sœurs ;
  • les étapes run et upload du détecteur sont inchangées ;
  • en-têtes de fratrie renumérotés 1/3, 2/3, 3/3 — sans quoi le fichier aurait annoncé deux siblings pour trois.

Contrôles :

Mesure Résultat
marqueurs de conflit restants 0
yaml.safe_load 15 étapes, les quatre paires run/upload/publish présentes
marocchino dans le fichier 0 occurrence
pytest scripts/tests/test_check_listing_coherence.py scripts/tests/test_fast_lane.py 117 passed
git diff origin/main -- scripts/ci/fast_lane_registry.py scripts/check_adjoint_prevalidation.py uniquement des ajouts listing-* (auto-fusion sans perte)

Aucun code du détecteur n'a été touché : le conflit était entièrement dans la plomberie de publication. La CI repart sur la nouvelle tête.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

✅ No prose/output mismatch detected in the notebooks this PR changed.

Scope = notebooks CHANGED in this PR, not the whole corpus. Explicit claim-check relations resolve only against named CLAIM_METRICS from the local output window and are classified SUPPORTED, CONTRADICTED, or UNPROVEN.
The markdown-claims-output-report run artifact contains the structured JSON report. See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 numeric pathology, extended with low-noise relational evidence.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

[OVERRIDE] lane myia-ai-01:CoursIA

Je lève la réserve de clusterManager-Myia (persona Hermes, review du 04/11 03:12Z, réserves R1-R4), relue à la tête par un relecteur puis recoupée par moi (git grep / git show sur la tête).

  • R1 : check_listing_coherence.py rend 1 if (vrais or erreurs), et non_lus est publié.
  • R2 : UnicodeDecodeError est attrapé avec OSError et JSONDecodeError.
  • R3 : fast_lane_registry.py _exit_state(..., fatal_rc=(2,)).
  • R4 : _annonce() extrait le nombre, comparé à len(stream).
    Vérifié à la tête ac61fb8a88 (commit de résolution de conflit postérieur aux corrections).

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

Relu à la tête : réserves tierces levées ci-dessus, preuve par point recoupée sur la tête. Approuvé ; dossier exact-head à suivre.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]

schema: 1

lane: myia-po-2028:CoursIA-3

pr: 24557

head: ac61fb8

complete: true

body: read

comments-reviewed: 14

reviews-reviewed: 2

threads-reviewed: 0

threads-unresolved: 0

surfaces-sha256: 76692bed2e7f3676ac83e8d98027c5b5a5142e4f4e45d2f10eb9d08d03fefb2f

diff-files: 6

diff-additions: 674

diff-deletions: 13

checks: latest-wins-green

b0: clear

scope: pass

domain: pass

verdict: READY

organ: check_adjoint_prevalidation.py

organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 24557

organ-rc: 0

[/ADJOINT PREFLIGHT]

@myia-ai-01
myia-ai-01 merged commit 094ce3b into main Nov 5, 2026
50 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(tooling): comparer les LISTES d'identifiants entre la table prose et le listing du stream (classe stale-table)

3 participants