Aller au contenu

Plan — Pipeline de collaborations entre chercheurs (V1, plateforme DataOps)

Date du plan : 2026-06-02. Socle décisionnel : ADR 0029 (architecture de la plateforme DataOps) + ADR 0054 (ingestion massive par snapshot S3, amende 0029) + ADR 0055 (catégorie dataops/ : code DataOps en Python) + ADR 0030 (RGPD, profilage) + ADR cluster 0011 (registry HTTP), 0019 (réseau Cilium default-deny), 0020 (exposition tout-Cilium), 0021 (cert-manager CA interne), 0024 (PostgreSQL CloudNativePG), 0026 (Dagster), 0028 (Marquez), 0036 (S3 RGW), 0038 (Lima seul banc local), 0043 (contrat d’interface), 0044 (Gitea intra-banc).

Révisé le 2026-06-10. Trois évolutions depuis la rédaction (2026-06-02) : (1) le socle cluster décrit en Phase 1 est désormais livré et accessible (Cilium tout-Cilium, cert-manager CA interne, Argo CD sur Gitea intra-banc, CloudNativePG, Dagster, Marquez) — la Phase 1 devient un état de référence, pas un chantier ; (2) la stratégie d’ingestion passe de l’API REST paginée delta au snapshot S3 OpenAlex (works + authors, toute la base), actée par ADR 0054 qui amende ADR 0029 ; (3) le RGPD (Phase 0) cesse d’être un gate bloquant : le dépôt livre un canevas techniquement capable de conformité (ré-dérivabilité, droit d’opposition, authentification), testé et déployable, l’arbitrage (base légale, responsable de traitement, DPO) relevant du déployeur — cf. README « Responsabilité & conformité » et ADR 0031. (4) le code DataOps (assets Dagster, dbt, sync) est en Python natif dans une nouvelle catégorie dataops/ (ADR 0055) — Dagster/dbt l’imposent —, à la même exigence de qualité que le code TypeScript ; le sync utilise rclone.

