expria-backend/docs/CHANGELOG-backend.md
Hermann_Kitio 5263372839
Some checks are pending
CI / quality (push) Waiting to run
feat(infra): route Gemini WS through SOCKS5 proxy (WARP)
- Add socks-proxy-agent dependency
- Add resolveGeminiProxyAgent() helper reading GEMINI_PROXY_URL env
- Apply agent to T1 and T2 Gemini WS factory defaults
- No proxy when GEMINI_PROXY_URL is unset (local dev unchanged)
- Tests: 311/311 green
2026-06-30 20:30:15 +03:00

216 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog — Expria Backend
Toutes les modifications notables du backend sont documentées dans ce fichier.
Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
---
## [Unreleased] — 2026-06-30 — Proxy SOCKS5 (Cloudflare WARP) pour les WS Gemini Live
### Added
- `resolveGeminiProxyAgent()` (`geminiLive.ts`) — helper qui lit la variable d'environnement **optionnelle** `GEMINI_PROXY_URL` et renvoie un `SocksProxyAgent` si elle est définie, sinon `undefined` (connexion directe). Contexte : l'IP du VPS de production (datacenter) est bloquée par Google ; Cloudflare WARP tourne en mode proxy SOCKS5 sur le VPS (`socks5://127.0.0.1:40000`). Seul le trafic WS Gemini est routé via ce proxy ; Supabase, DeepSeek et les clients restent en direct.
- Dépendance `socks-proxy-agent` (`package.json`).
- `GEMINI_PROXY_URL` ajoutée à `.env.example` (vide par défaut → dev local inchangé).
- Tests `geminiLive.test.ts` — 2 tests pour `resolveGeminiProxyAgent` (absente → `undefined` ; `socks5://…` → instance `SocksProxyAgent`).
### Changed
- Factory WS par défaut de `openGeminiLiveSession` (T2, `geminiLive.ts`) et `openGeminiLiveT1Session` (T1, `geminiLiveT1.ts`) — passe désormais `{ agent }` au constructeur `new WebSocket(url, options)` quand un proxy est résolu. La factory injectée par les tests (`clientFactory`) n'est pas affectée. Prompt système, interruption, flush, `runT1/T2LiveCorrection` et close codes inchangés.
### Notes
- Tests backend : 311/311 verts. `tsc --noEmit` OK.
- Activation en prod : définir `GEMINI_PROXY_URL=socks5://127.0.0.1:40000` côté VPS/Render (config d'env, hors code).
---
## [Unreleased] — 2026-06-30 — Sprint 7a-patch — T1 Live : suppression de la dépendance au questionnaire
### Changed
- `buildT1SystemPrompt` (`geminiLiveT1.ts`) — prompt système T1 désormais **statique** (signature sans argument). La section « CONTEXTE DU CANDIDAT » (5 variables `${reponses.*}`) est retirée et remplacée par une consigne d'écoute : « Écoute attentivement ce que le candidat dit. Quand on te le signale, formule UNE question de relance courte (10-20 mots) liée à ce que le candidat vient de dire. » Les 8 règles sont conservées (silence par défaut, relance sur signal, ton bienveillant, jamais d'évaluation/hors-rôle, règle 5 « DOIS poser des questions »). Règle 4 : retrait de « ou à son contexte ci-dessus ».
- `openGeminiLiveT1Session` (`geminiLiveT1.ts`) — option `reponses` retirée de `OpenGeminiLiveT1SessionOptions` et de la signature. Le handler de message client ignore désormais explicitement (log debug + return) tout message non reconnu (ni `audio` ni `end`) — un éventuel `{type:'context'}` d'un ancien front ne provoque ni crash ni close.
- `WS /t1/live` (`t1live.ts`) — la session Gemini s'ouvre **immédiatement après l'auth** (calque T2), dans `onOpen`. Le flux client devient `{type:'audio', data}` puis `{type:'end'}`.
- `docs/Prompt_t1live.md` — §1 (table « Subject-based »), §2 (règle 3), §3 (prompt statique, retrait des variables et de la section « Variables à substituer »), §4.1 (retrait du message de contexte), §4.3 (retrait du close 4004).
- Tests `geminiLiveT1.test.ts` / `t1live.test.ts` — appels `buildT1SystemPrompt()` sans argument, retrait de `reponses` des appels de session ; suppression du test d'intégration des réponses (remplacé par un test « écoute / plus de CONTEXTE DU CANDIDAT ») et du bloc `parseT1Context` ; tests interruption, flush terminal, timeout et correction inchangés.
### Removed
- Message client `{type:'context', reponses}` (1er message obligatoire) et close **4004 `CONTEXT_MISSING`** — la route ne lit plus de contexte.
- Fonction `parseT1Context` (`t1live.ts`) et ses imports `validateReponses` / `PresentationReponses`.
### Notes
- Tests backend : 309/309 verts. `tsc --noEmit` OK.
- **Suivi frontend (hors scope)** : le frontend Sprint 7b (non commité) envoie encore `{type:'context'}` et possède un `QuestionnaireT1Page` Live ; ce message est désormais inoffensif côté backend (ignoré). Le questionnaire Live devient sémantiquement obsolète — à retirer dans une session frontend dédiée.
---
## [Unreleased] — 2026-06-28 — Sprint 6d — T2 Live : durcissement prompt + VAD + cleanup SDK
### Changed
- `buildT2SystemPrompt` (`geminiLive.ts`) — prompt système T2 durci : 13 règles absolues (Bug 1). Interdiction stricte de poser des questions (aucun point d'interrogation), rôle passif/inerte, silence total après réponse, aucune formule de politesse de fin. Objectif : stopper la relance systématique de l'examinateur IA. Réf TD-22. ⚠ Spécifique T2 — l'interdiction du « ? » ne doit pas être propagée au prompt T1 (Sprint 7).
- `buildSetupFrame` (`geminiLive.ts`) — `realtimeInputConfig.automaticActivityDetection` (VAD) réintégré dans le setup frame Gemini (Bug 2), 4 champs : `disabled:false`, `startOfSpeechSensitivity:START_SENSITIVITY_LOW`, `endOfSpeechSensitivity:END_SENSITIVITY_LOW`, `silenceDurationMs:2000`.
- `geminiLive.test.ts` — assertion VAD mise à jour (`realtimeInputConfig` : `toBeUndefined` → présence + valeurs des 4 champs).
### Removed
- Dépendance `@google/genai` retirée de `package.json` / `package-lock.json` et script de debug `test-gemini-live.js` supprimé (Bug 8) — SDK abandonné au profit du WebSocket brut (`ws`).
### Notes
- Tests backend : 292/292 verts (0 échec).
- Validé au test manuel Golden Dataset Groupe D — Bug 1 (plus de relance systématique) et Bug 2 (VAD, pauses de réflexion respectées) confirmés en conditions réelles.
- **TD-22 (🟡 ouvert)** — comportement T2 garanti uniquement par prompt engineering ; pas de garantie déterministe sur modèle Flash Live. À revisiter si la relance persiste.
---
## [Unreleased] — 2026-04-26 — Sprint 6a — Backend T2 Live
### Added
- `buildT2SystemPrompt({role, contexte})` dans `geminiLive.ts` — prompt dynamique conforme `Prompt_t2live.md §3`, remplace la constante `T2_SYSTEM_PROMPT` (agent immobilier).
- Accumulation transcripts pendant la session WS : `inputTranscription[]` + `outputTranscription[]` parsés depuis les messages Gemini, reconstruits en transcript chronologique à la fin.
- VAD config dans le setup frame Gemini : `endOfSpeechSensitivity: END_SENSITIVITY_LOW`, `startOfSpeechSensitivity: START_SENSITIVITY_LOW`, `silenceDurationMs: 2000`.
- Timeout session 210 s (3 min 30) + warning client à 180 s (30 s restantes).
- Signal client `{type:'end'}` pour fin anticipée du dialogue.
- Close codes : 4005 `GEMINI_CONFIG`, 4006 `GEMINI_DISCONNECTED`.
- Orchestration `t2live.ts` : fetch sujet par UUID (`?sujet=<uuid>`, validation `mode='EO'` + `tache=2`), close 4004 `SUJET_NOT_FOUND` si absent.
- Post-session : `runT2LiveCorrection` — insert `productions(tache='EO_T2_LIVE')`, appel `deepseekCorrectEO(transcript, 'EO_T2')`, `PHONOLOGY_STUB` (TD-08), persist rapport + score + nclc, envoi `{type:'report'}` au client, close 1000.
- `TacheEO` étendu avec `'EO_T2'` dans `deepseek.ts` + `VALID_TACHES_EO` dans `corrections.ts`.
- 10 tests d'intégration `t2live.test.ts` (auth, sujet, pipeline correction nominal + erreurs).
- 11 tests `geminiLive.test.ts` (7 réécrits + 4 nouveaux : prompt builder, accumulation, timeout/warning, end signal).
### Changed
- `geminiLive.ts` réécrit — setup frame paramétrable, `inputAudioTranscription` + `outputAudioTranscription` activés, callback `onSessionEnd(transcript)`.
- `corrections.ts``VALID_TACHES_EO` inclut `'EO_T2'`.
### Notes
- Tests backend : 292/292 verts (+15 vs baseline 277).
- Phonologie T2 Live = 0 (TD-08 — pas d'audio brut pour évaluation phonologique).
- Le frontend n'est pas encore connecté — test e2e au Sprint 6c.
---
## [Unreleased] — 2026-04-26 — Sprint 5a — Backend billing cleanup
### Added
- `supabase/migrations/007_sprint_5a_stripe_webhook_events.sql` — table `stripe_webhook_events(id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW())` + index sur `processed_at`. Idempotente (`CREATE TABLE IF NOT EXISTS`).
- `src/lib/stripeWebhookEvents.ts` — helpers `isEventProcessed` / `markEventProcessed` (insert idempotent, conflit unique `23505` avalé silencieusement).
- `src/lib/__tests__/stripeWebhookEvents.test.ts` — 8 tests (lecture, écriture, edge cases vide/erreur DB).
- `src/lib/__tests__/createBillingPortalSession.test.ts` — 4 tests (succès, customerId vide, returnUrl vide, URL Stripe vide).
- `POST /stripe/customer-portal` — endpoint authentifié qui crée une Stripe Billing Portal Session (gestion abonnement self-service) et redirige l'utilisateur. 400 `NO_ACTIVE_SUBSCRIPTION` si pas de `stripe_customer_id` ; return_url = `${APP_URL}/dashboard`.
### Changed
- `POST /stripe/webhook` — déduplication explicite des events Stripe (TD-13 résolu) : check `isEventProcessed(event.id)` avant traitement → early return `200 { received: true, replayed: true }` ; `markEventProcessed` après succès uniquement (pas si exception, pour permettre rejeu Stripe).
- `src/lib/stripe.ts` — nouvelle fonction `createBillingPortalSession({ customerId, returnUrl })` (mirror de `createCheckoutSession`).
- `src/routes/__tests__/stripe.test.ts` — 5 nouveaux tests (2 idempotency webhook + 3 customer-portal route).
- `docs/ARCHITECTURE-backend.md` — §3 commentaire `plans.ts` corrigé (`POST /plans/upgrade-prorata` au lieu de `POST /plans/upgrade` qui n'existait pas) ; §6 retrait de la ligne dupliquée `POST /plans/upgrade` (la création d'abonnement passe par `POST /stripe/checkout`) ; §6 ajout `POST /stripe/customer-portal`.
### Fixed
- `src/lib/stripe.ts``cancel_url` Stripe Checkout corrigé : `${APP_URL}/tarifs?upgrade=cancelled``${APP_URL}/plan?upgrade=cancelled`. La route `/tarifs` n'existe pas côté frontend (route réelle : `/plan`) ; les checkouts annulés aboutissaient sur un 404. Bug détecté lors du Sprint 5c frontend (gestion des retours post-Checkout) et corrigé en cross-repo.
### Resolved
- **TD-13 🔴 → Résolu** : Webhook Stripe idempotent (table `stripe_webhook_events` + helpers + wiring route + 10 tests).
### Notes
- Tests : 261 → 278 verts (+17).
- Aucun changement code frontend dans ce sprint — Sprint 5b/5c/5d (frontend billing) livrés en parallèle.
---
## [Unreleased] — 2026-04-26 — Sprint 4.8 — Phonologie EO
### Added
- `src/lib/geminiPhonology.ts` — évaluation phonologique via Gemini 2.5 Flash (audio brut, JSON strict, timeout 45s + 1 retry). `PHONOLOGY_STUB` pour Mode A (transcript sans audio).
- `src/lib/__tests__/geminiPhonology.test.ts` — 9 tests (parse, cap 0..4, retry, erreurs HTTP).
- `src/controllers/__tests__/correctionEoPhonology.test.ts` — 4 tests (injection 5e critère, stub Mode A, fallback Gemini down, score max 20).
### Changed
- `POST /corrections/eo` — passe de 4 critères × /5 à 5 critères × /4 (score total /20 inchangé). Phonologie évaluée par Gemini en parallèle de la transcription (Mode B audio). Fallback stub si Gemini phonologie échoue.
- `src/lib/deepseek.ts` — prompt EO : cap critère /5 → /4, libellés officiels TCF Canada, mention phonologie évaluée séparément. Cap EE inchangé /5.
- TD-08 partiellement résolu (T1/T3). T2 Live reste à 0 (Sprint 6).
### Notes
- ⚠️ Breaking change frontend : criteres.length passe de 4 à 5, échelle /5 → /4.
- Tests : 248 → 261 verts (+13).
---
## [Unreleased] — 2026-04-25 — Sprint 4a/4b — Backend EO
### Added
- `POST /corrections/eo` aligné format Sprint 3.6a : revelation, diagnostic, criteres enrichis (exemple/suggestion/astuce), conseil_nclc adaptatif (4 niveaux selon score vs cible), erreurs_codes, jobs fire-and-forget modèle + exercices
- `POST /presentations/generate` — génération présentation T1 via DeepSeek (220-260 mots, registre oral NCLC 7-8, 5 champs)
- `POST /transcriptions/token` — token Deepgram éphémère (600s TTL, dormant côté frontend MVP)
- `src/lib/deepgram.ts` — client Deepgram /v1/auth/grant (scope Member requis)
- `src/lib/audioStorage.ts` — supprimé (audio non stocké côté serveur)
- Migration `006_sprint_4a_eo.sql` — documentation bucket Storage (no-op)
### Changed
- `correctEO` : accepte audioBase64+mimeType (Gemini batch) OU transcript texte
- MIME normalisé avant validation (audio/webm;codecs=opus → audio/webm)
- `sanitizeJsonContent` : gère single-quote JSON DeepSeek
- Gemini timeout 30s → 45s, DeepSeek correctEO 55s → 90s
- `gemini-2.0-flash``gemini-2.5-flash`
- conseil_nclc adaptatif EE + EO (4 niveaux : dépassé / atteint / proche / loin)
- Tests : 205 → 248 verts (+43)
---
## [Unreleased] — 2026-04-25 — Fix health check keepalive Supabase
### Changed
- Route `GET /` : ajout d'un ping Supabase (`profiles.select('id', { head: true }).limit(1)`) à chaque appel. Garde le pool de connexions DB actif via les pings UptimeRobot (toutes les 5 min). Réponse enrichie : `{ message, db: 'connected' | 'error' }`. Toujours 200 (liveness, pas readiness).
---
## [Unreleased] — 2026-04-22 — Sprint 3.6a — Qualité correction Backend
### Added
- Nouveaux prompts DeepSeek spécifiés dans `docs/Prompt_maître.md` et `docs/Prompt_production_modèle.md` — builders dynamiques `buildCorrectionPrompt`, `buildModelPrompt`, `buildExercicesPrompt` dans `src/lib/deepseek.ts`.
- `expria-frontend/docs/TAXONOMIE_ERREURS.md` — 63 codes d'erreurs TCF Canada sur 4 critères + 4 codes « autre ». Validation runtime via `src/lib/taxonomieErreurs.ts` (`isValidCode`, `isValidCritere`, `buildTaxonomyPromptSection`). Codes invalides retournés par DeepSeek sont filtrés ; le code `autre` sans description est rejeté.
- Génération parallèle correction + modèle — option (b) : `generateProductionModele` démarre en même temps que `correctEE` avec `nclcObtenu = nclcCible - 1` comme estimation provisoire, `await` uniquement sur la correction pour répondre à la requête HTTP.
- Exercices personnalisés fire-and-forget déclenchés après la résolution de la correction (dépendent de `rapport.erreurs_codes` et `rapport.criteres`). Format aligné sur les captures d'écran : `{difficulte, theme, diagnostic, consigne, extrait, indice, correction, explication}`.
- Nouveaux champs dans `productions` : `revelation` (JSONB), `diagnostic` (TEXT), `conseil_nclc` (JSONB), `erreurs_codes` (JSONB), `exercices` (JSONB), `modele` (JSONB), `nclc_cible` (INTEGER), `exercices_status` / `modele_status` (TEXT, 'pending'/'ready'/'error').
- Migration SQL `supabase/migrations/004_sprint_3_6a_qualite_correction.sql` — première migration versionnée du projet (cf. backend TD-06) ; idempotente grâce à `IF NOT EXISTS`.
- Paramètre `nclc_cible` optionnel sur `POST /corrections/ee` (défaut 9, valeurs acceptées : 9 ou 10 ; sinon 400 VALIDATION_ERROR).
- Index GIN sur `erreurs_codes` pour préparer l'agrégation du Sprint 3.6c (analyse patterns).
- Nouveau fichier de tests `src/controllers/__tests__/correctionController.test.ts` — 8 tests (parallélisme option b, statuts ready/error, nclc_cible propagé, simulation introuvable, autre utilisateur).
- 2 tests ajoutés à `simulationController.test.ts``getById` renvoie `nclc_cible`, `exercices`, `modele` + statuts.
- Logs d'erreur détaillés : `callDeepSeek` classifie TIMEOUT / ABORT / JSON_PARSE / NETWORK / OTHER ; `correctionController.correctEE` logue `{simulationId, tache, nclcCible, message, stack}` avant de retourner 500.
- FTD-23 🟡 ajoutée dans `expria-frontend/docs/TECH_DEBT.md``useAutosave` peut fire un PATCH `/simulations/:id/contenu` après correction, ce qui retourne 400 VALIDATION_ERROR. À corriger dans une session dédiée (préexistant au Sprint 3.6a, détecté lors des tests manuels).
### Changed
- `correctEE` dans `src/lib/deepseek.ts` — nouvelle signature `correctEE(CorrectionInput)` (contenu, tache, sujet, sourceDoc1/2, nclcCible) et nouvelle forme de retour `CorrectionRapport` (revelation, diagnostic, criteres avec exemple/suggestion/astuce, conseil_nclc, erreurs_codes). `EERapport` devient alias de `CorrectionRapport`. EO inchangé.
- `correctionController.correctEE` — charge le sujet + documents T3 depuis Supabase pour alimenter le prompt maître ; persiste les nouveaux champs (revelation, diagnostic, conseil_nclc, erreurs_codes, nclc_cible) + statuts pending initiaux ; lance `runModeleJob` en parallèle (option b) et `runExercicesJob` après correction.
- `simulationController.getById` — retourne désormais `nclc_cible`, `exercices`, `exercices_status`, `modele`, `modele_status` en plus du `rapport` enrichi ; fallback `'pending'` si les colonnes sont absentes (compat avec productions pré-migration).
- Timeout DeepSeek côté backend : `callDeepSeek` abort à **55 s** via `AbortSignal.timeout(55_000)` (avant : aucun timeout) ; timeout frontend corrections monte de **30 s à 60 s** — marge de 5 s entre abort backend et abort client.
- Routes `/simulations/*` : réorganisation défensive — les `PATCH /:id/contenu` et `PATCH /:id/sujet` sont déclarées avant `GET /:id` pour éviter tout risque de masquage.
- `deepseek.test.ts` réécrit (25 tests) — couvre correctEE nouvelle signature, generateProductionModele, generateExercices, helpers post-traitement, EO inchangé.
### Notes
- **Option A retenue** pour la compatibilité frontend : backend renvoie uniquement la nouvelle forme. Le Sprint 3.6b (frontend) est immédiatement suivant et corrige l'écran blanc sur `RapportPage`.
- **Option (b) retenue** pour le parallélisme : modèle en parallèle avec correction (nclcObtenu estimé), exercices strictement après correction.
- Migration SQL à exécuter manuellement via `supabase db push` ou SQL Editor du dashboard (cf. Règle F) — aucune exécution automatique.
- Tests : **174 tests verts** (+19 vs baseline 155), 18 fichiers de tests.
- TD-15 🟡 ouvert : si le process redémarre pendant un job fire-and-forget (modèle/exercices), le statut reste `pending` indéfiniment. À traiter après observation en production.