État d'exécution accumulé (tickets créés/mis à jour hors #43, notes de mémoire, tâche de fond) capturé au moment du commit de la feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
25 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #82 | 6 |
|
1784410760329 |
Cadrage — ticket #82
Objectif produit
Donner à chaque agent IdeA une policy explicite d'utilisation des tools MCP IdeA. Par défaut, un agent ne doit pouvoir appeler que les tools de lecture. L'utilisateur doit ensuite pouvoir accorder ou retirer des permissions agent par agent. La surface d'édition est laissée à UX/frontend et n'est pas dans le lot backend de base.
État réel du code
Surfaces inspectées :
crates/infrastructure/src/orchestrator/mcp/tools.rs: catalogue MCP actuel, 25 tools exposés.crates/infrastructure/src/orchestrator/mcp/server.rs:tools/callapplique déjà une policy optionnelle avant dispatch (tool_policies.get(requester)puisenforce_tool_policy).crates/domain/src/agent_tool_policy.rs: modèle existantAgentToolPolicy { allow, bound_issue, deny_others }.crates/infrastructure/src/orchestrator/mcp/policy.rs:ToolPolicyRegistryin-memory par requester.crates/application/src/ticket_assistant.rs: l'assistant de ticket pose une allowlist éphémère bornée à un ticket.crates/backend/src/openai_tools.rs: l'invoker OpenAI-compatible expose le même catalogue et dispatch viaOrchestratorService, mais ne consulte pas la policy avant appel.crates/domain/src/permission.rs+crates/infrastructure/src/store/permission.rs: système permissions/sandbox existant, persisté dans.ideai/permissions.json, orienté fichiers/commandes/sandbox/projections CLI.
Conclusion : il existe déjà un point d'application côté MCP stdio, mais pas de policy durable par agent et pas de default-deny pour les tools d'écriture. Le modèle existant est réutilisable comme base conceptuelle, mais il est actuellement trop spécialisé pour les assistants de ticket et stocké en mémoire.
Classification lecture / écriture des tools MCP IdeA
Lecture autorisée par défaut :
idea_list_agentsidea_context_readidea_memory_readidea_skill_readidea_workstate_readidea_ticket_readidea_ticket_listidea_ticket_read_carnetidea_sprint_list
Écriture / action / exécution à refuser par défaut :
idea_ask_agent: délégation active, peut lancer/réattacher une cible et produire des effets indirects.idea_run_in_background: exécute une commande et crée une tâche.idea_launch_agent: lance/attache une session agent.idea_stop_agent: tue une session.idea_update_context: écrit le contexte d'un agent.idea_context_propose: écrit/propose du contexte ; avectargetagent, c'est une écriture directe du.mdagent.idea_memory_write: écrit la mémoire projet.idea_workstate_set: écrit la ligne live-state du requester.idea_create_skill: crée un skill.idea_ticket_createidea_ticket_updateidea_ticket_update_statusidea_ticket_update_priorityidea_ticket_update_carnetidea_ticket_linkidea_ticket_unlink
Règle de maintenance : la classification doit vivre à côté du catalogue MCP, pas dans l'UI, pour que tout nouveau tool doive choisir explicitement read ou write/action.
Cause racine / besoin architectural
Aujourd'hui, l'absence de policy pour un requester signifie "autorisé" sur le serveur MCP, parce que l'enforcement ne s'exécute que si registry.get(requester) retourne une policy. C'était acceptable pour l'ancien cas ticket-assistant, où la policy éphémère était posée avant ouverture. Ce n'est pas acceptable pour des agents IdeA généraux : l'absence de configuration doit se résoudre en policy par défaut lecture seule.
Le besoin n'est pas couvert par Landlock/sandbox : Landlock protège le système de fichiers et l'exécution de commandes au niveau OS/PTY/structured process. Les tools MCP IdeA sont des capabilities applicatives internes (memory, context, tickets, delegation, workstate, etc.) qui doivent être refusées avant dispatch applicatif. Un sandbox peut empêcher certains effets externes, mais ne sait pas qu'un appel idea_memory_write ou idea_ask_agent est interdit.
Modèle de données proposé
Créer une policy MCP durable par projet, sparse par agent, distincte du modèle PermissionSet fichiers/commandes.
Option recommandée : nouveau document .ideai/mcp-tool-permissions.json plutôt qu'étendre .ideai/permissions.json.
Raison : .ideai/permissions.json a un sens précis et déjà chargé : permissions de fichiers/commandes + projection CLI + compilation Landlock. Mélanger les tools MCP avec ces capabilities risquerait de rendre confus un modèle qui n'a pas le même point d'application ni la même sémantique de sandbox.
Schéma conceptuel :
ProjectMcpToolPermissions {
version: 1,
projectDefault: McpToolPolicy? // absent => default lecture seule canonique
agents: Vec<AgentMcpToolPolicyOverride>
}
AgentMcpToolPolicyOverride {
agentId: AgentId,
policy: McpToolPolicy
}
McpToolPolicy {
allowedTools: Vec<String>, // noms exacts du catalogue MCP
deniedTools: Vec<String>?, // optionnel si UX veut exprimer un retrait explicite
mode: AllowListed | ReadOnlyPlus // à arbitrer avec UX, mais backend peut commencer simple
}
Simplification backend acceptable pour un premier lot : stocker directement allowedTools par agent, et résoudre ainsi :
- si override agent existe, il remplace le défaut projet ;
- sinon si défaut projet existe, l'utiliser ;
- sinon utiliser
READ_ONLY_TOOLScanonique ; - refuser tout tool absent de l'allowlist effective.
Le modèle doit valider les noms contre le catalogue connu ou au minimum rejeter les chaînes vides/dupliquées. Les tools inconnus ne doivent pas devenir des permissions latentes silencieuses.
Point d'application de la policy
Point principal : McpServer::tools_call, avant tout dispatch, exactement à l'endroit où l'enforcement éphémère existe déjà aujourd'hui.
À faire :
- remplacer/compléter
registry.get(requester)par une résolution effective : requester handshake ->AgentId-> policy durable projet -> fallback lecture seule ; - si le requester est vide/legacy
"mcp", appliquer aussi le fallback lecture seule, ou refuser les écritures fail-closed ; - refuser via erreur MCP lisible (
isError/JsonRpcError cohérent) avantTicketToolProvideret avantOrchestratorService::dispatch; - publier éventuellement
OrchestratorRequestProcessed { ok: false }pour garder une trace UI/diagnostic des refus, sans exécuter le tool.
Point secondaire obligatoire pour éviter le contournement : AppOpenAiToolInvoker doit appliquer la même résolution avant map_tool_call/dispatch. C'est le chevauchement direct avec #62 : #62 demande déjà la parité OpenAI-compatible + identité requester explicite. #82 dépend fonctionnellement de ce point ; sinon un agent utilisant un profil OpenAI-compatible pourrait contourner la policy MCP stdio.
Relation avec #62 et #60
#60 a fermé le bug de conversation muette et a sorti #62 comme dette sécurité adjacente.
#62 reste pertinent et doit être traité avant ou dans le premier lot de #82 :
- passer une identité requester explicite aux sessions structurées ;
LaunchAgentdoit utiliser l'agent id comme requester, pas une dérivation fragile du run dir ;- l'assistant de ticket doit garder
ticket-assistant:<project>:<issue>; - l'invoker OpenAI-compatible doit consulter la même policy que le serveur MCP stdio.
#82 généralise ensuite la policy à tous les agents déclarés, avec un défaut lecture seule durable. Les policies éphémères du ticket-assistant peuvent rester comme cas spécial plus restrictif/borné à un ticket, mais elles ne doivent pas masquer la policy globale agent si un assistant normal est lancé.
Frontières avec le système permissions/sandbox existant
À ne pas faire dans #82 :
- ne pas modifier la compilation Landlock ;
- ne pas ajouter de
Capability::McpTooldans le modèle fichiers/commandes sans arbitrage Architecture ; - ne pas projeter cette policy dans les settings Claude/Codex ;
- ne pas compter sur les prompts natifs des CLIs pour autoriser/refuser les tools IdeA.
Le système existant reste responsable de ce que le process agent peut faire au niveau OS. #82 est une policy applicative IdeA, appliquée côté serveur/bridge avant use case.
Découpage recommandé
Lot B1 — Domaine + catalogue + store durable
- Ajouter un modèle pur
McpToolPermissionPolicy/ProjectMcpToolPermissionsavec fallback lecture seule. - Déplacer la classification read/write dans une source backend canonique proche du catalogue MCP.
- Ajouter un port
McpToolPermissionStoreet un store FS sous.ideai/mcp-tool-permissions.json. - Tests domaine : fallback lecture seule, override agent, refus tool inconnu, nouveau catalogue sans classification explicite détecté par test.
Lot B2 — Enforcement MCP stdio
- Injecter le resolver/store dans
McpServerou dans un service de policy appelé partools_call. - Appliquer la policy avant ticket provider et avant orchestrator dispatch.
- Remplacer le comportement "pas de policy => tout passe" par "pas de policy => lecture seule" pour les agents généraux.
- Garder la policy ticket-assistant bornée au ticket comme restriction éphémère additionnelle ou cas de requester dédié.
- Tests : agent sans override peut
idea_memory_read/idea_ticket_list, mais pasidea_memory_write,idea_ask_agent,idea_ticket_update_carnet,idea_run_in_background.
Lot B3 — Parité OpenAI-compatible / dépendance #62
- Faire passer l'identité requester explicite jusqu'à tous les appels tools structurés.
- Brancher la même policy resolver dans
AppOpenAiToolInvoker. - Tests : un profil OpenAI-compatible refusé sur
idea_memory_writel'est de la même manière que via MCP stdio ; un tool lecture passe.
Lot B4 — API backend pour future UI
- Ajouter des use cases read/update de permissions MCP par agent/projet.
- Ajouter DTO/commands Tauri ou endpoints web selon la surface existante.
- Ne pas concevoir l'UI ici ; seulement exposer un contrat stable à UX/DevFrontend.
Lot UX/F — séparé
- UX décide la surface de modification agent par agent.
- Frontend consomme les APIs B4.
Rétrocompatibilité
Agents déjà déclarés : aucun champ à ajouter dans agents.json. En absence de document .ideai/mcp-tool-permissions.json ou d'override agent, ils deviennent lecture seule pour les tools MCP IdeA. C'est un changement volontaire demandé par l'utilisateur.
Attention migration : des workflows existants qui s'appuient sur idea_ask_agent, idea_memory_write, idea_context_propose, idea_workstate_set ou idea_ticket_update_carnet devront être explicitement autorisés agent par agent après livraison. Pour limiter la casse pendant le développement, prévoir un message de refus clair indiquant le tool refusé et l'agent/requester concerné.
Critères d'acceptation backend
- Un agent sans override ne peut appeler que les tools listés en lecture.
- Les tools d'écriture/action sont refusés avant effet applicatif.
- Une allowlist agent permet explicitement un tool d'écriture choisi.
- Un retrait/absence d'allowlist retire effectivement le droit au prochain appel, sans relancer l'application si possible.
- Le serveur MCP stdio et l'invoker OpenAI-compatible appliquent la même décision.
- Les permissions filesystem/commandes et le sandbox Landlock restent inchangés.
Conception UX/F — surface permissions MCP par agent
Décision de placement
La modification des permissions MCP IdeA vit dans le panneau projet Permissions, pas dans Settings et pas uniquement dans la fiche d'un agent.
Raison UX : ce réglage est une matrice de capacités applicatives par agent dans le projet courant. Il doit être consultable et comparable au même endroit que les permissions/sandbox existantes, sans polluer les paramètres globaux desktop (Settings) ni cacher un droit critique dans une fiche agent isolée. La fiche/liste d'agent peut afficher un raccourci ou un badge, mais l'édition canonique reste Permissions.
Le panneau Permissions devient une surface à deux onglets internes :
Système: permissions fichiers/commandes/sandbox existantes.Tools MCP IdeA: nouveau réglage #82.
La colonne de gauche reste le sélecteur de cible : Défaut projet, puis les agents. Le panneau de droite change selon l'onglet sélectionné.
Layout attendu
Permissions
[ Système ] [ Tools MCP IdeA ] [Actualiser]
┌──────────────────────────────┬──────────────────────────────────────────────┐
│ Défaut projet │ Tools MCP IdeA — DevFrontend │
│ Lecture seule │ Hérite du défaut projet │
│ │ [Utiliser le défaut projet v] │
│ Agents │ │
│ Main Hérité │ Résumé effectif │
│ Architect Override │ 9 lecture autorisés · 2 écriture autorisés │
│ DevFrontend Override │ │
│ QA Hérité │ Accord rapide │
│ Git Hérité │ [ ] Déléguer à un agent │
│ │ [x] Modifier les tickets │
│ │ [ ] Écrire la mémoire │
│ │ │
│ │ Détail des tools │
│ │ ▾ Lecture, autorisés par défaut (9) │
│ │ ✓ idea_ticket_read │
│ │ ✓ idea_context_read │
│ │ ▾ Écriture et actions (16) │
│ │ Tickets │
│ │ [x] idea_ticket_update_carnet │
│ │ [ ] idea_ticket_update_status │
│ │ Agents │
│ │ [ ] idea_ask_agent │
│ │ [ ] idea_launch_agent │
│ │ [Réinitialiser l'override] [Enregistrer] │
└──────────────────────────────┴──────────────────────────────────────────────┘
Sur desktop large : deux colonnes comme le panneau permissions actuel, avec la liste des cibles à gauche et l'éditeur à droite. Sur largeur contrainte : la cible sélectionnée reste au-dessus de l'éditeur, puis les groupes de tools s'empilent ; les actions restent en bas du panneau, non flottantes.
Modèle mental affiché
L'utilisateur ne manipule pas une liste brute de 25 cases. Il voit trois niveaux :
- Cible :
Défaut projetou un agent précis. - Mode :
Utiliser le défaut projetouOverride personnalisé. - Capabilités groupées : lecture, tickets, agents, contexte, mémoire, workstate, skills, exécution.
Pour un agent sans override, l'éditeur est en lecture de l'état hérité jusqu'à ce que l'utilisateur choisisse Créer un override. Les contrôles hérités sont visibles mais atténués, avec la mention Hérité du défaut projet. Cela permet de comprendre l'état effectif avant de modifier.
Pour un agent avec override, le badge de la colonne gauche affiche Override. Le panneau de droite affiche Override personnalisé et un bouton Réinitialiser l'override qui remet l'agent sur le défaut projet.
Présentation du catalogue
Les tools sont groupés par domaine fonctionnel, à partir des métadonnées du catalogue retourné par get_mcp_tool_permissions si disponibles côté backend, sinon par mapping frontend local strictement présentationnel. La classification read/write reste backend-canonique.
Groupes UX recommandés :
Lecture projet:idea_list_agents,idea_context_read,idea_memory_read,idea_skill_read,idea_workstate_read.Lecture tickets:idea_ticket_read,idea_ticket_list,idea_ticket_read_carnet,idea_sprint_list.Délégation agents:idea_ask_agent,idea_launch_agent,idea_stop_agent.Contexte et mémoire:idea_update_context,idea_context_propose,idea_memory_write.Tickets:idea_ticket_create,idea_ticket_update,idea_ticket_update_status,idea_ticket_update_priority,idea_ticket_update_carnet,idea_ticket_link,idea_ticket_unlink.Travail et exécution:idea_run_in_background,idea_workstate_set.Skills:idea_create_skill.
Chaque groupe affiche un compteur : 3/7 autorisés, et peut être replié/déplié. Les groupes lecture sont ouverts par défaut dans Défaut projet, mais repliés par défaut dans l'édition agent pour réduire le bruit. Les groupes écriture/action sont ouverts par défaut, car ce sont les décisions à risque.
Chaque ligne de tool contient :
- le nom exact monospace (
idea_ticket_update_carnet) ; - un libellé humain court (
Modifier le carnet d'un ticket) ; - un badge
LectureouÉcriture; - un état
Autorisé,Refusé, ouHérité; - une case à cocher uniquement quand la cible est éditable.
Les checkboxes sont réservées aux tools individuels. Les groupes utilisent un bouton discret Tout autoriser dans ce groupe / Tout retirer dans ce groupe, jamais une checkbox tri-state ambiguë.
Défaut projet vs overrides agent
Le Défaut projet est le point de départ appliqué à tous les agents sans override. Son état initial est Lecture seule : tous les tools classifiés lecture sont autorisés, tous les tools écriture/action sont refusés.
Pour un agent, afficher explicitement :
Hérite du défaut projetsi aucun override n'existe.Override personnalisési une allowlist agent existe.Diffère du défaut : +2 écriture, -1 lecturequand l'API permet de comparer l'allowlist effective au défaut.
Dans les lignes de tool agent :
- un tool hérité autorisé affiche une coche grisée +
Hérité; - un tool ajouté par override affiche une coche active + badge
Ajouté; - un tool retiré par override affiche une case vide + badge
Retirési le backend expose une notion de retrait par remplacement complet ; sinon afficher simplement l'état effectifRefusé.
Important : comme l'API update_agent_mcp_tool_permissions accepte une allowlist complète de noms de tools, l'UI doit traiter l'override agent comme un remplacement de l'état effectif, pas comme une série de patches implicites. Au moment où l'utilisateur crée un override depuis l'état hérité, la draft est préremplie avec l'allowlist effective courante.
Parcours principal — accorder un tool d'écriture
- L'utilisateur ouvre le panneau
Permissionsdepuis la barre de panneaux projet. - Il sélectionne l'onglet
Tools MCP IdeA. - Il clique l'agent cible dans la colonne gauche, par exemple
DevFrontend. - Si l'agent hérite du défaut, il clique
Créer un override. La liste devient éditable et reprend l'état effectif actuel. - Il ouvre le groupe concerné, par exemple
Tickets. - Il coche
idea_ticket_update_carnet — Modifier le carnet d'un ticket. - Le résumé en haut passe à
9 lecture autorisés · 1 écriture autoriséet une barre d'actions afficheModifications non enregistrées. - Il clique
Enregistrer. - Après succès, le badge de l'agent passe à
Overrideet la ligne du tool afficheAjouté.
Parcours principal — retirer un tool d'écriture
- L'utilisateur sélectionne un agent avec badge
Override. - Il ouvre le groupe contenant le tool autorisé.
- Il décoche le tool d'écriture.
- Le résumé et le compteur du groupe se mettent à jour immédiatement dans la draft.
- Il clique
Enregistrer. - Si l'override devient identique au défaut projet, proposer après sauvegarde de le nettoyer avec une action secondaire
Supprimer l'override inutile. Ne pas le faire automatiquement sans retour visuel.
Défaut projet
Le défaut projet est éditable dans le même onglet, mais avec une friction légère pour les tools d'écriture/action : quand l'utilisateur active un tool d'écriture au niveau défaut projet, afficher une confirmation inline avant sauvegarde :
Ce tool sera autorisé pour tous les agents sans override. Confirmer cette modification ?
Cette confirmation ne bloque pas l'édition agent par agent, car le cas utilisateur principal est d'accorder des tools d'écriture spécifiques à un agent donné.
États et feedback
- Chargement : skeleton compact dans la colonne cible et dans les groupes, pas de spinner plein écran.
- Erreur de chargement : message inline en haut du panneau avec bouton
Réessayer. - Erreur de sauvegarde : conserver la draft locale, afficher l'erreur au-dessus des actions, garder
Enregistrerdisponible. - Aucune agent : état vide dans la colonne gauche
Aucun agent dans ce projet.; le défaut projet reste éditable. - Tool inconnu dans une allowlist existante : afficher dans un groupe
Tools inconnusavec badgeInconnu, désactivé par défaut, et demander à DevFrontend de ne pas permettre de ré-enregistrer silencieusement une permission inconnue comme si elle était valide. Si le backend rejette les inconnus, afficher l'erreur telle quelle. - Modifications non enregistrées : actions
AnnuleretEnregistrervisibles dans l'éditeur ; changement de cible avec draft modifiée demande confirmation. - Sauvegarde réussie : feedback discret
Permissions enregistréespendant environ 2 secondes.
Accessibilité
- Les onglets
Système/Tools MCP IdeAutilisentrole="tablist",role="tab",aria-selectedet navigation clavier gauche/droite. - Chaque groupe repliable expose un bouton avec
aria-expandedet un nom incluant le compteur, par exempleTickets, 1 sur 7 autorisé. - Chaque checkbox a un label complet incluant le libellé humain et le nom du tool, par exemple
Modifier le carnet d'un ticket, idea_ticket_update_carnet. - Les badges couleur (
Lecture,Écriture,Hérité,Override) ne doivent jamais être le seul signal : le texte doit porter l'information. - Cibles tactiles et souris : 32 px minimum pour les lignes compactes, 40 px pour les actions principales.
- Focus visible sur onglets, lignes de cible, boutons de groupe, checkboxes et actions.
Ton et libellés
Conformément à la règle UX #78, les libellés humains sont en français. Les noms exacts des tools restent en anglais/monospace car ce sont des identifiants techniques.
Libellés principaux :
PermissionsSystèmeTools MCP IdeADéfaut projetLecture seuleHérite du défaut projetOverride personnaliséCréer un overrideRéinitialiser l'overrideModifications non enregistréesAnnulerEnregistrerAutoriséRefuséHéritéAjoutéRetiré
Critères d'acceptation UX/frontend
- Les permissions MCP sont éditables depuis le panneau projet
Permissions, ongletTools MCP IdeA. - L'utilisateur peut sélectionner
Défaut projetou un agent dans une colonne/listing de cibles. - Un agent sans override affiche clairement qu'il hérite du défaut projet, et ses contrôles ne deviennent éditables qu'après
Créer un override. - Un agent avec override est identifiable dans la liste par un badge textuel
Override. - Les ~25 tools ne sont pas affichés comme une liste plate : ils sont groupés par domaine, avec compteurs et sections repliables.
- Les tools lecture et écriture/action sont distingués par badges textuels et par hiérarchie visuelle.
- Le parcours d'autorisation d'un tool d'écriture agent par agent prend au maximum : ouvrir
Permissions, ongletTools MCP IdeA, choisir l'agent, créer/éditer l'override, cocher le tool, enregistrer. - Le retrait d'un tool d'écriture existant est symétrique : décocher puis enregistrer.
- Les changements non sauvegardés sont visibles et protégés lors d'un changement de cible.
- L'UI consomme
get_mcp_tool_permissions,update_project_mcp_tool_permissions,update_agent_mcp_tool_permissionssans hardcoder la classification lecture/écriture comme source de vérité métier. - Aucun changement de backend ou d'i18n n'est requis pour ce lot frontend.