Révisé le 2026-06-23. Deux évolutions du contrat cluster→atlas (#430) : (5) l’exposition des UI de plateforme passe de la Gateway Cilium L7 + LB-IPAM + *.cluster.lan à un L4 NodePort (http://<IP-nœud>:<nodePort>, zéro DNS/LB/Gateway), acté par ADR cluster 0092 (supersede 0020/0071) et un portail (ADR cluster 0091). Les endpoints intra-cluster consommés par atlas (pg-rw.postgres, marquez.marquez:5000, mlflow.mlflow:5000, RGW) sont inchangés (noms courts). Le socle n’expose donc plus de bordure L7 : la terminaison TLS externe d’un mart nominatif (Phase 6) relève désormais du déployeur. (6) le contrat acte le dimensionnement des volumes comme responsabilité d’atlas : le cluster fournit des StorageClasses extensibles (expansion: true sur le bloc Ceph) + une capacité abondante, sans imposer de taille ; atlas dimensionne ses propres données (base pgvector qui grandit avec les embeddings — PVC CNPG sur rook-ceph-block-replicated, resize RBD en ligne ; artefacts MLflow et datalake = objet S3, croissent dans le pool, pas de PVC à pré-dimensionner).

Livrer la V1 du pipeline de recommandation de collaborations sous la forme d’une plateforme DataOps alignée sur les standards du marché, et non d’une ossature minimale. Le démonstrateur incarne littéralement le profil attendu par la fiche dataops :

« Pipeline lakehouse de démonstration : ingestion → DuckDB/Iceberg sur le S3 Ceph du cluster, transformations dbt, orchestration Dagster, lineage OpenLineage. »

Concrètement, un flux batch orchestré par Dagster (planifié mensuel ; cadence source trimestrielle, cf. ADR 0054) synchronise le snapshot S3 d’OpenAlex — la base complète, entités works et authors, plus les références (referenced_works) et le FWCI portés par chaque œuvre —, transforme via dbt (dbt-duckdb) en couches staging → curated → marts, dérive un signal de citations croisées article↔article entre chercheurs (modèle dbt + SQL), produit un mart Parquet + manifest.json sur le S3 Ceph, alimente un index PostgreSQL/pgvector (FTS lexical + recherche sémantique), score les paires de façon déterministe (poids fixes EnsembleWeights), et expose le résultat via atlas-api (Hono + OpenAPI 3.1 + Scalar — rôle métier ET rôle exploration/vérification) et une PWA find-an-expert. Qualité par Great Expectations (en complément des tests dbt), lineage par OpenLineage → Marquez. Le tout posé sur un socle cluster fourni (Cilium + cert-manager + Argo CD/Gitea + Prometheus + CloudNativePG + Dagster + Marquez).

Ce que la V1 n’est pas : un système de ML entraîné/calibré (MLflow), un LLM génératif synchrone, un feature store (Feast), un format de table transactionnel (Iceberg), un serving versionné (KServe). Ces briques sont nommées au Palier 2 et explicitement hors périmètre.

  • Dépôt atlas (ce dépôt) : les ADR 0029/0030/0054 ; l’asset Dagster d’ingestion par snapshot S3 (works + authors, bootstrap puis incrémental) ; citation-fetch relégué aux compléments ciblés (< 10 k) ; le parsing des referenced_works/fwci portés par chaque œuvre du snapshot ; l’accès lakehouse DuckDB↔S3↔(JSONL.gz, Parquet) (httpfs/secret RGW/COPY, neuf) ; les modèles dbt (staging/curated/marts) dont la feature citations croisées ; le contrat manifest.json + partitions immuables + schema_version (validateur dans packages/citation) ; les suites Great Expectations ; le chargement de l’index Postgres/pgvector ; le 3ᵉ signal sur EnsembleWeights ; atlas-api (métier + exploration) ; les résumés extractifs ; la PWA find-an-expert ; la signature d’images. Seuls les manifestes applicatifs (Deployment/Service/Ingress/Application Argo CD de ses propres composants, code-location et définitions d’assets Dagster) vivent ici.
  • Dépôt cluster (séparé, ../cluster) : tout l’addon d’infrastructureCilium (CNI ; exposition des UI en L4 NodePort, ADR cluster 0092, zéro LB-IPAM/Gateway), cert-manager (CA interne), Argo CD (sur Gitea intra-banc), kube-prometheus-stack, Loki, CloudNativePG, Dagster (dagster-k8s), Marquez, MLflow, RGW Ceph + SeaweedFS — déployé via platform/<addon>/ + Ansible (bootstrap/), validé sur le banc Lima (ADR cluster 0038). Ce socle est déjà livré (cf. Phase 1). Aucun manifeste d’infra ne vit dans atlas.
  • Données : périmètre profilé en opt-out, base légale d’intérêt public / intérêt légitime (pas le consentement) — tout chercheur du périmètre d’ingestion est profilé par défaut, sauf opposition. Le dispositif consent-events Appwrite (ConsentType openalex_email de find-an-expert) sert de registre d’opposition (il retire les personnes s’étant opposées). L’outil est générique/multi-tenant : la déclaration des alliances par l’utilisateur filtre l’affichage, pas l’ingestion. Le dépôt livre un canevas techniquement capable de conformité (ré-dérivabilité, droit d’opposition, authentification), testé et déployable ; actionner la conformité (base légale, responsable de traitement, DPO) relève du déployeur (Phase 0, README « Responsabilité & conformité », ADR 0031).
  • RGPD : capacité technique, pas un gate bloquant. Le dépôt livre les mécanismes de conformité (ré-dérivabilité du mart et de l’index, droit d’opposition via consent-events, authentification obligatoire sur les routes nominatives) testés et déployables. Ce n’est pas au dépôt de trancher la base légale ni de désigner le responsable de traitement : cet arbitrage relève du déployeur (README « Responsabilité & conformité », ADR 0030, ADR 0031). Le développement et les tests se font sur la base complète ; le déployeur actionne sa conformité avant exploitation nominative.
  • Non-régression. À chaque étape côté atlas, pnpm ci:checks && pnpm ci:audit && pnpm docs:build reste vert. Le socle cluster est déjà livré : aucune action cluster n’est requise par ce plan (hormis les prérequis tracés en issue cluster, p. ex. l’egress Internet).
  • Validation E2E sur le banc local, à chaque incrément. Au-delà des tests unitaires, chaque incrément déployable est validé de bout en bout sur le banc Lima (cf. « Stratégie de validation E2E » ci-dessous) : packagé en image, poussé sur Gitea, réconcilié par Argo CD, exécuté comme run Dagster réel, puis vérifié dans son aval (objets RGW, lineage Marquez, lignes Postgres). On ne déclare un incrément « fait » qu’après cette preuve sur le cluster, pas seulement en vert local.
  • Libre & souverain. Aucune dépendance SaaS. La source d’ingestion elle-même est publique et libre d’accès : le snapshot OpenAlex sur AWS Open Data (accès anonyme, sans clé ni quota d’API). Tout composant (exposition, certs, monitoring, Postgres, Dagster, Marquez, modèle d’embedding ONNX, futur LLM) est auto-hébergé sur le cluster, artefacts mirrorés sur le registry interne. Licences permissives uniquement (Apache-2.0 / MIT / BSD). Dagster, dbt, DuckDB, Great Expectations, OpenLineage/Marquez, pgvector, Hono, Scalar : tous OSS.
  • IA 100 % interne, CPU, sans appel externe — free tier inclus. Aucune donnée (a fortiori un mart nominatif) n’est envoyée à une IA SaaS, même gratuite : ce serait un transfert de données personnelles contraire à ADR 0030, et le free tier n’y change rien (transfert hors UE, entraînement possible sur les données, quotas instables). Embeddings ONNX all-MiniLM-L6-v2 en V1 ; LLM self-host (Ollama, Mistral-7B-Instruct Apache-2.0, batch) au palier 2. Tout tourne sur le cluster, sans GPU.
  • Aligné-benchmark, assumé. On vise la plateforme DataOps représentative (Dagster + dbt + OpenLineage + Great Expectations + pgvector), pas l’ossature minimale « CronJob + SQL brut ». C’est un choix de positionnement : la stack est plus riche (composants stateful nouveaux : Dagster, CloudNativePG, Marquez), donc l’effort et la charge opérationnelle sont accrus — c’est explicitement assumé (cf. ADR 0029, Prix à payer, et la section Risques).
  • Interface DataOps↔scoring inchangée. Le contrat producteur↔consommateur reste un fichier Parquet + un manifest.json atomique sur S3 Ceph (partitions immuables, sha256, schema_version). dbt produit ce mart ; l’index Postgres/pgvector en est dérivé (exploration/recherche), jamais le contrat de transfert. Pas de queue, pas de base partagée, pas d’API sur le chemin de calcul.
  • Une PR par phase (sauf découpage explicitement prévu). Les PRs cluster et atlas sont distinctes par construction (dépôts séparés).
  • Nommage local strict. Jamais « OpenAlex » dans un identifiant (bucket, namespace, paquet, variable, label, modèle dbt) : utiliser citation. La marque n’apparaît que dans la prose. Seule exception en prose : s3://openalex désigne le bucket source externe (snapshot AWS Open Data) — la synchronisation écrit vers le bucket interne s3://citation/raw. Namespaces = citation-ingest, citation-marts, citation-serving, citation-pwa. Cette règle est une convention de dépôt, pas portée par ADR 0022 (qui ne traite que du préfixe atlas-) : dette connue, pas encore d’ADR dédié.
  • Commits. Conventional Commits, scope ∈ scope-enum de commitlint.config.js (vérifier avant chaque commit : citation, citation-cli, citation-fetch, citation-types, dataops, find-an-expert, researcher-profiles, infra, ci, docs…). Le code DataOps Python (assets Dagster, modèles dbt, sync) commite sous le scope dataops (ajouté au scope-enum, ADR 0055). Le scope atlas-api sera ajouté en Phase 5.2 (PR qui crée le service). Pas de Co-Authored-By.
  • Hooks lefthook JAMAIS bypassés : pas de --no-verify, LEFTHOOK=0, etc. Si un hook bloque, fixer en racine. knip casse pré-push sur main : résorber la dette à la racine, ne pas contourner.
  • Décisions structurantes via ADR (Nygard léger), jamais en bullets dans un TODO.
  • Idempotence & immutabilité. Avant écriture, vérifier que l’état cible n’est pas déjà atteint. Une partition de mart déjà produite ne se réécrit jamais en place (rejeu = nouveau run=<id>).
  • Effect & thin clients. Logique dans packages/* (ADR 0008), retours Effect<A, E> (ADR 0005), run aux points d’entrée (handler Hono, bin batch, op Dagster).
  • Validation systématique. Chaque étape se clôt par ses commandes de validation, qui doivent toutes passer.
PhaseTitreDépôtBloqueEffort
0RGPD (capacité technique, non bloquant)atlasM
1Socle cluster (FOURNI)cluster (livré)
2Ingestion massive snapshot S3 (works + authors)atlas3L
3Transformations dbt + mart + contrat + qualitéatlas4XL
4Indexation PostgreSQL/pgvectoratlas5L
5Scoring déterministe + atlas-api (métier + explo)atlas6XL
6PWA find-an-expert + expositionatlasM
7DevSecOps images (cosign/SLSA/SBOM/Trivy)atlas(transverse)M

Chemin critique. La Phase 1 (socle cluster) est livrée ; le chemin critique applicatif devient 2 → 3 → 4 → 5 → 6. La Phase 0 (RGPD) n’est plus bloquante : ses mécanismes sont livrés et testés, l’arbitrage relève du déployeur. Le seul prérequis d’infra restant est l’egress Internet pour le sync du snapshot, tracé en issue cluster #256 (à livrer en parallèle). La Phase 7 est transverse : applicable dès qu’une image existe (Phase 2), traitée en dernier sans bloquer la chaîne fonctionnelle.

Chaque incrément déployable est prouvé de bout en bout sur le banc local avant d’être déclaré fait — pas seulement par des tests unitaires. Le banc est un petit cluster (3 VMs Lima, 2 CPU / 8 Gio chacune) : la stratégie tient compte de cette contrainte.

  • Boucle GitOps complète. L’incrément est packagé en image (poussée sur le registry interne), ses manifestes sont poussés sur Gitea, Argo CD les réconcilie, et l’exécution est un run Dagster réel (K8sRunLauncher). On ne valide pas par kubectl apply manuel (ADR cluster 0046).
  • Échantillon ultra-borné. L’ingestion de test ne tire que quelques fichiers .gz par entité (filtre rclone), soit des dizaines de Mo — jamais le snapshot complet (1,6 To, irréaliste sur ce banc). L’échelle est un paramètre de configuration (défaut très petit pour le banc/CI, augmentable à la demande).
  • Vérification de l’aval. Selon la phase, la preuve est cherchée là où l’effet se matérialise : objets dans le RGW (sync, mart Parquet), lineage dans Marquez (assets, OpenLineage), lignes dans Postgres (index FTS/pgvector), réponse de l’API (Phase 5). Accès via la code-location déployée et, au besoin, kubectl/port-forward pour l’inspection.
  • Prérequis. L’E2E de la Phase 2 dépend de l’egress Internet (issue cluster #256) : tant qu’il n’est pas livré, la validation s’arrête au sync (test de l’op contre un RGW joignable), le bout-en-bout complet suit dès l’egress disponible.

Honnêteté sur l’effort. Ce plan représente nettement plus que la charge d’une ossature « CronJob + SQL brut » : c’est le prix assumé du positionnement plateforme. Outre les chantiers de code neuf à 100 % — (1) l’accès lakehouse DuckDB↔S3↔(JSONL.gz, Parquet) en Python (dataops/, profil dbt-duckdb) et les modèles dbt ; (2) le sync massif du snapshot S3 (≈ 330 Go gzippé / 1,6 To, entités works et authors) via rclone et son incrémental par updated_date (+ merged_ids) — citation-fetch (API REST, plafond 10 k) ne sert plus qu’aux compléments ; la feature citations croisées reste neuve (scorer.ts/ensemble.ts ne pondèrent que {tfidf, embedding}) ; (3) l’index PostgreSQL/pgvector + l’API d’exploration ; (4) l’intégration Dagster (assets, schedule, asset checks) + OpenLineage — la V1 introduit trois composants stateful nouveaux à exploiter (Dagster, CloudNativePG, Marquez), avec la charge opérationnelle correspondante. La couche DataOps est en Python (catégorie dataops/, ADR 0055), à la même exigence de qualité que le reste du dépôt. Ce qui est réutilisable l’est tel quel et est nommé étape par étape, sans survente.


Phase 0 — RGPD : capacité technique (non bloquant)

Section intitulée « Phase 0 — RGPD : capacité technique (non bloquant) »

Objectif. Doter le pipeline des mécanismes de conformité RGPD, testés et déployables, sans en faire un gate qui suspend le développement. Le dépôt livre un canevas techniquement capable ; actionner la conformité (base légale, responsable de traitement, information des personnes, analyse d’impact) relève du déployeur — c’est la position du README (« Responsabilité & conformité ») et de l’ADR 0031 (outil générique open-source). L’ADR 0030 rouvre ADR 0026 pour le profilage (sans le contredire), pose la base légale d’intérêt public / intérêt légitime en opt-out (le dispositif consent-events devient un registre d’opposition), acte le caractère générique/multi-tenant (déclaration des alliances = filtre d’affichage), et pose la ré-dérivabilité du mart et de l’index dérivé. Dépendances. Aucune. Ne bloque plus les phases suivantes : le développement et les tests se font sur la base complète, le déployeur actionne sa conformité avant exploitation nominative. Parallélisable ? Oui — les mécanismes (0.1 ré-dérivabilité, 0.2 droit d’opposition, auth) se livrent au fil des phases applicatives. Critère de sortie de phase. ADR 0030 Accepted (acquis) ; la ré-dérivabilité du mart et de l’index pgvector est spécifiée (régénération/masquage by-design) ; le périmètre servi est défini comme « ensemble des chercheurs du périmètre d’ingestion hors opposition (registre d’opposition consent-events) » ; le déployeur dispose de la documentation nécessaire pour tracer son propre arbitrage (base légale, responsable de traitement). pnpm docs:build vert.

Étape 0.1 — Acter l’ADR 0030 (cadre RGPD du profilage)

Section intitulée « Étape 0.1 — Acter l’ADR 0030 (cadre RGPD du profilage) »
  • Goal : Écrire l’ADR qui rouvre 0026 pour le profilage nominatif, pose les bornes techniques (base légale d’intérêt public / intérêt légitime en opt-out, registre d’opposition, ré-dérivabilité, auth obligatoire y compris routes d’exploration), acte le caractère générique/multi-tenant (déclaration des alliances = filtre d’affichage), et nomme le risque EU AI Act (scoring d’individus, recommandations nominatives).
  • Files (read) : docs/decisions/0026-rgpd-perimetre.md, docs/decisions/0029-architecture-pipeline-collaborations.md, docs/decisions/README.md, apps/find-an-expert/ (modules consent-events, current-consents, ConsentType, /api/v1/consents, ConsentStatusCard).
  • Files (write) : docs/decisions/0030-rgpd-profilage-collaborations.md, docs/decisions/README.md (entrée index).
  • Invariants à préserver : ADR 0026 non amendé (0030 le rouvre) ; format Nygard léger (## Contexte / ## Décision avec ### / ## Statut Accepted (2026-06-02) / ## Conséquences **Bénéfices.** / **Prix à payer.** / **Garde-fous.**).
  • Validation : pnpm docs:build ; liens relatifs [NNNN](NNNN-slug) vérifiés.
  • Done criteria :
    1. ADR 0030 présent, Accepted (2026-06-02), liant 0026 et 0029.
    2. Acte : base légale d’intérêt public / intérêt légitime, périmètre en opt-out (registre d’opposition consent-events) ; outil générique/multi-tenant (déclaration des alliances = filtre d’affichage) ; mart et index dérivé ré-dérivables ; auth obligatoire sur toute route nominative (y compris /search et filtres d’exploration) ; mention EU AI Act.
    3. ## Garde-fous rappelle la répartition des responsabilités : le dépôt livre la capacité technique, le déployeur actionne la conformité (base légale, responsable de traitement).
  • PR title : docs(adr): cadre RGPD du profilage de collaborations (ADR 0030)

Étape 0.2 — Concevoir la ré-dérivabilité du mart et de l’index

Section intitulée « Étape 0.2 — Concevoir la ré-dérivabilité du mart et de l’index »
  • Goal : Spécifier comment une opposition se propage à un mart de partitions immuables et à l’index pgvector dérivé : régénération de la partition courante par re-dérivation depuis curated filtré sur le registre d’opposition à T (on exclut les personnes opposées), masquage rétroactif des partitions historiques, et purge/recharge des lignes correspondantes dans l’index Postgres (qui n’est pas source de vérité, donc régénérable).
  • Files (read) : ADR 0030, ADR 0029 (interface Parquet + index dérivé), packages/citation-types.
  • Files (write) : page dédiée dans docs/architecture/ (« ré-dérivabilité du mart et de l’index »), référencée par l’ADR 0030.
  • Invariants à préserver : immutabilité des partitions de production (on n’écrit jamais en place) ; la ré-dérivation produit une nouvelle partition run=<id>, l’ancienne marquée obsolète ; l’index est régénéré, jamais traité comme autorité du contrat.
  • Validation : pnpm docs:build ; revue de cohérence « partitions immuables » + « mart ré-dérivable » + « index dérivé purgeable ».
  • Done criteria : Document décrivant (a) registre d’opposition (consent-events, qui retire les personnes opposées), (b) régénération de la partition mart, (c) masquage à la lecture pour l’historique, (d) purge/recharge de l’index pgvector, (e) SLA de propagation d’une opposition.
  • PR title : docs(architecture): ré-dérivabilité du mart et de l'index sous RGPD

Étape 0.3 — Documenter l’arbitrage que le déployeur doit conduire

Section intitulée « Étape 0.3 — Documenter l’arbitrage que le déployeur doit conduire »
  • Goal : Fournir au déployeur la matière pour conduire son propre arbitrage (base légale, responsable de traitement, rétention, information des personnes) — ce que le code ne peut pas trancher. Le dépôt documente, il ne se substitue pas à l’établissement exploitant.
  • Files (read) : ADR 0026 (questions ouvertes Q2/Q6), ADR 0030, ADR 0031, README (« Responsabilité & conformité »).
  • Files (write) : section « À la charge du déployeur » dans l’ADR 0030 ou la doc RGPD (check-list : base légale à confirmer, responsable de traitement à désigner, AIPD si nécessaire), renvoyant au README.
  • Invariants à préserver : aucune affirmation de conformité non validée n’est écrite (le code ne se prononce pas sur la base légale) ; la responsabilité incombe au déployeur, pas au dépôt.
  • Validation : pnpm docs:build ; la check-list déployeur est présente et liée depuis l’ADR 0030.
  • Done criteria : Le déployeur dispose d’une check-list claire pour son arbitrage. Aucune condition de sortie du dépôt n’est subordonnée à un arbitrage institutionnel externe (capacité technique livrée, conformité actionnée par le déployeur).
  • PR title : docs(adr): check-list RGPD à la charge du déployeur (ADR 0030)

Phase 1 — Socle cluster (FOURNI par le dépôt cluster)

Section intitulée « Phase 1 — Socle cluster (FOURNI par le dépôt cluster) »

Le socle décrit ici est déjà livré et accessible dans le dépôt cluster ; cette section en consigne l’état de référence (briques, endpoints, ADR cluster) que les Phases 2–6 consomment. Aucune action cluster n’est requise par ce plan — hormis les prérequis tracés en issue cluster #256. Cette phase ne se ré-exécute pas : la source de vérité opérationnelle est le contrat d’interface cluster/contract/*.example.yaml (ADR cluster 0043) et le guide-dev-data.md du dépôt cluster.

Le socle ci-dessous est opérationnel. Les Phases 2–6 s’y branchent via les endpoints réels du contrat ; chaque ligne renvoie à l’ADR cluster qui en fait foi.

Besoin plateformeFourni par (réel)Endpoint / accèsADR cluster
Exposition des UIL4 NodePort (zéro DNS, zéro LB-IPAM, zéro Gateway)http://<IP-nœud>:<nodePort> (port observé par le portail)0092 (supersede 0020)
Portail d’accès aux UIportail (liste les UI par couche + liens L4 + logins)http://<IP-nœud>:<nodePort>0091
GitOpsArgo CD lisant Gitea intra-banc (webhook)AppProject atlas (dest. citation-*, dagster, marquez)0022, 0044
Observabilitékube-prometheus-stack + LokiGrafana en NodePort http://<IP-nœud>:<nodePort>0016
PostgreSQL + pgvectorCloudNativePG, cluster pg HA ×3 (RBD ×3), bases dagster/pgvector/marquez, extension vector (dimension libre → atlas applique 384)pg-rw.postgres:5432 (RW) / pg-ro.postgres:5432 (RO), secrets pg-role-*0024
OrchestrationDagster (chart 1.13.7, K8sRunLauncher, event log CNPG), livré vide (workspace load_from: [])dagster-dagster-webserver.dagster:800026
LineageMarquez (chart 0.51.1, store CNPG)OPENLINEAGE_URL=http://marquez.marquez:50000028
Stockage objetRGW Ceph (CephObjectStore datalake, EC 2+1) via ObjectBucketClaim (OBC) ; SeaweedFS au banc légerrook-ceph-rgw-datalake.rook-ceph:80 (path-style) ; seaweedfs.s3:83330036
Registry d’imagesinterne HTTP sans authregistry:80/<repo>:<tag>0011
Contrat machine-lisiblecluster/contract/*.example.yaml ; access.sh génère atlas/.env.cluster.local0043, 0048

Historique (ce qui était « à monter », désormais livré autrement). Le plan initial prévoyait MetalLB (1.1) + ingress-nginx (1.2) : remplacés par Cilium tout-Cilium (0020). cert-manager (1.3) : livré en CA interne (0021), pas ACME. Argo CD (1.4) : lit Gitea intra-banc (0044), pas GitHub public. Monitoring (1.5) : livré (0016, palier 2). CloudNativePG (1.6), Dagster (1.7), Marquez (1.8) : livrés (0024/0026/0028). Le banc local est désormais Lima (0038), en remplacement de Vagrant.

À fournir encore côté cluster (prérequis tracés)

Section intitulée « À fournir encore côté cluster (prérequis tracés) »

L’issue cluster #256 a tranché la répartition. Côté cluster (livré par cette passe) :

  • 🔴 Egress Internet (bloquant) : le sync du snapshot (s3://openalex, AWS public) exige un accès Internet sortant ; le réseau est en default-deny (0019). Une NetworkPolicy egress est posée sur le namespace dagster (où tourne le rclone sync du code-location), en 0.0.0.0/0 restreint par ports 443/80 (pas de filtrage par plages IP S3 — piège connu sous Cilium).
  • 🟡 Doc branchement code-location : la procédure (patch du ConfigMap dagster-workspace via Argo CD + Deployment gRPC) est ajoutée à docs/guide-dev-data.md du dépôt cluster.

À la charge d’atlas (via Argo CD, dans le périmètre de l’AppProject) :

  • Namespaces citation-* : chaque app porte son propre namespace.yaml et ses NetworkPolicies (default-deny + egress Postgres/RGW/registry/Internet selon le rôle), en recopiant les jeux de référence dagster/ et marquez/ de platform/network-policies/ (côté cluster). cluster ne les crée pas.
  • OPENLINEAGE_URL : injectée par atlas dans l’env du code-location (valeur de contrat http://marquez.marquez.svc.cluster.local:5000, déjà exposée par le contrat).

Phase 2 — Ingestion massive par snapshot S3 (works + authors) (dépôt atlas)

Section intitulée « Phase 2 — Ingestion massive par snapshot S3 (works + authors) (dépôt atlas) »

Objectif. Asset Dagster d’ingestion massive qui synchronise le snapshot S3 d’OpenAlex (s3://openalex/data/{works,authors}) vers le lakehouse s3://citation/raw, en bootstrap complet puis en incrémental par partition updated_date, avec gestion des entités fusionnées (merged_ids). Source décisionnelle : ADR 0054. citation-fetch (API REST, plafond 10 k) est relégué aux compléments ciblés. Watermark de date persisté. Dépendances. Phase 1 (livrée) pour le déploiement (Dagster, Argo CD, OBC) ; prérequis egress Internet (issue cluster #256) pour atteindre s3://openalex. Le secret S3 et le bucket interne viennent de l’ObjectBucketClaim citation-datalake (déclaré par atlas). Parallélisable ? 2.1 (bootstrap) puis 2.2 (incrémental + merged_ids). 2.3 (code-location + déploiement) après 2.1. 2.4 (compléments API) indépendant. Critère de sortie de phase. L’asset de sync écrit s3://citation/raw/{works,authors}/updated_date=YYYY-MM-DD/…, le watermark de date avance, les merged_ids sont rapatriés bruts (raw/merged_ids/<entity>/, appliqués en aval par dbt), aucune réécriture en place ; le run apparaît dans l’event log Dagster. En local, le sync est borné (max_partitions/sample_size, échelle pilotée par config) — jamais 1,6 To. pnpm ci:checks vert.

Étape 2.1 — Bootstrap du snapshot works + authors → s3://citation/raw

Section intitulée « Étape 2.1 — Bootstrap du snapshot works + authors → s3://citation/raw »
  • Goal : Asset Dagster raw_snapshot qui exécute la synchronisation initiale complète de s3://openalex/data/{works,authors} (JSONL gzippé, partitions updated_date) vers s3://citation/raw/{works,authors}/updated_date=…/. La logique vit en Python dans dataops/citation-dagster/ (ADR 0055), pas dans packages/citation (TS) ; le sync utilise rclone (deux remotes, transfert inter-endpoints, ADR 0054).
  • Files (read) : packages/citation/src/db/ (accès S3 à étendre, Phase 3.1), packages/citation-types (types à étendre, works + authors), services/crf/ (patron conteneurisation).
  • Files (write) : définition de l’asset + logique de sync via rclone sync (deux remotes : openalex anonyme → ceph RGW, ADR 0054) dans un op conteneurisé — rclone gère le transfert inter-endpoints distincts, le parallélisme et la reprise ; paramètre d’échelle (limite de fichiers .gz via filtre rclone pour le test local) ; Dockerfile de la code-location (rclone inclus, licence MIT).
  • Invariants à préserver : nommage citation (jamais « openalex » dans un identifiant) ; chemin {works,authors}/updated_date=…/ ; le brut est rapatrié gzippé tel quel (immuable, non transformé ici). Prérequis : egress Internet (issue cluster #256).
  • Validation : pnpm ci:checks ; E2E sur le banc Lima (cf. « Stratégie de validation E2E ») : sync borné à quelques fichiers .gz par entité, écrivant dans le RGW, vérifié via la code-location déployée par Argo CD et un run Dagster réel. Note de volumétrie (≈ 330 Go gzippé / 1,6 To pour la prod) dans docs/architecture/.
  • Done criteria : asset raw_snapshot défini, sync borné de works + authors vérifié en E2E sur le banc, échelle pilotable par config.
  • PR title : feat(citation): bootstrap du snapshot S3 OpenAlex (works + authors)

Étape 2.2 — Incrémental par updated_date + watermark de date + merged_ids

Section intitulée « Étape 2.2 — Incrémental par updated_date + watermark de date + merged_ids »
  • Goal : Ne re-synchroniser que les partitions updated_date postérieures au watermark de date persistant, et rapatrier bruts les merged_ids (s3://openalex/legacy-data/merged_ids/<entity>/) vers raw/merged_ids/<entity>/sans les appliquer (la fusion effective est faite en aval par dbt, étape 3 ; immutabilité de raw/ préservée). Traite la tension de cadence (source gratuite trimestrielle, schedule mensuel idempotent — cf. ADR 0054).
  • Files (read) : logique de sync 2.1, legacy-data/merged_ids/<entity>/ (fichiers YYYY-MM-DD.csv.gz, colonnes merge_date,id,merge_into_idconfirmé).
  • Files (write) : module watermark.py (reader/writer de raw/_watermark.json, écrit après sync réussi par entité), _sync_merged_ids (rapatriement brut), config incrémentale (max_partitions/max_merged_files).
  • Invariants à préserver : le watermark n’avance qu’après sync complet et réussi ; idempotence (rejeu = partition suivante, pas de re-sync) ; immutabilité (pas de réécriture en place) ; entre deux trimestres, un passage ne trouve aucune nouvelle partition et n’écrit rien.
  • Validation : pnpm dataops:check (ruff + pytest + couverture) ; smoke test réel (openalex → MinIO local) : deux passages consécutifs avancent le watermark partition par partition ; un sync échoué ne fait pas avancer le watermark.
  • Done criteria : incrémental par date + merged_ids (rapatriés) + watermark testés. Fait (2026-06-10).
  • PR title : feat(dataops): incrémental snapshot par updated_date + merged_ids

Étape 2.3 — Code-location Dagster + Secret OBC + déploiement GitOps

Section intitulée « Étape 2.3 — Code-location Dagster + Secret OBC + déploiement GitOps »
  • Goal : Packager la code-location Dagster atlas (assets de sync) comme image (registry interne), la câbler au bucket s3://citation (RGW Ceph, path-style) via le Secret/ConfigMap de l’ObjectBucketClaim, l’enregistrer dans le workspace Dagster (vide), et la déployer en GitOps.
  • Files (read) : OBC citation-datalake (Secret généré : AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY + ConfigMap BUCKET_HOST/BUCKET_NAME/BUCKET_PORT), contrat cluster (endpoints.example.yaml, namespaces-secrets.example.yaml).
  • Files (write) : OBC citation-datalake ; manifeste de la code-location (Deployment gRPC) ; patch du ConfigMap dagster-workspace (ajout de la location, via Argo CD) ; injection OPENLINEAGE_URL ; NetworkPolicy (vers RGW + egress Internet pour le sync) ; Application Argo CD lue depuis Gitea.
  • Invariants à préserver : default-deny → NetworkPolicy explicite ; pas de credentials en clair (référence au Secret OBC) ; endpoint RGW path-style ; l’orchestrateur Dagster reste côté cluster (Phase 1), seule la code-location applicative est ici.
  • Validation : pnpm ci:checks ; déploiement banc Lima via Argo CD : la code-location s’enregistre dans Dagster (workspace), un run lit le Secret OBC et atteint le RGW.
  • Done criteria : code-location déployée par Argo CD, visible dans le webserver Dagster, accès au bucket via OBC, NetworkPolicy en place.
  • PR title : feat(citation): code-location Dagster déployée en GitOps

Étape 2.4 — Compléments ciblés via API REST (citation-fetch, < 10 k)

Section intitulée « Étape 2.4 — Compléments ciblés via API REST (citation-fetch, < 10 k) »
  • Goal : Conserver un asset secondaire pour des compléments ponctuels (entité précise, fenêtre étroite) via l’API REST, sous le plafond des 10 000 résultats (fetch-citation.ts). Ce n’est plus le chemin d’ingestion massive.
  • Files (read) : packages/citation-fetch/src/, packages/citation/src/fetch/.
  • Files (write) : asset/op de complément réutilisant citation-fetch (rate-limit 1 req/s conservé), écrivant dans une zone dédiée du lakehouse.
  • Invariants à préserver : plafond 10 k respecté (au-delà = snapshot) ; rate-limit 1 req/s ; citation-fetch non modifié.
  • Validation : pnpm ci:checks ; un complément ciblé < 10 k s’écrit correctement.
  • Done criteria : asset de complément testé, distinct du chemin massif.
  • PR title : feat(citation): compléments ciblés via API REST (< 10 k)

Note — parsing du snapshot. Le parsing des champs (referenced_works, fwci, cited_by_count au niveau works, et l’entité authors) se fait dans les modèles dbt en Python (dataops/, étape 3.2), pas dans packages/citation-types (TS). Les types TS de citation-types ne servent qu’au contrat de transfert (manifest) consommé côté TS par atlas-api (Phase 5), pas au parsing du brut.


Phase 3 — Transformations dbt + mart + contrat + qualité (dépôt atlas)

Section intitulée « Phase 3 — Transformations dbt + mart + contrat + qualité (dépôt atlas) »

Objectif. Le cœur DataOps. Écrire l’accès lakehouse DuckDB↔S3↔Parquet (100 % neuf), les modèles dbt (dbt-duckdb) en couches staging → curated → marts, la feature citations croisées (NEUVE, modèle dbt + SQL), le manifest.json atomique (écrit en dernier) + partitions immuables + schema_version (validateur dans packages/citation, pas citation-validate), les tests dbt + suites Great Expectations (asset checks Dagster), et le lineage OpenLineage → Marquez. Les modèles marts matérialisent le mart Parquet ; le manifest est écrit en dernier par l’asset de matérialisation. Dépendances. Phase 2 (le brut + les références existent). Phase 0 (ré-dérivabilité spécifiée → implémentée ici). Phase 1 (Dagster orchestre les assets dbt + Marquez collecte le lineage). Parallélisable ? 3.1 (accès lakehouse) est le socle de tout le reste. 3.2 (projet dbt + staging/curated) puis 3.3 (feature citations croisées en marts) puis 3.4 (matérialisation Parquet + manifest) sont séquentiels. 3.5 (qualité GE + lineage) et 3.6 (validateur contrat) après 3.4. 3.7 (ré-dérivabilité) en dernier. Critère de sortie de phase. Les assets dbt orchestrés par Dagster produisent s3://citation/curated/… puis s3://citation/marts/collab/dt=YYYY-MM/run=<id>/ + manifest.json ; la feature citations croisées est calculée et non nulle sur fixtures ; les tests dbt passent et les asset checks Great Expectations sont verts ; le lineage source→staging→curated→mart est visible dans Marquez ; un consommateur valide row_count+sha256 et refuse une schema_version inconnue ; aucune partition réécrite en place. pnpm ci:checks vert.

Étape 3.1 — Accès lakehouse DuckDB↔S3↔(JSONL.gz, Parquet) (NEUF)

Section intitulée « Étape 3.1 — Accès lakehouse DuckDB↔S3↔(JSONL.gz, Parquet) (NEUF) »
  • Goal : Configurer l’accès lakehouse DuckDB↔S3 en Python (dataops/, ADR 0055) : extension httpfs, CREATE SECRET (RGW path-style), lecture du brut JSONL gzippé (read_json_auto sur s3://citation/raw/**/*.gz), lecture/écriture Parquet (COPY … (FORMAT PARQUET)), partitionnement Hive. C’est le backend que dbt-duckdb consomme via son profiles.yml ; il vit dans la code-location Python, pas dans packages/citation (TS). Le wrapper TS packages/citation/src/db/index.ts (DuckDB local trivial) reste côté consommateur (lecture du mart validé par atlas-api, Phase 5), il n’écrit pas le lakehouse.
  • Files (read) : dataops/citation-dagster/ (code-location), schéma du brut snapshot (Phase 2).
  • Files (write) : profil dbt-duckdb (profiles.yml : httpfs, secret RGW path-style depuis l’env du Secret/OBC) + helpers Python d’accès S3 si nécessaire ; tests pytest d’intégration contre un endpoint S3 de test (MinIO/SeaweedFS).
  • Invariants à préserver : configuration S3 path-style ; pas de credentials en dur (env du Secret) ; couverture pytest au seuil (dataops:check).
  • Validation : pnpm dataops:check (ruff + pytest + couverture) ; intégration : lire un JSONL.gz puis écrire/relire un Parquet sur un S3 de test, partition Hive.
  • Done criteria : profil dbt-duckdb opérationnel (JSONL.gz lu, Parquet écrit/relu, partition Hive), testé en Python, prêt pour les modèles 3.2.
  • PR title : feat(dataops): accès lakehouse DuckDB↔S3 (dbt-duckdb, JSONL.gz + Parquet)

