Aller au contenu

Gestion de projets avec un assistant IA qui respecte les droits

TaskTracker

Stage chez ABA Technology (Nextronic) · juil.–août 2026 · réalisé seul, neuf sprints

En bref

TaskTracker est la plateforme de gestion de projets et de tâches que j'ai construite pour ABA Technology pendant mon stage — besoins, conception, code et déploiement, seul, sur neuf sprints agiles.

Elle couvre le quotidien d'une équipe de réalisation : projets et clients, tâches sur un tableau Kanban, feuilles de temps, messagerie en temps réel, statistiques. Et un assistant qui répond à des questions comme « quelles sont mes tâches en retard ? » en langage naturel, à partir des données de l'organisation. La partie qui mérite d'être lue, c'est comment on empêche cet assistant de répondre avec des données que la personne qui demande ne devrait pas voir.

Le problème

Le sujet était un outil de gestion ; les contraintes sont venues des parties prenantes. 19 exigences fonctionnelles, recueillies et formalisées avec elles avant la moindre ligne de code, et un modèle de droits à cinq rôles — administrateur, chef de projet, chef d'équipe, collaborateur et observateur en lecture seule — chacun voyant une part différente de la même organisation.

C'est ce second point qui rendait la partie IA difficile. Un assistant à génération augmentée par la recherche fouille tout ce qu'il a indexé. S'il a indexé toute l'organisation, lui poser une question contourne chaque règle d'accès que le reste de l'application applique : l'assistant devient le seul endroit où un observateur peut lire ce qu'un chef de projet a écrit. Ajouter un assistant était facile. En ajouter un qui ne fuit pas, c'était la vraie exigence.

Architecture

Une application Next.js 16 en couches : les actions serveur appellent les services, les services portent les règles métier, Prisma gère la base. Rien de ce qui vient du navigateur ne fait foi pour les autorisations.

  • Les sessions sont signées en HMAC-SHA256 et vérifiées par une comparaison à temps constant. Chaque action revérifie le rôle et l'organisation côté serveur ; la frontière de l'organisation est appliquée dans chaque requête, jamais déduite de l'URL.
  • PostgreSQL avec pgvector, onze tables. Projets, tâches et notes de projet sont vectorisés avec nomic-embed-text et stockés en fragments de connaissance à côté des données d'origine — réindexés à chaque création, modification ou suppression.
  • L'assistant tourne sur Ollama (llama3.2:3b par défaut) : les données de l'organisation ne partent jamais vers une API tierce. Il répond uniquement à partir du contexte retrouvé, et chaque réponse revient avec ses sources.
  • La messagerie temps réel repose sur un serveur WebSocket autonome qui authentifie les connexions avec le même cookie de session signé que l'application ; l'application lui transmet les événements via un point d'entrée protégé par un secret.
  • Docker Compose lance les quatre services — application, base, serveur de modèles, serveur WebSocket — et crée une organisation de démonstration reproductible au premier démarrage.

Décisions & compromis

Le filtre de droits s'applique dans la recherche vectorielle

La forme habituelle du RAG : chercher dans l'index, puis filtrer les résultats. Ça échoue deux fois. Ça fuit — des lignes invisibles ont quand même décidé quels cinq fragments reviennent. Et ça affame — si les plus proches correspondances sont toutes hors périmètre, la réponse est vide alors que des données pertinentes et visibles existent.

Le périmètre vient donc en premier : les identifiants des projets, tâches et notes que cet utilisateur peut lire. Il est appliqué dans la clause WHERE, avant ORDER BY embedding <=> query : les plus proches voisins sont choisis parmi les lignes autorisées uniquement. Le coût, c'est une requête de périmètre par question ; le bénéfice, c'est qu'aucun chemin de code ne montre au modèle une ligne que l'utilisateur ne pourrait pas ouvrir.

Des réponses fondées et sourcées, par un petit modèle local

Un modèle de 3 milliards de paramètres invente volontiers une échéance. Le prompt le limite au contexte retrouvé et lui demande de le dire quand ce contexte ne contient pas la réponse ; la réponse porte les identifiants des fragments utilisés, si bien que chaque affirmation se retrace jusqu'à la tâche d'origine. Dans un outil d'équipe, un petit modèle qui dit « je ne sais pas » vaut mieux qu'un grand qui devine — et un modèle local garde les données à la maison.

Une seule session pour deux serveurs

Le serveur WebSocket est un processus à part, parce qu'un déploiement serverless ne peut pas garder de connexions ouvertes. Le raccourci tentant : un second jeton, plus simple, pour le socket. À la place, il lit le même cookie signé en HMAC que l'application : un seul schéma d'authentification à réussir, pas deux à garder synchronisés. Et le seul moyen de diffuser vers les clients passe par l'application, derrière un secret partagé.

La démo en ligne retire des fonctions plutôt que de les simuler

Vercel ne peut faire tourner ni serveur WebSocket ni modèle local. Plutôt que de simuler l'un ou l'autre, la version en ligne indique clairement que le temps réel et l'assistant nécessitent l'installation Docker, et le README propose trois façons d'évaluer : en ligne, Docker complet, ou développement local. Une démo qui fait semblant vaut moins qu'une démo honnête sur ses limites.

Ce qui a été difficile

Faire tourner un modèle sur un processeur. Dans Docker, une réponse prend entre 30 secondes et deux minutes, la première étant la plus lente le temps de charger le modèle. D'où des choix concrets : un délai généreux et configurable, un petit modèle par défaut qu'une variable d'environnement permet de remplacer, et des messages d'erreur qui nomment la cause probable — modèle non téléchargé, index vide, machine trop lente, assistant désactivé — au lieu d'un échec générique.

Garder l'index honnête. Un vecteur est une copie des données, et les copies vieillissent. Chaque écriture qui modifie un projet, une tâche ou une note met à jour ou supprime son fragment dans le même appel de service, et un script de réindexation reconstruit tout quand il le faut. Un index périmé ne plante pas : il répond, avec assurance, l'échéance de la semaine dernière.

Résultat

Une application complète et conteneurisée : le plan de sprints mené à son terme, cinq rôles, une messagerie temps réel et un assistant qui fonctionne, livrés en quatre services qui démarrent en une commande et créent les mêmes données de démonstration sur chaque machine. Elle tourne en ligne pour la visite et sous Docker pour l'évaluation complète.

Ce que j'en retiens : posséder un service de bout en bout, c'est aussi posséder les parties ingrates — les données de démonstration, le tableau de dépannage, le message d'expiration — et ce sont celles qu'un évaluateur rencontre en premier.

La suite

Sortir la limitation des tentatives de connexion de la mémoire du processus. Elle compte les échecs par instance, ce qui va sur un conteneur et devient faux derrière un répartiteur de charge ; Postgres ou Redis règlerait ça.

Ensuite, mesurer l'assistant au lieu de lui faire confiance : un petit jeu de questions aux réponses connues, rejoué à chaque changement de prompt ou de modèle, pour que la qualité de la recherche soit un chiffre plutôt qu'une impression.