chore(tickets): synchronise index/carnets tickets #131-#139 + note mémoire assets/persistance plugins

État runtime .ideai séparé du code (index, counter, carnets, note mémoire
plugin-asset-serving-and-owned-storage-contracts). Purge des tickets obsolètes
51/63/66.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:06:30 +02:00
parent 171c6c923c
commit 59d81851e9
28 changed files with 557 additions and 116 deletions

View File

@ -77,3 +77,4 @@
- [ticket113-controlled-args-field-rootcause](ticket113-controlled-args-field-rootcause.md) — memory note ticket113-controlled-args-field-rootcause
- [ticket120-hello-plugin-recurrence-investigation-angle](ticket120-hello-plugin-recurrence-investigation-angle.md) — memory note ticket120-hello-plugin-recurrence-investigation-angle
- [ticket120-hello-plugin-blackscreen-recurrence](ticket120-hello-plugin-blackscreen-recurrence.md) — memory note ticket120-hello-plugin-blackscreen-recurrence
- [plugin-asset-serving-and-owned-storage-contracts](plugin-asset-serving-and-owned-storage-contracts.md) — memory note plugin-asset-serving-and-owned-storage-contracts

View File

@ -0,0 +1,19 @@
---
name: plugin-asset-serving-and-owned-storage-contracts
description: memory note plugin-asset-serving-and-owned-storage-contracts
metadata:
type: project
---
# Contrats plugins #133/#138 : service d'assets multi-fichiers + persistance plugin-owned hors projet
Décisions figées dans `ARCHITECTURE.md` §22 (2026-08-02).
## #133 — service des assets `idea-plugin://`
`asset_allowed` (`crates/app-tauri/src/plugins.rs:504-536`) doit servir tout chemin relatif confiné dès lors que les gardes déjà présentes tiennent : registre actif (`lifecycle_state.is_runtime_active()`) + `content_hash` du package matché + confinement canonicalize (déjà en place lignes 467-483). Fin de l'allowlist `declared_main || declared_icon || starts_with("assets/")` qui cassait tout import ESM relatif secondaire (`./constants.js`, `./core/x.js`) → "Importing a module script failed.". Pas de résolution `node_modules`/bare specifiers — hors scope, figé. Débloque #134 (implémentation) et #135 (audit confinement install + désinstallation 100%).
## #138 — persistance plugin-owned
`ctx.storage` (déjà typé dans `sdk/IdeaSDK/src/runtime.ts`, jamais câblé côté `frontend/src/plugins/runtime/loader.ts` ni implémenté côté Rust — vérifié : zéro port/commande/répertoire) devient l'API canonique unique pour l'état interne du plugin (prefs/cache/index). Nouveau répertoire `app_data/plugins/data/<pluginId>/`, frère de `plugins/installed/<pluginId>/` (jamais dedans, pour survivre aux réinstalls et donner une racine univoque à purger). `plugin_uninstall` doit purger les deux répertoires. `ctx.services.config`/`workspace` restent réservés au project-owned (fichiers réels du projet, jamais l'état interne du plugin). L'exemple `hello-plugin` doit migrer ses compteurs internes de `.ideai/hello-plugin.json` vers `ctx.storage`. Débloque #139.
Détail complet et rationale : `ARCHITECTURE.md` §22. Tickets liés : #133/#134/#135 (assets), #138/#139 (storage). Les deux tickets #133/#138 sont passés en `QA` avec carnet détaillé.

View File

@ -0,0 +1,6 @@
---
issueRef: "#131"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1785687359220
---

View File

@ -0,0 +1,39 @@
---
id: "4092a7bf-5abb-4056-9e6d-226c392b2279"
number: 131
title: "Configurer l'effort par agent avec presets adaptatifs selon le profil AI"
status: "open"
priority: "medium"
sprint: null
links: []
agentRefs: []
attachments: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1785687359220
updatedAt: 1785687359220
version: 1
---
Objectif: permettre de choisir l'effort de chaque agent, idéalement via une droplist qui s'adapte au profil AI sélectionné (ex: Codex, Claude Code, OpenCode), tout en conservant un fallback sûr.
Cadrage validé en pré-analyse:
- Faisabilité: oui.
- Recommandation produit/contrat: approche hybride contrôlée.
- UI recommandée: droplist dépendante du profil AI + option "Personnalisé" ouvrant un champ texte/valeur libre si nécessaire.
Attendus de conception:
- Le profil AI déclare ses options natives d'effort/presets quand elles existent.
- La UI affiche ces options dans une droplist ordonnée du plus léger au plus profond.
- Si le provider n'expose pas d'options propres, fallback vers des presets génériques (ex: Rapide / Standard / Approfondi) clairement marqués comme options par défaut.
- Une option "Personnalisé" reste disponible pour couvrir les providers ou cas non modélisables proprement.
Attendus de contrat:
- Ajouter sur le profil AI un mécanisme déclaratif d'options d'effort (ex: effort_options avec label + valeur interne + éventuels hints).
- Le DTO d'agent doit persister soit un preset choisi, soit une valeur brute personnalisée.
- Prévoir la rétrocompatibilité avec les profils/configs existants, notamment les champs Codex déjà proches de cette notion.
Risques à traiter:
- Mapping imparfait entre presets UI et paramètres natifs des providers.
- Cohérence des libellés entre providers.
- Découverte UX de l'option "Personnalisé".
- Rétrocompatibilité/persistance sur les profils existants.

View File

@ -0,0 +1,6 @@
---
issueRef: "#132"
version: 1
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1785687502830
---

View File

@ -0,0 +1,41 @@
---
id: "37c98a91-ee51-42b3-b804-8c0732784e11"
number: 132
title: "Ajouter un outil MCP IdeA pour éditer le contexte projet global"
status: "open"
priority: "medium"
sprint: null
links: []
agentRefs: []
attachments: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
createdAt: 1785687502830
updatedAt: 1785687502830
version: 1
---
Objectif: permettre à un agent autorisé d'éditer le contexte projet global via un outil MCP IdeA dédié, au lieu de passer uniquement par une proposition enregistrée.
Constat actuel:
- Le contexte projet global est lisible via `idea_context_read`.
- Son évolution passe aujourd'hui par `idea_context_propose` sans `target`, ce qui enregistre une proposition pour validation mais n'applique pas directement la modification.
- Pour certains workflows d'orchestration, il manque une capacité native explicite d'édition contrôlée du contexte projet global.
Attendu produit/technique:
- Introduire un outil MCP IdeA dédié pour mettre à jour le contexte projet global.
- Définir clairement qui peut l'utiliser (ex: Main uniquement, ou liste d'agents autorisés).
- Préserver les garde-fous de concurrence et de traçabilité déjà attendus sur les contextes.
- Clarifier la relation entre ce nouvel outil et `idea_context_propose` (complément, remplacement partiel, ou voie restreinte selon les droits).
Points à cadrer:
- Modèle d'autorisation: quels agents peuvent écrire le contexte global.
- Concurrence/versioning: écrasement simple vs contrôle optimiste.
- Auditabilité: auteur, date, historique/provenance des changements.
- UX/runtime: comportement si un agent non autorisé tente l'opération.
- Compatibilité avec la règle actuelle de single-writer réservée à l'orchestrateur.
Critères de sortie:
- Contrat MCP défini.
- Règles d'autorisation explicites.
- Comportement d'erreur et de concurrence défini.
- Décision documentée sur la coexistence avec `idea_context_propose`.

