Architecture — CVN-N014-ED-S02 (s43 cross-pod transport)¶
Story : CVN-N014-ED-S02 · Epic : CVN-N014-ED · Plan : dossier · ADR : 0100 Inv 1.
Le flux (Route Y — pass-by-référence vers S3)¶
acquire_cell (pod par crypto) gate (un autre pod)
train K draws → p_buy (ndarray) load_cohort_predictions(store, prefix, …)
persist_predictions(store, prefix, …) for (crypto, fold, family) in EXPECTED cohort:
np.savez → BytesIO store.file_exists(key)? → get_object → np.load
store.upload_fileobj(buf, key) _validate_npz (fail-loud)
return summary{keys:[…]} ── XCom ──► pool + verdict
(clé JSON, PAS le tableau)
S3: s3://cvntrade-artifacts/s43-predictions/<run_id>/<leaf>.npz
- Le tableau ne touche jamais XCom :
XComObjectStorageBackend.serialize_valuefaitjson.dumps(…, XComEncoder)avant le seuil →TypeErrorsurndarray(vérifié S01). Donc l'array → S3, la clé JSON → XCom (portée par le flip S01). - Store :
commun.s3.cvntrade_s3_manager.CVNTrade_S3Manager— bucketcvntrade-artifacts(Scaleway, connaws_default), méthodesfile_exists/upload_fileobj/get_object. Le même bucket que MLflow + L2-events.
Keying — run-isolé (§7.3 du plan)¶
-_s43_prefix(base, context) (dans le DAG) construit <base>/<run_id assaini> ; acquire_cell et gate partagent le même DAG run → même préfixe → le gate lit exactement les blobs de CE run (jamais les restes d'un run précédent).
- Clés disjointes par (crypto, fold, family) → écritures concurrentes sans race.
- Retry : la tâche réécrit (idempotent) son objet et return la clé de son try réussi → le gate (qui charge les clés attendues, pas un list-prefix) ne lit jamais deux tries.
Manifest — expected-keys, pas list-prefix¶
Le gate charge la cohorte attendue (_resolve_cohort_symbols, le même helper que resolve_cohort qui a lancé les pods → ne peuvent diverger) × familles, en clés déterministes. Pas de list_files(prefix) dans le chemin nominal → orphelins / retries / restes ne polluent pas le pool. list_files reste dispo pour diagnostic/réconciliation.
Contrat .npz — fail-loud (§7.4)¶
np.savez stocke y_va, P (n_draws × n_val, ordre préservé, float64), run_ids (unicode), schema_version, crypto, fold_id, family. _validate_npz au load :
- blob absent → None (toléré : pod échoué / K=0) ;
- blob présent mais schema_version ≠ SCHEMA_VERSION, array manquant, ou P/y_va shape incompatible → ValueError (fail-loud, ADR-25 : la corruption doit remonter, ce n'est pas un « pod manquant »).
Pourquoi S3 et pas un FS partagé / un serde¶
- FS RWX partagé : off-default sur le cluster (SBS = RWO), concurrence/SPOF/GC → à contre-courant d'une stack à pods stateless. S3 est déjà le substrat.
- Serde
airflow.serialization: couplerait chaque consumer + re-route le tableau par le chemin metadata/offload. Le pass-by-ref explicite est local à s43, conforme à la pratique Airflow + ADR-0100 Inv 1.
Conformité ADR¶
- ADR-0100 Inv 1 (JSON-or-pass-by-ref) : respecté — XCom = référence JSON, S3 = payload.
- ADR-25 (no silent fallback) : persist no-raise (→ absent), load fail-loud sur corruption.
- Périmètre : transport s43 only. Science → CVN-N001-EI-S05 ; généralisation policy → S03.