diff --git a/ROADMAP.md b/ROADMAP.md index 9775183..2726686 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -125,6 +125,19 @@ plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et (~70 000). Comportement antérieur au lot 4 (les grosses définitions `EasyWMS` sont dans le même cas) : à borner et signaler (`truncated`/`hint`, D24) dans un lot futur. +- **Une ligne Writing est énorme** (mesuré le 25/08/2026, révision du lot 4). + `call_query_api("Products", query_type: 1, limit: 1)` → **95 288 caractères + pour une seule ligne** : le modèle Writing sérialise l'agrégat complet + (navigations, `$id`…), là où la même ligne en Reading pèse ~4 500 caractères. + Au-delà du seuil de rejet client (~70 000, D24) dès `limit: 1`. À traiter + avec le point `get_workflow_details` ci-dessus : même famille D24 (borner et + signaler), et l'avertissement mérite d'apparaître dans la description du + paramètre `query_type`. +- **Champ `tool` absent des erreurs construites localement** (constaté le + 25/08/2026). Les `catch` locaux d'`api-tools.js` renvoient + `{ success: false, error }` sans le champ `tool` du contrat (convention 3) — + motif antérieur au lot 4. Cosmétique : soit laisser l'erreur remonter au + wrapper (qui ajoute `tool`), soit l'ajouter aux enveloppes locales. - **Déploiement SSH sur la VM** : l'exécutable est validé, la configuration SSH reste à faire. - **Historique des shipment templates** : hors de portée, les logs concernés diff --git a/docs/handoff-lot4.md b/docs/handoff-lot4.md deleted file mode 100644 index 26251ee..0000000 --- a/docs/handoff-lot4.md +++ /dev/null @@ -1,240 +0,0 @@ -# Passation — lot 4 (L4.0, L4.1, L4.2) - -Tu travailles sur `mcp-wms-api` : un serveur MCP (Node.js, CommonJS, stdio) qui -donne à Claude un accès en lecture à un WMS EasyWMS (Mecalux) via ses API REST. -Lis [../CLAUDE.md](../CLAUDE.md) et [../DECISIONS.md](../DECISIONS.md) avant de -toucher au code. - -**Mission** : trois blocs de [../ROADMAP.md](../ROADMAP.md) — -corriger la resource `api://catalog` qui enseigne le piège D3 (L4.0), -exposer `query_type` sur les outils de requête (L4.1), et rendre les -applications autres qu'`EasyWMS` accessibles via un paramètre `application` -sur les outils AD et workflow (L4.2). - -**Hors périmètre** : -- **L4.3** (identifier le MCP dans les logs) : dépend d'une configuration côté - WMS, hors de portée d'une session de codage. -- **L4.4** (API WorkflowLog) et l'exploration du contexte **Metrics** : ce sont - des investigations, elles feront l'objet d'une phase séparée dont le livrable - sera un rapport, pas du code. N'y touche pas. -- **L4.5** (`Parameters`, `QueryExecuteStream`…) : consigné, pas ce lot. -- **Aucun nouvel outil** : le compte reste à 23. Les deux correctifs sont des - paramètres sur des outils existants. -- Ne pousse rien (`git push` interdit), ne touche pas au `.env`, n'appelle - jamais `execute_command` (il écrit dans le WMS). - ---- - -## Contexte matériel - -- Profil de travail : `LIMAGRAIN` (par défaut), host `10.255.255.2`, tenant - `LIMAGRAI2512`. -- **Le profil `AD` est cassé et c'est diagnostiqué — ne le réinvestigue pas** - (tenant introuvable côté STS, point ouvert de la ROADMAP). `npm test -- --all` - échoue sur AD ; la baseline se mesure avec `npm test` (profil par défaut), - attendu **4/4, code de sortie 0**. -- Baseline protocolaire à préserver : **23 outils**, **6 resources**, aucune - écriture sur stdout hors JSON-RPC. - -Handshake + comptages : - -```bash -printf '%s\n%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"}' '{"jsonrpc":"2.0","id":3,"method":"resources/list"}' | node src/index.js 2>/dev/null | node -e "let b='';process.stdin.on('data',d=>b+=d).on('end',()=>{for(const l of b.split('\n').filter(Boolean)){const m=JSON.parse(l);if(m.id===2)console.log('tools:',m.result.tools.length);if(m.id===3)console.log('resources:',m.result.resources.length);}});" -``` - -Appeler un outil via le protocole (la seule preuve qu'un outil marche) : - -```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/call","params":{"name":"NOM","arguments":{}}}' | node src/index.js 2>/dev/null -``` - -Sonder le WMS directement (court-circuite les outils) : - -```bash -node -e " -require('dotenv').config(); -const pm=require('./src/config/profile-manager'); pm.loadProfiles(); -const api=require('./src/services/api-service').getInstance(); -(async()=>{ /* … */ })();" -``` - -## Contraintes non négociables - -1. `console.error()` uniquement — une écriture sur stdout casse Claude Desktop - (D6). -2. Un outil ne plante jamais le serveur : erreurs en réponse structurée - `{ success: false, error, tool }`, `isError: true` (wrapper de - `src/index.js`). -3. Messages d'erreur actionnables. -4. Aucun accès base de données (D1). -5. **D23** : le wrapper valide les **noms** de paramètres et les **requis** - contre les schémas — déclare `query_type` et `application` dans les - `inputSchema`, sinon le wrapper les rejettera. Le wrapper ne valide **pas - les valeurs** : les gardes de valeur (`query_type` hors 0-3, etc.) vivent - dans le code de l'outil. -6. **D8** : tout état lié au tenant s'invalide par `profileManager.onSwitch()`, - jamais à la main depuis un autre module. Les caches modifiés en L4.2 - restent abonnés. -7. **D24** : toute sortie potentiellement volumineuse est bornée et signalée - (`truncated`/`hint`). Les pages de workflows CustomApp (153) tiennent - largement ; ne l'oublie pas si tu exposes des listes plus larges. - -**Numéros de décision réservés** : **D25** = exposition de `query_type` (son -rapport à D3), **D26** = paramètre `application` et clés de cache. Vérifie que -D24 est bien la dernière décision avant d'écrire. - ---- - -## Phase 0 — Confirmer les mesures - -Toutes rejouées le 25/08/2026 (LIMAGRAI2512) par la sonde directe. À -**confirmer**, pas à réinvestiguer : - -| Sonde | Constaté le 25/08 | -|---|---| -| `POST /QueryExecute` `{Application:'EasyWMS', QueryType:1, Expression:'Context.Products.OrderBy(z => z.Id)', Take:1}` | **OK, 1 ligne** — le modèle Writing répond | -| Même appel avec `QueryType:3` | HTTP 500 : `'ApplicationMetricDataContext' ne contient pas de définition pour 'Products'` — le contexte Metrics existe, son modèle est autre (ne l'explore pas, hors périmètre) | -| `QueryType:2` | non configuré sur ce tenant (`Could not resolve serviceType 'IDataWarehouse…'`, mesure du 24/08) | -| `POST /Workflow/GetByApplication` payload `['CustomApp', tenant, 5, 0]` (API AD) | **5 workflows** sous la clé **`entities`** de la réponse : `CST_SendRejectContainersToPK`, `Helper_ContainerByCode`, … | - -Points déjà tranchés, **ne les réinvestigue pas** (mesures des 24-25/08 dans la -ROADMAP) : -- Le champ `Application` de `QueryExecute` **ne partitionne rien** (contexte - commun au tenant) : inutile de le paramétrer côté requêtes. -- Les 11 entités `CustomApp` ne sont requêtables dans **aucun** contexte — - l'API AD est le seul accès au spécifique client. -- `ClientModule` est sans effet (L4.3). - ---- - -## L4.0 — Corriger la resource `api://catalog` - -**Problème.** `src/resources/apis.js` (resource lue par les sessions Claude) -documente un exemple `QueryExecute` avec `"QueryType": 1` — exactement ce que -D3 interdit de recopier —, un `.Select(z => z)` dans l'expression (contraire à -la répartition expression/options), et l'entité fantôme `Aliases` (corrigée -partout ailleurs au lot 2). - -**À faire.** Exemple avec `QueryType: 0` et expression sans `Select`, -`Aliases` → `Alias`, renvoi vers `get_entity_metadata` comme source de vérité -sur les entités. Relis toute la resource pendant que tu y es — signale (sans -forcément corriger) toute autre affirmation contredite par DECISIONS.md. - -**Vérification attendue** : lire `api://catalog` via `resources/read` en -protocole ; la sortie ne contient plus ni `"QueryType": 1` ni `Aliases`. - -## L4.1 — Exposer `query_type` sur les outils de requête - -**Problème.** `QueryType` est figé à `0` en dur à deux endroits : -`src/services/api-service.js:333` (`executeQuery`) et `:370` -(`executeScalarQuery`). Le modèle Writing — opérationnel, mesuré — est -inatteignable. - -**À faire.** -- Paramètre `query_type` (entier, défaut `0`) sur **trois outils** : - `call_query_api`, `query_wms_entities`, `count_wms_entities`. Pas sur - `get_entity_schema` ni `search_wms_data` — ils restent des raccourcis - Reading. -- Garde de valeur **dans le code** (le wrapper D23 ne valide pas les - valeurs) : hors `0..3` → erreur locale actionnable avant tout réseau, - nommant les quatre contextes. `2` et `3` sont transmis tels quels : le WMS - répond, et depuis L1.1 son diagnostic remonte entier. -- **D3 reste la règle par défaut** : en Writing les statuts sont des - énumérations, `== "Release"` échoue. La bascule est un opt-in explicite — - les descriptions d'outils doivent porter l'avertissement. Acte le rapport - D3/`query_type` en **D25**. -- **Interaction avec le resolver (attention, c'est le point délicat)** : la - table de résolution est construite sur le Metadata **Reading**. Quand - `query_type != 0` : un nom qui se résout se résout normalement (`Products` - marche en Writing, mesuré) ; un nom **inconnu** du Reading ne doit **pas** - être bloqué en dur — passe-le tel quel avec un `warning` dans la réponse - (même mécanique que le repli « Metadata injoignable » existant), car le - modèle Writing/Metrics peut contenir des entités hors Reading. - -**Vérification attendue** (protocole, LIMAGRAIN) : -- `call_query_api("Products", query_type: 1, limit: 1)` → succès, 1 ligne. -- `call_query_api("Products", query_type: 3)` → erreur structurée contenant - `ApplicationMetricDataContext`. -- `call_query_api("Products", query_type: 7)` → erreur locale avant réseau - (aucun `[API] POST` dans stderr), nommant les valeurs valides. -- `query_wms_entities("Container", limit: 1)` sans `query_type` → strictement - le comportement d'aujourd'hui (résolu `Containers`, Reading). -- Un nom inconnu avec `query_type: 1` → transmis tel quel avec `warning`, pas - d'échec local. -- `count_wms_entities("Product", query_type: 1)` → un nombre (~51 160). - -## L4.2 — Paramètre `application` sur les outils AD et workflow - -**Problème.** `application` vient du profil (`WMS_APPLICATION`, partagé) : -`src/services/ad-service.js:85` et `src/services/workflow-service.js:50`. Le -MCP n'interroge donc jamais que `EasyWMS`. Or **CustomApp porte le spécifique -client** (153 workflows `CST_*` sur ce tenant) — précisément ce qu'on cherche -en debug — et 9 applications sont déclarées (`POST /AD/api/Application/GetAll`, -payload `null`). - -**À faire.** -- Paramètre `application` (défaut : l'application du profil, donc comportement - inchangé sans lui) sur : `get_ad_elements`, `search_ad_elements`, - `get_ad_element_details`, `search_workflows`, `get_workflow_details`, - `list_workflow_categories`. -- **Clés de cache** : `ad-service` passe de « un cache par type » à « un cache - par (application, type) » ; `workflow-service` de « un cache global » à « un - cache par application ». Sans ça, un appel CustomApp pollue le cache EasyWMS. - L'abonnement `onSwitch()` continue d'invalider **tout** (D8). Acte le contrat - en **D26**. -- `get_application_summary` doit refléter les nouvelles clés (état par - application et par type) sans exploser en volume (D24). -- `list_workflow_categories` : adosse la liste à `Application/GetAll` (9 - applications, comptes réels par application) plutôt qu'aux `applicationName` - du seul cache EasyWMS. La note de L1.3 reste vraie — il n'existe pas de champ - catégorie ; la réponse liste des applications et le dit. Le paramètre - `category` de `search_workflows` (filtre sur `applicationName`) doit rester - cohérent avec le nouveau paramètre `application` — documente leur - articulation dans les descriptions, ne casse ni l'un ni l'autre. -- **Pente naturelle interdite** : ne précharge pas les 9 applications (le type - `Resource` pèse 29 374 éléments sur la seule EasyWMS). Le chargement reste - paresseux, par application effectivement demandée. - -**Vérification attendue** (protocole, LIMAGRAIN) : -- `search_workflows("CST_", application: "CustomApp")` → objets peuplés - (`CST_SendRejectContainersToPK`…). -- `get_ad_elements("Workflow", application: "CustomApp")` → éléments `CST_*`. -- Séquence **séquentielle** EasyWMS → CustomApp → EasyWMS sur - `search_workflows` : les comptes restent distincts (~4 012 vs 153), aucune - pollution croisée ; `get_application_summary` montre les deux caches. -- Sans paramètre `application` → comportement strictement inchangé. -- `switch_wms_profile` puis retour → caches invalidés (log `Cache cleared`). - ---- - -## Méthode - -1. Phase 0 d'abord (sondes rejouées telles quelles — la forme `entities` de la - réponse AD est déjà établie, ne la redécouvre pas). -2. L4.0, puis L4.1, puis L4.2 — chaque bloc vérifié **en exécution via le - protocole** avant de passer au suivant. -3. **Attention à la concurrence** : le serveur traite les `tools/call` en - concurrence (point ouvert de la ROADMAP). Pour les vérifications qui - enchaînent bascules ou séquences de cache, envoie les requêtes - **séquentiellement** (attendre chaque réponse), pas en rafale. -4. Les schémas changent : reboucler sur les 23 noms de `tools/list` (aucun - `Unknown tool` ; `execute_command` vérifié statiquement, non appelé) et - vérifier qu'un paramètre inconnu est toujours rejeté (D23). -5. Baseline avant/après : handshake (23/6) + `npm test` (4/4, exit 0). -6. « Non résolu » est une réponse acceptable pour une investigation - time-boxée ; une hypothèse présentée comme solution ne l'est pas. - -## Livraison - -- Un commit par bloc (L4.0, L4.1, L4.2), messages expliquant le pourquoi, - **mesures rejouées dans le corps du message**. -- Documentation dans les mêmes commits : **D25** et **D26** dans DECISIONS.md ; - CLAUDE.md — nuancer « `QueryType: 0`, jamais 1 » en « défaut 0, `query_type` - est un opt-in documenté (D25) », documenter le paramètre `application` et les - nouvelles clés de cache dans la section Caches ; ROADMAP.md — retirer L4.0, - L4.1, L4.2 (L4.3, L4.4, L4.5 restent). -- Ne pousse pas. `.env` intact. Toute anomalie hors périmètre découverte en - route : dans ROADMAP.md, pas dans le code. -- Compte-rendu final : pour chaque bloc, la vérification attendue rejouée et - son résultat **mesuré** (colle les sorties), plus la baseline finale. - Laisse `handoff-lot4.md` en place pour la révision.