Files
IdeA/.ideai/tickets/77/issue.md
Blomios 1be5f8cd18 chore(ideai): #77 en QA, ouverture de #78 et #79
Enregistre l'état du registre de tickets : #77 entre en QA, #78 ouvre la
dette UX de langue de l'écran Settings et #79 le test flaky relevé en
cours de route.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:27:06 +02:00

6.5 KiB

id, number, title, status, priority, sprint, links, agentRefs, createdBy, updatedBy, createdAt, updatedAt, version
id number title status priority sprint links agentRefs createdBy updatedBy createdAt updatedAt version
eecf77ee-dfcb-436c-aa5f-0c7033c7bdfa 77 Appairage : appareils enregistrés persistants, révocables, et code éphémère à usage unique qa high null
target kind
#76 relatesTo
target kind
#75 relatesTo
target kind
#68 relatesTo
kind agent_id
agent a6ced819-b893-4213-b003-9e9dc79b9641
kind agent_id
agent a6ced819-b893-4213-b003-9e9dc79b9641
1784279214815 1784287453378 4

Besoin utilisateur

Deux constats live (téléphone, instance exposée derrière reverse proxy) :

  1. Le code d'appairage est redemandé à chaque ouverture de la page. Impraticable.
  2. Le code étant affiché sur la machine, un appairage est impossible quand l'utilisateur n'est pas chez lui.

Cause racine du #1 — trois couches, confirmées

  • Le cookie de session n'a ni Max-Age ni Expires (crates/web-server/src/lib.rs:1629). C'est un cookie de session au sens navigateur : détruit à la fermeture. Sur mobile, purge agressive en arrière-plan → réappairage quasi systématique. C'est la cause directe.
  • Les sessions vivent en mémoire (sessions: Mutex<HashSet<String>>, ligne 422). Un redémarrage désappaire tous les appareils, même avec un cookie persistant.
  • Le code d'appairage est régénéré à chaque démarrage (new_pairing_code() appelé dans with_core, ligne 437). Le code noté hier ne vaut plus rien.

Design validé (discussion utilisateur ↔ Main, 2026-07-17)

Appareils

  • Un appareil appairé est persistant, nommé, listable, révocable. Session valide indéfiniment jusqu'à révocation.
  • Le serveur est la source de vérité de la durée de vie. Le cookie n'est qu'un porteur, renouvelé à chaque visite (le plafond navigateur réel est ~400 jours côté Chrome — « indéfini » se tient côté serveur, pas côté cookie).
  • Vocabulaire figé : « appareils », jamais « utilisateurs ». Ce design est mono-utilisateur assumé : tous les appareils ont les pleins pouvoirs, pas de comptes, pas de mots de passe, pas de permissions différenciées. Le jour où plusieurs personnes seront nécessaires, ce sera un autre chantier — le vocabulaire ne doit pas avoir menti entre-temps.

Code d'appairage

  • Éphémère, à usage unique, généré à la demande. TTL court (5-10 min à arbitrer). Générer un nouveau code invalide le précédent.
  • Suppression du code statique au démarrage, y compris l'eprintln!("IdeA pairing code: …") (ligne 619) : un secret permanent qui part dans stderr/journald/toute capture de sortie. Après ce lot, aucun secret au repos — un code n'existe que quand il est demandé.
  • Génération : depuis l'app desktop (serveur embarqué, accès direct à l'état) et depuis l'UI web déjà appairée.
  • Pas de commande CLI de génération, pas de socket de contrôle, pas de store partagé entre processus. Décision explicite : supprime l'IPC, la concurrence d'écriture CLI↔serveur et la classe de bugs associée.
  • Flag --new-code au lancement du serveur headless : affiche un code au démarrage. C'est le bootstrap du premier lancement et la trappe de secours quand plus aucune UI n'est accessible. Le flag ne doit rien rendre persistant (laissé par mégarde dans une unit systemd, il ne doit pas transformer chaque redémarrage en distribution de code).
  • Le code réapparaît dans stderr avec --new-code : risque résiduel accepté, car TTL court + usage unique rendent sans valeur un log qui fuite plus tard.

Condition de validité du design

L'écran de gestion des appareils doit vivre dans l'UI web, pas seulement dans l'app desktop. Sinon, sur une installation headless, générer un code impose un redémarrage — donc couper PTY, agents en cours et WebSockets — à chaque nouvel appareil. Avec l'écran dans l'UI web, la boucle se ferme : --new-code donne le premier appareil, tout le reste se gère depuis cet appareil, et --new-code redevient une trappe de secours.

Accès distant — trou assumé

Le design ne couvre pas l'appairage d'un appareil neuf à distance : générer un code suppose une UI appairée ou un accès à la machine. Le filet est SSH (se connecter, relancer avec --new-code). Assumé consciemment, acceptable tant que les appareils persistent réellement — le cas devient rare. Passkey/WebAuthn gardée en réserve, hors périmètre de ce lot.

Durcissements non négociables

  1. TTL sur le code — pas seulement l'usage unique. Un code généré et jamais consommé qui reste valide pour toujours recrée le problème d'aujourd'hui.
  2. Limite de tentatives sur POST /api/pair — 8 caractères hex = 4,3e9 combinaisons, brute-forçable en quelques semaines à débit soutenu contre un code permanent. TTL et rate-limit, pas l'un ou l'autre.
  3. Tokens de session hachés sur disque, jamais en clair. Le store .ideai/ part dans les backups et les synchros ; en clair il donne un accès shell. À traiter comme un fichier de mots de passe.
  4. La révocation doit couper les WebSockets déjà ouverts. Un WS établi ne revalide jamais le cookie : révoquer un appareil qui a un PTY ouvert le laisserait piloter la machine. Une révocation qui ne coupe pas la connexion vivante n'est pas une révocation.

Surface de gestion

Lister les appareils (nom, date d'appairage, dernière activité), les révoquer un par un, et révoquer tout — pour le jour où un téléphone est perdu et où lister avant d'agir n'est pas souhaitable.

Points d'entrée code

  • crates/web-server/src/lib.rs : ServerState (418-482), create_session/has_session/revoke_session (469-484), eprintln! du code (619), comparaison du code (1612), pose du cookie (1629-1640), logout_response (1643), new_pairing_code (2220), new_session_token (2230).
  • crates/app-tauri/src/embedded_server.rs : exposition pairing_code (97, 334).
  • frontend/src/features/web/PairingScreen.tsx, frontend/src/adapters/http/webSession.ts.

DoD

  • Un appareil appairé le reste après fermeture du navigateur et après redémarrage du serveur.
  • Aucun code d'appairage n'existe au repos ; un code demandé expire et ne sert qu'une fois.
  • Révocation effective immédiatement, WebSockets vivants inclus.
  • Écran de gestion accessible depuis l'UI web et l'app desktop.
  • Validation live : téléphone appairé une fois, toujours connecté le lendemain après redémarrage de l'AppImage.