Révision lot 4 : validé ; ligne Writing énorme et champ tool absent consignés
Les vérifications attendues des trois blocs rejouées via le protocole passent : api://catalog assaini (plus de QueryType 1, d'Aliases ni de suffixe d'assembly), query_type opérationnel (Writing 1 ligne, Metrics en erreur parlante, 7 rejeté localement sans aucun trafic réseau, warning de résolution conservé jusque dans l'échec WMS), caches par application étanches (4012/153, retour en cache hit, invalidation complète à la bascule). Baseline 23/6, npm test 4/4 exit 0, diffs propres. Deux découvertes consignées en points ouverts : une ligne Products en Writing pèse 95 288 caractères (famille D24), et les catch locaux d'api-tools omettent le champ tool du contrat (antérieur au lot). Leçon de révision : mon appel search_workflows(search_term=...) des lots précédents était silencieusement ignoré pré-D23 — le paramètre s'appelle query. La validation D23 a transformé mon erreur en signal. handoff-lot4.md supprimé (livré et révisé). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+13
@@ -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
|
(~70 000). Comportement antérieur au lot 4 (les grosses définitions
|
||||||
`EasyWMS` sont dans le même cas) : à borner et signaler (`truncated`/`hint`,
|
`EasyWMS` sont dans le même cas) : à borner et signaler (`truncated`/`hint`,
|
||||||
D24) dans un lot futur.
|
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
|
- **Déploiement SSH sur la VM** : l'exécutable est validé, la configuration SSH
|
||||||
reste à faire.
|
reste à faire.
|
||||||
- **Historique des shipment templates** : hors de portée, les logs concernés
|
- **Historique des shipment templates** : hors de portée, les logs concernés
|
||||||
|
|||||||
@@ -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.
|
|
||||||
Reference in New Issue
Block a user