Étape 3.2 — Projet dbt-duckdb : stagingcurated

Section intitulée « Étape 3.2 — Projet dbt-duckdb : staging → curated »
  • Goal : Initialiser le projet dbt (dbt-duckdb, profiles.yml pointant le backend S3 de 3.1) et écrire les couches staging (typage/nettoyage des works et authors lus depuis le snapshot JSONL.gz, + authorships, referenced_works) et curated (works et authors canoniques, authorships, edges = arêtes article→référence dédupliquées). Tests dbt (not_null, unique, relationships) sur chaque couche.
  • Files (read) : schéma du brut snapshot (Phase 2, s3://citation/raw/{works,authors}), packages/citation-types, accès lakehouse 3.1 (lecture JSONL.gz).
  • Files (write) : projet dbt (models/staging/ dont stg_citation_works + stg_citation_authors, models/curated/ dont curated_works + curated_authors, dbt_project.yml, profiles.yml, schemas/tests), matérialisation curated en Parquet s3://citation/curated/dt=YYYY-MM/run=<id>/, assets dbt exposés à Dagster (dagster-dbt).
  • Invariants à préserver : déduplication déterministe (même brut → même curated) ; partitions immuables ; filtrage sur le périmètre servi (hors opposition) via le registre d’opposition (lien Phase 0) ; modèles nommés sans marque (stg_citation_*, curated_*).
  • Validation : pnpm ci:checks ; dbt build (ou via Dagster) sur fixtures : staging/curated produits, tests dbt verts, dédup vérifiée (pas de doublon d’arête).
  • Done criteria : projet dbt opérationnel, curated en Parquet partitionné, tests dbt verts, assets exposés à Dagster.
  • PR title : feat(citation): projet dbt-duckdb (staging → curated)

Étape 3.3 — Feature citations croisées (NEUVE, modèle dbt + SQL)

Section intitulée « Étape 3.3 — Feature citations croisées (NEUVE, modèle dbt + SQL) »
  • Goal : Calculer le signal cœur comme modèle dbt marts : pour chaque paire de chercheurs (A, B), le nombre de citations croisées article↔article (un article de A cite/est cité par un article de B). 100 % de nouveau code métier ; aucune feature de référence n’existe aujourd’hui.
  • Files (read) : edges/curated (3.2), packages/researcher-profiles/src/, match-formatter.ts (alignement des champs explicatifs).
  • Files (write) : modèle dbt marts/collab_pairs calculant cross_citations (+ direction, fenêtre temporelle), table de fait des paires de chercheurs ; tests dbt singuliers sur la feature.
  • Invariants à préserver : déterminisme strict (même curated → mêmes valeurs) ; pas de fuite hors périmètre servi (les personnes opposées sont exclues) ; symétrie/asymétrie documentée (A cite B vs B cite A).
  • Validation : pnpm ci:checks ; golden test sur fixtures à citations croisées connues : valeur calculée == valeur attendue ; test dbt singulier vert.
  • Done criteria : feature cross_citations calculée (modèle dbt), golden test vert, sémantique documentée.
  • PR title : feat(citation): feature citations croisées (modèle dbt)

Étape 3.4 — Matérialisation du mart collab + manifest.json atomique

Section intitulée « Étape 3.4 — Matérialisation du mart collab + manifest.json atomique »
  • Goal : Matérialiser le modèle marts (paires + features) en s3://citation/marts/collab/dt=YYYY-MM/run=<id>/, puis écrire en dernier un manifest.json atomique {partition, schema_version, row_count, parts:[{key,sha256,bytes}], produced_at}. dbt produit le mart ; l’asset de matérialisation Dagster écrit le manifest.
  • Files (read) : modèles 3.2/3.3, citation-types, accès lakehouse 3.1.
  • Files (write) : asset/op de matérialisation + générateur de manifest.json (calcul sha256/bytes par part, écriture atomique en dernier), schedule mensuel du pipeline complet.
  • Invariants à préserver : manifest.json écrit après tous les parts (sentinelle de complétude) ; schema_version dérivée de citation-types ; jamais de réécriture en place (rejeu = nouveau run=<id>).
  • Validation : pnpm ci:checks ; le manifest référence exactement les parts présents avec sha256 corrects ; couper un run avant le manifest → pas de manifest (consommateur doit refuser de lire).
  • Done criteria : mart + manifest produits ; atomicité vérifiée ; schema_version présente.
  • PR title : feat(citation): mart collab + manifest.json atomique

Étape 3.5 — Qualité Great Expectations + lineage OpenLineage → Marquez

Section intitulée « Étape 3.5 — Qualité Great Expectations + lineage OpenLineage → Marquez »
  • Goal : Brancher les suites Great Expectations (sur raw/curated/marts) comme asset checks Dagster (en complément des tests dbt), et activer l’émission OpenLineage native de dbt et Dagster vers Marquez (Phase 1.8).
  • Files (read) : assets dbt (3.2/3.3), matérialisation (3.4), platform/marquez/ (côté cluster, OPENLINEAGE_URL).
  • Files (write) : suites GE (raw : champs présents ; curated : intégrité référentielle ; marts : bornes de cross_citations, non-nullité), wiring asset checks Dagster, config OpenLineage (dbt openlineage/Dagster dagster-openlineage ou intégration native).
  • Invariants à préserver : asset checks en porte d’entrée et de sortie (qualité bloquante en cas d’échec) ; aucune PII dans les métadonnées de lineage (noms techniques uniquement) ; variante Elementary documentée mais GE retenu.
  • Validation : pnpm ci:checks ; sur fixtures : un échec GE injecté bloque le run ; le lineage source→staging→curated→mart apparaît dans Marquez.
  • Done criteria : suites GE en asset checks (bloquantes), lineage visible dans Marquez.
  • PR title : feat(citation): qualité Great Expectations + lineage OpenLineage

Étape 3.6 — Validateur du contrat de données (dans packages/citation)

Section intitulée « Étape 3.6 — Validateur du contrat de données (dans packages/citation) »
  • Goal : Bibliothèque de validation du contrat : un consommateur valide row_count + sha256 de chaque part avant de lire et refuse une schema_version inconnue. Le validateur vit dans packages/citation (PAS packages/citation-validate, qui est l’outil interactif OpenAlex sans rapport).
  • Files (read) : manifest.json (3.4), packages/citation-types, packages/citation (db/contrat).
  • Files (write) : module validateur de manifest dans packages/citation, exporté pour atlas-api (Phase 5) et le chargement d’index (Phase 4).
  • Invariants à préserver : schema_version inconnue → refus (pas de best-effort) ; sha256 mismatch → refus ; validation avant toute lecture ; ne pas détourner citation-validate.
  • Validation : pnpm test --filter citation ; cas négatifs : version inconnue refusée, sha256 corrompu refusé, manifest absent refusé.
  • Done criteria : validateur testé (positifs + 3 négatifs), exporté depuis packages/citation.
  • PR title : feat(citation): validateur du contrat de données (manifest)

Étape 3.7 — Ré-dérivabilité du mart (implémentation Phase 0)

Section intitulée « Étape 3.7 — Ré-dérivabilité du mart (implémentation Phase 0) »
  • Goal : Implémenter le mécanisme spécifié en 0.2 : régénération de la partition courante depuis curated filtré sur le registre d’opposition à T (re-run dbt, on exclut les personnes opposées), masquage des partitions historiques pour les chercheurs s’étant opposés, + purge/recharge des lignes correspondantes dans l’index pgvector (Phase 4).
  • Files (read) : spec 0.2, consent-events (registre d’opposition), mart 3.4.
  • Files (write) : liste d’exclusion dérivée de consent-events, paramétrage dbt de filtrage, asset Dagster de ré-dérivation (nouveau run=<id>), procédure de masquage à la lecture.
  • Invariants à préserver : immutabilité préservée (on régénère, on ne modifie pas) ; opposition effective dans le SLA défini en 0.2 ; masquage couvrant l’historique sans réécrire les anciennes partitions.
  • Validation : pnpm ci:checks ; test : un chercheur s’étant opposé disparaît du mart régénéré et est masqué à la lecture de l’historique.
  • Done criteria : régénération + masquage testés, conformes à l’ADR 0030.
  • PR title : feat(citation): ré-dérivabilité du mart sous opposition

Phase 4 — Indexation PostgreSQL/pgvector (dépôt atlas)

Section intitulée « Phase 4 — Indexation PostgreSQL/pgvector (dépôt atlas) »

Objectif. Charger le mart dans PostgreSQL (CloudNativePG, Phase 1.6) pour l’exploration et la recherche : schéma works/authorships/pairs, FTS tsvector sur titres/topics/mots-clés (lexical), colonne vector(384) + index pgvector sur les embeddings all-MiniLM-L6-v2 réutilisés (produits par researcher-profiles, CPU, aucun nouveau modèle). Job/asset Dagster de matérialisation mart→Postgres. L’index est dérivé du mart (régénérable), jamais source de vérité du contrat. Dépendances. Phase 3 (mart + manifest validables). Phase 1 (CloudNativePG + pgvector). Phase 0 (ré-dérivabilité — l’index est purgeable, 3.7). Parallélisable ? 4.1 (schéma + migrations) avant 4.2 (chargement FTS) et 4.3 (chargement vecteurs). 4.4 (asset Dagster d’orchestration du chargement) après 4.1–4.3. Critère de sortie de phase. Un asset Dagster charge la dernière partition validée (contrat 3.6) du mart dans Postgres ; une recherche FTS et une recherche vectorielle (kNN pgvector) renvoient des résultats cohérents sur fixtures ; un chercheur s’étant opposé est purgé de l’index ; le chargement est idempotent (recharge = remplacement de la partition, pas de doublon). pnpm ci:checks vert.

Étape 4.1 — Schéma Postgres + migrations (works/authorships/pairs)

Section intitulée « Étape 4.1 — Schéma Postgres + migrations (works/authorships/pairs) »
  • Goal : Définir le schéma d’exploration et ses migrations : tables works, authorships, pairs (paires de chercheurs + features dont cross_citations) et une table researchers portant la colonne vector(384). Attention à la granularité : embedding-profile.ts produit un vecteur par chercheur (EmbeddingProfile { researcherId, vector }, mean-pooling des œuvres), donc la colonne vector(384) est portée par l’entité chercheur (clé researcherId), pas par works (qui n’a pas d’embedding produit). Les colonnes tsvector (lexical) vivent sur works/researchers selon le champ indexé. Métadonnées de partition (dt, run, institution, période) pour le filtrage structuré.
  • Files (read) : schéma du mart (Phase 3), packages/citation-types, platform/cloudnative-pg/ (côté cluster, DSN/extension).
  • Files (write) : migrations SQL (création tables + extension vector + index), module d’accès Postgres dans packages/citation (Effect), secret de connexion référencé (pas en clair).
  • Invariants à préserver : pgvector activé via l’image CNPG (Phase 1.6) ; nommage sans marque ; aucune écriture côté index ne fait autorité sur le contrat Parquet ; index régénérable.
  • Validation : pnpm ci:checks ; migrations appliquées sur un Postgres de test (conteneur pgvector), schéma créé, CREATE EXTENSION vector ok.
  • Done criteria : schéma + migrations testés, module d’accès Postgres exporté.
  • PR title : feat(citation): schéma Postgres d'index (works/authorships/pairs)
  • Goal : Charger titres/topics/mots-clés du mart dans les colonnes tsvector, index GIN, pour la recherche plein-texte.
  • Files (read) : mart (3.4), schéma 4.1, validateur de contrat 3.6.
  • Files (write) : loader lexical (lit le Parquet validé, peuple tsvector), index GIN, requêtes FTS de base (réutilisées par atlas-api Phase 5).
  • Invariants à préserver : validation du contrat avant chargement (refus d’une schema_version inconnue) ; chargement par remplacement de partition (idempotent, pas de doublon) ; périmètre servi (hors opposition) uniquement.
  • Validation : pnpm ci:checks ; sur fixtures : une requête FTS renvoie les œuvres attendues, ranking cohérent.
  • Done criteria : FTS chargé et indexé (GIN), requête lexicale testée.
  • PR title : feat(citation): chargement FTS tsvector (lexical)

Étape 4.3 — Chargement vecteurs + index pgvector (sémantique)

Section intitulée « Étape 4.3 — Chargement vecteurs + index pgvector (sémantique) »
  • Goal : Charger les embeddings all-MiniLM-L6-v2 déjà produits par researcher-profiles dans la colonne vector(384) de la table researchers (un vecteur par chercheur, clé researcherId), créer l’index pgvector (HNSW/IVFFlat), pour la recherche sémantique kNN de chercheurs.
  • Files (read) : packages/researcher-profiles/src/services/embedding-profile.ts (type EmbeddingProfile { researcherId, vector }, embeddings ONNX CPU réutilisés tels quels), schéma 4.1.
  • Files (write) : loader vectoriel (alimente researchers.vector(384) depuis les EmbeddingProfile existants), index pgvector + paramètres (lists/ef), requête kNN de base.
  • Invariants à préserver : aucun nouveau modèle, aucun GPU (embeddings réutilisés) ; un vecteur par chercheur (pas par work) ; dimension 384 cohérente avec all-MiniLM-L6-v2 ; chargement idempotent ; validation du contrat avant chargement.
  • Validation : pnpm ci:checks ; sur fixtures : une requête kNN renvoie les voisins sémantiques attendus.
  • Done criteria : vecteurs chargés, index pgvector créé, requête sémantique testée.
  • PR title : feat(citation): index pgvector (recherche sémantique)

Étape 4.4 — Asset Dagster de matérialisation mart→Postgres

Section intitulée « Étape 4.4 — Asset Dagster de matérialisation mart→Postgres »
  • Goal : Orchestrer le chargement (4.2 + 4.3) comme asset Dagster index_load en aval du mart (lineage OpenLineage continué jusqu’à l’index), avec validation du contrat et idempotence par partition.
  • Files (read) : loaders 4.2/4.3, validateur 3.6, schedule du pipeline (3.4).
  • Files (write) : asset index_load (dépend de l’asset mart), asset check de cohérence (count Postgres == row_count du manifest), wiring OpenLineage.
  • Invariants à préserver : l’index n’est chargé qu’à partir d’une partition validée ; recharge = remplacement (pas de doublon) ; purge des chercheurs s’étant opposés propagée (lien 3.7).
  • Validation : pnpm ci:checks ; sur banc/fixtures : l’asset index_load se matérialise après le mart, l’asset check de cohérence passe, le lineage va jusqu’à l’index dans Marquez.
  • Done criteria : asset index_load orchestré, cohérence vérifiée, lineage complet.
  • PR title : feat(citation): asset Dagster de chargement de l'index

Phase 5 — Scoring déterministe + atlas-api (métier + EXPLORATION) (dépôt atlas)

Section intitulée « Phase 5 — Scoring déterministe + atlas-api (métier + EXPLORATION) (dépôt atlas) »

Objectif. Brancher le 3ᵉ signal (citations croisées) sur EnsembleWeights à poids fixes (déterministe, pas de modèle entraîné), puis créer atlas-api (clone du patron services/crf : Hono + OpenAPI 3.1 + Scalar) avec deux rôles : (a) métier/recommendations (paires scorées) + /summary (résumé extractif) ; (b) exploration/vérification/search (FTS lexical + pgvector sémantique), filtres structurés (/works, /authorships, /pairs par chercheur/run/partition/institution/période), /stats. Cache local sur PVC RBD (découple la latence du RGW), métriques Prometheus, auth obligatoire (RGPD, ADR 0030 — y compris routes d’exploration). Dépendances. Phase 3 (mart + contrat), Phase 4 (index Postgres/pgvector). Phase 1 (déploiement + monitoring) pour la prod. Parallélisable ? 5.1 (3ᵉ signal) indépendant de 5.2 (squelette API). 5.3 (lecture mart + cache RBD), 5.4 (endpoints métier), 5.5 (endpoints exploration), 5.6 (métriques) après 5.2. Critère de sortie de phase. atlas-api lit le mart validé (contrat 3.6) et l’index Postgres (Phase 4), répond à /recommendations (3 signaux pondérés fixes), /summary (extractif), /search (lexical + sémantique), aux filtres structurés et à /stats ; expose /metrics scrappé par Prometheus ; sert depuis le cache RBD quand le RGW est indisponible ; refuse tout accès non authentifié. OpenAPI 3.1 + Scalar servis. pnpm ci:checks vert.

Étape 5.1 — 3ᵉ signal sur EnsembleWeights (poids fixes)

Section intitulée « Étape 5.1 — 3ᵉ signal sur EnsembleWeights (poids fixes) »
  • Goal : Ajouter le signal cross_citations à l’ensemble de scoring, au point d’extension EnsembleWeights existant (packages/researcher-profiles/src/services/ensemble.ts, aujourd’hui {tfidf, embedding}) ; poids fixes (déterministe), pas d’apprentissage.
  • Files (read) : packages/researcher-profiles/src/services/ensemble.ts, scorer.ts, EnsembleWeights, feature 3.3.
  • Files (write) : extension de EnsembleWeights avec le 3ᵉ poids, intégration de la feature dans le score, tests de pondération.
  • Invariants à préserver : déterminisme (mêmes entrées → même score) ; les deux signaux existants inchangés en comportement ; poids documentés (justification dans l’ADR 0029).
  • Validation : pnpm ci:checks ; test : score d’une paire à citations croisées connues == valeur attendue par la combinaison pondérée.
  • Done criteria : 3ᵉ signal branché, poids fixes documentés, tests verts.
  • PR title : feat(researcher-profiles): signal citations croisées dans EnsembleWeights

Étape 5.2 — Squelette atlas-api (clone patron services/crf)

Section intitulée « Étape 5.2 — Squelette atlas-api (clone patron services/crf) »
  • Goal : Créer services/atlas-api à partir du patron services/crf/src/server/app.ts : Hono + OpenAPI 3.1 + Scalar, healthcheck, structure de handlers, headers de sécurité (CSP).
  • Files (read) : services/crf/src/server/app.ts (patron), services/crf/ (structure).
  • Files (write) : services/atlas-api/ (app Hono, doc OpenAPI 3.1, Scalar, Dockerfile) ; ajout du scope atlas-api au scope-enum de commitlint.config.js.
  • Invariants à préserver : standardisation handler→app identique à crf ; CSP/headers repris ; nommage citation/atlas (pas de marque).
  • Validation : pnpm ci:checks ; atlas-api démarre, /health OK, doc OpenAPI servie, Scalar accessible.
  • Done criteria : service démarrable, OpenAPI 3.1 valide, Scalar servi.
  • PR title : feat(atlas-api): squelette Hono + OpenAPI 3.1 (patron crf)

Étape 5.3 — Lecture du mart validé + cache local RBD

Section intitulée « Étape 5.3 — Lecture du mart validé + cache local RBD »
  • Goal : atlas-api lit le mart via l’accès lakehouse (3.1), valide le contrat (3.6) avant lecture, et maintient un cache local sur PVC RBD pour servir même si le RGW est indisponible (rappel : EC 2+1, perte d’1 nœud bloque les I/O datalake).
  • Files (read) : accès lakehouse 3.1, validateur 3.6, manifest 3.4.
  • Files (write) : couche d’accès au mart dans atlas-api, logique de cache RBD (télécharge la dernière partition valide, sert depuis le cache), PVC RBD (RWO) dans les manifestes applicatifs.
  • Invariants à préserver : validation du contrat avant lecture ; schema_version inconnue → 503/erreur explicite, pas de réponse silencieuse ; le cache ne sert qu’une partition validée ; refus de servir une partition sans manifest.
  • Validation : pnpm ci:checks ; test : RGW coupé → atlas-api sert depuis le cache RBD ; manifest corrompu → refus.
  • Done criteria : lecture validée + cache RBD testés (dégradation gracieuse).
  • PR title : feat(atlas-api): lecture du mart validé + cache RBD

Étape 5.4 — Endpoints métier /recommendations + /summary

Section intitulée « Étape 5.4 — Endpoints métier /recommendations + /summary »
  • Goal : /recommendations (paires nominatives scorées par les 3 signaux pondérés fixes, 5.1) et /summary (résumé extractif déterministe, template à trous depuis match-formatter : sharedDomains/distinctTopicsA/distinctTopicsB/sharedKeywords, pas de LLM).
  • Files (read) : match-formatter.ts, scoring 5.1, données du mart (3.3/3.4).
  • Files (write) : endpoints /recommendations + /summary dans atlas-api, formateur de template extractif.
  • Invariants à préserver : déterminisme (mêmes entrées → même texte) ; pas d’appel génératif (LLM = palier 2) ; explicabilité conservée ; auth obligatoire (route nominative).
  • Validation : pnpm ci:checks ; golden test sur un résumé pour une paire de fixtures ; /recommendations ordonne par score attendu.
  • Done criteria : /recommendations + /summary stables, golden test vert, auth exigée.
  • PR title : feat(atlas-api): endpoints métier /recommendations + /summary

Étape 5.5 — Endpoints exploration /search + filtres structurés + /stats

Section intitulée « Étape 5.5 — Endpoints exploration /search + filtres structurés + /stats »
  • Goal : Le second rôle (demande explicite) : /search (recherche lexicale FTS et sémantique pgvector, Phase 4), filtrage structuré /works /authorships /pairs (filtres chercheur/run/partition/institution/période → vérification/débogage de l’indexation), /stats (compteurs par partition/run). En lecture seule sur l’index Postgres ; n’invalide pas le contrat Parquet (qui reste batch).
  • Files (read) : module d’accès Postgres (4.1), requêtes FTS (4.2) et kNN (4.3).
  • Files (write) : endpoints d’exploration dans atlas-api (schémas OpenAPI 3.1 typés, pagination, filtres), client de l’index Postgres.
  • Invariants à préserver : auth obligatoire y compris ici (ADR 0030 : toute route nominative, exploration comprise) ; lecture seule (aucune écriture via l’API) ; périmètre servi (hors opposition) ; cardinalité maîtrisée (pas de fuite de volumétrie).
  • Validation : pnpm ci:checks ; /search?mode=lexical et ?mode=semantic renvoient des résultats cohérents sur fixtures ; un filtre par run/partition isole la bonne tranche ; accès non authentifié refusé.
  • Done criteria : /search (lexical+sémantique), filtres structurés, /stats opérationnels et authentifiés.
  • PR title : feat(atlas-api): exploration /search + filtres structurés + /stats
  • Goal : Exposer /metrics (latence, hits/miss du cache RBD, fraîcheur de la partition servie, refus de contrat, latence FTS/kNN) scrappé par le Prometheus de la Phase 1.
  • Files (read) : atlas-api (5.2–5.5), patron observabilité du dépôt.
  • Files (write) : instrumentation + /metrics, ServiceMonitor (manifeste applicatif atlas), dashboard Grafana de base (provisionné).
  • Invariants à préserver : pas de donnée personnelle dans les labels (cardinalité + RGPD) ; /metrics non exposé publiquement (interne au scrape).
  • Validation : pnpm ci:checks ; sur banc : Prometheus scrappe atlas-api, métriques visibles dans Grafana.
  • Done criteria : /metrics + ServiceMonitor + dashboard ; scrape vérifié.
  • PR title : feat(atlas-api): métriques Prometheus custom

Phase 6 — PWA find-an-expert + exposition (dépôt atlas)

Section intitulée « Phase 6 — PWA find-an-expert + exposition (dépôt atlas) »

Objectif. Transformer find-an-expert (SvelteKit) en PWA (vite-plugin-pwa, Workbox, Dexie offline), brancher la consommation d’atlas-api (recommandations + résumé + recherche), garantir la qualité via Lighthouse CI, exposer avec TLS + auth obligatoire (Appwrite). Depuis l’ADR cluster 0092, le socle ne fournit plus de bordure L7 : la terminaison TLS externe d’un mart nominatif relève du déployeur (TLS natif ou reverse-proxy d’instance — cf. Étape 6.4). Dépendances. Phase 5 (atlas-api). Terminaison TLS externe à la charge du déployeur (ADR cluster 0092). Auth Appwrite et dispositif consent-events (réinterprété en registre d’opposition) déjà présents dans find-an-expert. Parallélisable ? 6.1 (PWA) et 6.3 (Lighthouse CI) indépendants de 6.2 (intégration API). 6.4 (exposition) en dernier. Critère de sortie de phase. find-an-expert est installable (PWA), consomme recommandations/résumés/recherche via atlas-api derrière auth, fonctionne hors-ligne (Dexie) pour les données déjà chargées, passe les seuils Lighthouse CI, est exposée en HTTPS via l’ingress. pnpm ci:checks vert.

Étape 6.1 — PWA (vite-plugin-pwa + Workbox + Dexie)

Section intitulée « Étape 6.1 — PWA (vite-plugin-pwa + Workbox + Dexie) »
  • Goal : Rendre find-an-expert installable et résiliente hors-ligne.
  • Files (read) : apps/find-an-expert/ (SvelteKit, auth/consentement Appwrite).
  • Files (write) : config vite-plugin-pwa/Workbox (manifest web, service worker, stratégies de cache), store Dexie pour le cache offline.
  • Invariants à préserver : flux d’auth Appwrite et dispositif consent-events (ConsentStatusCard, /api/v1/consents) inchangés (réinterprétés comme registre d’opposition) ; pas de cache offline de données nominatives de personnes opposées ; ADR 0020 (ESLint Svelte strict).
  • Validation : pnpm ci:checks ; build PWA ; service worker enregistré ; installation testée.
  • Done criteria : app installable, service worker actif, cache offline Dexie fonctionnel.
  • PR title : feat(find-an-expert): PWA (vite-plugin-pwa + Workbox + Dexie)

Étape 6.2 — Intégration atlas-api (reco + résumé + recherche)

Section intitulée « Étape 6.2 — Intégration atlas-api (reco + résumé + recherche) »
  • Goal : Consommer /recommendations, /summary et /search d’atlas-api depuis la PWA, derrière auth.
  • Files (read) : OpenAPI d’atlas-api (Phase 5), find-an-expert (couche data).
  • Files (write) : client API typé (généré depuis l’OpenAPI), vues recommandations + résumé + recherche, gestion d’erreur (contrat refusé → message clair).
  • Invariants à préserver : appels authentifiés uniquement ; affichage filtré par les alliances/projets déclarés par l’utilisateur (filtre d’affichage), sur le périmètre servi (hors opposition) ; pas d’appel non authentifié à atlas-api.
  • Validation : pnpm ci:checks ; test e2e/composant : une recommandation + son résumé + une recherche s’affichent pour un utilisateur authentifié de fixture.
  • Done criteria : reco + résumés + recherche affichés derrière auth, erreurs gérées.
  • PR title : feat(find-an-expert): consommation d'atlas-api (reco + résumé + recherche)
  • Goal : Garde-fou qualité PWA (perf, a11y, best-practices, PWA installability) en CI.
  • Files (read) : workflow CI existant, budget bundle déjà en place.
  • Files (write) : config Lighthouse CI + seuils, job CI.
  • Invariants à préserver : seuils réalistes (pas de faux-vert) ; cohérence avec le budget bundle existant.
  • Validation : pnpm ci:checks ; run Lighthouse CI passant les seuils.
  • Done criteria : Lighthouse CI branché, seuils définis et atteints.
  • PR title : ci(find-an-expert): Lighthouse CI

Étape 6.4 — Exposition de la PWA, TLS + auth obligatoires

Section intitulée « Étape 6.4 — Exposition de la PWA, TLS + auth obligatoires »
  • Goal : Exposer la PWA avec auth obligatoire pour tout accès au mart/index nominatif. Depuis l’ADR cluster 0092, le socle n’expose plus de bordure L7/Gateway (les UI de plateforme sont en L4 NodePort, HTTP clair, réseau privé) : l’exposition externe et chiffrée d’un mart nominatif relève donc du déployeur — soit TLS terminé par l’app/un ingress propre à l’instance, soit un reverse-proxy de bordure. Le dépôt fournit le canevas (auth, NetworkPolicy) ; la terminaison TLS externe est un choix d’instance (ADR 0031).
  • Files (read) : contrat cluster (exposition L4 NodePort, ADR cluster 0092 ; le reste des UI via le portail, 0091), manifestes applicatifs find-an-expert.
  • Files (write) : Service/exposition find-an-expert (namespace citation-pwa), Application Argo CD, NetworkPolicy. La terminaison TLS (cert + reverse-proxy ou ingress d’instance) est posée par le déployeur, hors canevas générique.
  • Invariants à préserver : aucun accès non authentifié à un mart/index nominatif (lien ADR 0030) ; TLS obligatoire avant toute exposition externe d’un mart nominatif (ADR cluster 0003 : TLS interne absent → le point de chiffrement est à la charge du déployeur, plus une bordure fournie) ; default-deny respecté.
  • Validation : pnpm ci:checks ; sur banc : la PWA répond en HTTPS (TLS d’instance), accès non authentifié refusé.
  • Done criteria : PWA exposée en HTTPS (terminaison TLS du déployeur), auth obligatoire vérifiée, réconciliée par Argo CD.
  • PR title : feat(find-an-expert): exposition PWA, TLS + auth obligatoires

Objectif. Signer les images conteneur (cosign / provenance SLSA), produire un SBOM et scanner (Trivy). Aujourd’hui inexistant : release.yml ne fait que la provenance npm OIDC. Dépendances. Au moins une image existe (Phase 2). Transverse : applicable à toutes les images (code-location Dagster citation-ingest, atlas-api, find-an-expert). Parallélisable ? 7.1 (signature) → 7.2 (vérification au déploiement). 7.3 (SBOM + Trivy) indépendant. Critère de sortie de phase. Chaque image publiée sur le registry interne est signée (cosign), accompagnée d’une provenance SLSA et d’un SBOM, scannée par Trivy en CI (seuil de sévérité bloquant défini) ; le cluster peut (à terme) vérifier la signature. pnpm ci:checks vert.

Étape 7.1 — Signature cosign + provenance SLSA des images

Section intitulée « Étape 7.1 — Signature cosign + provenance SLSA des images »
  • Goal : Signer chaque image au build (cosign, keyless OIDC si possible) et attacher une attestation de provenance SLSA.
  • Files (read) : .github/workflows/release.yml (provenance npm existante), Dockerfiles des services/code-locations.
  • Files (write) : job CI build+sign+attest des images, push signé vers le registry interne.
  • Invariants à préserver : hooks/CI non bypassés ; registry interne HTTP sans auth (ADR cluster 0011) → documenter la limite (signature vérifiable même sans auth registry).
  • Validation : pnpm ci:checks ; cosign verify sur une image publiée passe.
  • Done criteria : images signées + attestation SLSA ; vérification cosign documentée.
  • PR title : ci: signature cosign + provenance SLSA des images

Étape 7.2 — Vérification de signature au déploiement

Section intitulée « Étape 7.2 — Vérification de signature au déploiement »
  • Goal : N’admettre que des images signées. Le benchmark DevSecOps ne nomme que cosign et Trivy : la vérification peut passer par une cosign policy (admission via policy-controller Sigstore) ou la vérification Argo CD ; Kyverno est une option locale (hors benchmark) si une policy d’admission plus générale est souhaitée.
  • Files (read) : étape 7.1 ; artefact côté cluster (policy d’admission) — coordination inter-dépôt.
  • Files (write) : côté atlas : doc de la clé/issuer ; côté cluster : policy d’admission (PR séparée dans ../cluster).
  • Invariants à préserver : ne pas bloquer les images d’infra existantes non encore signées (rollout progressif) ; tester sur le banc Lima d’abord.
  • Validation : sur banc : déploiement d’une image non signée refusé, image signée admise.
  • Done criteria : policy de vérification sur le banc ; rollout documenté.
  • PR title (cluster) : feat(platform): admission des images signées (cosign)
  • Goal : Générer un SBOM par image et scanner les vulnérabilités (Trivy) en CI, seuil de sévérité bloquant.
  • Files (read) : Dockerfiles, CI.
  • Files (write) : génération SBOM attachée à l’image (Trivy la produit nativement — c’est l’outil du benchmark ; syft reste une option locale si un SBOM CycloneDX/SPDX dédié est requis), job Trivy en CI, seuil bloquant documenté.
  • Invariants à préserver : seuil réaliste (CRITICAL bloquant, HIGH alerte au minimum) ; pas de faux-vert ; SBOM attaché comme attestation.
  • Validation : pnpm ci:checks ; Trivy en CI échoue sur une vuln CRITICAL injectée de test, passe sinon.
  • Done criteria : SBOM par image + scan Trivy bloquant en CI.
  • PR title : ci: SBOM + scan Trivy des images

Explicitement hors périmètre V1, nommés pour ne pas être réinventés et pour garder les points d’extension ouverts :

  • Iceberg. Migration Parquet → Iceberg non destructive quand un besoin de time-travel/upsert/évolution de schéma transactionnelle se mesure. Le contrat manifest.json (schema_version) est le point de bascule ; DuckDB/Dagster restent les moteurs.
  • Modèle supervisé de prédiction d’excellence collaborative (+ MLflow). Remplacer le scoring déterministe à poids fixes par un modèle appris. Cible : à partir des métadonnées des articles récents (< 5 ans) — co-auteurs, hiérarchie domain/field/subfield/topic + keywords, et FWCI (Field-Weighted Citation Impact, métadonnée OpenAlex au niveau work) — prédire pour un chercheur (a) sa prochaine thématique d’excellence et (b) les profils de chercheurs formant une collaboration d’excellence, puis filtrer les chercheurs correspondants. Label = co-publication future (split temporel : features sur [..T], observation sur [T+1, T+n], anti-fuite). Modèle léger CPU (régression logistique / gradient boosting type LightGBM/scikit-learn, aucun GPU), entraîné dans un asset Dagster, tracé/versionné via MLflow (registry + artifact store sur le S3 Ceph). Prérequis : ingérer le FWCI (absent des types actuels — chantier d’ingestion à part, voir Risques). Le point d’extension EnsembleWeights reste l’interface (les poids passent de fixes à appris).
  • LLM génératif (Ollama CPU). Résumés pré-calculés en batch avec Mistral-7B-Instruct (Apache-2.0), poids mirrorés sur le registry interne. Justification du report : aucun GPU au cluster → un 7B CPU = dizaines de secondes/résumé, rédhibitoire en synchrone. Les résumés extractifs (Phase 5.4) restent le défaut.
  • KServe / serving versionné. Si un modèle calibré (MLflow) est introduit.
  • Drift. Détection de dérive de données/score une fois un modèle entraîné en place (au-delà des asset checks Great Expectations).
  • Feast. Seulement si un cas de train/serve skew apparaît (streaming) — aujourd’hui sur-dimensionné pour un batch mensuel mono-producteur.

  • GPU absent. Aucun GPU sur les 4 nœuds → tout LLM génératif est cantonné au batch (palier 2). La V1 ne dépend d’aucun GPU (embeddings ONNX all-MiniLM-L6-v2 en CPU, scoring déterministe, résumés extractifs, recherche pgvector/FTS). Risque levé pour la V1, bloquant pour le génératif synchrone.
  • Volumétrie du snapshot S3. Le snapshot OpenAlex pèse ≈ 330 Go compressés / 1,6 To décompressés (works + authors). Inconnues de charge : durée du sync initial (bootstrap), quota du datalake objet Ceph, coût des jointures dbt/DuckDB (works × referenced_works) pour la feature citations croisées. Action : mesurer le bootstrap sur un sous-échantillon, documenter dans docs/architecture/, prévoir reprise et partitionnement par updated_date. Le sync exige un egress Internet (tension avec le default-deny — tracé en issue cluster #256).
  • Tests sur petit cluster local. Le banc Lima est un petit cluster : on ne peut pas y synchroniser 1,6 To. La mécanique d’ingestion (sync, parsing JSONL, dbt, manifest, index) se valide sur un sous-échantillon borné du vrai snapshot (un dossier updated_date, échelle pilotée par configuration). Ne jamais supposer le volume complet en local ; le run complet est une opération de prod uniquement.
  • Cadence trimestrielle vs exigence mensuelle. La source gratuite (AWS Open Data) est rafraîchie trimestriellement ; le mensuel et les change-files quotidiens sont payants (ADR 0054). Le schedule mensuel reste idempotent (entre deux trimestres, il ne trouve aucune nouvelle partition). Si une fraîcheur mensuelle réelle devient nécessaire, l’offre payante est à arbitrer par le déployeur.
  • Modèle d’excellence collaborative (palier 2) — questions de fond. Le modèle supervisé visé (prédire la prochaine thématique d’excellence et les collaborations d’excellence, label = co-publication future) soulève des questions à trancher au palier 2 : (a) fenêtre temporelle du split (taille de [..T] et de [T+1, T+n]) et son effet sur le volume de labels positifs (les co-publications sont rares → classes déséquilibrées) ; (b) définition opérationnelle de « l’excellence » à partir du FWCI (seuil ? FWCI moyen du collectif ? percentile par champ ?) ; (c) biais du label « co-publication future » (ne capture que la collaboration formalisée, ignore les collaborations informelles ou empêchées ; effet Matthieu — les chercheurs déjà excellents co-publient davantage) ; (d) métrique d’évaluation (precision@k du filtrage de chercheurs plutôt qu’accuracy, vu le déséquilibre). À documenter dans un ADR dédié au palier 2 avant d’entraîner quoi que ce soit. La V1 (scoring déterministe) ne dépend d’aucune de ces réponses.
  • Charge opérationnelle des nouveaux composants stateful. La V1 introduit trois sous-systèmes stateful neufs à exploiter sur un cluster non-HA : Dagster (webserver + daemon + run workers + event log Postgres), CloudNativePG (Postgres : event log Dagster + index pgvector) et Marquez (store de lineage). Coûts induits : sauvegardes/restaurations Postgres, montées de version dbt/Dagster/Marquez, supervision et alerting de trois nouveaux sous-systèmes, gestion de schéma de l’index. C’est le prix assumé du positionnement plateforme (cf. ADR 0029, Prix à payer) ; à séquencer et documenter dans les RUNBOOK côté cluster. Note : l’event log Dagster et l’index pgvector sont sur RBD réplication ×3 (ADR cluster 0001), pas sur l’erasure coding du datalake — leur disponibilité ne suit donc pas la même règle que le stockage objet (voir puce suivante).
  • SPOF cluster non-HA assumés. Control-plane unique (ADR cluster 0002) = SPOF de l’API. Le datalake objet (RGW) est en erasure coding 2+1 min_size=3 (ADR cluster 0004) → perte d’1 nœud sur 4 bloque les I/O objet, donc le sync du snapshot, le mart Parquet et sa lecture. En revanche, l’event log Dagster et l’index pgvector sont sur RBD réplication ×3 (ADR cluster 0001) : ils tolèrent la perte d’1 nœud (leur min_size diffère). Mitigation V1 : cache local RBD d’atlas-api (Phase 5.3) pour servir en lecture pendant une indisponibilité du RGW ; le pipeline batch attend le rétablissement (tolérable car batch). HA control-plane et redondance datalake = hors V1, risques assumés et tracés.
  • Pas de TLS interne / pas de chiffrement at-rest (ADR cluster 0003). Acceptable tant que tout est interne (UI de plateforme en L4 NodePort, HTTP clair sur réseau privé, ADR cluster 0092) ; à corriger avant exposition externe d’un mart ou d’un index nominatif → terminaison TLS + auth obligatoire (Phases 5–6). Depuis l’ADR cluster 0092, le socle ne fournit plus de bordure L7 (Gateway/cert-manager) : la terminaison TLS externe d’un mart nominatif relève du déployeur (TLS natif de l’app ou reverse-proxy d’instance). Le chiffrement at-rest reste un risque ouvert pour des données nominatives (Postgres et S3) — relève aussi de l’arbitrage du déployeur.
  • Appwrite auto-hébergé vs SaaS. find-an-expert dépend de node-appwrite (~25.2.0, ADR 0010) pour l’auth ET le consentement — ce n’est pas « zéro DB ». Deux options : (a) auto-héberger Appwrite (Appwrite + MariaDB + Redis) sur le cluster — non trivial mais souverain, cohérent avec le principe libre/souverain ; (b) SaaS Appwrite — casse la souveraineté, dépendance externe, transfert de données personnelles (impact RGPD). Décision attendue : option (a) par défaut, à acter dans un ADR dédié si retenue — chantier d’infra à part entière, à séquencer avant la mise en prod de la Phase 6.
  • Conformité RGPD à la charge du déployeur. Le dépôt livre un canevas techniquement capable (ré-dérivabilité, droit d’opposition, authentification), testé et déployable — le développement n’est pas suspendu à un arbitrage externe (cf. README « Responsabilité & conformité », ADR 0030/0031). En revanche, l’exploitation avec données nominatives réelles relève du déployeur : c’est à lui de trancher la base légale, désigner le responsable de traitement et, le cas échéant, conduire l’analyse d’impact. Le risque résiduel (un déployeur qui exploite sans cet arbitrage) sort du périmètre du dépôt et lui est explicitement imputé.
  • knip cassé sur main. Dette pré-existante au pré-push : à résorber en racine au fil des PRs, jamais contourner. Peut impacter le rythme des premières PRs touchant les paquets citation et la création d’atlas-api.