From 7c722dae9139a8f80705505cd98c49666279f5a7 Mon Sep 17 00:00:00 2001 From: Arthur Ria Date: Mon, 24 Aug 2026 16:33:11 +0200 Subject: [PATCH] Passation : prompt autoportant pour le lot 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute docs/handoff-lot1.md, le prompt à donner à une nouvelle session Claude Code pour implémenter le lot 1 de la roadmap. Il vivait jusqu'ici dans un dossier temporaire de session, donc perdable. Contient la phase 0 de vérifications préalables (les quatre contextes de requête, le périmètre applications, le champ ClientModule), les trois correctifs avec leurs preuves et leurs vérifications attendues, et les consignes de livraison. Document à usage unique : à supprimer une fois le lot 1 livré, la référence durable restant ROADMAP.md. Co-Authored-By: Claude Opus 5 --- docs/README.md | 1 + docs/handoff-lot1.md | 268 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 docs/handoff-lot1.md diff --git a/docs/README.md b/docs/README.md index 2b2ba65..2b3bbae 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ Documents de référence sur EasyWMS et ses API, conservés dans le dépôt pour | Fichier | Nature | |---|---| +| [handoff-lot1.md](handoff-lot1.md) | **Passation** — prompt autoportant pour le lot 1 de la [roadmap](../ROADMAP.md). À supprimer une fois le lot livré | | [logs.md](logs.md) | **Rédigé pour ce projet** — accès aux logs WMS, outils, limites | | [ad-api-validation.md](ad-api-validation.md) | **Rédigé pour ce projet** — campagne de validation curl des 19 types AD testés (17 valides) | | [api/Application Service API Reference.md](api/Application%20Service%20API%20Reference.md) | Référence de l'API ApplicationService | diff --git a/docs/handoff-lot1.md b/docs/handoff-lot1.md new file mode 100644 index 0000000..74cc72e --- /dev/null +++ b/docs/handoff-lot1.md @@ -0,0 +1,268 @@ + + +# Passation — lot 1 + +> **Comment s'en servir.** Ouvrir une nouvelle session Claude Code dans +> `D:\GIT\_PERSO\mcp-wms-api` et lui donner tout ce qui suit, ou simplement : +> « Lis `docs/handoff-lot1.md` et exécute-le. » +> +> Rédigé le 24/08/2026. Toutes les mesures citées ont été prises contre le WMS +> réel (profil `LIMAGRAIN`, tenant `LIMAGRAI2512`). + +--- + +Tu interviens sur le dépôt `D:\GIT\_PERSO\mcp-wms-api` : un serveur MCP (Node.js, CommonJS) qui donne à Claude un accès en lecture à un WMS EasyWMS de Mecalux, via ses API REST uniquement. Lis `CLAUDE.md`, `DECISIONS.md` et `ROADMAP.md` avant de coder — ils contiennent les conventions et les pièges déjà constatés en production. + +Ta mission : implémenter le **lot 1** décrit dans `ROADMAP.md`. Trois correctifs indépendants, tous reproduits contre le WMS réel le 24/08/2026. Ne fais rien du lot 2 ni du lot 3. + +## Contexte matériel + +Le profil `LIMAGRAIN` du `.env` local est fonctionnel et pointe sur un WMS joignable. `npm test` passe 4/4 aujourd'hui — c'est ta baseline, il doit continuer à passer. + +Deux commandes de vérification que tu réutiliseras : + +```bash +npm test +``` + +```bash +printf '%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node src/index.js +``` + +Le serveur parle JSON-RPC sur stdin/stdout. Pour appeler un outil, ajoute une ligne `{"jsonrpc":"2.0","id":N,"method":"tools/call","params":{"name":"...","arguments":{...}}}` après la notification `initialized`. + +## Contraintes non négociables + +1. **`console.error()` uniquement, jamais `console.log()`.** stdout est réservé au JSON du protocole MCP ; une seule écriture parasite casse la session Claude Desktop. Voir D6. +2. **Un outil ne lève jamais d'exception hors du wrapper.** Toute erreur revient en `{ success: false, error, tool }` avec `isError: true`. +3. **Les messages d'erreur sont lus par un agent, pas par un humain** : ils doivent dire quoi faire ensuite. +4. **Aucun accès base de données** (D1). Tout passe par les API REST. +5. Préfixer les logs par composant : `[Server]`, `[API]`, `[Workflow]`, `[WorkflowTools]`… +6. Ne touche pas à `.env` (ignoré par git, contient des credentials réels). + +--- + +## Phase 0 — Vérifications avant de coder + +**À faire en premier, avant toute modification de code.** Ces deux points +conditionnent des évolutions du lot 4 ; ils ne sont pas à implémenter +maintenant, mais tu dois les vérifier pour ne pas coder le lot 1 d'une manière +qui les bloquerait, et pour confirmer ou infirmer les mesures ci-dessous. + +Aucune modification de code dans cette phase. Uniquement des appels en lecture. + +### V1 — Modèle de données : Reading ou Writing ? + +`QueryType` est figé à `0` (Reading) en dur dans `src/services/api-service.js` +(`executeQuery` ~ligne 249, `executeScalarQuery` ~ligne 286). Aucun outil +n'expose de bascule. + +Or `QueryContextType` a **quatre** valeurs, pas deux. Mesures déjà faites sur +`LIMAGRAI2512` — confirme-les : + +| Valeur | Contexte | Résultat mesuré | +|---|---|---| +| `0` | Reading (`ApplicationReadingContext`) | opérationnel, seul utilisé | +| `1` | Writing (`ApplicationWritingRepository`) | **opérationnel** — `Context.Products` répond | +| `2` | DataWarehouse | non configuré : `Could not resolve serviceType 'IDataWarehouse…'` | +| `3` | Metrics (`ApplicationMetricDataContext`) | contexte présent, modèle non exploré | + +Astuce de diagnostic à retenir : le message d'erreur **nomme le contexte** +utilisé. C'est le moyen le plus rapide de savoir quel `QueryType` a réellement +servi. + +Rappel D3 : en `QueryType: 1` les champs de statut sont des énumérations, donc +`== "Release"` échoue. Le défaut devra rester `0`. + +### V2 — Applications : le MCP ne voit-il que `EasyWMS` ? + +Constat à vérifier : `Application` vient de `WMS_APPLICATION` dans `.env`, +partagé par tous les profils, sans surcharge par appel ni paramètre d'outil. + +`POST /AD/api/Application/GetAll` déclare **9 applications** sur le tenant +`LIMAGRAI2512`. Mesures à confirmer : + +| Application | Workflows | Queries | Entities | +|---|---:|---:|---:| +| EasyWMS | 4012 | 2239 | 338 | +| **CustomApp** | **153** | **54** | **11** | +| AGV | 71 | 14 | 5 | +| Notifications | 26 | 35 | 24 | +| GalileoFaults | 9 | 20 | 24 | +| Common | 1 | 7 | 25 | + +`CustomApp` porte le spécifique client (workflows et entités préfixés `CST_`) — +exactement ce qu'on cherche en debug, et invisible aujourd'hui. + +Deux sous-questions, de difficulté très différente : + +**a) API AD.** L'application est un simple champ du payload +`[application, tenant, pageSize, offset]`. Mesuré : `["CustomApp", tenant, 5, 0]` +sur `/Workflow/GetByApplication` renvoie bien des workflows `CST_`. Confirme-le, +et note que les caches de `ad-service` et `workflow-service` sont aujourd'hui +indexés **sans** l'application : les rendre multi-applications imposera une clé +de cache incluant l'application, sinon les résultats se mélangeront. + +**b) QueryExecute — déjà tranché, ne le réinvestigue pas.** Le champ +`Application` **ne partitionne pas** le contexte de lecture : `Context.AgvTasks` +(entité de l'application AGV) répond aussi bien avec `Application: "AGV"` qu'avec +`Application: "EasyWMS"`. Le contexte est commun au tenant. + +Et les entités `CustomApp` ne sont interrogeables **dans aucun contexte** : +les 11 entités `CST_` testées sous les quatre `QueryType`, au singulier et au +pluriel, échouent toutes ; `Metadata/Entities` et `Metadata/EntitiesAll` +renvoient 0 entité pour `CustomApp` ; aucune n'est marquée `isDataWarehouse`. +Ce sont des définitions EasyBuilder sans projection requêtable. + +Deux conséquences à retenir, elles portent sur le **lot 2** : + +- la table de résolution de L2.1 devra agréger le Metadata de **toutes** les + applications (`?applicationName=` par application : 232 + 20 + 6 + …), pas du + seul `EasyWMS` ; +- inutile d'ajouter un paramètre `application` à `QueryExecute`, il ne changerait + rien. Le paramètre n'a de sens que sur les outils **AD et workflow**. + +Contente-toi de confirmer ces mesures si tu veux, mais **ne relance pas +d'investigation dessus** : c'est du temps perdu. + +### V3 — Champs de `QueryExecute` que le MCP n'envoie pas + +La référence officielle de l'API (`https:///ApplicationService/Help`) +documente des champs inexploités. Un seul te concerne pour le lot 1 : + +**`ClientModule`.** Le MCP ne l'envoie pas, donc ses requêtes apparaissent dans +les logs du WMS sous `Execute error. Client: GNA` — le client OAuth partagé, +indistinguable du vrai client GNA. Le champ est accepté (vérifié). + +**Tu peux le renseigner dans le cadre de L1.1**, c'est cohérent avec l'objectif +de traçabilité et ça tient en une ligne : envoyer `ClientModule: "MCP-WMS"` dans +les corps de `QueryExecute` et `QueryScalarExecute`. Vérifie ensuite dans les +logs du WMS (`search_logs`) que les requêtes du MCP apparaissent bien sous ce +nom. Si la vérification n'est pas concluante, retire le changement et signale-le +plutôt que de le laisser non vérifié. + +Les autres champs (`Parameters`, `CommandTimeout`, `QueryId`, +`QueryExecuteStream`) sont consignés en L4.5 : **n'y touche pas**. + +### Ce que tu fais de ces vérifications + +- Rapporte les résultats dans ton compte-rendu final, en distinguant ce que tu + as mesuré de ce que tu supposes. +- Mets à jour la section **Lot 4** de `ROADMAP.md` si tes mesures diffèrent, ou + si tu as tranché la question (b). +- **N'implémente ni V1 ni V2.** C'est le lot 4. Seul `ClientModule` (V3) entre + dans ton périmètre, et seulement s'il est vérifiable. +- Deux incidences concrètes sur ta façon de coder le lot 1, et elles seules : + - **L1.1** : inclure `Application` et `QueryType` dans le payload rapporté par + l'erreur enrichie. Sans ça, un futur bug multi-applications sera aussi + opaque que les 500 d'aujourd'hui. + - **L1.2** : la table de routage ne doit rien présumer de la signature des + outils — ajouter demain un paramètre `application` ou `query_type` ne doit + pas la toucher. + +--- + +## L1.1 — Remonter le détail des erreurs HTTP + +**Problème.** Toute erreur d'API se résume aujourd'hui à `Request failed with status code 500`. Or le WMS renvoie déjà le diagnostic complet dans le corps de la réponse. Vérifié : + +``` +POST /QueryExecute Expression: "Context.Container.OrderBy(z => z.Id)" +→ 500, content-type: application/json +→ {"ClassName":"System.AggregateException","Message":"Compile Error: ... + 'ApplicationReadingContext' ne contient pas de définition pour 'Container' ..."} +``` + +Ce corps est jeté par le `catch`. Une session Cowork a passé une heure sur des 500 dont la cause était écrite noir sur blanc dans la réponse HTTP. + +**À faire.** Dans `src/services/api-service.js`, aux blocs `catch` de `post()` (ligne ~164) et `get()` (ligne ~212), enrichir l'erreur propagée avec : + +- le statut HTTP, +- l'URL complète et le verbe, +- le payload envoyé, +- le corps de la réponse, **tronqué à ~2000 caractères**, +- le message d'origine. + +Points d'attention : + +- Le corps peut être un objet ou une chaîne. S'il est structuré, `Message` (et `InnerException.Message`) portent l'essentiel — privilégie-les au `JSON.stringify` intégral, qui noie l'information dans du bruit `WatsonBuckets` / `HResult`. +- **Ne casse pas le retry 401 existant** : le rejeu après refresh de token doit rester intact, et une erreur pendant le rejeu doit elle aussi être enrichie. +- Le point de passage est unique : ne duplique pas cette logique dans les modules `tools/`. +- N'inclus jamais le token Bearer ni un mot de passe dans le message enrichi. + +**Vérification attendue.** Appeler `query_wms_entities` avec `entity_type: "Container"` (singulier — volontairement faux) doit faire apparaître `ApplicationReadingContext ne contient pas de définition pour 'Container'` dans la réponse de l'outil. + +--- + +## L1.2 — Fiabiliser le routage des outils + +**Problème.** `src/index.js` route les appels d'outils par **préfixe de nom** dans une cascade de `else if` (handler `tools/call`, ~ligne 167). Deux outils sont listés dans `tools/list` mais n'atteignent jamais leur module : + +| Outil | Cause | Erreur reproduite | +|---|---|---| +| `get_entity_metadata` | `startsWith('get_entity_')` le capte pour `wmsQueryTools`, dont la branche vient **avant** la sienne | `Unknown WMS query tool: get_entity_metadata` | +| `list_log_files` | la branche logs teste `includes('_logs')`, or le nom contient `_log_files` | `Unknown tool: list_log_files` | + +**À faire.** Remplacer la cascade par une **table explicite `nom d'outil → module`**, construite au démarrage en parcourant les `listTools()` des 8 modules de `src/tools/`. Le dispatch devient un simple lookup. + +Exigences : + +- Si deux modules déclarent le même nom d'outil, **échouer au démarrage** avec un message nommant les deux modules — c'est un bug de développement, pas un cas d'exécution. +- Un outil listé est un outil routé, par construction : c'est tout l'intérêt du changement. +- Le wrapper d'erreur existant du handler `tools/call` doit être conservé tel quel. +- Les 23 outils doivent rester exposés, aux mêmes noms. Ce n'est pas une refonte de l'API : aucun renommage. + +**Vérification attendue.** `get_entity_metadata` et `list_log_files` répondent tous les deux, et un `tools/list` renvoie toujours 23 outils. Vérifie aussi que rien n'a régressé sur les 21 autres : chaque nom listé doit résoudre vers un module. + +--- + +## L1.3 — Corriger les projections de champs des workflows + +**Problème.** L'API Application Dictionary renvoie ses champs **en minuscules**. Les clés réelles d'un objet workflow, relevées en direct : + +``` +$id, validFrom, validTo, versionId, version, lockInfo, id, name, +commonInfo, applicationName, dataType, data +``` + +Deux endroits supposent autre chose : + +1. `src/tools/workflow-tools.js`, fonction `searchWorkflows` (~ligne 110) : projette `w.Id`, `w.Code`, `w.Name`, `w.Category`, `w.Description`, `w.Version`, `w.Created`, `w.Modified`. Tous valent `undefined`, `JSON.stringify` les supprime — d'où **50 objets vides `{}`** alors que `count` est correct. +2. `src/services/workflow-service.js` (~lignes 123, 167, 187) : lit `w.category || w.Category`. Ces deux clés n'existent pas. Conséquence mesurée : `list_workflow_categories` renvoie **0 catégorie** et `getWorkflowStats()` classe les **4012** workflows en `Uncategorized`. + +C'est une application incomplète du piège D5 : le service gère bien la double casse pour `name`/`id` (d'où un filtrage correct), mais la projection du tool et la notion de catégorie ont été écrites sur des champs imaginaires. + +**À faire.** + +- Aligner la projection de `searchWorkflows` sur les clés réelles, en conservant le repli `w.id || w.Id` de D5. +- `Code`, `Description`, `Created`, `Modified` n'existent dans **aucune** casse : ne les projette pas plutôt que d'exposer des `null` permanents. Si tu juges qu'un équivalent existe dans `commonInfo`, inspecte-le d'abord et décide sur pièces. +- Pour la catégorie : le seul champ approchant est `applicationName`, **mais il vaut `EasyWMS` pour tous les workflows**. La notion de catégorie n'a donc aucun support dans les données. Traite-la honnêtement — mappe sur `applicationName` en l'assumant, ou retire la notion — mais **n'invente pas une taxonomie** en dérivant des catégories d'un préfixe de nom. Le paramètre `category` de `search_workflows` doit rester cohérent avec ce que tu décides, et `list_workflow_categories` ne doit pas mentir sur ce qu'il renvoie. +- Vérifie au passage `get_workflow_details` : il renvoie l'objet brut, il ne devrait pas être affecté — confirme-le plutôt que de le supposer. + +**Vérification attendue.** `search_workflows({query: "stacker"})` renvoie des objets peuplés avec au minimum `id` et `name` (attendu : des noms comme `StackerCrane_SortPickingTasksBalancingOutputs_PR`). `list_workflow_categories` renvoie un résultat cohérent avec la réalité des données, et non `0`. + +--- + +## Méthode + +Fais la **phase 0** d'abord, sans toucher au code. Puis les trois correctifs dans l'ordre : L1.1 en premier, parce qu'il rend les deux autres plus faciles à diagnostiquer. + +**Vérifie contre le WMS réel, pas seulement par lecture de code.** Chaque correctif a une vérification attendue décrite ci-dessus ; exécute-la. Les appels concernés sont en lecture seule, aucun risque d'écriture WMS. Si un point ne peut pas être vérifié, dis-le explicitement plutôt que de le déclarer fait. + +Avant de conclure, relance la baseline complète : + +- `npm test` → 4/4, +- handshake MCP → 23 outils, 6 resources, +- les trois vérifications attendues ci-dessus. + +## Livraison + +- Un commit par correctif (L1.1, L1.2, L1.3), messages en français, pour que chacun soit revertable seul. Stage chemin par chemin, **pas de `git add -A`**. +- Termine les messages de commit par `Co-Authored-By: Claude Opus 5 `. +- **Ne pousse pas** — la branche `main` a un remote, le push est une décision du propriétaire du dépôt. +- Mets à jour `ROADMAP.md` : retire le lot 1 une fois livré. +- Ajoute à `DECISIONS.md` l'entrée **D22** (routage par table explicite : pourquoi le routage par préfixe a été abandonné, avec les deux collisions comme preuve). Suis le format des entrées existantes. **D21 est réservée au lot 2, ne l'utilise pas.** +- Si tu découvres une anomalie hors périmètre, **ne la corrige pas** : ajoute-la à `ROADMAP.md` et signale-la dans ton compte-rendu. + +Dans ton compte-rendu final, indique ce que tu as vérifié en exécution et ce que tu n'as pas pu vérifier.