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égoriedataops/(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: truesur le bloc Ceph) + une capacité abondante, sans imposer de taille ; atlas dimensionne ses propres données (basepgvectorqui grandit avec les embeddings — PVC CNPG surrook-ceph-block-replicated, resize RBD en ligne ; artefacts MLflow et datalake = objet S3, croissent dans le pool, pas de PVC à pré-dimensionner).
Introduction
Section intitulée « Introduction »Objectif
Section intitulée « Objectif »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.
Périmètre — atlas vs cluster
Section intitulée « Périmètre — atlas vs cluster »- 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-fetchrelégué aux compléments ciblés (< 10 k) ; le parsing desreferenced_works/fwciporté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 contratmanifest.json+ partitions immuables +schema_version(validateur danspackages/citation) ; les suites Great Expectations ; le chargement de l’index Postgres/pgvector ; le 3ᵉ signal surEnsembleWeights;atlas-api(métier + exploration) ; les résumés extractifs ; la PWAfind-an-expert; la signature d’images. Seuls les manifestes applicatifs (Deployment/Service/Ingress/ApplicationArgo 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’infrastructure — Cilium (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é viaplatform/<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 dansatlas. - 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-eventsAppwrite (ConsentType openalex_emaildefind-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).
Principes directeurs
Section intitulée « Principes directeurs »- 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:buildreste vert. Le socleclusterest déjà livré : aucune actionclustern’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-v2en 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.jsonatomique 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
clusteretatlassont distinctes par construction (dépôts séparés).
Conventions agent (à respecter à chaque étape)
Section intitulée « Conventions agent (à respecter à chaque étape) »- 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://openalexdésigne le bucket source externe (snapshot AWS Open Data) — la synchronisation écrit vers le bucket internes3://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éfixeatlas-) : dette connue, pas encore d’ADR dédié. - Commits. Conventional Commits, scope ∈
scope-enumdecommitlint.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 scopedataops(ajouté auscope-enum, ADR 0055). Le scopeatlas-apisera ajouté en Phase 5.2 (PR qui crée le service). Pas deCo-Authored-By. - Hooks lefthook JAMAIS bypassés : pas de
--no-verify,LEFTHOOK=0, etc. Si un hook bloque, fixer en racine.knipcasse pré-push surmain: 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), retoursEffect<A, E>(ADR 0005),runaux points d’entrée (handler Hono,binbatch, op Dagster). - Validation systématique. Chaque étape se clôt par ses commandes de validation, qui doivent toutes passer.
Vue d’ensemble des phases
Section intitulée « Vue d’ensemble des phases »| Phase | Titre | Dépôt | Bloque | Effort |
|---|---|---|---|---|
| 0 | RGPD (capacité technique, non bloquant) | atlas | — | M |
| 1 | Socle cluster (FOURNI) | cluster (livré) | — | — |
| 2 | Ingestion massive snapshot S3 (works + authors) | atlas | 3 | L |
| 3 | Transformations dbt + mart + contrat + qualité | atlas | 4 | XL |
| 4 | Indexation PostgreSQL/pgvector | atlas | 5 | L |
| 5 | Scoring déterministe + atlas-api (métier + explo) | atlas | 6 | XL |
| 6 | PWA find-an-expert + exposition | atlas | — | M |
| 7 | DevSecOps 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.
Stratégie de validation E2E (banc Lima)
Section intitulée « Stratégie de validation E2E (banc Lima) »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 parkubectl applymanuel (ADR cluster 0046). - Échantillon ultra-borné. L’ingestion de test ne tire que quelques fichiers
.gzpar entité (filtrerclone), 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ésworksetauthors) via rclone et son incrémental parupdated_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.tsne 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égoriedataops/, 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/(modulesconsent-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écisionavec###/## StatutAccepted (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 :
- ADR 0030 présent,
Accepted (2026-06-02), liant 0026 et 0029. - 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/searchet filtres d’exploration) ; mention EU AI Act. ## Garde-fousrappelle 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).
- ADR 0030 présent,
- 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
curatedfiltré 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 actionclustern’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’interfacecluster/contract/*.example.yaml(ADR cluster 0043) et leguide-dev-data.mddu dépôt cluster.
État de référence (ce que le cluster fournit)
Section intitulée « État de référence (ce que le cluster fournit) »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 plateforme | Fourni par (réel) | Endpoint / accès | ADR cluster |
|---|---|---|---|
| Exposition des UI | L4 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 UI | portail (liste les UI par couche + liens L4 + logins) | http://<IP-nœud>:<nodePort> | 0091 |
| GitOps | Argo CD lisant Gitea intra-banc (webhook) | AppProject atlas (dest. citation-*, dagster, marquez) | 0022, 0044 |
| Observabilité | kube-prometheus-stack + Loki | Grafana en NodePort http://<IP-nœud>:<nodePort> | 0016 |
| PostgreSQL + pgvector | CloudNativePG, 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 |
| Orchestration | Dagster (chart 1.13.7, K8sRunLauncher, event log CNPG), livré vide (workspace load_from: []) | dagster-dagster-webserver.dagster:80 | 0026 |
| Lineage | Marquez (chart 0.51.1, store CNPG) | OPENLINEAGE_URL=http://marquez.marquez:5000 | 0028 |
| Stockage objet | RGW Ceph (CephObjectStore datalake, EC 2+1) via ObjectBucketClaim (OBC) ; SeaweedFS au banc léger | rook-ceph-rgw-datalake.rook-ceph:80 (path-style) ; seaweedfs.s3:8333 | 0036 |
| Registry d’images | interne HTTP sans auth | registry:80/<repo>:<tag> | 0011 |
| Contrat machine-lisible | cluster/contract/*.example.yaml ; access.sh génère atlas/.env.cluster.local | — | 0043, 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 namespacedagster(où tourne lerclone syncdu code-location), en0.0.0.0/0restreint 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-workspacevia Argo CD + Deployment gRPC) est ajoutée àdocs/guide-dev-data.mddu 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 proprenamespace.yamlet ses NetworkPolicies (default-deny + egress Postgres/RGW/registry/Internet selon le rôle), en recopiant les jeux de référencedagster/etmarquez/deplatform/network-policies/(côté cluster).clusterne les crée pas. OPENLINEAGE_URL: injectée par atlas dans l’env du code-location (valeur de contrathttp://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_snapshotqui exécute la synchronisation initiale complète des3://openalex/data/{works,authors}(JSONL gzippé, partitionsupdated_date) verss3://citation/raw/{works,authors}/updated_date=…/. La logique vit en Python dansdataops/citation-dagster/(ADR 0055), pas danspackages/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 :openalexanonyme →cephRGW, 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.gzvia filtre rclone pour le test local) ; Dockerfile de la code-location (rcloneinclus, 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.gzpar 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) dansdocs/architecture/. - Done criteria : asset
raw_snapshotdé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_datepostérieures au watermark de date persistant, et rapatrier bruts lesmerged_ids(s3://openalex/legacy-data/merged_ids/<entity>/) versraw/merged_ids/<entity>/— sans les appliquer (la fusion effective est faite en aval par dbt, étape 3 ; immutabilité deraw/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>/(fichiersYYYY-MM-DD.csv.gz, colonnesmerge_date,id,merge_into_id— confirmé). - Files (write) : module
watermark.py(reader/writer deraw/_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 buckets3://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+ ConfigMapBUCKET_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 ConfigMapdagster-workspace(ajout de la location, via Argo CD) ; injectionOPENLINEAGE_URL; NetworkPolicy (vers RGW + egress Internet pour le sync) ;ApplicationArgo 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-fetchnon 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_countau niveauworks, et l’entitéauthors) se fait dans les modèles dbt en Python (dataops/, étape 3.2), pas danspackages/citation-types(TS). Les types TS decitation-typesne servent qu’au contrat de transfert (manifest) consommé côté TS paratlas-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) : extensionhttpfs,CREATE SECRET(RGW path-style), lecture du brut JSONL gzippé (read_json_autosurs3://citation/raw/**/*.gz), lecture/écriture Parquet (COPY … (FORMAT PARQUET)), partitionnement Hive. C’est le backend que dbt-duckdb consomme via sonprofiles.yml; il vit dans la code-location Python, pas danspackages/citation(TS). Le wrapper TSpackages/citation/src/db/index.ts(DuckDB local trivial) reste côté consommateur (lecture du mart validé paratlas-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 : staging → curated
Section intitulée « Étape 3.2 — Projet dbt-duckdb : staging → curated »- Goal : Initialiser le projet dbt (
dbt-duckdb,profiles.ymlpointant le backend S3 de 3.1) et écrire les couchesstaging(typage/nettoyage desworksetauthorslus depuis le snapshot JSONL.gz, +authorships,referenced_works) etcurated(worksetauthorscanoniques,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/dontstg_citation_works+stg_citation_authors,models/curated/dontcurated_works+curated_authors,dbt_project.yml,profiles.yml, schemas/tests), matérialisationcurateden Parquets3://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/curatedproduits, tests dbt verts, dédup vérifiée (pas de doublon d’arête). - Done criteria : projet dbt opérationnel,
curateden 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_pairscalculantcross_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_citationscalculé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) ens3://citation/marts/collab/dt=YYYY-MM/run=<id>/, puis écrire en dernier unmanifest.jsonatomique{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(calculsha256/bytespar 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_versiondérivée decitation-types; jamais de réécriture en place (rejeu = nouveaurun=<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_versionpré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 (dbtopenlineage/Dagsterdagster-openlineageou 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+sha256de chaque part avant de lire et refuse uneschema_versioninconnue. Le validateur vit danspackages/citation(PASpackages/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é pouratlas-api(Phase 5) et le chargement d’index (Phase 4). - Invariants à préserver :
schema_versioninconnue → refus (pas de best-effort) ; sha256 mismatch → refus ; validation avant toute lecture ; ne pas détournercitation-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
curatedfiltré 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 (nouveaurun=<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 dontcross_citations) et une tableresearchersportant la colonnevector(384). Attention à la granularité :embedding-profile.tsproduit un vecteur par chercheur (EmbeddingProfile { researcherId, vector }, mean-pooling des œuvres), donc la colonnevector(384)est portée par l’entité chercheur (cléresearcherId), pas parworks(qui n’a pas d’embedding produit). Les colonnestsvector(lexical) vivent surworks/researchersselon 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 danspackages/citation(Effect), secret de connexion référencé (pas en clair). - Invariants à préserver :
pgvectoractivé 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 (conteneurpgvector), schéma créé,CREATE EXTENSION vectorok. - 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)
Étape 4.2 — Chargement FTS (tsvector) lexical
Section intitulée « Étape 4.2 — Chargement FTS (tsvector) lexical »- 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 paratlas-apiPhase 5). - Invariants à préserver : validation du contrat avant chargement (refus d’une
schema_versioninconnue) ; 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-v2déjà produits parresearcher-profilesdans la colonnevector(384)de la tableresearchers(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(typeEmbeddingProfile { researcherId, vector }, embeddings ONNX CPU réutilisés tels quels), schéma 4.1. - Files (write) : loader vectoriel (alimente
researchers.vector(384)depuis lesEmbeddingProfileexistants), 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_loaden 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_countdu 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’assetindex_loadse 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_loadorchestré, 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’extensionEnsembleWeightsexistant (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
EnsembleWeightsavec 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 patronservices/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 scopeatlas-apiauscope-enumdecommitlint.config.js. - Invariants à préserver : standardisation handler→app identique à
crf; CSP/headers repris ; nommagecitation/atlas(pas de marque). - Validation :
pnpm ci:checks;atlas-apidémarre,/healthOK, 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-apilit 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_versioninconnue → 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-apisert 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 depuismatch-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+/summarydansatlas-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 ;/recommendationsordonne par score attendu. - Done criteria :
/recommendations+/summarystables, 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=lexicalet?mode=semanticrenvoient 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,/statsopérationnels et authentifiés. - PR title :
feat(atlas-api): exploration /search + filtres structurés + /stats
Étape 5.6 — Métriques Prometheus custom
Section intitulée « Étape 5.6 — Métriques Prometheus custom »- 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 applicatifatlas), dashboard Grafana de base (provisionné). - Invariants à préserver : pas de donnée personnelle dans les labels (cardinalité + RGPD) ;
/metricsnon exposé publiquement (interne au scrape). - Validation :
pnpm ci:checks; sur banc : Prometheus scrappeatlas-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-expertinstallable 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,/summaryet/searchd’atlas-apidepuis 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)
Étape 6.3 — Lighthouse CI
Section intitulée « Étape 6.3 — Lighthouse CI »- 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(namespacecitation-pwa),ApplicationArgo 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
Phase 7 — DevSecOps images (dépôt atlas)
Section intitulée « Phase 7 — DevSecOps images (dépôt atlas) »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 verifysur 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-controllerSigstore) 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)
Étape 7.3 — SBOM + scan Trivy
Section intitulée « Étape 7.3 — SBOM + scan Trivy »- 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 ;
syftreste 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
Palier 2 — hors V1, en ligne de mire
Section intitulée « Palier 2 — hors V1, en ligne de mire »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 → Icebergnon destructive quand un besoin de time-travel/upsert/évolution de schéma transactionnelle se mesure. Le contratmanifest.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’extensionEnsembleWeightsreste 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.
Risques & questions ouvertes
Section intitulée « Risques & questions ouvertes »- 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-v2en 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 dansdocs/architecture/, prévoir reprise et partitionnement parupdated_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 (leurmin_sizediffè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-expertdépend denode-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é.
knipcassé surmain. 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 paquetscitationet la création d’atlas-api.