Statut : r1 (companion du plan de fix et de la RCA ; normé par ADR-0096).
Objet : cartographier l'impact du fix sur tout ce qui existe déjà — modules, infra, composants, bibliothèques, process — avant de lancer le fix. Pas un plan d'action (cf. fix plan) ; une analyse de surface d'impact + de blast-radius.
OMP/OPENBLAS/MKL/NUMEXPR_NUM_THREADS + LOKY_MAX_CPU_COUNT + OMP_WAIT_POLICY=passive, posés avant tout import Python, au point d'entrée partagé de l'image de compute, depuis $CVN_POD_CPU_LIMIT (Downward API).
L2 — assertion fail-loud
refus de démarrage si caps unset / > limite / CVN_POD_CPU_LIMIT non déclaré (event=pod_thread_caps_unsafe).
L3 — process-parallelism
pas de n_jobs=-1 sur pod de compute ; défaut n_jobs=1 ; produit n_jobs × thread_cap ≤ limit.
L4 — s42
skip_phase_aexplicite par-run (défaut inchangé) + belt num_threads.
L5 — s43
bootstrap B=2000série (déjà codé série) + test Invariant 5 (pas de n_jobs=-1).
L6 — A6
détection replay-divergence indépendante (gate du futur défaut skip).
Le blast-radius dominant = L1/L2 : le point d'entrée partagé touche tous les pods qui en héritent, pas seulement s4x.
reçoit le wrapper de cap. ⚠️ entrypoint.sh actuel est cron-based (pas le chemin des pods KPO, qui lancent python … directement) → le cap doit s'insérer dans le command/args du template KPO ou un bootstrap sourcé par la commande de compute, pas dans entrypoint.sh. À cartographier explicitement (cf. fix plan pré-condition inventaire).
Haute
Pod specs KPO (dags/dag_diagnostic__*.py, dags/launch__*.py)
doivent déclarer le Downward API CVN_POD_CPU_LIMIT (resourceFieldRef: limits.cpu). ~20 DAGs concernés (s18/s22/s26/s27/s28/s40/s41/s42 + launch promotion/discovery/retrain/backtesting/cleanup). Si centralisé dans _common.py:STANDARD_POD → 1 point. Pods n'héritant pas de STANDARD_POD = trou (blocker committee #2).
Haute
Helm / values-prod
aucune valeur runtime nouvelle si le cap lit la limite via Downward API (pas de nouveau ConfigMap). Les limits.cpudoivent exister sur chaque pod sinon fail-loud cpu_limit_undeclared → audit des specs sans resources.limits.
Moyenne
Autoscale Scaleway PRO2-M
inchangé. Le cap réduit le CPU réellement brûlé par pod (de ~3.2 cœurs busy-wait → travail utile) → potentiellement moins de pression d'autoscale, jamais plus.
Faible (positif)
Contention node-level
n_pods × cap > n_node_cores (5×4 > 16) → slowdown ~25%, pas livelock (cf. ADR-0096 Consequences). Acceptable one-shot.
contient n_jobs=-1 (LightGBM) — vecteur exact de l'Invariant 5. À borner (n_jobs=1 ou produit borné) avant que ce DAG tourne sur un pod cappé, sinon node_cores × cap threads.
label_pipeline = n_jobs=1 (safe) ; TwoStage = hyperconfig.n_jobs (à auditer) ; trainers OpenMP couverts par thread-cap. Le harness FTF tourne sur pod de compute → hérite du cap.
Audit n_jobs
dags/_common.py
point d'injection canonique du Downward API + (option) du wrapper. Changement structurel ici = le plus sûr.
central
1.3 Bibliothèques (le cap les touche toutes via env)¶
Lib
Mécanisme capté
Risque résiduel
LightGBM / XGBoost / CatBoost
OpenMP (OMP_NUM_THREADS) + param num_threads.
belt code-level en plus.
numpy / scipy / sklearn
BLAS (OPENBLAS_/MKL_NUM_THREADS) — dominant sur le bootstrap s43.
dépend du backend BLAS lié (OpenBLAS vs MKL) → threadpoolctl.threadpool_info() à confirmer une fois (ADR-0096 validation note).
joblib / loky
LOKY_MAX_CPU_COUNT + interdiction n_jobs=-1.
le seul vecteur process ; le -1 de s18_step3_parity est la cible.
ADR-0096 ajouté (active, committee PASSED) ; ADR-0095 §A3 défère à 0096. Catalogue adr/index.md mis à jour.
CI gates
nouveau gate lint/guard n_jobs=-1 interdit sur pods de compute (reco committee #3, enforce Invariant 5). Gate optionnel threadpoolctl (reco #1).
Story workflow
S04 gate In testing → Tested reste bloqué tant que le fix n'a pas re-tourné un verdict. Le fix lui-même n'est pas une Story S04 — c'est un correctif infra transverse (rattaché au RCA).
Deploy sequence
L1/L2 = changement de toutes les images de compute → déploiement phasé (WARN → observe → fail-loud) pour borner le blast-radius (cf. fix plan §3).
Runbooks
runbook s43 inchangé fonctionnellement ; ajout note "le pod cappe ses threads, ne pas surcharger n_jobs".
2. Surface NON impactée (negative scope — important)¶
Aucun changement de logique métier / pipeline de trading : filtres, inference, labels, backtest engine — intouchés. Le fix est env + startup-check + bornage de parallélisme.
Aucun changement de résultat numérique : caps + série donnent des mêmes verdicts (le bootstrap s43 est déjà bit-identical via default_rng(seed), série). Le cap ne change que le temps, pas les chiffres.
Aucun nouveau param PG / Console (ADR-59 non sollicité).
Pas de migration de données / de schéma ; rollback = revert env + assertion (état pré-incident), sans impact data/logique.
3. Risques d'interaction (ce qui pourrait casser ailleurs)¶
Risque
Probabilité
Mitigation
Un pod non-diagnostic (launch promotion/discovery/retrain/backtesting) hérite du cap et ralentit
Moyenne
cap sur cgroup = budget garanti ; passive → slowdown borné, jamais livelock. Inventaire pré-deploy.
Un pod sans limits.cpu → fail-loud cpu_limit_undeclared au démarrage (régression de démarrage)
Moyenne
audit des specs sans resources.limitsavant le deploy fail-loud (phase WARN d'abord).
Le BLAS lié n'est pas celui cappé (MKL vs OpenBLAS) → cap manqué
Faible
threadpoolctl.threadpool_info() une fois en CI/startup.
s18_step3_parityn_jobs=-1non corrigé avant deploy → re-livelock process-level
Moyenne
borner ce -1 (L3) + CI guard.
Limite fractionnaire (3.5 → cap 4) sur-souscrit légèrement
Faible
slowdown sous passive, pas livelock ; ou floor via millicores.