View File

@ -0,0 +1,24 @@
---
issueRef: "#133"
version: 4
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedAt: 1785703664966
---
## Décision d'architecture (2026-08-02)
Documentée dans `ARCHITECTURE.md` §22.1 (nouvelle section "Plugins — service des assets multi-fichiers & persistance plugin-owned").
**Contrat tranché :** `asset_allowed` (`crates/app-tauri/src/plugins.rs:504-536`) doit servir tout chemin relatif confiné dès lors que les trois gardes déjà présentes sont satisfaites — entrée registre trouvée + `lifecycle_state.is_runtime_active()` + `entry.content_hash == hash` de l'URL (intégrité du package entier) — sans plus restreindre au triplet `declared_main || declared_icon || starts_with("assets/")`. Le confinement canonicalize aval (lignes 467-483, `target.starts_with(&root)`) reste inchangé et continue de protéger contre l'évasion de racine. `validator.validate(manifest)` reste appelé comme garde d'intégrité globale du manifeste, mais cesse de gater le service fichier par fichier.
**Rationale sécurité :** aucune perte de garantie — le modèle de menace est fixé par `content_hash` à l'installation (audité en #135), donc restreindre les fichiers *siblings* d'un package déjà intégralement vérifié n'arrête aucune attaque supplémentaire, ça casse juste des graphes de modules ESM légitimes.
**Limite figée :** pas de résolution `node_modules`/bare specifiers — hors scope, aucun résolveur de module à construire. Un plugin avec dépendances tierces les bundle ou vendore en relatif, à son choix.
**Contrat de confinement/désinstallation formalisé :** racine servie = exclusivement `app_data/plugins/installed/<pluginId>/` ; jamais d'écriture/exposition hors project root ou `.ideai/` de l'utilisateur ; désinstallation = suppression complète + entrée registre, zéro résidu (périmètre détaillé pour #135).
## Débloque
- **#134** : remplacer la dernière ligne de `asset_allowed``Ok(declared_main || declared_icon || rel.as_str().starts_with("assets/"))` — par une autorisation basée uniquement sur les gardes déjà calculées plus haut dans la fonction. Tests de non-régression path-traversal et hash/lifecycle invalides déjà spécifiés dans #134, contrat inchangé.
- **#135** : périmètre d'audit = confinement à l'install (`RelativePath::new` déjà rejette `..`/absolu côté domaine — vérifier qu'il est bien appliqué à l'INSTALL, pas seulement au SERVE) + désinstallation 100%.
Aucun changement de code applicatif dans ce ticket (portée strictement architecture, conforme à l'objectif du ticket). Fichier touché : `ARCHITECTURE.md` (§22.1 ajouté).

View File

@ -0,0 +1,31 @@
---
id: "f5296d8b-6bef-45c4-ac8a-cf6e0f90ae9c"
number: 133
title: "Plugins: contrat de service des assets idea-plugin:// (multi-fichiers ESM) & confinement"
status: "qa"
priority: "high"
sprint: null
links: []
agentRefs: [{"agentId":"b4730d7f-c54d-4736-8a04-c6203aa2fd49","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
createdAt: 1785702640094
updatedAt: 1785703664966
version: 4
---
Bug diagnostiqué : `crates/app-tauri/src/plugins.rs:504-536` (`asset_allowed`) n'autorise que `main`/`icon` déclarés au manifeste, ou un chemin préfixé `assets/`. Tout import ESM relatif secondaire (`./constants.js`, `./core/x.js`) depuis le `main` est donc rejeté 403 → "Importing a module script failed." côté navigateur. Le SDK (sdk/IdeaSDK/README.md) documente `main: dist/index.js` comme point d'entrée sans jamais imposer un bundle mono-fichier, ce qui sous-entend un support multi-fichiers jamais réellement vérifié (l'exemple hello-plugin est mono-fichier).
Objectif de ce ticket : trancher le contrat d'architecture, PAS l'implémenter.
À décider et documenter :
1. Élargir la politique de service à : tout chemin relatif confiné du package installé, dès lors que `entry.content_hash == hash` (intégrité du package entier déjà vérifiée) ET `entry.lifecycle_state.is_runtime_active()` ET confinement canonicalize (`target.starts_with(root)`, déjà en place lignes 467-483). Ces trois garanties suffisent déjà sans dépendre d'une déclaration par-fichier dans le manifeste.
2. Figer la limite explicite : imports ESM relatifs uniquement, pas de résolution `node_modules`/bare specifiers (hors scope, pas de résolveur de modules à construire) — un plugin qui a des dépendances tierces doit les vendorer en relatif ou les bundler lui-même, à son choix, jamais une obligation d'IdeA.
3. Formaliser le contrat de confinement + désinstallation propre : aucune écriture ne doit jamais sortir de `app_data/plugins/installed/<id>` (pas de pollution project root ni `.ideai/`), et la désinstallation doit être 100% (dossier + entrée registry, zéro résidu), à la manière VSCode.
Livrable : note d'architecture (+ mise à jour de la doc plugin existante si présente) qui fait foi pour les tickets d'implémentation liés (DevBackend, SDK/doc, QA).
Critères d'acceptation :
- Le contrat écrit référence explicitement le code actuel (plugins.rs:504-536) et explique pourquoi hash+lifecycle+confinement remplacent l'allowlist par fichier sans régression de sécurité.
- La limite bare-specifiers/node_modules est tranchée noir sur blanc (in ou out, et pourquoi).
- Le contrat de confinement/désinstallation est écrit explicitement (racine autorisée, ce qui est interdit, ce que "propre" veut dire).

View File

@ -0,0 +1,6 @@
---
issueRef: "#134"
version: 3
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedAt: 1785707290184
---

View File

@ -0,0 +1,33 @@
---
id: "e98c3f80-9fd6-444a-be80-ad695809c71c"
number: 134
title: "Plugins: servir tout fichier confiné du package installé (fix racine multi-fichiers ESM)"
status: "qa"
priority: "critical"
sprint: null
links: [{"target":"#133","kind":"dependsOn"}]
agentRefs: [{"agentId":"fe887179-933f-47d4-960f-c3b06827f86c","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
createdAt: 1785702651199
updatedAt: 1785707290184
version: 3
---
Implémente le contrat décidé en #133.
Modifier `asset_allowed` / `plugin_asset_response_with_stores` dans `crates/app-tauri/src/plugins.rs:504-536` : remplacer la condition `declared_main || declared_icon || rel.as_str().starts_with("assets/")` par une autorisation basée sur les garanties déjà vérifiées avant cette ligne (hash de contenu du package `entry.content_hash.as_str() == hash`, `entry.lifecycle_state.is_runtime_active()`) et sur le confinement canonicalize déjà en place lignes 467-483 (`target.starts_with(&root)`).
Ne pas retirer `validator.validate(&manifest_bytes.bytes, &package)` : cette vérification reste une garde d'intégrité globale du manifeste, mais ne doit plus servir à restreindre le service fichier par fichier.
Respecter strictement la limite figée en #133 (imports relatifs uniquement, pas de résolveur node_modules/bare specifiers — hors scope).
Tests à ajouter dans `crates/app-tauri/src/plugins.rs` (module de tests existant en bas de fichier) :
- requête d'un fichier non déclaré dans le manifeste (ex: `dist/core/helper.js`) → 200 OK si hash+lifecycle valides.
- path traversal (`../`) → toujours 403 (non-régression, déjà couvert mais à revérifier après le changement).
- hash de contenu différent ou plugin non `runtime_active` → toujours 403 (non-régression).
Critères d'acceptation :
- `cargo test -p app-tauri` vert, nouveaux cas inclus.
- Un plugin composé de `dist/index.js` + `dist/constants.js` (import relatif) se charge sans 403 via le protocole `idea-plugin://`.
- Aucune régression sur les tests de confinement/path-traversal existants.

View File

@ -0,0 +1,26 @@
---
issueRef: "#135"
version: 4
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedAt: 1785708498776
---
## Audit QA
- `install_from_directory` : audit confirme qu'avant correctif le store ne rejetait pas explicitement les symlinks source ; il les ignorait. Le correctif fait maintenant échouer l'installation sur toute entrée symlink ou unsupported dans l'arbre source.
- `install_from_archive` : audit confirme un trou réel de confinement avant correctif. L'extraction reposait entièrement sur `unzip` sans validation applicative des entrées. Le correctif remplace cette extraction par une lecture Rust confinée qui rejette les entrées `../`/absolues via `enclosed_name()` et refuse explicitement les symlinks d'archive.
- Confinement d'écriture : après correctif, aucune écriture d'install ne sort de `app_data/plugins/_staging/...` puis `app_data/plugins/installed/<id>` ; le test `../../../../outside.txt` prouve l'absence d'écriture hors racine.
- `hash_dir` / collecte fichiers : durci pour échouer si un package contient encore un symlink ou une entrée non supportée, au lieu de l'ignorer.
- `plugin_uninstall` / `remove_package` : audit confirmé par test multifichier. La désinstallation supprime le dossier entier, retire l'entrée registry et laisse le runtime catalog vide.
## Tests ajoutés/ajustés
- `plugin::tests::install_from_directory_rejects_source_symlink`
- `plugin::tests::install_from_archive_rejects_parent_traversal_without_writing_outside_stage`
- `plugin::tests::install_from_archive_rejects_symlink_entries`
- `plugin_install_load::uninstall_multifile_plugin_removes_package_registry_and_runtime_residue`
- Stabilisation des tests SDK `hello-plugin` : fixture matérialisée avec `dist/index.js` dans un temp dir pour supprimer une dépendance implicite à un build préalable.
## Verdict
- Correctif confinement install/uninstall validé.
- `cargo test -p infrastructure -p application -p app-tauri` vert avec `CARGO_HOME=/tmp/idea-cargo-home` dans cet environnement.

View File

@ -0,0 +1,31 @@
---
id: "13442dae-7860-45e3-9287-31b9bde19170"
number: 135
title: "Plugins: audit confinement install & désinstallation 100% propre"
status: "qa"
priority: "high"
sprint: null
links: [{"target":"#133","kind":"dependsOn"}]
agentRefs: [{"agentId":"ab328d90-c307-4771-a3b6-6c56089c8506","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
createdAt: 1785702662938
updatedAt: 1785708498776
version: 4
---
Applique le contrat de confinement/désinstallation décidé en #133.
Auditer `plugin_install_from_archive` / `plugin_install_from_directory` (`crates/app-tauri/src/plugins.rs:67-142`) et le store d'infrastructure (`crates/infrastructure/src/plugin/mod.rs`) pour confirmer qu'aucune écriture ne peut jamais sortir de `app_data/plugins/installed/<id>` :
- Un plugin dont le manifeste ou l'archive contient un chemin `../` ou un symlink pointant hors de sa racine doit échouer à l'install (vérifier que `RelativePath::new` — qui rejette déjà `..` et les chemins absolus, cf. `crates/domain/src/plugin.rs` — est bien appliqué à l'INSTALL, pas seulement au SERVE ajouté en #134).
- Aucune écriture ne doit jamais toucher le project root ni `.ideai/` du projet ouvert : le plugin est un citoyen de `app_data`, jamais du repo utilisateur.
Vérifier que `plugin_uninstall` (`crates/app-tauri/src/plugins.rs:142`) supprime bien 100% : dossier entier + entrée registry, zéro résidu. Étendre si besoin les tests existants (`uninstall_removes_registry_package_and_stops_mcp`, `uninstall_then_reinstall_leaves_runtime_catalog_active_without_residue` dans `crates/application/src/plugin/mod.rs`) pour couvrir explicitement le cas d'un plugin multi-fichiers (plusieurs fichiers sous `dist/`).
Si un chemin d'attaque (symlink sortant, `../` dans une archive zip malveillante) n'est pas déjà bloqué à l'install, ouvrir un correctif dans ce même ticket (pas de nouveau ticket) : c'est un renforcement du même contrat, pas une nouvelle feature.
Critères d'acceptation :
- Rapport d'audit écrit (dans le carnet du ticket) listant les points vérifiés et leur statut.
- Test explicite : tentative d'installer un plugin avec chemin `../` ou symlink sortant → échec propre, aucun fichier écrit hors racine.
- Test explicite : désinstallation d'un plugin multi-fichiers → dossier disparu, entrée registry disparue, aucun résidu.
- `cargo test -p app-tauri -p infrastructure -p application` vert.

View File

@ -0,0 +1,6 @@
---
issueRef: "#136"
version: 3
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedAt: 1785708478849
---

View File

@ -0,0 +1,27 @@
---
id: "214e7c14-fab8-45ed-bb71-5f309a8479a6"
number: 136
title: "SDK plugins: aligner doc/exemple sur le support multi-fichiers ESM"
status: "qa"
priority: "high"
sprint: null
links: [{"target":"#134","kind":"dependsOn"}]
agentRefs: [{"agentId":"8f7da528-58df-4315-97e9-0562230ecc19","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
createdAt: 1785702672090
updatedAt: 1785708478849
version: 3
---
Une fois #134 livré, aligner le SDK et sa documentation pour que le support multi-fichiers soit explicite et testé, pas seulement sous-entendu.
Étendre `sdk/IdeaSDK/examples/hello-plugin` (ou ajouter un nouvel exemple dédié, ex: `hello-plugin-multi`) avec un vrai split en plusieurs fichiers ESM compilés séparément — `src/index.ts` qui importe `./constants.ts` et `./core/...` — sans bundler forcé (juste `tsc`, comme l'exemple actuel). Ce sera la preuve vivante et le test de non-régression du contrat de #133/#134.
Mettre à jour `sdk/IdeaSDK/README.md` (autour de la ligne 50 où `main` est décrit comme "compiled ESM entrypoint") :
- Clarifier explicitement que `main` est le point d'entrée, mais que des imports relatifs vers d'autres fichiers du même package sont servis nativement par le protocole `idea-plugin://` (plus besoin de tout bundler en un seul fichier).
- Documenter la limite figée en #133 : imports relatifs uniquement ; les dépendances tierces (npm) doivent être bundlées ou vendorées en relatif par le plugin, IdeA ne résout pas `node_modules`.
Critères d'acceptation :
- L'exemple multi-fichiers build (`npm run build` ou équivalent existant) et se charge dans IdeA sans erreur `Importing a module script failed.` (vérification manuelle ou via #136).
- Le README ne laisse plus entendre un support non vérifié ; la limite bare-specifiers est écrite noir sur blanc.

View File

@ -0,0 +1,6 @@
---
issueRef: "#137"
version: 1
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedAt: 1785702681696
---

View File

@ -0,0 +1,27 @@
---
id: "2ef9b71a-9869-4476-88b6-65aaefc993e1"
number: 137
title: "QA: validation end-to-end plugin multi-fichiers ESM (chargement + désinstallation propre)"
status: "open"
priority: "critical"
sprint: null
links: [{"target":"#134","kind":"dependsOn"},{"target":"#136","kind":"dependsOn"}]
agentRefs: [{"agentId":"ab328d90-c307-4771-a3b6-6c56089c8506","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
createdAt: 1785702681696
updatedAt: 1785702681696
version: 1
---
Validation réelle, sortie observée — pas seulement des tests unitaires — du fix multi-fichiers (#134) et de l'exemple SDK (#136).
À exécuter dans une build réelle (AppImage ou dev Tauri) :
1. Installer le plugin multi-fichiers issu de #136 (fichiers ESM non bundlés, imports relatifs). Confirmer l'absence de l'erreur navigateur "Importing a module script failed." et l'activation correcte (`activate(ctx)` appelé, contributions menus/layouts visibles et fonctionnelles selon ce que déclare l'exemple).
2. Vérifier l'isolement : le plugin ne dépose rien dans le project root ni dans `.ideai/` du projet ouvert (contrat de #133/#135).
3. Désinstaller depuis l'UI (Panneau Plugins) : vérifier disque (aucun résidu sous `app_data/plugins/installed/<id>`), registre (entrée disparue), et absence de toute trace côté projet.
4. Réinstaller le même plugin après désinstallation : doit repartir propre, sans conflit résiduel.
Critères d'acceptation :
- Rapport QA avec preuve d'exécution réelle (logs/captures), verdict vert ou liste d'écarts bloquants.
- Si régression détectée sur #134/#135/#136, retour précis (repro + fichier/ligne suspecté) au dev concerné avant clôture.

View File

@ -0,0 +1,30 @@
---
issueRef: "#138"
version: 4
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
updatedAt: 1785703680849
---
## Décision d'architecture (2026-08-02)
Documentée dans `ARCHITECTURE.md` §22.2 (même nouvelle section que #133).
**Constat vérifié dans le code :** `sdk/IdeaSDK/src/runtime.ts` déclare déjà `ActivateContext.storage?: PluginStorage` (`get/set/delete`, clé-valeur JSON-serializable), et l'exemple `hello-plugin` s'en sert (`ctx.storage?.get<string>("helloPlugin.ownerAgentId")`). Mais ce champ n'est **jamais peuplé** : `frontend/src/plugins/runtime/loader.ts` (~lignes 249-256) ne câble que `logger`, `subscriptions`, `services``ctx.storage` vaut toujours `undefined` en exécution. Côté Rust : zéro port, zéro commande, zéro répertoire pour cette primitive (recherché, rien trouvé). Faute d'API réelle, l'exemple détourne `ctx.services.workspace`/`ctx.services.config` pour écrire son état interne sous `.ideai/hello-plugin.txt` et `.ideai/hello-plugin.json`.
**Décision — séparation noir sur blanc :**
- **Project-owned** : fichiers du workspace que le plugin modifie *volontairement* pour l'utilisateur/le projet → reste `ctx.services.workspace.*`/`ctx.services.config.*`, sandbox projet existant inchangé.
- **Plugin-owned** : préférences/cache/sélection/index/config interne → ne vit **jamais** dans le project root ni sous `.ideai/`. Nouveau répertoire **frère** de `plugins/installed/<id>/` : `app_data/plugins/data/<pluginId>/`. Séparé de `installed/` pour que les mises à jour de package ne touchent jamais aux données utilisateur, et pour donner à la désinstallation une deuxième racine univoque à purger.
**API canonique tranchée : `ctx.storage` seul, pas de second API document.** `ctx.storage.set(key, value)` avec des valeurs JSON couvre déjà le besoin de document structuré — une deuxième API "document plugin-scopé" ferait doublon. `ctx.services.config` reste réservé au project-owned.
**Cycle de vie figé :**
- `ctx.storage.get/set/delete` → commandes Tauri (ex. `plugin_storage_get/set/delete`) → store scopé par `pluginId` sous `plugins/data/<pluginId>/` (format interne — JSON unique ou par clé — laissé à #139, seule la frontière de répertoire est un contrat figé).
- `plugin_uninstall` (`crates/app-tauri/src/plugins.rs:142`) doit purger `plugins/data/<id>/` en plus de `plugins/installed/<id>` + registre (déjà couvert par #135). Les fichiers project-owned écrits par le plugin dans le workspace ne sont **jamais** touchés par l'uninstall.
## Débloque #139
1. Implémenter `ctx.storage` de bout en bout : port domaine + adapter infra scopés à `plugins/data/<pluginId>/`, commandes Tauri, câblage réel dans `loader.ts` (absent aujourd'hui), confinement en esprit identique à #133/#135.
2. Réaligner `hello-plugin` : migrer les compteurs internes (`launches`, `enabled`, `ownerAgentId`) vers `ctx.storage`. Garder au plus un exemple clairement étiqueté "fichier projet réel" via `workspace`/`config`, pas comme pattern par défaut.
3. `sdk/IdeaSDK/README.md` section "Structured Config Documents" à corriger : ne plus donner `.ideai/hello-plugin.json` comme exemple d'état interne, remplacer par un exemple `ctx.storage`, documenter la séparation project-owned/plugin-owned.
4. Preuve requise : test de purge (installer → écrire via `ctx.storage` → désinstaller → `plugins/data/<id>/` disparu) + absence de tout chemin `.ideai/...` dans les exemples SDK par défaut.
Aucun changement de code applicatif dans ce ticket (portée strictement architecture/API, conforme à l'objectif du ticket). Fichier touché : `ARCHITECTURE.md` (§22.2 ajouté).

View File

@ -0,0 +1,33 @@
---
id: "e33fd0ea-e7c6-40dc-a244-f158e44ac4a7"
number: 138
title: "SDK plugins: contrat de persistance plugin-owned hors projet et effacement total à la désinstallation"
status: "qa"
priority: "critical"
sprint: null
links: []
agentRefs: [{"agentId":"b4730d7f-c54d-4736-8a04-c6203aa2fd49","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedBy: {"kind":"agent","agent_id":"b4730d7f-c54d-4736-8a04-c6203aa2fd49"}
createdAt: 1785702748769
updatedAt: 1785703680849
version: 4
---
Le lot #133/#135 traite déjà le confinement du package installé et labsence décritures parasites au runtime, mais il reste un trou produit/API majeur : le SDK public et son exemple `sdk/IdeaSDK/examples/hello-plugin` montrent encore des écritures plugin sous `.ideai/hello-plugin.txt` et `.ideai/hello-plugin.json`, alors que lobjectif utilisateur est un modèle type VSCode où létat propre au plugin ne pollue jamais le projet et disparaît entièrement à la désinstallation.
Objectif de ce ticket : trancher le contrat darchitecture/API, PAS limplémenter.
À décider et documenter :
1. Séparer noir sur blanc les deux familles de données plugin :
- données métier du PROJET que le plugin modifie volontairement dans le workspace utilisateur (autorisées, explicites, relèvent de `workspace.*` / éventuellement `config` quand on touche un vrai fichier du projet) ;
- données PROPRES AU PLUGIN (prefs, cache, dernière sélection, index interne, état UI durable, config interne) qui doivent vivre hors project root, dans un store plugin-scopé sous app data, jamais sous `.ideai/` ni ailleurs dans le repo utilisateur.
2. Dire si `ctx.storage` clé/valeur suffit comme primitive canonique pour cet état plugin-owned, ou sil faut une API publique supplémentaire de document structuré plugin-scopé (ex: JSON app-data du plugin) pour éviter de pousser les auteurs à détourner `ctx.services.config` vers `.ideai/*.json`.
3. Figer le contrat de désinstallation : la suppression du plugin doit aussi supprimer 100% de son état plugin-owned hors projet (storage, éventuels docs/config plugin-scopés, caches internes), sans toucher aux fichiers métier du projet que lutilisateur a explicitement demandé au plugin de modifier.
4. Imposer lalignement doc/exemples SDK : ne plus montrer `.ideai/...` comme emplacement par défaut pour létat interne dun plugin.
Critères dacceptation :
- Une note darchitecture/API explicite distingue « project-owned » vs « plugin-owned ».
- La source de vérité et le cycle de vie du stockage plugin-owned sont écrits noir sur blanc (création, lecture, suppression à luninstall).
- Le ticket précise si une nouvelle API SDK est nécessaire ou si `ctx.storage` devient la voie canonique, et pourquoi.
- Le contrat est compatible avec lexigence utilisateur : plugin désinstallé => plus aucun état propre au plugin, ni dans le projet, ni dans lapp data plugin.

View File

@ -0,0 +1,6 @@
---
issueRef: "#139"
version: 3
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedAt: 1785709504027
---

View File

@ -0,0 +1,17 @@
---
id: "a5bac900-806d-4e13-866f-7ebae686a43d"
number: 139
title: "SDK plugins: aligner lAPI publique et les exemples sur une persistance plugin-owned hors projet"
status: "qa"
priority: "critical"
sprint: null
links: [{"target":"#138","kind":"dependsOn"}]
agentRefs: [{"agentId":"fe887179-933f-47d4-960f-c3b06827f86c","role":"assigned"},{"agentId":"8f7da528-58df-4315-97e9-0562230ecc19","role":"assigned"}]
attachments: []
createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"}
createdAt: 1785702774592
updatedAt: 1785709504027
version: 3
---
Implémenter le contrat décidé en #138. Ce ticket ne doit démarrer quaprès arbitrage architecture, car la forme exacte de lAPI publique peut varier (`ctx.storage` canonique seul, ou nouvelle API publique de document structuré plugin-scopé hors projet).\n\nPérimètre attendu après #138 :\n1. Faire de la voie canonique plugin-owned celle décidée en #138, et retirer lincitation actuelle à écrire létat interne du plugin dans le workspace utilisateur / `.ideai/`.\n2. Mettre à jour `sdk/IdeaSDK/README.md` et `sdk/IdeaSDK/examples/hello-plugin` pour que lexemple de référence nécrive plus `.ideai/hello-plugin.txt` ni `.ideai/hello-plugin.json` comme état interne par défaut.\n3. Si #138 décide quune nouvelle API SDK publique est nécessaire (par ex. document structuré plugin-scopé hors projet), lexposer de bout en bout : types SDK, façade runtime publique, adaptateurs hôte nécessaires, et documentation dusage.\n4. Garantir que la désinstallation du plugin purge aussi létat plugin-owned correspondant, conformément au contrat #138, sans supprimer les fichiers métier du projet que le plugin aurait modifiés explicitement.\n\nCritères dacceptation :\n- Le README SDK sépare explicitement données project-owned vs plugin-owned.\n- Lexemple de référence nemploie plus `.ideai/...` pour stocker son état interne.\n- Si une nouvelle API publique a été décidée en #138, elle est documentée, typée et couverte par des tests.\n- La purge de létat plugin-owned à la désinstallation est prouvée par tests ciblés sur le chemin réellement choisi par #138.\n- Aucun message public du SDK ne laisse entendre que `.ideai/` est le lieu normal de persistance interne dun plugin.

View File

@ -1,6 +0,0 @@
---
issueRef: "#51"
version: 1
updatedBy: {"kind":"user"}
updatedAt: 1783941336721
---

View File

@ -1,15 +0,0 @@
---
id: "6b74084c-0323-4f87-83f0-d9f944c364dc"
number: 51
title: "Clean des session headless"
status: "open"
priority: "medium"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1783941336721
updatedAt: 1783941336721
version: 1
---

View File

@ -1,6 +0,0 @@
---
issueRef: "#63"
version: 1
updatedBy: {"kind":"user"}
updatedAt: 1784098438671
---

View File

@ -1,15 +0,0 @@
---
id: "b5753734-1474-48d5-af6c-4a41b78005dc"
number: 63
title: "Systeme de test de l'UI"
status: "open"
priority: "medium"
sprint: null
links: []
agentRefs: []
createdBy: {"kind":"user"}
updatedBy: {"kind":"user"}
createdAt: 1784098438671
updatedAt: 1784098438671
version: 1
---

View File

@ -1,6 +0,0 @@
---
issueRef: "#66"
version: 2
updatedBy: {"kind":"user"}
updatedAt: 1784193460805
---

View File

@ -1,27 +0,0 @@
---
id: "f805cd9b-8ecd-401a-8f03-665a27fe73cb"
number: 66
title: "Image Docker serveur/client IdeA"
status: "open"
priority: "medium"
sprint: "028179b1-eaf4-41e9-9c1f-7c37125117e6"
links: [{"target":"#65","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedBy: {"kind":"user"}
createdAt: 1784187597004
updatedAt: 1784193460805
version: 2
---
Objectif : livrer une image Docker exécutant le mode serveur/client d'IdeA, bâtie sur le binaire headless `idea-serve` (#65), pas sur le binaire Tauri. Cadré par Architect (suite #13).
Plan de lots (Architect) :
- **L4 — Build web transport HTTP** : produire les assets client Vite en `VITE_TRANSPORT=http` (npm, jamais pnpm), vérifier qu'aucun import Tauri ne fuit dans ce mode, packager dans `/usr/share/idea/web`.
- **L5 — Docker runtime** : Dockerfile multi-stage (build Rust headless + build frontend + runtime minimal Debian/Ubuntu selon deps PTY/process). Défauts : `IDEA_APP_DATA_DIR=/data`, `--listen 0.0.0.0:17373`, `--web-root /usr/share/idea/web`. Volumes `/data` (app-data : projects/profiles/templates/tasks/logs) et `/workspace` (projets manipulés par les agents). Entrypoint `idea-serve`. Healthcheck HTTP local. Conteneur reste HTTP interne ; reverse proxy TLS externe obligatoire en prod distante (`--allow-remote`/`--public-origin https`/`--trust-reverse-proxy`, doc B8). Pas de TLS applicatif V1.
- **L6 — Agents CLI en conteneur** : décider image minimale (profils détectés au runtime, exécutables attendus dans PATH) vs image `idea-server-agents` (CLIs redistribuables installées si licence OK). Seed profils compatibles conteneur. Documenter env (OPENAI_API_KEY, ANTHROPIC_API_KEY, vars opencode) et montages de credentials. Tester au moins un agent bout en bout. Hors périmètre : installer automatiquement des CLIs propriétaires sans validation licence.
Lock app-data-dir : peu critique en Docker mono-conteneur mono-writer ; multi-conteneurs sur le même /data explicitement hors support sans lock distribué.
Hors périmètre V1 : Kubernetes, multi-tenant, auth externe, TLS intégré.
Dépend de #65 (binaire headless).

View File

@ -1,3 +1,3 @@
{
"nextNumber": 131
"nextNumber": 140
}

View File

@ -713,19 +713,6 @@
},
"updatedAt": 1783959345112
},
{
"issueRef": "#51",
"path": "51",
"title": "Clean des session headless",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"createdBy": {
"kind": "user"
},
"updatedAt": 1783941336721
},
{
"issueRef": "#52",
"path": "52",
@ -841,19 +828,6 @@
},
"updatedAt": 1784406936659
},
{
"issueRef": "#63",
"path": "63",
"title": "Systeme de test de l'UI",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"createdBy": {
"kind": "user"
},
"updatedAt": 1784098438671
},
{
"issueRef": "#64",
"path": "64",
@ -882,20 +856,6 @@
},
"updatedAt": 1784210420516
},
{
"issueRef": "#66",
"path": "66",
"title": "Image Docker serveur/client IdeA",
"status": "open",
"priority": "medium",
"sprint": "028179b1-eaf4-41e9-9c1f-7c37125117e6",
"assignedAgentIds": [],
"createdBy": {
"kind": "agent",
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
},
"updatedAt": 1784193460805
},
{
"issueRef": "#67",
"path": "67",
@ -1706,6 +1666,147 @@
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
},
"updatedAt": 1785669830933
},
{
"issueRef": "#131",
"path": "131",
"title": "Configurer l'effort par agent avec presets adaptatifs selon le profil AI",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"createdBy": {
"kind": "agent",
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
},
"updatedAt": 1785687359220
},
{
"issueRef": "#132",
"path": "132",
"title": "Ajouter un outil MCP IdeA pour éditer le contexte projet global",
"status": "open",
"priority": "medium",
"sprint": null,
"assignedAgentIds": [],
"createdBy": {
"kind": "agent",
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
},
"updatedAt": 1785687502830
},
{
"issueRef": "#133",
"path": "133",
"title": "Plugins: contrat de service des assets idea-plugin:// (multi-fichiers ESM) & confinement",
"status": "qa",
"priority": "high",
"sprint": null,
"assignedAgentIds": [
"b4730d7f-c54d-4736-8a04-c6203aa2fd49"
],
"createdBy": {
"kind": "agent",
"agent_id": "b4730d7f-c54d-4736-8a04-c6203aa2fd49"
},
"updatedAt": 1785703664966
},
{
"issueRef": "#134",
"path": "134",
"title": "Plugins: servir tout fichier confiné du package installé (fix racine multi-fichiers ESM)",
"status": "qa",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"fe887179-933f-47d4-960f-c3b06827f86c"
],
"createdBy": {
"kind": "agent",
"agent_id": "b4730d7f-c54d-4736-8a04-c6203aa2fd49"
},
"updatedAt": 1785707290184
},
{
"issueRef": "#135",
"path": "135",
"title": "Plugins: audit confinement install & désinstallation 100% propre",
"status": "qa",
"priority": "high",
"sprint": null,
"assignedAgentIds": [
"ab328d90-c307-4771-a3b6-6c56089c8506"
],
"createdBy": {
"kind": "agent",
"agent_id": "b4730d7f-c54d-4736-8a04-c6203aa2fd49"
},
"updatedAt": 1785708498776
},
{
"issueRef": "#136",
"path": "136",
"title": "SDK plugins: aligner doc/exemple sur le support multi-fichiers ESM",
"status": "qa",
"priority": "high",
"sprint": null,
"assignedAgentIds": [
"8f7da528-58df-4315-97e9-0562230ecc19"
],
"createdBy": {
"kind": "agent",
"agent_id": "b4730d7f-c54d-4736-8a04-c6203aa2fd49"
},
"updatedAt": 1785708478849
},
{
"issueRef": "#137",
"path": "137",
"title": "QA: validation end-to-end plugin multi-fichiers ESM (chargement + désinstallation propre)",
"status": "open",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"ab328d90-c307-4771-a3b6-6c56089c8506"
],
"createdBy": {
"kind": "agent",
"agent_id": "b4730d7f-c54d-4736-8a04-c6203aa2fd49"
},
"updatedAt": 1785702681696
},
{
"issueRef": "#138",
"path": "138",
"title": "SDK plugins: contrat de persistance plugin-owned hors projet et effacement total à la désinstallation",
"status": "qa",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"b4730d7f-c54d-4736-8a04-c6203aa2fd49"
],
"createdBy": {
"kind": "agent",
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
},
"updatedAt": 1785703680849
},
{
"issueRef": "#139",
"path": "139",
"title": "SDK plugins: aligner lAPI publique et les exemples sur une persistance plugin-owned hors projet",
"status": "qa",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"fe887179-933f-47d4-960f-c3b06827f86c",
"8f7da528-58df-4315-97e9-0562230ecc19"
],
"createdBy": {
"kind": "agent",
"agent_id": "a6c6ea12-bfc6-4bdc-8031-324102dfa34d"
},
"updatedAt": 1785709504027
}
]
}