Skip to content

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_value fait json.dumps(…, XComEncoder) avant le seuil → TypeError sur ndarray (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 — bucket cvntrade-artifacts (Scaleway, conn aws_default), méthodes file_exists / upload_fileobj / get_object. Le même bucket que MLflow + L2-events.

Keying — run-isolé (§7.3 du plan)

<predictions_dir>/<sanitized run_id>/s43-pred-<crypto>-<fold>-<family>.npz
- _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 absentNone (toléré : pod échoué / K=0) ; - blob présent mais schema_versionSCHEMA_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.