Aller au contenu

Audit de l'intégration Effect (vers un socle d'exécution) — 2026-06-04

Date de l’audit : 2026-06-04. Méthode : workflow multi-agents (7 dimensions auditées en parallèle — runtime, observabilité, Schema, gestion d’erreurs, dépendances @effect/*, tests, frontières SvelteKit). Chaque constat est prouvé par le code (chemins de fichiers lus) puis, pour les constats majeurs, vérifié de manière adversariale par un second agent qui défend la position inverse. Plusieurs « écarts » ont été nuancés ou dégonflés à la vérification — ces nuances sont conservées ci-dessous.

L’usage de la bibliothèque Effect (effect et l’écosystème @effect/*) dans tout le dépôt : packages métier, services, CLIs, apps SvelteKit. La question directrice : sur l’échelle « Effect à la marge » → « Effect comme socle d’exécution », où en est atlas, et quels écarts restent vers une intégration poussée (runtime central, injection par Layer, télémétrie intégrée, validation unifiée) ?

atlas est dans le premier tiers : Effect est un langage de description du métier, jamais une couche d’exécution.

  • Zéro runtime applicatif dans tout le dépôt (ManagedRuntime introuvable, aucun alias Runtime). Les deux seuls Layer.* non-test (makeCrfClientLayer, makeCliContextLayer) sont des factories Layer.succeed jamais montées à une frontière — utilisées uniquement par leurs propres tests. (confirmé)
  • Effect est omniprésent comme bibliothèque (~18 paquets déclarent effect ; le métier REDCap/citation/researcher-profiles est intégralement en pipelines Effect, Data.TaggedError, Schedule, Context.Tag), mais chaque frontière improvise son exécution : 3 primitives Effect distinctes (NodeRuntime.runMain, runPromiseExit, runPromise) plus un wrapper non-Effect (le runMain de cli-toolkit = main().catch(exit 1)), réparties sur 5 des CLIs. (nuancé : « 5 runners » est un décompte par site d’appel)

Le socle conceptuel (types, erreurs, pipelines) est là ; la couche d’exécution — runtime, Layer d’injection, propagation de contexte logger/config/tracer — est absente. C’est la frontière entre « on écrit du Effect » et « on tourne sur Effect ».

  • Schema à la frontière HTTP de services/crf : validation query/json via S.standardSchemaV1 + hono-openapi, hook d’erreur unique. Décodage + erreurs typées corrects.
  • crf-project-template : usage Schema canonique (schéma déclaratif = type + validateur, decodeUnknownEitherParseError typé). Référence interne.
  • Modèle d’erreur cohérent par frontière : Data.TaggedError côté pipelines/CLI (14 classes, 6 fichiers) ; ApplicationError extends Error côté HTTP SvelteKit/BaaS. La séparation suit la frontière SvelteKit/Appwrite — choix assumé, pas une dette accidentelle.
  • Découplage @sveltejs/kit propre : auth/baas/sveltekit-handler n’importent SvelteKit qu’en import type.
  • Régime de test Effect-natif réel : @effect/vitest + it.effect dominant dans citation-validate, systématique dans fetch-one-api-page.
  • Adaptateur Effect↔HTTP de référence : runEffect/runEffectRaw de services/crf (Effect.map + Effect.catchAll/Match.tag puis runPromise) préserve le mapping erreur→statut. Brique réutilisable.
  • Compatibilité de versions favorable : effect@3.21.2, @effect/opentelemetry@0.63 (peer effect ^3.21.0) directement compatible, peers OTel 2.x déjà alignés, @effect/platform@0.96.1 présent transitivement.

Priorisés par ratio valeur/effort (S/M/L/XL · risque · dépendance). Les deux structurants sont E10 (runtime) et E6 (frontière) ; tout le reste s’y rattache ou se traite indépendamment à faible coût.

#ÉcartEffortRisqueDépend de
E1Faux vert csv.test.ts : 3 tests it(() => Effect.gen(…)) retournent un Effect jamais exécuté → verts sans assertion (prouvé par mutation).Sfaible
E2Divergence de patterns RECORD_ID_PATTERN (/^[a-z0-9]{20,}$/i service vs /^[\w-]+$/ core) et CRF_NAME_PATTERN. (nuance : RecordIdSchema service est code mort)Smoyen
E3Acter la stratégie Effect en ADR : runtime central, frontière unique, Schema vs zod, atlas-errors vs TaggedError, convention de test. Décisionnel, non tracé.Sfaible
E4Retirer les phantom deps @effect/* : cluster/rpc/sql (0 import) dans cli/net,cli/crf,cli/biblio ; experimental/platform-node dans citation.Sfaible
E5Durcir knip contre le faux-négatif peer-deps (une dep satisfaisant un peerDep d’un paquet importé est comptée « utilisée » → audit:unused ne voit pas ces phantoms).MmoyenE4
E6Adaptateur Effect↔SvelteKit dans sveltekit-handler (symétrique de runEffect) : catchAll(tag → {body,status}) avant runPromise, pour ne plus aplatir en 500.MmoyenE3
E7Câbler CrfClient comme service Effect via makeCrfClientLayer (écrit, inutilisé). (nuance : gain = injectabilité/test, pas gestion de scope ; mock déjà fonctionnel)MmoyenE10
E8Layer logger/telemetry partagé via Effect (remplacer quiet/withMinimumLogLevel(None) re-appliqués et l’OTel hors Effect), configuré une fois au runtime.MfaibleE10
E9Pont OTel↔Effect : ajouter @effect/opentelemetry, NodeSdk.layer (opt-in conservé), Effect.withSpan sur le client REDCap. (nuance : impact nul sans withSpan ; dette latente)LmoyenE10, E4/E5
E10Runtime Effect central par type de processus : ManagedRuntime.make(AppLayer) (logger+config+services), créé une fois — CLIs, services/crf, apps SvelteKit. Pièce maîtresse.L→XLmoyenE3
E11Unifier l’amorçage CLI : runner Effect unique (remplace le runMain non-Effect de cli-toolkit), migrer les ~14 runPromise isolés de researcher-profiles, etc.LmoyenE10
E12Schema-as-brand dans crf-core : source unique Schema.String.pipe(pattern, brand) → type + décodeur + prédicat + pattern OpenAPI ; supprime la triple redondance.LmoyenE2
E13Décoder les réponses externes (OpenAlex) : remplacer as T de fetch-one-api-page par un Schema<T> décodé, ResponseParseError portant le ParseError.Lmoyen
E14Layers de test partagés (test-utils Effect) + TestClock pour rate-limit/retries/timeouts. (nuance : « zéro Layer » faux — crf-client teste un vrai Layer)XLélevéE10, E12

Chaque écart actionnable ouvre une issue GitHub ; les décisions structurantes (E3) ouvrent des ADR avant tout code. Le découpage et l’exécution sont portés par le plan de résorption Effect. Le résumé vivant des pratiques reste tenu dans Normes et pratiques appliquées ; ce rapport en fige l’état au 2026-06-04 avec les preuves.

  • Ne PAS migrer atlas-errors vers TaggedError : cela imposerait effect à toute la couche auth/baas/SvelteKit (qui ne l’embarque pas) pour un gain de discrimination nul (instanceof suffit). Le vrai écart est la couture (E6), pas le modèle.
  • Effect ne franchit jamais la frontière serveur (coût bundle, contrainte ADR 0005). Le runtime serveur des apps reste strictement côté serveur — risque réel d’entraîner effect dans le bundle client si l’adaptateur est mal placé.
  • Compat @effect/opentelemetry : alignée aujourd’hui, mais 0.x suit les mineurs d’effect — à épingler et surveiller ; vérifier le double SDK avec l’OTel transitif déjà tiré par @sentry/sveltekit.
  • Faux verts au-delà de csv.test.ts : le pattern it(() => Effect.gen(…)) non exécuté peut exister ailleurs ; toute migration de tests doit s’accompagner d’un garde-fou outillé, sinon régression silencieuse.
  • E2 avant E12 : unifier crf-core sur Schema-as-brand sans aligner d’abord les patterns figerait une incohérence — décider la règle, puis unifier.
  • Runtime central et 12-factor : centraliser config/logger ne doit pas figer la lecture d’env au boot là où la relecture runtime est voulue (cf. find-an-expert qui relit l’env à chaque appel).