# 🔍 AUDIT DE STABILITÉ — PROJET VEZA **Date** : 2025-01-27 **Objectif** : Identifier toutes les faiblesses potentielles dans la robustesse, cohĂ©rence, performances et rĂ©silience du systĂšme **Phase** : Zero-Bug / Launch-Ready --- ## 📋 TABLE DES MATIÈRES 1. [Backend Go](#1-backend-go) 2. [Chat Server (Rust)](#2-chat-server-rust) 3. [Stream Server (Rust)](#3-stream-server-rust) 4. [Global Project](#4-global-project) 5. [RĂ©sumĂ© des Risques](#5-rĂ©sumĂ©-des-risques) --- ## 1. BACKEND GO ### 1.1 Handlers HTTP #### ✅ **P0 - Erreurs JSON non traitĂ©es silencieusement** — **RÉSOLU** **Localisation** : `internal/handlers/common.go:280-287` **Status** : ✅ **RÉSOLU** — Phase 4 JSON Hardening complĂ©tĂ©e **Solution implĂ©mentĂ©e** : - CrĂ©ation de `BindAndValidateJSON` dans `CommonHandler` avec : - VĂ©rification de la taille du body (10MB max) - Gestion robuste des erreurs JSON (syntaxe, type, body vide, etc.) - Validation automatique avec le validator centralisĂ© - Retour d'`AppError` au lieu d'erreurs gĂ©nĂ©riques - Tous les handlers dans `internal/handlers/` refactorisĂ©s pour utiliser `BindAndValidateJSON` + `RespondWithAppError` - Handlers critiques refactorisĂ©s : auth, social, marketplace, playlists, profile, comment, role, analytics, bitrate, settings, room, webhook, config_reload, password_reset **Impact** : Plus aucune erreur JSON ne passe silencieusement. Toutes les erreurs de parsing/validation sont renvoyĂ©es avec un format unifiĂ© et des codes HTTP appropriĂ©s. **Note** : Il reste ~26 occurrences dans `internal/api/` (handlers dans des packages diffĂ©rents utilisant des patterns diffĂ©rents). À refactoriser dans une phase ultĂ©rieure si nĂ©cessaire. --- #### ⚠ **P1 - Erreurs silencieuses dans les handlers** **Localisation** : `internal/handlers/auth.go`, `internal/handlers/social.go` **ProblĂšme** : Certains handlers retournent des erreurs gĂ©nĂ©riques sans contexte suffisant. Exemple : ```go if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } ``` **Impact** : Difficile de diagnostiquer les problĂšmes en production. **Recommandation** : Utiliser systĂ©matiquement `RespondWithAppError` avec contexte enrichi. --- #### ⚠ **P1 - Validation d'input incomplĂšte** **Localisation** : Tous les handlers **ProblĂšme** : Certains handlers n'utilisent pas `ValidateRequest` avant de traiter les donnĂ©es. **Impact** : Risque d'injection SQL, XSS, ou corruption de donnĂ©es. **Recommandation** : Middleware de validation automatique pour toutes les routes POST/PUT. --- ### 1.2 Base de donnĂ©es #### ❌ **P0 - Absence de transactions dans certaines opĂ©rations critiques** **Localisation** : `internal/core/marketplace/service.go:134-136` **ProblĂšme** : `CreateOrder` utilise une transaction, mais d'autres opĂ©rations multi-Ă©tapes non : ```go // Exemple problĂ©matique (si non transactionnel) func (s *Service) UpdateUserProfile(ctx context.Context, userID uuid.UUID, profile *Profile) error { // Étape 1: Mise Ă  jour user s.db.Update(&user) // Étape 2: Mise Ă  jour profile s.db.Update(&profile) // Si Ă©tape 2 Ă©choue, Ă©tape 1 reste appliquĂ©e → INCOHÉRENCE } ``` **Impact** : IncohĂ©rence DB en cas d'erreur partielle. **Recommandation** : Audit complet des opĂ©rations multi-Ă©tapes, wrapper dans transactions. --- #### ⚠ **P1 - Erreurs DB non wrap** **Localisation** : Plusieurs services **ProblĂšme** : Certaines erreurs DB sont retournĂ©es directement sans contexte : ```go if err := s.db.First(&user, "id = ?", id).Error; err != nil { return nil, err // Pas de contexte } ``` **Impact** : Debugging difficile, pas de traçabilitĂ©. **Recommandation** : Toujours wrapper avec `fmt.Errorf("failed to find user %s: %w", id, err)`. --- #### ⚠ **P1 - Pas de retry automatique pour les erreurs transitoires** **Localisation** : Tous les appels DB **ProblĂšme** : Pas de retry automatique pour `database/sql` errors (timeouts, connection pool exhausted). **Impact** : Échecs temporaires non rĂ©cupĂ©rĂ©s automatiquement. **Recommandation** : Wrapper DB avec retry logic (exponential backoff) pour erreurs transitoires. --- ### 1.3 Workers #### ⚠ **P1 - Race condition potentielle lors des retries** **Localisation** : `internal/workers/job_worker.go:127-135` ```go if job.Retries < w.maxRetries { job.Retries++ delay := time.Duration(job.Retries) * 5 * time.Second time.Sleep(delay) // ⚠ Bloque le worker w.Enqueue(job) // ⚠ Pas de lock sur job } ``` **ProblĂšme** : Si plusieurs workers tentent de retry le mĂȘme job simultanĂ©ment, `Retries` peut ĂȘtre incrĂ©mentĂ© plusieurs fois. **Impact** : Jobs retry plus que `maxRetries`, ou jobs dupliquĂ©s dans la queue. **Recommandation** : Utiliser un mutex ou atomic operations pour `job.Retries`, ou marquer le job comme "retrying" en DB avant rĂ©-enqueue. --- #### ⚠ **P1 - Pas de timeout explicite pour les jobs** **Localisation** : `internal/workers/job_worker.go:116` ```go jobCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() ``` **ProblĂšme** : Timeout hardcodĂ©, pas configurable. Si un job prend plus de 5 minutes, il est annulĂ© brutalement. **Impact** : Jobs longs (ex: transcodage) peuvent ĂȘtre interrompus. **Recommandation** : Timeout configurable par type de job. --- #### ⚠ **P2 - Queue in-memory sans persistance** **Localisation** : `internal/workers/job_worker.go` **ProblĂšme** : La queue est en mĂ©moire (`chan Job`). Si le serveur crash, les jobs en attente sont perdus. **Impact** : Perte de jobs non traitĂ©s lors d'un crash. **Recommandation** : Utiliser une queue persistante (Redis, RabbitMQ) pour les jobs critiques. --- ### 1.4 Password Reset #### ✅ **Bien protĂ©gĂ© contre l'Ă©numĂ©ration** **Localisation** : `internal/core/auth/service.go:372-379` ```go if err == gorm.ErrRecordNotFound { return nil // Toujours retourner succĂšs } ``` **Status** : ✅ ImplĂ©mentation correcte — toujours retourner succĂšs mĂȘme si email n'existe pas. --- #### ⚠ **P1 - Timing attack potentiel** **Localisation** : `internal/services/password_reset_service.go:70-125` **ProblĂšme** : Le temps de traitement peut diffĂ©rer entre : - Email existe → GĂ©nĂ©ration token + Hash + DB write - Email n'existe pas → Simple DB query **Impact** : Attaquant peut dĂ©tecter si un email existe via timing. **Recommandation** : Ajouter un dĂ©lai artificiel pour Ă©galiser les temps de rĂ©ponse. --- ### 1.5 Health Check #### ✅ **Robuste si DB en panne** **Localisation** : `internal/handlers/health.go:70-77`, `internal/handlers/status_handler.go` **Status** : ✅ `/health` est stateless (toujours OK). `/status` gĂšre correctement les erreurs DB et retourne `degraded`. --- #### ⚠ **P2 - Pas de circuit breaker** **Localisation** : Health checks **ProblĂšme** : Si DB est down, chaque health check tente une connexion (timeout 5s). Pas de circuit breaker pour Ă©viter de surcharger DB. **Impact** : Si DB est down, health checks continuent Ă  tenter des connexions. **Recommandation** : ImplĂ©menter un circuit breaker pour les dĂ©pendances externes. --- ## 2. CHAT SERVER (RUST) ### 2.1 Race Conditions #### ❌ **P0 - Race condition dans TypingIndicatorManager** **Localisation** : `src/typing_indicator.rs:34-48` ```rust pub async fn user_started_typing(&self, user_id: Uuid, conversation_id: Uuid) { let mut typing = self.typing_users.write().await; let conversation_typing = typing .entry(conversation_id) .or_insert_with(HashMap::new); conversation_typing.insert(user_id, Utc::now()); } ``` **ProblĂšme** : Le `RwLock` protĂšge la HashMap, mais si deux utilisateurs tapent simultanĂ©ment dans la mĂȘme conversation, l'ordre d'insertion peut varier. **Impact** : Timestamps peuvent ĂȘtre inversĂ©s, causant des broadcasts dans le mauvais ordre. **Recommandation** : Utiliser un `Mutex` au lieu de `RwLock` pour garantir l'ordre, ou utiliser un canal sĂ©rialisĂ©. --- #### ⚠ **P1 - Race condition dans DeliveredStatusManager** **Localisation** : `src/delivered_status.rs` **ProblĂšme** : Si plusieurs messages sont marquĂ©s comme "delivered" simultanĂ©ment, les updates DB peuvent se chevaucher. **Impact** : Statuts de livraison incohĂ©rents. **Recommandation** : Utiliser une queue sĂ©rialisĂ©e pour les updates de statut. --- #### ⚠ **P1 - Race condition dans ReadReceiptManager** **Localisation** : `src/read_receipts.rs` **ProblĂšme** : MĂȘme problĂšme que DeliveredStatusManager. **Recommandation** : Queue sĂ©rialisĂ©e ou transaction DB. --- ### 2.2 Panics Potentiels #### ❌ **P0 - Panics dans WebSocket handler** **Localisation** : `src/websocket/handler.rs:175-176` ```rust let incoming: IncomingMessage = serde_json::from_str(text) .map_err(|e| ChatError::serialization_error("IncomingMessage", text, e))?; ``` **Status** : ✅ Bien gĂ©rĂ© — erreur retournĂ©e, pas de panic. --- #### ⚠ **P1 - `.unwrap()` dans plusieurs fichiers** **Localisation** : 31 fichiers identifiĂ©s avec `unwrap()` ou `expect()` **Exemples** : - `src/config.rs` : `unwrap()` sur variables d'environnement - `src/database/pool.rs` : `unwrap()` sur connexions DB - `src/jwt_manager.rs` : `expect()` sur parsing JWT **Impact** : Panics possibles si donnĂ©es inattendues. **Recommandation** : Remplacer tous les `unwrap()` par `?` ou gestion d'erreur explicite. --- #### ⚠ **P1 - Pas de panic boundary dans handle_socket** **Localisation** : `src/websocket/handler.rs:77-163` **ProblĂšme** : Si une panic survient dans `handle_incoming_message`, elle peut faire crasher toute la task Tokio. **Impact** : Un client malveillant peut faire crasher le serveur. **Recommandation** : Wrapper `handle_incoming_message` dans `std::panic::catch_unwind` ou utiliser `tokio::spawn` avec supervision. --- ### 2.3 Gestion des Tasks #### ⚠ **P1 - Tasks orphelins possibles** **Localisation** : `src/typing_indicator.rs` (task de monitoring) **ProblĂšme** : La task de monitoring des timeouts est spawnĂ©e au dĂ©marrage mais n'a pas de mĂ©canisme de shutdown propre. **Impact** : Task continue Ă  tourner mĂȘme aprĂšs arrĂȘt du serveur. **Recommandation** : Utiliser un `CancellationToken` pour arrĂȘter proprement les tasks. --- #### ⚠ **P1 - Pas de timeout explicite pour les opĂ©rations DB** **Localisation** : Tous les appels DB **ProblĂšme** : Pas de timeout sur les queries SQLx. Si DB est lente, les requĂȘtes peuvent bloquer indĂ©finiment. **Impact** : Deadlock ou timeout trĂšs long. **Recommandation** : Ajouter des timeouts sur tous les appels DB (via `sqlx::query().fetch_timeout()`). --- ### 2.4 Robustesse WebSocket #### ✅ **Bien gĂ©rĂ© — dĂ©connexions propres** **Localisation** : `src/websocket/handler.rs:134-137` ```rust Ok(Message::Close(_)) => { info!("👋 Connexion WebSocket fermĂ©e par le client"); break; } ``` **Status** : ✅ DĂ©connexions gĂ©rĂ©es proprement. --- #### ⚠ **P1 - Pas de heartbeat timeout** **Localisation** : `src/websocket/handler.rs` **ProblĂšme** : Pas de mĂ©canisme pour dĂ©tecter les connexions "zombies" (client dĂ©connectĂ© mais serveur ne le sait pas). **Impact** : Connexions mortes occupent des ressources. **Recommandation** : ImplĂ©menter un heartbeat (ping/pong) avec timeout. --- ### 2.5 Permissions #### ✅ **Bien implĂ©mentĂ© — PermissionService** **Localisation** : `src/security/permission.rs` **Status** : ✅ VĂ©rifications de permissions prĂ©sentes avant chaque action. --- #### ⚠ **P1 - Risque de bypass si PermissionService Ă©choue** **Localisation** : `src/websocket/handler.rs:194-200` ```rust state .permission_service .can_send_message(sender_uuid, conversation_id) .await .map_err(|e| { warn!(...); // ⚠ Que se passe-t-il si l'erreur est ignorĂ©e ? })?; ``` **ProblĂšme** : Si `can_send_message` retourne une erreur, elle est loggĂ©e mais le handler peut continuer selon l'implĂ©mentation. **Impact** : Bypass de permissions si erreur DB. **Recommandation** : Toujours refuser l'action si permission check Ă©choue (fail-secure). --- ## 3. STREAM SERVER (RUST) ### 3.1 StreamProcessor #### ❌ **P0 - Tasks non cancellĂ©es proprement en cas d'erreur** **Localisation** : `src/core/processing/processor.rs:168-169` ```rust monitor_handle.abort(); event_handle.abort(); ``` **ProblĂšme** : `abort()` tue brutalement les tasks. Si elles Ă©taient en train d'Ă©crire en DB, la transaction peut rester ouverte. **Impact** : Handles orphelins, transactions DB non commitĂ©es. **Recommandation** : Utiliser `CancellationToken` pour arrĂȘter proprement, attendre la fin des tasks avant `abort()`. --- #### ⚠ **P1 - Erreurs FFmpeg non propagĂ©es correctement** **Localisation** : `src/core/processing/processor.rs:154-156` ```rust FFmpegEvent::Error(msg) => { tracing::warn!("⚠ Erreur FFmpeg dĂ©tectĂ©e: {}", msg); } ``` **ProblĂšme** : Les erreurs FFmpeg sont loggĂ©es mais ne causent pas l'arrĂȘt du traitement. Le job continue mĂȘme si FFmpeg a une erreur fatale. **Impact** : Jobs peuvent se terminer en "succĂšs" alors que FFmpeg a Ă©chouĂ©. **Recommandation** : DĂ©tecter les erreurs fatales FFmpeg et arrĂȘter le traitement immĂ©diatement. --- #### ⚠ **P1 - DB pas toujours sync en cas de crash** **Localisation** : `src/core/processing/processor.rs:238-243` ```rust async fn finalize(&self, tracker: Arc) -> Result<(), AppError> { tracker.persist_all().await?; // ... } ``` **ProblĂšme** : Si le serveur crash avant `finalize()`, les segments dĂ©tectĂ©s mais non persistĂ©s sont perdus. **Impact** : IncohĂ©rence entre fichiers segments et DB. **Recommandation** : Persister immĂ©diatement chaque segment (dĂ©jĂ  fait dans `SegmentTracker::register`), mais vĂ©rifier que c'est bien transactionnel. --- ### 3.2 SegmentTracker #### ⚠ **P1 - Corruption d'Ă©tat concurrent possible** **Localisation** : `src/core/processing/segment_tracker.rs:59-78` ```rust pub async fn register(&self, segment: SegmentInfo) -> Result<(), AppError> { { let mut segments = self.segments.write().await; segments.push(segment.clone()); } self.persist_segment(&segment).await?; } ``` **ProblĂšme** : Si deux segments sont enregistrĂ©s simultanĂ©ment, l'ordre d'insertion dans le vecteur peut varier, mais la persistance DB se fait sĂ©quentiellement. **Impact** : Segments peuvent ĂȘtre persistĂ©s dans le mauvais ordre. **Recommandation** : Utiliser un canal sĂ©rialisĂ© pour les registrations, ou un mutex global. --- ### 3.3 FFmpegMonitor #### ⚠ **P1 - Regex non robustes** **Localisation** : `src/core/processing/ffmpeg_monitor.rs:22-24` ```rust static ref OPENING_SEGMENT_REGEX: Regex = Regex::new( r"Opening '([^']+)' for writing" ).unwrap(); ``` **ProblĂšme** : Si FFmpeg change son format de log, la regex ne matchera plus. Pas de fallback. **Impact** : Segments non dĂ©tectĂ©s, job Ă©choue silencieusement. **Recommandation** : Ajouter un fallback : dĂ©tecter les segments depuis le rĂ©pertoire de sortie si regex Ă©choue. --- #### ⚠ **P1 - Gestion des IO errors incomplĂšte** **Localisation** : `src/core/processing/ffmpeg_monitor.rs:90-94` ```rust while let Ok(Some(line)) = lines.next_line().await { self.process_line(&line).await?; } ``` **ProblĂšme** : Si `next_line()` retourne une erreur (ex: stderr fermĂ©), la boucle s'arrĂȘte silencieusement. **Impact** : Monitoring s'arrĂȘte sans notification, job continue mais plus de tracking. **Recommandation** : Logger l'erreur et propager pour arrĂȘter le job. --- ### 3.4 API HLS #### ✅ **Path traversal protĂ©gĂ©** **Localisation** : `src/routes/encoding.rs:128-133`, `internal/services/hls_service.go:137-151` **Status** : ✅ VĂ©rification du chemin absolu avec `HasPrefix` pour Ă©viter path traversal. --- #### ⚠ **P1 - Erreurs HTTP silencieuses** **Localisation** : `src/routes/encoding.rs:144-148` ```rust if !segment_path.exists() { return Err(AppError::NotFound { ... }); } ``` **ProblĂšme** : Si le fichier existe mais n'est pas lisible (permissions), l'erreur sera gĂ©nĂ©rique. **Impact** : Debugging difficile. **Recommandation** : DiffĂ©rencier "not found" vs "permission denied" vs "IO error". --- ## 4. GLOBAL PROJECT ### 4.1 CohĂ©rence Inter-Services #### ❌ **P0 - Pas de transaction distribuĂ©e** **Localisation** : Tous les services **ProblĂšme** : Si un message est créé dans le chat server mais que le backend Go Ă©choue Ă  crĂ©er une notification, les deux DB sont incohĂ©rentes. **Impact** : DonnĂ©es incohĂ©rentes entre services. **Recommandation** : ImplĂ©menter un pattern Saga ou Event Sourcing pour garantir la cohĂ©rence. --- #### ⚠ **P1 - Pas de validation croisĂ©e des IDs** **Localisation** : Communication inter-services **ProblĂšme** : Le chat server accepte des `conversation_id` sans vĂ©rifier qu'ils existent dans le backend Go. **Impact** : Messages peuvent ĂȘtre créés pour des conversations inexistantes. **Recommandation** : Validation croisĂ©e via API ou cache partagĂ©. --- ### 4.2 Tests #### ❌ **P0 - Manque de tests unitaires critiques** **Localisation** : Tous les services **ProblĂšme** : Beaucoup de tests sont `#[ignore]` car nĂ©cessitent une DB de test. **Impact** : Pas de validation automatique des corrections. **Recommandation** : Utiliser des mocks (ex: `sqlx::test`) ou des containers Docker pour les tests. --- #### ⚠ **P1 - Pas de tests de charge** **Localisation** : Aucun **ProblĂšme** : Pas de validation que le systĂšme supporte 100+ clients simultanĂ©s. **Impact** : ProblĂšmes de performance non dĂ©tectĂ©s. **Recommandation** : Tests de charge avec k6 ou locust. --- ### 4.3 Fuites Goroutine / Tokio Task #### ⚠ **P1 - Goroutines sans mĂ©canisme de shutdown** **Localisation** : `internal/jobs/cleanup_sessions.go:33-45` ```go go func() { for range ticker.C { // ... } }() ``` **ProblĂšme** : Pas de moyen d'arrĂȘter cette goroutine proprement. **Impact** : Goroutine continue aprĂšs arrĂȘt du serveur. **Recommandation** : Utiliser `context.Context` avec cancellation. --- #### ⚠ **P1 - Tokio tasks spawnĂ©es sans supervision** **Localisation** : `veza-chat-server/src/optimized_persistence.rs:264-285` ```rust tokio::spawn(async move { engine_clone.batch_processing_loop().await; }); ``` **ProblĂšme** : Si la task panic, elle n'est pas relancĂ©e. **Impact** : Service peut s'arrĂȘter silencieusement. **Recommandation** : Utiliser un supervisor task qui relance les tasks en cas de panic. --- ### 4.4 Logging Contextuel #### ⚠ **P1 - Pas de correlation-id systĂ©matique** **Localisation** : Tous les services **ProblĂšme** : Pas de `correlation-id` ou `trace-id` pour suivre une requĂȘte Ă  travers les services. **Impact** : Debugging difficile en production. **Recommandation** : ImplĂ©menter OpenTelemetry ou un systĂšme de tracing distribuĂ©. --- #### ⚠ **P2 - Logs non structurĂ©s dans certains endroits** **Localisation** : Quelques handlers **ProblĂšme** : Certains logs utilisent `fmt.Printf` au lieu de `tracing` ou `zap`. **Impact** : Logs non queryables. **Recommandation** : Standardiser sur `tracing` (Rust) et `zap` (Go). --- ### 4.5 Risques d'IncohĂ©rence DB #### ❌ **P0 - Jobs, messages, segments peuvent ĂȘtre incohĂ©rents** **Localisation** : Tous les services **ProblĂšme** : Si un job de transcodage Ă©choue aprĂšs avoir créé des segments en DB, les segments restent orphelins. **Impact** : DB contient des donnĂ©es incohĂ©rentes. **Recommandation** : Jobs de cleanup pĂ©riodiques pour supprimer les donnĂ©es orphelines. --- #### ⚠ **P1 - Pas de vĂ©rification d'intĂ©gritĂ©** **Localisation** : Aucun **ProblĂšme** : Pas de job qui vĂ©rifie que les fichiers segments correspondent aux enregistrements DB. **Impact** : IncohĂ©rences non dĂ©tectĂ©es. **Recommandation** : Job de vĂ©rification d'intĂ©gritĂ© quotidien. --- ## 5. RÉSUMÉ DES RISQUES ### 🔮 P0 — Must-Fix avant dĂ©ploiement 1. **Backend Go** : Erreurs JSON non traitĂ©es silencieusement 2. **Backend Go** : Absence de transactions dans opĂ©rations critiques 3. **Chat Server** : Race condition dans TypingIndicatorManager 4. **Chat Server** : Panics possibles (31 fichiers avec `unwrap()`) 5. **Stream Server** : Tasks non cancellĂ©es proprement 6. **Global** : Pas de transaction distribuĂ©e 7. **Global** : Manque de tests unitaires critiques 8. **Global** : Jobs/messages/segments peuvent ĂȘtre incohĂ©rents ### 🟠 P1 — Production-grade minimal 1. **Backend Go** : Erreurs silencieuses, validation input incomplĂšte 2. **Backend Go** : Race condition dans workers retries 3. **Backend Go** : Timing attack password reset 4. **Chat Server** : Race conditions dans DeliveredStatusManager/ReadReceiptManager 5. **Chat Server** : Pas de panic boundary dans WebSocket handler 6. **Chat Server** : Tasks orphelins, pas de heartbeat timeout 7. **Stream Server** : Erreurs FFmpeg non propagĂ©es, DB pas toujours sync 8. **Stream Server** : Corruption d'Ă©tat concurrent dans SegmentTracker 9. **Stream Server** : Regex non robustes, IO errors incomplĂštes 10. **Global** : Pas de validation croisĂ©e IDs, pas de tests de charge 11. **Global** : Fuites goroutine/task, pas de correlation-id ### 🟡 P2 — QualitĂ© continue 1. **Backend Go** : Pas de circuit breaker health check 2. **Backend Go** : Queue in-memory sans persistance 3. **Global** : Logs non structurĂ©s, pas de vĂ©rification d'intĂ©gritĂ© --- ## 📊 STATISTIQUES - **P0 (Critique)** : 8 problĂšmes - **P1 (Important)** : 11 problĂšmes - **P2 (AmĂ©lioration)** : 3 problĂšmes - **Total** : 22 problĂšmes identifiĂ©s --- ## 🔗 LIENS AVEC TRIAGE ACTUEL Voir `TRIAGE.md` pour l'Ă©tat fonctionnel des features. Cet audit se concentre sur la **robustesse** et la **stabilitĂ©**, pas sur les features manquantes. --- **Prochaines Ă©tapes** : GĂ©nĂ©rer `HARDENING_PLAN.md` avec plan de correction priorisĂ©.