---
issueRef: "#82"
version: 6
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 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/call` applique déjà une policy optionnelle avant dispatch (`tool_policies.get(requester)` puis `enforce_tool_policy`).
- `crates/domain/src/agent_tool_policy.rs` : modèle existant `AgentToolPolicy { allow, bound_issue, deny_others }`.
- `crates/infrastructure/src/orchestrator/mcp/policy.rs` : `ToolPolicyRegistry` in-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 via `OrchestratorService`, 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_agents`
- `idea_context_read`
- `idea_memory_read`
- `idea_skill_read`
- `idea_workstate_read`
- `idea_ticket_read`
- `idea_ticket_list`
- `idea_ticket_read_carnet`
- `idea_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 ; avec `target` agent, c'est une écriture directe du `.md` agent.
- `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_create`
- `idea_ticket_update`
- `idea_ticket_update_status`
- `idea_ticket_update_priority`
- `idea_ticket_update_carnet`
- `idea_ticket_link`
- `idea_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 :

```text
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 :

1. si override agent existe, il remplace le défaut projet ;
2. sinon si défaut projet existe, l'utiliser ;
3. sinon utiliser `READ_ONLY_TOOLS` canonique ;
4. 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) avant `TicketToolProvider` et avant `OrchestratorService::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 ;
- `LaunchAgent` doit 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::McpTool` dans 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` / `ProjectMcpToolPermissions` avec fallback lecture seule.
- Déplacer la classification read/write dans une source backend canonique proche du catalogue MCP.
- Ajouter un port `McpToolPermissionStore` et 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 `McpServer` ou dans un service de policy appelé par `tools_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 pas `idea_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_write` l'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

```text
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 :

1. Cible : `Défaut projet` ou un agent précis.
2. Mode : `Utiliser le défaut projet` ou `Override personnalisé`.
3. 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 `Lecture` ou `Écriture` ;
- un état `Autorisé`, `Refusé`, ou `Hé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 projet` si aucun override n'existe.
- `Override personnalisé` si une allowlist agent existe.
- `Diffère du défaut : +2 écriture, -1 lecture` quand 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 effectif `Refusé`.

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

1. L'utilisateur ouvre le panneau `Permissions` depuis la barre de panneaux projet.
2. Il sélectionne l'onglet `Tools MCP IdeA`.
3. Il clique l'agent cible dans la colonne gauche, par exemple `DevFrontend`.
4. Si l'agent hérite du défaut, il clique `Créer un override`. La liste devient éditable et reprend l'état effectif actuel.
5. Il ouvre le groupe concerné, par exemple `Tickets`.
6. Il coche `idea_ticket_update_carnet — Modifier le carnet d'un ticket`.
7. Le résumé en haut passe à `9 lecture autorisés · 1 écriture autorisé` et une barre d'actions affiche `Modifications non enregistrées`.
8. Il clique `Enregistrer`.
9. Après succès, le badge de l'agent passe à `Override` et la ligne du tool affiche `Ajouté`.

### Parcours principal — retirer un tool d'écriture

1. L'utilisateur sélectionne un agent avec badge `Override`.
2. Il ouvre le groupe contenant le tool autorisé.
3. Il décoche le tool d'écriture.
4. Le résumé et le compteur du groupe se mettent à jour immédiatement dans la draft.
5. Il clique `Enregistrer`.
6. 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 `Enregistrer` disponible.
- 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 inconnus` avec badge `Inconnu`, 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 `Annuler` et `Enregistrer` visibles dans l'éditeur ; changement de cible avec draft modifiée demande confirmation.
- Sauvegarde réussie : feedback discret `Permissions enregistrées` pendant environ 2 secondes.

### Accessibilité

- Les onglets `Système` / `Tools MCP IdeA` utilisent `role="tablist"`, `role="tab"`, `aria-selected` et navigation clavier gauche/droite.
- Chaque groupe repliable expose un bouton avec `aria-expanded` et un nom incluant le compteur, par exemple `Tickets, 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 :

- `Permissions`
- `Système`
- `Tools MCP IdeA`
- `Défaut projet`
- `Lecture seule`
- `Hérite du défaut projet`
- `Override personnalisé`
- `Créer un override`
- `Réinitialiser l'override`
- `Modifications non enregistrées`
- `Annuler`
- `Enregistrer`
- `Autorisé`
- `Refusé`
- `Hérité`
- `Ajouté`
- `Retiré`

### Critères d'acceptation UX/frontend

- Les permissions MCP sont éditables depuis le panneau projet `Permissions`, onglet `Tools MCP IdeA`.
- L'utilisateur peut sélectionner `Défaut projet` ou 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`, onglet `Tools 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_permissions` sans 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.