--- id: "4d78a0e9-b54c-4db6-92e5-1461c17d7c84" number: 71 title: "Diagnostic d'accessibilité du serveur : dire pourquoi ça ne marche pas, pas seulement que ça tourne" status: "open" priority: "medium" sprint: null links: [{"target":"#68","kind":"relatesTo"},{"target":"#65","kind":"relatesTo"}] agentRefs: [] createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"} updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"} createdAt: 1784210443885 updatedAt: 1784210443885 version: 1 --- Issu d'une session de debug live réelle (2026-07-16) où un serveur `idea-serve` CORRECTEMENT configuré est resté injoignable, sans que rien dans le produit n'indique pourquoi. Le serveur tournait, se croyait bon, affichait « listening on … ». Deux boucles de debug successives ont été nécessaires, dont aucune n'était diagnosticable depuis le produit. **Problème de fond : l'échec est invisible.** Un serveur peut être parfaitement configuré et totalement inatteignable ; aujourd'hui la seule façon de le savoir est de sortir du produit (curl, ss, ufw, dig). Les trois échecs réels rencontrés, tous silencieux : 1. **Bind loopback + reverse proxy sur une autre machine** → le proxy ouvre une connexion vers *sa propre* loopback, la requête n'arrive jamais. Aucun message, juste un timeout côté navigateur. Aggravé par `docs/server-client-mode-remote.md`, dont l'exemple du cas distant utilise `--listen 127.0.0.1:17373` en supposant sans le dire que le proxy est co-localisé (correction doc traitée à part). 2. **Pare-feu hôte (ufw actif, port non ouvert)** → dissymétrie caractéristique : joignable depuis la machine locale, timeout depuis toute autre machine. Rien ne le signale. 3. **Origine non conforme → 403 muet** → `--public-origin` compare en ÉGALITÉ STRICTE. Ouvrir l'UI par l'IP (`http://192.168.1.75:17373`) au lieu du nom de domaine donne un 403 sur toute l'API, sans explication dans l'UI. C'est le piège n°1 pour un nouvel utilisateur. **Périmètre proposé (à cadrer par Architect).** **Lot 1 — Surfacer les origines rejetées. Meilleur rapport valeur/coût : l'événement EXISTE déjà.** `SecurityLogEvent::OriginRejected { origin, route }` dans `crates/web-server/src/lib.rs` est aujourd'hui simplement `eprintln!` sur stderr — donc invisible en desktop, et invisible en Docker sans aller lire les logs. L'exposer (DTO + surface UI) transforme le mystère en diagnostic d'une ligne : « un client s'est connecté depuis `http://192.168.1.75:17373`, or l'origine autorisée est `https://idea.anthonybouteiller.ovh` ». Attention : c'est un log de sécurité, borner la rétention et ne pas en faire un canal de fuite d'information. **Lot 2 — Dire quand le serveur est JOIGNABLE, pas seulement qu'il tourne.** Le serveur ne peut pas se tester depuis l'extérieur, mais il peut détecter ce qui rend l'échec certain : - pare-feu hôte actif dont le port d'écoute n'est pas autorisé (ufw/firewalld) ; - `public_origin` dont le DNS ne résout vers aucune interface locale ⇒ indice fort que le proxy est ailleurs, donc qu'un bind loopback ne peut pas marcher ; - afficher l'upstream exact à coller dans la config du proxy (`http://192.168.1.75:17373`) plutôt que de le laisser deviner. Ces détections sont des heuristiques : elles doivent INFORMER, jamais bloquer le démarrage. **Hors périmètre :** le choix du mode par intention (« proxy sur cette machine » vs « sur une autre machine ») appartient à F2 de #68 — c'est le panneau desktop, ça ne change aucun contrat backend. **Attention frontière :** les lots ci-dessus doivent servir AUSSI le mode headless/Docker (`idea-serve` sans desktop), pas seulement le panneau desktop. Le diagnostic ne doit pas naître prisonnier de l'UI Tauri.