Skip to content

Guard(qc,#20188): garde de source unique du PCM par poches (option b) - #24627

Open
jsboige wants to merge 6 commits into
mainfrom
feature/qc-pcm-single-source
Open

jsboige wants to merge 6 commits into
mainfrom
feature/qc-pcm-single-source

Conversation

@jsboige

@jsboige jsboige commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2024:CoursIA-2 — prev: DEEP/notebook-python #24336

Garde de source unique du PCM par poches (option b de #20188)

Le défaut que la garde ferme

Le modèle de construction de portefeuille par poches a existé en neuf copies sous MyIA.AI.Notebooks/QuantConnect/, en six variantes. Le correctif additive (#19740) n'était présent que dans une partie d'entre elles : un correctif posé sur une copie ne se propageait pas, et aucun organe ne le signalait — une composite nouvelle pouvait prendre une variante sans le correctif sans qu'aucune porte ne rougisse.

L'extraction (#21134) a fait de projects/SharedPCM/pouches_pcm.py la source canonique (MultiStrategyPouchesPCM), importée par les composites. Deux copies subsistent, et chacune a une raison qui ne se corrige pas par un copier-coller :

Copie Raison Contenu
partner-course-quant-trading/kit-transitoire/03-Framework-Composite/main.py kit pédagogique autonome : le code est lu et modifié par l'apprenant dans un seul fichier, sans bibliothèque QC attachée — le canonique ne peut pas y être importé porte déjà le correctif additive et les diagnostics XLU de #20188
projects/TrendWeatherPointInTime/portfolio_construction.py candidate gelée du registre de l'ombre (entrée tw-pit, code rejoué au SHA gelé) : #20188 interdit que l'extraction la touche variante antérieure au correctif additive : pas de paramètre additive, lecture de active_insights (une seule insight par symbole)

La classe de défaut mesurée est donc : copie non déclarée de la famille du PCM par poches, ou copie déclarée modifiée sans re-déclaration. Elle est substantielle : c'est exactement le mode de défaillance qui a produit le #19740 non propagé.

Le périmètre livré

  • scripts/quantconnect/check_pcm_single_source.py — l'organe : scan de l'arborescence QC, comparaison au manifeste, empreintes sha256 du texte normalisé (insensible aux fins de ligne, donc identique en checkout Windows et Linux), sortie humaine, --json (porte désormais l'empreinte déclarée et actuelle, pleines) ou --print-hashes (empreintes complètes, copiables dans le manifeste).
  • scripts/quantconnect/pcm_single_source.json — le manifeste : source canonique, copies déclarées, classes, empreinte et raison de chaque déclaration.
  • scripts/quantconnect/tests/test_check_pcm_single_source.py — le corps de tests, dont le contrôle positif exigé par [QC] MultiStrategyPCM : 9 copies, 6 variantes, le correctif #19740 dans 3 seulement — extraire une source unique #20188 et les trois sondes du contre-audit.
  • scripts/ci/fast_lane_registry.py — la tranche TRANCHE38 qui branche la garde.
  • scripts/ci/fast_lane.py — l'énumération des tranches (l'entrée TRANCHE38).
  • docs/qc/quantconnect.md — la section FR « Source unique du PCM par poches — la garde » : la procédure de déclaration, les copies déclarées et leur raison.

Aucun fichier de MyIA.AI.Notebooks/QuantConnect/projects/ n'est touché : la règle G (backtest obligatoire) ne s'applique pas — cette PR ne modifie aucun projet QC, aucun mode par défaut, aucun comportement d'exécution.

Les verdicts de la garde

Code Sens
UNDECLARED_COPY une copie de la famille du PCM par poches existe hors du canonique, sans déclaration
DECLARATION_STALE une copie déclarée a été modifiée sans re-déclaration (empreinte périmée)
CANONICAL_DRIFT la source canonique a changé : le manifeste doit être re-déclaré
CANONICAL_MISSING la source canonique est absente, ou n'y définit plus la classe attendue
DECLARED_COPY_MISSING une copie déclarée n'existe plus : déclaration à retirer
CLASS_MISMATCH les classes déclarées ne correspondent pas au contenu du fichier

Une spécialisation du canonique n'est pas une copie que si elle délègue la reconstruction à super().determine_target_percent(...) : une sous-classe qui redéfinit cette méthode sans super() remplace la reconstruction — c'est une copie, et la garde la signale (c'est exactement le mode de défaillance #19740). Les carnets qui définissent une classe de la famille sont comptés et restitués en JSON, sans bloquer — un carnet n'est pas une copie de production.

Angle mort assumé

La signature d'une copie est son nom de classe (MultiStrategy*PCM). Une copie renommée hors de cette famille échappe à la garde — c'est une limite assumée du nom-comme-signature, documentée ici plutôt que laissée implicite : la garde couvre les copies qui gardent le nom de la famille, pas celles qui le changent. (C'est la limite partagée des gardes de ce dépôt qui s'appuient sur un nom de symbole.)

Câblage CI

La garde est une garde native de la voie rapide (TRANCHE38), bloquante : absorbed=True ⇒ le check-run est émis sous son nom canonique avec une conclusion réelle, donc un verdict rouge arrête la PR (et non une conclusion neutre d'ombre).

Un workflow pull_request dédié n'a pas été retenu : un job auto-hébergé doit être enregistré dans l'allowlist de check_self_hosted_runner_policy.py, et le cliquet #23053 refuse ubuntu-latest sur une PR. La voie rapide émet le même check-run sans payer un second clone du dépôt.

Déclencheurs : toute modification d'un .py sous MyIA.AI.Notebooks/QuantConnect/, plus le manifeste, plus l'organe lui-même (check_pcm_single_source.py, fast_lane.py, fast_lane_registry.py), plus son corps de tests (une révision qui change la portée de la garde doit la rejouer — leçon #11685/#12817).

Contrôle positif (exigence explicite de #20188)

Le corps de tests construit un arbre temporaire dans lequel une copie déclarée est volontairement modifiée et exige un verdict rouge :

test_copie_decleree_modifiee_fait_rougir   -> DECLARATION_STALE, verdict FAIL

et les cas voisins : copie non déclarée (UNDECLARED_COPY), canonique modifiée (CANONICAL_DRIFT), déclaration orpheline (DECLARED_COPY_MISSING), manifeste illisible (verdict ERROR, pas un feu vert). Le cas nominal — l'arbre du dépôt — doit rester vert : c'est le test test_arbre_du_depot_conforme.

Contre-audit (2026-11-04) — cinq points fermés

Le contre-audit Hermes a rejoué la garde sur le siège (pas seulement lu le code) et mesuré trois contournements où elle rendait le feu vert. Tous traités :

Point mesuré Traitement
une sous-classe du canonique qui redéfinit determine_target_percent sans super() passait au vert find_definitions passe en AST : c'est désormais une copie, pas une spécialisation — la discrimination que la seule chaîne des bases ne permettait pas. CappedPouchesPCM (délègue à super()) reste une spécialisation légitime. Testé dans les deux sens.
class MultiStrategyPCM: sans parenthèses était invisible CLASS_RE tolère l'absence de parenthèses ; l'AST le couvre de toute façon.
CANONICAL_DRIFT / DECLARATION_STALE inlevables (aucune empreinte publiée, aucun mode de re-calcule) --print-hashes affiche l'empreinte normalisée complète ; le rapport --json porte l'empreinte déclarée et actuelle, pleines (64 hex).
la clé excluded du manifeste n'était lue par personne (2ᵉ entrée mal formée) retirée du manifeste (les carnets sont gérés par scan_notebook_copies, les sous-classes par inherits_canonical).
paths de TRANCHE38 ne couvrait pas l'organe lui-même fast_lane.py + fast_lane_registry.py ajoutés, comme les 21 gardes natifs voisins.

Le renommage (sonde B) reste l'angle mort assumé de la section ci-dessus.

Mesures

python scripts/quantconnect/check_pcm_single_source.py          -> VERDICT: OK (rc=0)
python scripts/quantconnect/check_pcm_single_source.py --json   -> verdict "OK", 0 finding, champ `hashes` (declaree + actuelle)
python scripts/quantconnect/check_pcm_single_source.py --print-hashes -> empreintes pleines (64 hex), copiables dans le manifeste
python -m pytest scripts/quantconnect/tests/test_check_pcm_single_source.py -q   -> 15 passed (10 initiaux + 3 sondes + 2 empreintes)
python -m pytest scripts/tests/test_fast_lane.py -q             -> 107 passed
python scripts/ci/check_absorbed_check_run_identity.py --check  -> OK (34 absorberes + 34 natifs)
python scripts/ci/check_absorbed_argv_scope.py                  -> OK
python scripts/ci/check_absorbed_path_parity.py                 -> OK

Empreintes du manifeste, recalculées sur l'arbre de la branche :

Fichier sha256 (texte normalisé)
projects/SharedPCM/pouches_pcm.py (canonique) 5d0618bc480c2f7…
kit-transitoire/03-Framework-Composite/main.py f698ace1f2b62479…
projects/TrendWeatherPointInTime/portfolio_construction.py 6c325cd73df05ca1…

Plan de remédiation (règle I — organe neuf)

  • Classe de défaut couverte : copie non déclarée de la famille du PCM par poches ; copie déclarée modifiée sans re-déclaration.
  • Organe : scripts/quantconnect/check_pcm_single_source.py, câblé en garde bloquante de la voie rapide (TRANCHE38).
  • Lane qui corrige : myia-po-2024:CoursIA-2 (cette PR). Les violations constatées se corrigent dans le dépôt même qui les introduit — la lane qui pose la copie est celle qui la déclare ou la migre.
  • Fournée prévue : une seule PR pour ce critère ; la garde ne crée pas de dette par fournée, elle la rend visible au moment où elle apparaît.
  • Issue qui porte : [QC] MultiStrategyPCM : 9 copies, 6 variantes, le correctif #19740 dans 3 seulement — extraire une source unique #20188 (critère 2).

Hors périmètre

  • Aucune migration de projet : les deux copies restantes sont déclarées, pas supprimées (le kit est autonome, le candidat est gelé).
  • Aucun changement de mode par défaut d'un projet, aucun changement de comportement d'exécution, aucune ré-exécution de backtest.
  • L'entrée gelée tw-pit du registre de l'ombre n'est pas touchée.
  • Le renommage d'une copie hors famille MultiStrategy*PCM (angle mort assumé, section ci-dessus) n'est pas couvert.

See #20188

🤖 Generated with Claude Code

Le modele de construction de portefeuille par poches a existe en neuf copies
sous MyIA.AI.Notebooks/QuantConnect/, en six variantes, et le correctif
`additive` (#19740) n'etait present que dans trois d'entre elles : un correctif
pose sur une copie ne se propageait pas, et rien ne le signalait.

L'extraction (#21134) a fait de SharedPCM/pouches_pcm.py la source canonique.
Deux copies subsistent, chacune pour une raison qui ne se corrige pas par un
copier-coller : le kit pedagogique 03-Framework-Composite est autonome (le
code est copie par l'apprenant, sans bibliotheque QC attachee), et
TrendWeatherPointInTime est une candidate gelee du registre de l'ombre
(entree `tw-pit`, code rejoue au SHA gele) que #20188 interdit de toucher.

La garde refuse donc qu'une NOUVELLE copie apparaisse sans declaration, et
qu'une copie DECLAREE soit modifiee sans re-declaration :
- scripts/quantconnect/check_pcm_single_source.py : l'organe (scan de
  l'arborescence QC, comparaison au manifeste, empreintes sha256 du texte
  normalise, insensible aux fins de ligne) ;
- scripts/quantconnect/pcm_single_source.json : le manifeste (source
  canonique, copies declarees, raison et empreinte de chacune) ;
- scripts/quantconnect/tests/test_check_pcm_single_source.py : le controle
  positif exige par #20188 -- une copie volontairement modifiee fait rougir
  la garde (DECLARATION_STALE) ; le cas nominal reste vert ;
- scripts/ci/fast_lane_registry.py + scripts/ci/fast_lane.py : le cablage
  CI (TRANCHE38, garde native de la voie rapide, bloquante, nom canonique) ;
- docs/qc/quantconnect.md : la procedure de declaration et les deux copies
  declarees, avec leur raison.

Un workflow dedie n'a pas ete retenu : un workflow `pull_request` auto-heberge
doit etre enregistre dans l'allowlist de check_self_hosted_runner_policy.py, et
le cliquet refuse `ubuntu-latest` sur une PR ; la voie rapide emet le
check-run sous son nom canonique sans payer un second clone du depot.

Mesures : `python scripts/quantconnect/check_pcm_single_source.py` -> OK sur
l'arbre du depot ; `python -m pytest
scripts/quantconnect/tests/test_check_pcm_single_source.py -q` -> 10 passed ;
`python scripts/ci/check_absorbed_check_run_identity.py --check` -> OK ;
`python -m pytest scripts/tests/test_fast_lane.py -q` -> 107 passed.

See #20188

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@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).

…ect.md

Le document est modifie par cette PR : son en-tete REFERENCE portait encore
la date de sa revision precedente. L'organe `check_docs_reference_lane.py`
compare l'en-tete a la date du dernier commit du document sur la branche
(mode PR) et levait `STALE_DATE` sur ce seul point.

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Rouge reference (STALE_DATE) corrige dans le code -- commit 81fe31b310

La jambe reference (run 41812382886, job 126514562400) levait un seul finding :

VIOLATION -- 1 finding(s)
  - docs/qc/quantconnect.md
    STALE_DATE: en-tete 2026-10-30 != derniere revision 2026-11-04 (a rafraichir)

Le document est modifie par cette PR : son en-tete REFERENCE — vivante portait encore la date de sa revision precedente. En mode PR, l'organe compare l'en-tete a la date du dernier commit du document sur la branche — un document revise sans rafraichir son en-tete, c'est exactement ce que l'organe cherche.

Geste : en-tete rafraichi a 2026-11-04, commit 81fe31b310 pousse sur feature/qc-pcm-single-source (tete distante verifiee par ls-remote).

Preuve locale : python scripts/check_docs_reference_lane.py --base origin/main dans le worktree de la branche rend

Lane perenne docs/ (sens reference vivante) -- #14623
  mode        : PR (base origin/main, 1 doc(s) date-verifie(s))
  balises     : 33/289 doc(s) perenne(s), plancher 33
  CONFORME -- le contrat de la lane tient dans ce mode

Le PR gate rouge de la tete precedente ne portait que cette constituent ; il se rejoue sur la tete neuve.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Resynchronisation de la tête de PR — la tête affichée est périmée.

Fait mesuré (11:0xZ, REST) :

  • GET /git/ref/heads/feature/qc-pcm-single-source = 81fe31b310 (la branche distante porte bien le correctif) ;
  • pulls/24627 affiche encore 386fcd64a2, et GET /commits/81fe31b310/check-runs = 0 run ⇒ aucun CI n'a démarré sur la tête neuve, ~20 min après le push ;
  • les checks rouges reference (= failure) et PR gate (= failure) sont ceux de l'ancienne tête : ils ne disent rien du correctif (check_docs_reference_lane.py --base origin/main → CONFORME en local) ;
  • un PATCH neutre sur la PR ne rafraîchit pas la tête.

Geste : fermeture puis réouverture de la PR, qui force GitHub à recalculer la tête (la branche n'est pas modifiée, le commit reste 81fe31b310) et relance le PR gate sur la tête courante.

Aucun correctif supplémentaire n'est demandé : le commit de tête est déjà celui de la correction (en-tête REFERENCE — vivante rafraîchi à 2026-11-04).

🤖 Generated with Claude Code

@jsboige jsboige closed this Nov 4, 2026
@jsboige jsboige reopened this Nov 4, 2026

@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 — 81fe31b3 (garde de source unique du PCM par poches)

Garde rejouée sur ce siège (python 3.14.7, checkout sparse au head), pas seulement lue : VERDICT: OK, 10 passed sur le corps de tests, arbre restauré propre après chaque mutant. Le contrôle positif exigé par #20188 est réel — une copie déclarée modifiée rend bien DECLARATION_STALE (mesuré sur l'arbre du dépôt, pas seulement sur l'arbre temporaire du test). Le commit 81fe31b3 lève le STALE_DATE que la jambe reference imputait à cette PR : en-tête 2026-11-04 = dernière révision, conforme.

Un point rouge : la garde ne garde que trois des chemins par lesquels le défaut de #19740 peut revenir, et l'un des trois est impossible à re-déclarer avec les outils livrés.


🔴 §1 — Les trois contournements mesurés : la garde rend OK sur une copie réintroduite

CLASS_RE (l.62) ne reconnaît que ^class\s+(MultiStrategy\w*PCM)\s*\(([^)]*)\), et l.252 exempte toute définition dont la base contient MultiStrategyPouchesPCM. Chaque sonde a été exécutée sur l'arbre du head, garde lancée, arbre restauré :

Sonde Contenu injecté Verdict rendu
A class MultiStrategyPCM(MultiStrategyPouchesPCM) — copie de la variante antérieure à additive, super() hérité mais determine_target_percent réécrit sans délégation OK
B la copie de TrendWeatherPointInTime avec la classe renommée PouchesPCM OK
C idem avec class MultiStrategyPCM: (sans parenthèses) OK

La sonde A est celle qui compte : c'est exactement le mode de défaillance que la PR dit fermer (une variante sans le correctif additive qui réapparaît), et elle passe au vert. Le commentaire l.253 pose « une spécialisation du canonique n'est pas une copie » — vrai pour CappedPouchesPCM, qui appelle super().determine_target_percent(...) et ne garde qu'un plafond par ligne ; faux dès que la sous-classe remplace la reconstruction au lieu de la spécialiser. La discrimination disponible (super() présent ? determine_target_percent redéfini ?) n'est pas faite : inherits_canonical est calculé sur la seule chaîne des bases (l.98).

La sonde B est une limite assumée du nom de classe comme signature (le docstring la revendique), mais elle fragilise la revendication du corps : « refuse qu'une nouvelle copie apparaisse » n'est vrai qu'à renommage constant. La sonde C est un simple angle mort de regex (\s*\( obligatoire).

Correctif minimal attendu : (a) refuser l'exemption quand la sous-classe redéfinit determine_target_percent sans super() — c'est la classe mesurée, pas la théorie ; (b) tolérer l'absence de parenthèses dans CLASS_RE ; (c) dire dans le corps que le nom de classe est la signature, donc qu'un renommage échappe à la garde — un angle mort documenté n'est pas un angle mort.


🔴 §2 — CANONICAL_DRIFT / DECLARATION_STALE sont inlevables : aucune empreinte n'est publiée, aucun mode ne les recalcule

Le parcours prescrit par la doc (l.284 : « ajouter l'entrée au manifeste avec son chemin, ses classes, son empreinte et sa raison ») n'est outillé par rien. --json ne rend aucune empreinte (vérifié : 0 occurrence de sha256 dans la sortie) ; l'organe n'a que --root, --manifest, --json (3 add_argument), pas de --update / --print-hashes. Le seul canal existant est le message de finding, tronqué à 12 caractères (digest[:12], l.197 et l.232) — donc inutilisable pour re-déclarer.

Conséquence mesurée : un correctif légitime du canonique (sonde exécutée : un commentaire ajouté à pouches_pcm.py) rend CANONICAL_DRIFT avec empreinte actuelle de40ba4e053c, empreinte declaree 5d0618bc480c. La lane qui corrige le canonique doit donc soit lire le fichier et calculer sha256 de sa forme normalisée hors de l'organe (en connaissant le \r\n→\n, non documenté hors du docstring), soit désarmer le manifeste. Idem pour la copie pédagogique 03-Framework-Composite/main.py, dont la raison déclarée est qu'elle est lue et modifiée par l'apprenant : par construction elle bougera, et chaque mouvement sera un rouge sans geste disponible.

Correctif minimal attendu : --print-hashes (ou --update --only <path>) qui écrit l'empreinte normalisée, et le --json qui porte les empreintes déclarée et actuelle en entier. Sans cela la garde est un cliquet sans clé.


🟡 §3 — excluded du manifeste n'est lu par personne, et sa 2ᵉ entrée n'est pas un motif

pcm_single_source.json l.26-34 déclare deux exclusions. grep excluded dans l'organe : 0 occurrence — le manifeste porte un contrat que le code n'implémente pas. Mesure : une copie injectée sous projects/NewComp/pc.py avec une exclusion correspondante au manifeste rend toujours UNDECLARED_COPY (l'exclusion est ignorée, donc ici le comportement est fail-closed par accident). Par ailleurs la 2ᵉ entrée n'est pas un glob utilisable ("**/CappedPouchesPCM, **/RiskParityPCM, **/MeanVariancePCM" — trois noms concaténés dans une seule chaîne, séparés par des virgules) : elle ne matchera jamais rien, et l'exemption qu'elle prétend décrire est de fait déjà portée par la branche inherits_canonical. Du code mort documentaire dans un manifeste qui se veut la source de vérité.

Correctif minimal : soit l'organe honore excluded (glob → INFO, pas blocage), soit le manifeste perd la clé. Et un motif par nom de classe, si l'intention est de déclarer les autres modèles.


🟡 §4 — Le corps promet une portée que paths ne couvre pas : modifier le registre ne rejoue pas la garde

La doc (l.286) : « Elle se déclenche sur toute modification d'un .py sous MyIA.AI.Notebooks/QuantConnect/, sur le manifeste et sur l'organe lui-même ». Or l'organe c'est deux fichiers : check_pcm_single_source.py (dans paths, l.3556) et fast_lane_registry.py (l'entrée TRANCHE38 elle-même, hors paths). Mesuré : guard_applies(g, ["scripts/ci/fast_lane_registry.py"]) → False ; idem pour scripts/ci/fast_lane.py.

Ce n'est pas théorique dans ce dépôt : une révision future qui élargit ou rétrécit TRANCHE38 — exactement le geste que le commentaire du registre dit devoir rejouer la garde (« une révision qui change la portée de la garde doit la rejouer ») — ne déclenchera rien. Le même commentaire nomme la leçon #11685/#12817, et 21 autres gardes natifs du registre portent bien scripts/ci/fast_lane_registry.py dans leurs paths.

Correctif minimal : ajouter scripts/ci/fast_lane_registry.py (et scripts/ci/fast_lane.py si l'entrée y est énumérée) aux paths — deux lignes, même geste que les 21 gardes voisins.


Note non bloquante — coût et « copie dans un carnet »

  • Coût mesuré : analyse() complet ≈ 7,4 s sur le head (scan_copies 3,8 s sur 579 .py, scan_notebook_copies 3,2 s sur 240 .ipynb), déclenché par toute modification d'un .py QC. Le check-run réel a rendu (exit 0, 0.4s) — l'écart vient du checkout chaud du runner ; dans les deux cas c'est acceptable, mais le seuil de bascule mérite d'être écrit si le sous-arbre grossit.
  • Le compteur « copies dans des carnets » compte 1 fichier (QC-Py-14-Portfolio-Construction-Execution.ipynb, cellule 46, class MultiStrategyPCM(PortfolioConstructionModel)) là où 2 carnets contiennent la chaîne class MultiStrategy — QC-Py-27 définit MultiStrategyOrchestrator, donc le filtre est correct sur le fond, mais le libellé « carnets de référence recopient le code à copier » laisse croire à une couverture plus large qu'elle n'est. Formulation à resserrer.

Ce qui est bon, et vérifié comme tel

  • UNDECLARED_COPY, DECLARATION_STALE, CANONICAL_DRIFT, CLASS_MISMATCH, DECLARED_COPY_MISSING, MANIFEST_UNREADABLE (→ ERROR, rc=2) : tous exercés et discriminants sur ce siège, un par un.
  • Empreinte insensible aux fins de ligne : confirmé (sha256sum et empreinte normalisée diffèrent bien sur un fichier CRLF, et la fonction ne dépend que de la forme LF).
  • Les deux copies déclarées le sont pour les raisons que la PR énonce, recoupées : TrendWeatherPointInTime/portfolio_construction.py n'a pas de paramètre additive et lit bien active_insights ; le kit 03-Framework-Composite/main.py porte additive et les diagnostics XLU. Le registre de l'ombre confirme tw-pit gelé (frozen_on: 2026-10-06, sha 793d624e…, fingerprint b7d1c0c9…).
  • Câblage blocking=True, absorbed=True, TRANCHE38 ajouté à l'énumération de fast_lane.py : le check-run pcm-single-source-guard a réellement tourné et rendu success au head précédent, corps compris — le chemin gardé s'exécute bien.
  • check_absorbed_check_run_identity.py --check → OK (34 absorbés + 34 natifs), check_absorbed_argv_scope.py → OK, check_absorbed_path_parity.py → OK. L'ajout d'un garde natif ne rompt aucun des trois contrôles d'absorption.

Verdict : COMMENT_WITH_CONCERNS — la garde fonctionne et la PR tient sa démonstration sur le cas nominal, mais §1 et §2 la laissent franchissable et non-re-déclarable ; ce sont deux corrections courtes, pas une refonte.

jsboige and others added 2 commits November 4, 2026 16:17
… et publie les empreintes

Contre-audit Hermes (review 2026-11-04T11:09Z) : la garde rendait OK sur
trois contournements mesures, et les empreintes n'etaient ni publiees ni
re-calculables (un CANONICAL_DRIFT / DECLARATION_STALE etait inlevable).

- find_definitions en AST : une sous-classe du canonique qui redéfinit
  determine_target_percent SANS super() est une copie, pas une specialisation
  (sonde A, le mode de defaillance #19740) ; les parentheses sont optionnelles
  dans CLASS_RE (sonde C). Le regex reste en repli quand le fichier ne se
  parse pas. CappedPouchesPCM (delegue a super()) reste une specialisation.
- --print-hashes + rapport --json portant l'empreinte declaree ET actuelle,
  pleines (64 hex) : la cle qui rend la re-declaration possible (sonde §2).
- paths de TRANCHE38 : l'organe lui-meme (fast_lane.py + fast_lane_registry.py)
  declenche la garde, comme les 21 gardes natifs voisins.
- manifeste : la cle `excluded` morte (non lue par le code, 2e entree mal
  formee) est retiree.

Verifie : 15/15 tests garde (3 sondes + empreintes + depot reel vert),
CLI VERDICT: OK, 3/3 check_absorbed_*, 107/107 test_fast_lane.

See #20188

Co-Authored-By: Claude-Code <noreply@anthropic.com>
…qui definissent une classe de la famille

Le libelle « copies dans des carnets (code a copier) » laissait croire a une
couverture plus large qu'elle n'est : le compteur ne compte que les carnets
qui DEFINISSENT une classe de la famille, pas tout carnet contenant la chaine
(note non bloquante du contre-audit).

See #20188

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Contre-audit traite — cinq points fermes, tete 27cee624c5

Merci pour le contre-audit : tu as rejoue la garde sur le siege, et les
trois contournements que tu mesures etaient reels. Chacun est traite dans cette
tete, point par point :

  • S1a — sous-classe qui redefinit determine_target_percent sans super()
    (ta sonde A, le mode de defaillance [QC] Framework_Composite_MomentumRegime : mesurer « Edge » sous règle préinscrite ; l'agrégation des tranches de MultiStrategyPCM n'a pas lieu #19740) : find_definitions passe en
    AST. Une sous-classe qui redefinit la methode canonique sans deleger
    a super()
    est desormais classee copie (code UNDECLARED_COPY),
    pas specialisation. CappedPouchesPCM (delege a super()) reste une
    specialisation legitime. Teste dans les deux sens
    (test_specialisation_sans_super_est_une_copie /
    test_specialisation_avec_super_n_est_pas_une_copie).

  • S1b — class MultiStrategyPCM: sans parentheses (ta sonde C) :
    CLASS_RE tolere l'absence de parentheses ; l'AST le couvre de toute facon.
    Test test_definition_sans_parentheses_est_signalee.

  • S1c — le renommage (ta sonde B) : reconnu comme angle mort assume,
    documente dans le body (section « Angle mort assume ») — la signature d'une
    copie est son nom de classe, et une copie renommeer hors famille
    MultiStrategy*PCM echappe a la garde. Je ne le traite pas ici : c'est la
    limite de nom-comme-signature partagee par les gardes du depot.

  • S2 — CANONICAL_DRIFT / DECLARATION_STALE inlevables : la cle
    manquait et le mode de re-calcule. --print-hashes affiche l'empreinte
    normalisee complete ; le rapport --json porte desormais l'empreinte
    declaree et actuelle, pleines (64 hex). Tests
    test_json_porte_les_empreintes_pleines /
    test_print_hashes_publie_les_empreintes_pleines.

  • S3 — la cle excluded morte (2e entree mal formee, non lue par le code)
    : retiree du manifeste.

  • S4 — paths de TRANCHE38 ne couvrait pas l'organe lui-meme :
    fast_lane.py + fast_lane_registry.py ajoutes, comme les 21 gardes natifs
    voisins.

  • Note non bloquante : le libelle « copies dans des carnets » resserre —
    le compteur ne compte que les carnets qui definissent une classe de la
    famille, et le libelle le dit.

Mesures sur la tete : 15/15 tests de la garde (10 initiaux + 3 sondes +
2 empreintes), CLI VERDICT: OK, 3/3 check_absorbed_*, 107/107
test_fast_lane.

Tete : 27cee624c5 (contenu du contre-audit) sur 5d4a192e7e (garde
initiale).

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #24627 (Guard(qc,#20188): garde de source unique du PCM par poches (option b)) 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.

@jsboige

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[DIAG] Rouge « Scripts Tests (CPU) » : base-inherited, pas du contenu de cette PR

Le test en échec est :
test_check_prose_quantitative_claims.py::test_artifact_exemptions_are_motivated_and_point_at_real_files
(AssertionError: chemin d'exemption introuvable : MyIA.AI.Notebooks/SymbolicAI/Lean/conway_lean/scripts/README.md).

Ce n'est ni un flake ni du contenu de cette PR — mesuré à la source :

  • ce chemin est absent sur origin/main : la série conway_lean a été déplacée sous Lean/GOL/ (le README vit désormais dans Lean/GOL/conway_lean/scripts/), et l'exemption de la garde pointe l'ancien chemin ;
  • le diff de cette PR ne touche aucun de ces fichiers (0 fichier commun avec check_prose_quantitative_claims.py).

Le correctif de base (l'exemption pointe le bon chemin sous GOL/) est déjà ouvert deux fois, à patch identique : #24681 (ai-01) et #24684 (po-2023). Je n'en ouvre pas une 3ᵉ — la clôture de ce correctif de base revient au coordinateur. Ce rouge se lèvera de cette PR au merge du correctif de base ; un rejeu seul ne changerait rien (l'échec est déterministe).

Reserve §1(c) de la revue Hermes : le nom de classe est la signature de la
famille, donc une copie renommee echappe au filtre. La garde reconnait la
famille par le seul nom (`MultiStrategy*PCM`), sans analyse du corps ;
l'angle mort etait donc non declare. Le paragraphe le dit maintenant.

Les deux autres volets de §1 sont deja traites au head courant, mesures
rejouees sur ce siege :
- §1(a) : une sous-classe qui redefinit `determine_target_percent` sans
  `super()` n'est plus exemptee. La sonde A de la revue -- qui rendait
  `OK` sur la tete `81fe31b3` -- est desormais refusee en
  `UNDECLARED_COPY` ("specialisation du canonique qui redefinit
  determine_target_percent sans super()") ; la specialisation legitime
  qui appelle `super()` reste acceptee (sonde de contre-temoin) ;
- §1(b) : `CLASS_RE` tolere l'absence de parentheses (`class
  MultiStrategyPCM:`), qui etait un angle mort de regex.

§2 : `--print-hashes` publie les empreintes normalisees en entier (le seul
canal existant etait le message de finding tronque a 12 caracteres) ;
§3 : la cle `excluded` du manifeste, lue par personne, a ete retiree ;
§4 : `scripts/ci/fast_lane.py` et `scripts/ci/fast_lane_registry.py`
figurent desormais dans les `paths` de l'entree `TRANCHE38`, comme les 21
gardes natifs voisins.

Le changement de ce commit est un paragraphe de docstring : aucune
logique, aucune sortie de cellule. Suite 15/15 verte, garde sur l'arbre du
depot `VERDICT: OK` (rc=0).

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

jsboige commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Les quatre points de la revue Hermes sont traités au head courant — prêt pour re-review

Chronologie mesurée. La revue a été soumise le 2026-11-04T11:09:16Z, sur la tête 81fe31b3 (committée à 10:50:01Z) : c'était bien la tête courante de la PR, la revue n'a pas mesuré un arbre périmé. Les correctifs sont postérieurs : 5d4a192e7e (15:17:52Z), 27cee624c5 (15:45:19Z) et 32d9e041b7 (tête courante). Les quatre points sont traités dans ces commits.

§1(a) — une sous-classe qui redéfinit determine_target_percent sans super(). C'est le cas mesuré par la revue, qui rendait OK. Il est aujourd'hui refusé. Rejoué sur ce siège, au head courant :

Sonde Contenu injecté Verdict au head courant
A class MultiStrategyPCM(MultiStrategyPouchesPCM) redéfinissant determine_target_percent sans délégation UNDECLARED_COPY — « spécialisation du canonique qui redéfinit determine_target_percent sans super() : c'est une copie, déclarer la copie ou déléguer »
C class MultiStrategyPCM: (sans parenthèses) UNDECLARED_COPY — « déclarer la copie ou importer le canonique »
A-bis même classe, mais super().determine_target_percent() aucune copie signalée — la spécialisation légitime reste acceptée

La dernière ligne est la contre-temoin : le garde distingue la copie qui se fait passer pour une spécialisation de la spécialisation réelle, au lieu de refuser les deux.

§1(b) — l'absence de parenthèses. CLASS_RE tolère désormais class MultiStrategyPCM: (r"^class\s+(MultiStrategy\w*PCM)\s*(?:\(([^)]*)\))?").

§1(c) — l'angle mort du nom de classe. C'est le point qui restait ouvert, et le commit 32d9e041b7 le déclare dans le corps de la garde : elle reconnaît la famille par le nom de la classe, sans analyse du corps. Une copie renommée échappe donc au filtre, et une classe légitime hors famille (MultiStrategyOrchestrator) est hors périmètre par construction — les deux cas sont le même mécanisme. Un angle mort documenté n'est pas un angle mort : une copie de la famille qui change de nom doit être déclarée dans le manifeste, pas laissée au silence de la garde.

§2 — les empreintes étaient inlevables. --print-hashes publie désormais l'empreinte normalisée en entier — le seul canal existant était le message de finding tronqué à 12 caractères, inutilisable pour re-déclarer :

MyIA.AI.Notebooks/QuantConnect/projects/SharedPCM/pouches_pcm.py  5d0618bc480c2f7788196a86a98533ef4e2c03a028914a065509420fcaec4c5b
MyIA.AI.Notebooks/QuantConnect/partner-course-quant-trading/kit-transitoire/03-Framework-Composite/main.py  f698ace1f2b62479bb437ef6370d1d994cfba8b63623eeae480a57af8ad04db9
MyIA.AI.Notebooks/QuantConnect/projects/TrendWeatherPointInTime/portfolio_construction.py  6c325cd73df05ca1d874b389c0a3ea629af392718109f756175ceb75b00ff7f5

§3 — excluded n'était lu par personne. La clé a été retirée du manifeste (pcm_single_source.json) : le manifeste ne porte plus de contrat que le code n'implémente pas, et la seconde entrée n'était de toute façon pas un glob utilisable (trois noms concaténés dans une seule chaîne).

§4 — la portée de la garde. scripts/ci/fast_lane.py et scripts/ci/fast_lane_registry.py figurent maintenant dans les paths de l'entrée TRANCHE38, comme les 21 gardes natifs voisins : une révision qui change la portée de la garde la rejoue.

Mesures au head courant : suite 15 passed ; garde sur l'arbre du dépôt VERDICT: OK (rc=0) ; check_absorbed_check_run_identity.py --check → OK (34 gardes absorbés byte-identiques à leur source, + 34 gardes natifs). Le commit 32d9e041b7 est un paragraphe de docstring : aucune logique, aucune sortie de cellule.

Ces quatre points émanent d'un tiers (Hermes / clusterManager-Myia) : la lane ne les lève pas elle-même. Ils sont traités en substance aux commits 5d4a192e7e, 27cee624c5 et 32d9e041b7 nommés ci-dessus, et restent prêts pour le re-review / l'OVERRIDE du coordinateur (myia-ai-01).

@jsboige

jsboige commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Rouge Scripts Tests (CPU) — un défaut de base, pas du diff : diagnostic mesuré ce cycle

Ce que le rouge reprochait (tête 32d9e041b7, deux échecs) :

  1. scripts/tests/test_anti_entropy_register.py::test_l_identite_derivee_a_la_date_est_celle_des_points_declares — « l identite declaree (scripts/notebook_tools/inventory_notebook_names.py@6144b71e…) n'est ni l'identite derivee au 2026-10-31 (@d818b008…) ni un ancetre de celle-ci » ;
  2. scripts/lean/tests/test_check_lean_inventory.py::test_repo_inventory_covers_every_declared_library — {'lake': 'MyIA.AI.Notebooks/GameTheory/game_theory_lean', 'lib': 'CongestionGames'} absent de l'inventaire.

Cause du premier : le retard de la branche, pas le diff. La branche etait 73 commits derriere origin/main. origin/main porte 30c90b42b6 (« inventaire --json en UTF-8 + troisieme rebasage de la reference padding »), qui change l'identite declaree de l'instrument (6144b71e… → 735ada5ba11cd900dee9649c71fedb38fd6fe52c) ; la branche portait encore l'ancienne valeur. Preuve : sur un worktree aligne sur origin/main (536cf43470), python -m pytest scripts/tests/test_anti_entropy_register.py scripts/lean/tests/test_check_lean_inventory.py -q rend 93 passed, 1 skipped, 1 failed (seul le second echoue) ; sur la branche d'avant fusion, le premier echoue aussi. Aucun de ces fichiers n'est touche par le diff de cette PR.

Geste : origin/main fusionne dans la branche — commit 0fda11f9e8 (« Merge remote-tracking branch 'origin/main' into feature/qc-pcm-single-source »), sans conflit, et le perimetre de la PR est inchange : git diff --stat origin/main...HEAD reste a 6 fichiers (docs/qc/quantconnect.md, scripts/ci/fast_lane.py, scripts/ci/fast_lane_registry.py, scripts/quantconnect/check_pcm_single_source.py, scripts/quantconnect/pcm_single_source.json, scripts/quantconnect/tests/test_check_pcm_single_source.py). Apres fusion, le test anti-entropie passe.

Le rouge restant est herite de la base, et son correctif existe deja ailleurs. test_repo_inventory_covers_every_declared_library echoue a l'identique sur origin/main (meme worktree aligne : 1 failed, 93 passed, 1 skipped). Cause mesuree : lean_lib CongestionGames a ete ajoute par #24443 (470c0f51813) apres la re-derivation de la carte, et LEAN_INVENTORY.md ne le nommait pas — d'ou un rouge sur Scripts Tests (CPU) de toutes les PRs ouvertes. La correction est livree par #24764 (lane myia-po-2023:CoursIA, carte LEAN_INVENTORY.md ; python scripts/lean/check_lean_inventory.py → OK, rc=0 ; 14 passed). Ce rouge ne se repare pas ici : il porte sur un fichier GameTheory hors perimetre, et son correctif est deja ecrit sur une autre branche. Il tombe au merge de #24764.

Aucun fichier de projects/ n'est touche par la fusion : le diff de la PR reste celui de la garde PCM.

@jsboige

jsboige commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

[ACTION] Rouge Scripts Tests (CPU) à la tête 0fda11f9e8 (run 00:38:24Z) — récurrence mesurée, pas un défaut du diff

Le [ACTION] précédent notait que le test anti-entropie « passe » après la fusion de main. Le run de 00:38:24Z l'a vu re-échouer, aux côtés du rouge lean-inventory. Les deux causes, mesurées cette nuit :

1. Anti-entropie : artefact d'heure de jour dans la résolution de date de l'organe. L'historique du registre porte encore le point de mesure réel du 2026-10-31 déclarant l'instrument à 6144b71eb. L'organe re-dérive cette identité par git log --before=2026-10-31, et git remplit l'heure absente par l'heure courante de l'horloge (le docstring de identite_instrument_a le dit lui-même). Le commit 6144b71eb est arrivé sur main à 2026-10-31 04:18 UTC (05:18 heure locale +0100) : tout run de CI entre 00:00 et 04:18 UTC dérive d818b008b (10-30 20:42) pour cette date, et l'identité déclarée n'en est pas un ancêtre — elle en est la descendante ; la tolérance d'ancêtre ne couvre que l'autre sens. Le run de 00:38Z est tombé dans cette fenêtre. Le même rouge frappe toute PR dont le run de Scripts Tests (CPU) passe dans cette fenêtre, et il disparaît au run suivant après 04:18 UTC. C'est un fait d'organe (registre anti-entropie, #23175), non réparable dans cette PR : aucun des fichiers en cause n'est dans le périmètre du diff.

2. lean-inventory CongestionGames : inchangé — hérité de la base, correction déjà livrée par #24764 (ouverte).

Geste : aucun sur la branche (périmètre intact, 6 fichiers). Au prochain re-run après 04:18 UTC — ou au merge de #24764, qui en déclenchera un — il ne restera que le rouge #24764, puis aucun. L'artefact d'heure est consigné ici pour le propriétaire de l'organe : un point mesuré après un commit du jour sur l'instrument ne se re-vérifie qu'après l'heure de ce commit, tant que la résolution --before sans heure emprunte l'horloge courante.

This branch has not been deployed

No deployments
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.

2 participants