- Prompt_t1live.md : spec prompt examinateur T1, note anti-TD-22 (regle 7 du T2 non propagee), contrat WS complet (messages + close codes) pour le frontend 7b, comportement de fin (activityEnd final + relance terminale coupee). - TECH_DEBT-backend.md : TD-23 (non-determinisme Gemini Live T1 + decouverte flush inputTranscription a activityEnd en VAD manuel), TD-24 (dette nommage gate oral_t2_live couvrant aussi T1), TD-25 (dette DRY runT1LiveCorrection ~= runT2LiveCorrection, report conscient).
19 KiB
TECH_DEBT.md — Expria / Coach TCF Canada
Document de référence — Version 1.0 Ce document recense les décisions techniques prises par pragmatisme qui devront être revisitées, les stubs temporaires, et les fonctionnalités reportées. À mettre à jour après chaque session de développement.
Format : chaque entrée a un identifiant (TD-XX), une priorité, et un statut. Priorités : 🔴 Critique (bloque la production) / 🟡 Important / 🟢 Mineur
1. Stubs temporaires — à compléter
TD-01 — src/lib/supabase.ts (backend)
Priorité : 🔴 Critique
Statut : Ouvert
Description : Client Supabase créé comme stub. Fonctionne en développement avec les variables d'environnement mais n'a pas de gestion d'erreur robuste si SUPABASE_URL ou SUPABASE_SERVICE_ROLE_KEY sont absentes.
À faire : Ajouter une validation au démarrage — si les variables manquent, le serveur refuse de démarrer avec un message clair.
Session concernée : Initialisation backend
TD-02 — src/lib/planController.ts (backend)
Priorité : 🟡 Important
Statut : Résolu — session Stripe
Description : Stub créé pour permettre les tests de updateUserPlan. La vraie implémentation (mise à jour Supabase + gestion Stripe) n'est pas encore codée.
À faire : Implémenter lors de la session Stripe (POST /stripe/webhook).
Session concernée : Tests automatisés
TD-03 — src/lib/stripe.ts (backend)
Priorité : 🟡 Important
Statut : Résolu — session Stripe
Description : Stub créé pour permettre les tests de verifyStripeWebhook et calculateProrata. La vraie implémentation Stripe n'est pas encore codée.
À faire : Implémenter lors de la session Stripe.
Session concernée : Tests automatisés
2. Décisions pragmatiques — à revisiter
TD-04 — Déploiement manuel (frontend + backend)
Priorité : 🟢 Mineur Statut : Ouvert — accepté jusqu'aux premiers revenus Description : Cloudflare Pages et Render ne supportent pas l'auto-deploy depuis Codeberg. Le déploiement est manuel (CLI + dashboard). À faire : Migrer vers VPS Hetzner + Coolify pour restaurer l'auto-deploy. Voir ARCHITECTURE.md §9 Phase 2. Condition de résolution : Quand Expria génère ses premiers revenus réguliers.
TD-05 — Comptes de test avec emails @gmail.com
Priorité : 🟢 Mineur
Statut : Ouvert
Description : Les comptes de test utilisent @gmail.com au lieu de @expria.local prévu dans TEST_ENVIRONMENT.md. Raison : Supabase bloque la création d'utilisateurs avec des domaines non standards via l'API admin, et le dashboard est inaccessible depuis la Russie.
Emails actuels :
test.free@gmail.comtest.standard@gmail.comtest.premium@gmail.comtest.quota@gmail.comÀ faire : Mettre à jour TEST_ENVIRONMENT.md pour refléter les vrais emails. Vérifier que la validation@expria.localdans le middleware n'est pas implémentée (elle ne l'est pas).
TD-06 — Pas de migration SQL versionnée pour les tables initiales
Priorité : 🟡 Important
Statut : Ouvert
Description : Les tables profiles et productions ont été créées directement via SQL Editor, sans fichier de migration dans supabase/migrations/. Viole la Règle F de DEVELOPMENT_PRINCIPLES.md.
À faire : Créer les fichiers de migration correspondants :
supabase/migrations/001_create_profiles.sqlsupabase/migrations/002_create_productions.sqlsupabase/migrations/003_create_test_accounts.sqlImpact : Si la base doit être recréée (nouveau projet Supabase), les migrations permettent de tout reconstruire en une commande.
TD-07 — Ancien projet Supabase partagé
Priorité : 🟡 Important
Statut : Ouvert — accepté temporairement
Description : Le nouveau projet Expria V2 utilise la même base Supabase que l'ancien projet (en maintenance). Les anciennes tables ont été remplacées mais d'autres tables de l'ancien projet subsistent (sujets, eo_t2_results, payment_transactions, etc.).
À faire : Nettoyer les tables inutilisées quand V2 est stable en production.
Tables à évaluer : anon_rate_limits, contact_submissions, eo_t2_results, events, payment_transactions, sujets, waitlist
Condition de résolution : Après 30 jours de production stable de V2.
TD-13 — Webhook Stripe non idempotent
Priorité : 🔴 Critique
Statut : Résolu — Sprint 5a (2026-04-26)
Description : Stripe peut livrer un même event webhook deux fois (retries réseau, rejeu manuel depuis le dashboard). La route POST /stripe/webhook traite désormais chaque réception via une déduplication explicite : check stripe_webhook_events(id) avant traitement, INSERT après succès.
Résolution Sprint 5a :
- Migration
supabase/migrations/007_sprint_5a_stripe_webhook_events.sql— tablestripe_webhook_events(id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW())+ index surprocessed_at. - Helper
src/lib/stripeWebhookEvents.ts—isEventProcessed/markEventProcessed(insert idempotent, conflit unique avalé silencieusement). src/routes/stripe.ts— early return200 { received: true, replayed: true }si l'event est déjà journalisé ;markEventProcessed(event.id)après traitement réussi (pas si exception, pour permettre rejeu Stripe).- 8 tests unitaires + 2 tests d'intégration (
isEventProcessed/markEventProcessed+ comportement route).
TD-15 — Jobs asynchrones modèle/exercices : status peut rester "pending" indéfiniment
Priorité : 🟡 Important
Statut : Ouvert — introduit au Sprint 3.6a
Description : Le flux POST /corrections/ee lance deux jobs DeepSeek en fire-and-forget (runModeleJob, runExercicesJob dans correctionController.ts). Si le process Node redémarre (deploy Render, crash, OOM) pendant l'exécution d'un de ces jobs, la colonne exercices_status ou modele_status reste figée à 'pending' — l'utilisateur voit un loader infini côté frontend.
Impact actuel : faible en conditions normales (DeepSeek répond en ~5-15 s, Render redémarre rarement). Perceptible uniquement si un deploy a lieu pendant une correction active.
À faire :
- Option 1 (simple) : job de reprise au boot → scanner
productions WHERE (exercices_status='pending' OR modele_status='pending') AND created_at < NOW() - INTERVAL '2 minutes'→ relancer. - Option 2 (robuste) : file d'attente persistée (pg-boss, BullMQ) au lieu de fire-and-forget.
- Option 3 (minimal) : timeout côté frontend → si
pendingdepuis > 2 min, afficher "La génération a échoué, réessayer ?" + endpointPOST /simulations/:id/retry-jobs. Session concernée : à planifier après livraison Sprint 3.6a/3.6b en prod stable. Condition de résolution : après 7 jours d'observation en prod avec monitoring des colonnes*_status='pending'âgées.
TD-14 — Erreurs TypeScript TS2835 pré-existantes
Priorité : 🟡 Important
Statut : Résolu — session correction build TypeScript
Description : Erreurs TS2835 sur plusieurs fichiers de routes.
Non bloquant (tests verts) mais à corriger.
Gate de qualité actuel : npm run test.
À faire : Ajouter les extensions .js aux imports relatifs ou ajuster moduleResolution dans tsconfig.json pour permettre npm run build de passer.
3. Fonctionnalités reportées
TD-08 — Phonologie T2 EO à 0
Priorité : 🟡 Important
Statut : Partiellement résolu — Sprint 4.8
Description : L'évaluation de la phonologie est désormais opérationnelle pour EO T1 et T3 : POST /corrections/eo reçoit l'audio brut (Mode B), Gemini 2.5 Flash évalue la phonologie en parallèle de la transcription via evaluatePhonology (cf. src/lib/geminiPhonology.ts), et le score /4 est injecté comme 5e critère du rapport. Le format passe officiellement à 5 critères × /4 (total /20 inchangé).
Reste à faire : EO T2 Live (Sprint 6) continue de retourner phonologie 0/4 — pas d'audio brut côté backend dans le pipeline WebSocket actuel (t2live.ts proxifie l'audio entre client et Gemini Live sans le bufferiser pour évaluation différée). À résoudre lors du Sprint 6 en accumulant l'audio côté backend ou en demandant à Gemini Live de produire une note phonologique en fin de session.
Session concernée : T2 Live (WebSocket) — Sprint 6.
TD-09 — ScriptProcessorNode déprécié (T2 live)
Priorité : 🟢 Mineur
Statut : Reporté à après le lancement
Description : Le traitement audio côté client utilise ScriptProcessorNode qui est déprécié. Doit être remplacé par AudioWorklet.
Impact : Fonctionne mais génère des warnings dans la console. Peut poser problème sur certains navigateurs futurs.
À faire : Migrer vers AudioWorklet après le lancement MVP.
TD-10 — Analyse des patterns (Premium) non implémentée
Priorité : 🟡 Important
Statut : Résolu — Sprint 3.6c
Description : La feature d'analyse des patterns sur les 5 dernières productions (Premium) a été livrée Sprint 3.6c (table pattern_analyses, generatePatternExercices).
TD-11 — Indice de préparation non implémenté
Priorité : 🟢 Mineur
Statut : Résolu — Sprint 3.6c
Description : Le calcul de l'indice de préparation (0-100) a été livré Sprint 3.6c en même temps que l'analyse des patterns (colonne preparation_index + preparation_message).
4. Tests à automatiser
TD-12 — Tests manuels du Golden Dataset non automatisés
Priorité : 🟢 Mineur Statut : Accepté — par conception Description : Les 41 tests du Golden Dataset sont manuels. Certains pourraient être automatisés (tests d'intégration HTTP avec Supertest). À faire : Ajouter des tests d'intégration pour les routes critiques après le lancement MVP.
TD-16 — Bucket Supabase Storage audio-productions créé manuellement
Priorité : 🟡 Important Statut : Résolu — Sprint 4b Description : Décision Hermann (2026-04-25) : abandon du stockage audio backend. La transcription live passe par Deepgram en connexion directe navigateur ↔ Deepgram via token éphémère. L'audio brut est téléchargé en local par l'utilisateur. Plus aucun bucket Storage requis côté serveur.
TD-17 — Limite audioBase64 in-memory à 14 Mo (≈ 10 Mo binaire)
Priorité : 🟢 Mineur
Statut : Résolu — Sprint 4b
Description : Plus de payload audio reçu côté backend (POST /corrections/eo accepte uniquement transcript). La limite n'a plus lieu d'être.
TD-18 — RLS Storage audio-productions non testée en intégration
Priorité : 🟡 Important Statut : Résolu — Sprint 4b Description : Plus de bucket Storage backend à protéger. Les policies RLS de la migration 006 sont supprimées (DROP IF EXISTS) au profit d'un commentaire historique.
TD-19 — Token Deepgram non rotatif côté frontend
Priorité : 🟡 Important
Statut : Ouvert — introduit au Sprint 4b
Description : POST /transcriptions/token retourne un token Deepgram éphémère valide 600 s (10 min). Une session EO T1 (2 min) tient largement, mais une session T3 (4:30) ou un enchaînement de 2 tâches dépasse la fenêtre si l'utilisateur prend des pauses. Si le token expire en cours de session, la connexion Deepgram drop sans renégociation automatique.
À faire (côté frontend Sprint 4c) :
- Demander un nouveau token via
/transcriptions/tokenà T-60 s avant expiration. - Reconnecter Deepgram en réutilisant la même session WebSocket si supporté. Condition de résolution : stratégie de rotation de token implémentée et testée côté frontend.
TD-20 — transcribeAudio (Gemini) sans consommateur
Priorité : 🟢 Mineur
Statut : Ouvert — introduit au Sprint 4b
Description : La fonction transcribeAudio dans src/lib/gemini.ts n'est plus appelée par le flux EO (Deepgram a remplacé Gemini batch). Conservée volontairement comme point d'extension futur pour TD-08 (évaluation phonologique séparée) ou un fallback si Deepgram est indisponible.
À faire :
- Si TD-08 reste fermé 30 jours après la mise en prod du Sprint 4b sans plan d'usage, supprimer
transcribeAudioetgemini.tscomplet. Condition de résolution : décision sur TD-08 (résolution ou abandon).
TD-21 — Pas de rate limiting sur /transcriptions/token
Priorité : 🟢 Mineur Statut : Ouvert — introduit au Sprint 4b Description : Un utilisateur authentifié peut générer un nombre illimité de tokens Deepgram. Chaque token consomme un crédit côté Deepgram (selon usage de la connexion live qui suit). Un user malveillant pourrait scripter des appels en boucle pour épuiser le quota Deepgram. À faire :
- Ajouter un rate limit (par user, ex. 30 tokens/heure) via le middleware
rateLimit.tsexistant. Condition de résolution : middleware rate-limit branché sur la route et testé.
TD-22 — Comportement T2 Live garanti uniquement par prompt engineering
Priorité : 🟡 Important
Statut : Ouvert — introduit au Sprint 6d
Description : Le respect du rôle passif de l'examinateur T2 (réponses courtes, aucune question posée, aucune relance, silence après réponse) repose entièrement sur le durcissement du prompt système dans buildT2SystemPrompt (src/lib/geminiLive.ts, cf. docs/Prompt_t2live.md §3). Le modèle Gemini Flash Live n'offre aucune garantie déterministe : il peut malgré tout poser une question, ajouter une formule de politesse de fin ou relancer le candidat.
À faire : Revisiter si la relance persiste en tests manuels — pistes : reformulation/renforcement du prompt, post-filtrage des sorties, ou évaluation d'un modèle Live plus dirigeable.
⚠ Spécificité T2 : l'interdiction de poser des questions (et du point d'interrogation, règle 7) est propre à la Tâche 2. Ne jamais la propager au prompt T1 (Sprint 7), où l'examinateur doit relancer le candidat.
Session concernée : T2 Live — Sprint 6d.
TD-23 — Comportement Gemini Live T1 non déterministe + contrainte VAD manuel
Priorité : 🟡 Important
Statut : Ouvert — introduit au Sprint 7a
Description : Deux risques distincts sur le flux T1 Live (geminiLiveT1.ts) :
- Relance non garantie. Comme pour le T2 (TD-22), le modèle Gemini Flash
Live n'offre aucune garantie déterministe : sur le signal d'injection
(
clientContentde relance), il peut ignorer la consigne, formuler une relance hors-sujet, enchaîner plusieurs questions, ou commenter la langue du candidat malgré l'interdiction du prompt. - Découverte spike — flush VAD manuel. En VAD manuel
(
realtimeInputConfig.automaticActivityDetection.disabled = true), Gemini ne flusheinputTranscription(texte candidat) qu'à l'envoi d'unactivityEnd, pas en continu. Le backend doit donc envoyeractivityEndaux bornes de tour pour récupérer le transcript. Effet de bord :activityEnddéclenche AUSSI une réponse audio de l'examinateur (relance « terminale »), qu'il faut couper en fin de session (audio non forwardé, texte jeté — cf.Prompt_t1live.md §5).
À faire : Surveiller en tests manuels (Groupe D étendu) la pertinence des
relances et l'absence de relance terminale audible. Pistes si dérive :
renforcement du prompt, post-filtrage des sorties, ajustement du grace delay
(T1_TERMINAL_FLUSH_GRACE_MS).
Session concernée : T1 Live — Sprint 7a.
TD-24 — Dette de nommage : oral_t2_live gate aussi le T1 Live
Priorité : 🟢 Mineur
Statut : Ouvert — introduit au Sprint 7a
Description : La route WS /t1/live réutilise authenticate de
t2live.ts, qui gate sur la permission checkFeatureAccess(plan, 'oral_t2_live').
La feature oral_t2_live contrôle donc aussi l'accès au T1 Live, ce qui est
sémantiquement trompeur. Le couplage casserait si une différenciation d'accès
T1 vs T2 était souhaitée un jour (ex. T1 ouvert à Standard, T2 réservé Premium).
À faire : Renommer en oral_live (générique) ou introduire oral_t1_live
distinct. C'est une décision d'architecture + une migration lib/access.ts
(et potentiellement l'enum de features). Hors scope Sprint 7a.
Session concernée : T1 Live — Sprint 7a.
TD-25 — Dette DRY : runT1LiveCorrection ≈ runT2LiveCorrection
Priorité : 🟢 Mineur
Statut : Ouvert — report conscient (Sprint 7a)
Description : runT1LiveCorrection (t1live.ts) est à ~90 % identique à
runT2LiveCorrection (t2live.ts) : même pipeline (guard transcript vide →
insert productions → DeepSeek correctEO → phonologie stub → update → frame
report + close), mêmes codes d'erreur (EMPTY_TRANSCRIPT,
PERSISTENCE_FAILED, CORRECTION_FAILED) et de fermeture (1000 / 1011). Les
seules divergences : tache (EO_T1 vs EO_T2_LIVE), sujet_id (null vs
sujet.id), arguments DeepSeek ('EO_T1', 9, null vs 'EO_T2', 9, sujet.consigne), signature (présence ou non de sujet), préfixe de log.
À faire : Factoriser en un helper partagé
runEoLiveCorrection({ clientWs, profile, transcript, tache, sujetId, consigne, logTag }).
Report assumé : factoriser à 2 cas seulement risque l'abstraction prématurée
et touche t2live.ts (stable, déjà commité). À factoriser quand un 3e cas
live apparaîtra (ex. T3 Live).
Session concernée : T1 Live — Sprint 7a.
5. Historique des résolutions
| ID | Description | Résolu le | Comment |
|---|---|---|---|
| TD-02 | planController.ts complété | 2026-04-16 | Session Stripe |
| TD-03 | stripe.ts complété | 2026-04-16 | Session Stripe |
| TD-14 | Erreurs TS2835 + TS18046 + TS7053 corrigées | 2026-04-17 | Session build Render |
| TD-10 | Analyse des patterns (Premium) livrée | 2026-04-25 | Sprint 3.6c |
| TD-11 | Indice de préparation livré | 2026-04-25 | Sprint 3.6c |
| TD-16 | Bucket Storage abandonné | 2026-04-25 | Sprint 4b — Deepgram direct |
| TD-17 | Limite audio in-memory caduque | 2026-04-25 | Sprint 4b |
| TD-18 | RLS Storage caduque | 2026-04-25 | Sprint 4b |
| TD-13 | Webhook Stripe idempotent | 2026-04-26 | Sprint 5a |