Files
IdeA/.ideai/tickets/68/carnet.md
Blomios 481a7e2498 chore(ideai): clôture de #74 et #68, ouverture de #75 et #76
Enregistre l'état du registre de tickets : #74 et #68 passent en closed
après la validation live de l'intégration web, #75 (ce fix) entre en QA
et #76 ouvre la dette de casse du code d'appairage côté serveur.

État runtime uniquement : aucun code de feature n'est touché.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:40:26 +02:00

6.0 KiB

issueRef, version, updatedBy, updatedAt
issueRef version updatedBy updatedAt
#68 8
kind agent_id
agent a6ced819-b893-4213-b003-9e9dc79b9641
1784277313706

Ticket #68 — Serveur embarqué activable depuis le desktop : carnet de chantier

Statut : livré et mergé dans develop (f965f64) le 2026-07-16, en exécution autonome de nuit. Reste à faire : la validation live utilisateur. Rien n'a été cliqué dans l'app réelle — voir la réserve plus bas.

Livraison en deux temps

B1 — seam shared-core (mergé plus tôt, b7d38f1 + 328c941). web_server::run_embedded_with_core(config, Arc<BackendCore>). Sans lui, le desktop aurait construit une seconde composition root dans le même processus — deux event bus, deux registres live, deux jeux de stores — et le conflit d'écriture app-data-dir serait réapparu à l'intérieur d'un seul process.

B2/F1/F2 — le reste (49e1d5a backend, 7efa634 frontend, merge b30c9c7).

  • crates/app-tauri/src/embedded_server.rs : controller de cycle de vie sur AppState, commandes embedded_server_start/stop/status et get/save/preview_server_exposure_settings, FsServerExposureSettingsStore.
  • frontend/src/features/settings/ : SettingsView, DeploymentSettings, useDeployment ; adapters/desktopServer.ts ; port DesktopServerGateway.
  • ProjectsView : showSettings: boolean → navigation interne AI Profiles / Deployment. Le libellé alternant « Close AI Profiles » a disparu.

Décisions structurantes appliquées

  • Pas de PanelId: "settings". Architect s'est corrigé en cours de route : Settings existe déjà dans ProjectsView comme surface principale, hors modèle viewPlacement. C'est de la configuration globale persistée, pas une vue projet dockable/détachable.
  • Persistance dans <app-data-dir>/deployment/server-exposure.json, pas localStorage/uiPreferences : falsifier cette config peut exposer le serveur, c'est de la config de sécurité. Écriture atomique, 0600 posé avant le rename.
  • Le pairing code n'est jamais persisté — secret runtime, affiché seulement si running.
  • Le frontend n'invente jamais une IP : candidateLanAddresses et upstreamUrl viennent du backend, d'où l'existence de preview.
  • EmbedderSettings / ModelServersPanel non rapatriés : refonte latérale hors sprint. La liste de sections est le seam où ils s'inséreront.

⚠️ Le brief de Main était FAUX — intercepté par DevFrontend

Main décrivait le mode remoteProxyOtherMachine avec deux champs (origine publique + IP du proxy). validate_settings en exige un troisième : lanBindAddress, et rejette loopback/unspecified. Construit selon le brief, le lot aurait été vert et cassé : chaque save et chaque start en mode 3 auraient échoué, sans qu'aucun test ne le voie.

Corrigé par un select alimenté par candidateLanAddresses — la règle « le frontend n'invente jamais une IP » tient. Leçon consignée dans 7efa634.

Vérification réelle (Main puis Git, tous deux hors sandbox)

npm run typecheck        → propre
npx vitest run           → 87 files / 789 passed
cargo test -p app-tauri  → 43 passed, 1 ignored
cargo test -p web-server → 66 passed

Re-vérifié sur develop après merge : 24 suites Rust, 0 échec.

Invariants vérifiés par exécution, pas par lecture de rapport : run_embedded_with_core est le seul appelant (aucun run_embedded nu dans app-tauri) ; aucune fuite d'EmbeddedServer*/ServerExposure* dans domain ni application ; ordre du store write(tmp)chmod 0600rename ; stop() appelé à la sortie d'app (lib.rs:153) ; features/web n'importe pas la surface Settings (épinglé par test) et le transport web rend UNSUPPORTED_ON_WEB.

Piège d'environnement : app-tauri et web-server ont un sandbox qui bloque TcpListener::bind (EPERM). Le test end_to_end_over_real_loopback est #[ignore] préexistant — lancé hors sandbox avec --ignored, il passe. Garde d'environnement confirmée par exécution, pas faux vert.

RÉSERVE — aucune validation live

Tout est vert, rien n'a été cliqué. Le panneau n'a jamais été ouvert dans l'app réelle : ni le démarrage/arrêt, ni l'affichage du pairing code, ni les trois modes, ni la persistance entre deux lancements. jsdom ne prouve pas le rendu. Une AppImage a été construite pour que l'utilisateur teste — c'est le point de vérité qui manque.

Dette identifiée, non traitée (ne bloque rien)

  1. preview valide avant de prévisualiser (embedded_server.rs:211) : un brouillon mode 3 ne peut pas être prévisualisé tant qu'il n'a pas le lanBindAddress que la liste des candidats doit justement fournir. Contourné côté UI par une sonde localOnly toujours valide. Défaut d'ordonnancement backend.
  2. Code mort : le warning missingTrustedProxy (embedded_server.rs:452) est inatteignable — validate_settings rejette les trustedProxies vides en mode 3 avant que preview_settings ne puisse le produire.
  3. Aucun événement de statut : le backend n'émet rien, onStatusChanged est du polling dans l'adapter Tauri. La forme du port est prête pour un vrai événement, sans toucher l'UI.
  4. Fenêtre umask sur le temporaire du store : le 0600 arrive après la création. Aucun secret dans ce fichier (modes, ports, IP), risque faible. Un OpenOptions::mode(0o600) à la création fermerait le sujet.
  5. Piège latent hors #68 : les stubs unsupported.ts préexistants (WebRemoteGateway, WebWindowGateway) throwent de façon synchrone depuis des méthodes qui rendent des Promise — .then(ok, err) ne les attraperait pas. Sans effet aujourd'hui (les appelants await), mais mérite un nettoyage.

Contexte : pourquoi #68 est passé avant #71 et #73

Arbitrage utilisateur, contre la recommandation d'Architect. Ce dernier proposait #72 → #73 → #71 → #68, ce qui reléguait en dernier la fonctionnalité demandée le matin même. L'utilisateur a tranché : #72 ajusté, puis #68. #71 (diagnostics) et #73 (TLS intégré) suivent.