Passation lot 4 : query_type, application AD/workflow, catalog D3 (L4.0-L4.2)

Mesures rafraîchies du 25/08/2026 : Writing (QueryType 1) répond 1 ligne
sur Context.Products, Metrics (3) existe sans Products, CustomApp
renvoie ses workflows CST_ sous la clé entities de GetByApplication.
Découverte consignée en L4.0 : la resource api://catalog documente un
exemple QueryType: 1 (le piège D3), un Select dans l'expression et
l'entité fantôme Aliases.

D25 (rapport D3/query_type) et D26 (clés de cache par application)
réservées. L4.3-L4.5 et l'exploration Metrics explicitement hors
périmètre. Le point délicat resolver/Writing est cadré : nom inconnu du
Reading transmis tel quel avec warning quand query_type != 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Arthur Ria
2026-08-25 10:58:50 +02:00
parent 702c2ecd2a
commit 35b53dbef5
2 changed files with 251 additions and 0 deletions
+11
View File
@@ -17,6 +17,17 @@ contient que ce qui reste à faire.
Deux angles morts constatés le 24/08/2026, plus larges que les lots 2 et 3. Les Deux angles morts constatés le 24/08/2026, plus larges que les lots 2 et 3. Les
chiffres ci-dessous sont mesurés sur le tenant `LIMAGRAI2512`. chiffres ci-dessous sont mesurés sur le tenant `LIMAGRAI2512`.
### L4.0 — La resource `api://catalog` enseigne le piège D3
Constaté le 25/08/2026 en préparant la passation : `src/resources/apis.js`
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 D13), et l'entité fantôme `Aliases`
(corrigée partout ailleurs au lot 2). Cette resource est lue par les sessions
Claude : c'est une source de désinformation active. Corriger l'exemple
(`QueryType: 0`, expression sans `Select`), `Aliases``Alias`, et renvoyer
vers `get_entity_metadata` comme source de vérité.
### L4.1 — Le modèle Writing est inatteignable ### L4.1 — Le modèle Writing est inatteignable
`QueryType` est figé à `0` (Reading) en dur dans `api-service.js` `QueryType` est figé à `0` (Reading) en dur dans `api-service.js`
+240
View File
@@ -0,0 +1,240 @@
# 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.