# Roadmap Travaux planifiés, par lot. Chaque lot est livrable indépendamment. Constats issus de la session de diagnostic du **24/08/2026** (profil `LIMAGRAIN`, host `10.255.255.2`, tenant `LIMAGRAI2512`), déclenchée par un rapport d'usage d'une session Cowork. Toutes les anomalies ci-dessous ont été **reproduites** contre le WMS réel — ce ne sont pas des hypothèses. Les décisions actées vivent dans [DECISIONS.md](DECISIONS.md) ; ce fichier ne contient que ce qui reste à faire. --- ## Cause racine commune Le MCP interpole `entity_type` dans `Context.{entity_type}` **sans aucune validation** (vérifié : aucune liste blanche dans le code). Or le nom attendu par le contexte de lecture n'est pas le nom d'entité de l'Application Dictionary. L'API Metadata (`GET /Metadata/Entities`, **232 entités**) donne la correspondance exacte : | `Name` (renvoyé par `search_ad_elements`) | `TableName` (attendu par `Context.`) | |---|---| | `Container` | `Containers` | | `Product` | `Products` | | `ContainerType` | `ContainerTypes` | | `Alias` | `Alias` — **invariant, pas de pluriel** | | `Item` | *n'existe pas dans le modèle Reading* | Ce n'est donc pas une règle de pluralisation : c'est un mapping, et seul `TableName` fait foi. `TableName` est unique sur les 232 entités. Conséquences déjà constatées : - une session utilisant les noms de l'AD (singuliers) déclenche un **HTTP 500** sur chaque requête ; - la liste d'entités documentée était fausse (`Aliases` n'existe pas, c'est `Alias`) ; - le MCP n'expose que 12 entités figées là où l'API en connaît 232. --- ## Lot 2 — Correctif de fond ### L2.1 — Résolution des entités via l'API Metadata Accepter `entity_type` au nom d'entité (`Container`) ou au nom de jeu (`Containers`), insensible à la casse, et émettre `Context.{TableName}`. Cache identique aux autres (TTL partagé, invalidation au changement de profil). Sur nom inconnu, échouer **avant tout appel réseau**, avec un message actionnable : > « Item » n'existe pas dans le modèle Reading. Proches : ItemGroup, StockItem. > 232 entités disponibles — utilisez `get_entity_metadata` pour la liste. Supprime la cause des 500 et débloque 232 entités au lieu de 12. ### L2.2 — Rejeter les paramètres inconnus Le SDK MCP ignore silencieusement les paramètres non déclarés : un appel `read_recent_logs(lines: 60)` retombe sur le défaut `count = 100` sans le moindre signal, et l'appelant conclut à un paramètre ignoré. Ajouter `additionalProperties: false` aux 23 schémas d'outils. C'est le correctif retenu **à la place** d'une uniformisation des noms de paramètres : renommer casse les usages existants pour un gain cosmétique, alors que la cause réelle est l'absence de signal. ### L2.3 — Arguments manquants : deux garde-fous Découverts lors de la révision du lot 1 (24/08/2026), en bouclant sur les 23 outils avec des arguments vides. Préexistants au lot 1 (vérifié sur le diff) : - `get_workflow_details` sans `workflow_id` renvoie `success: true` avec **le premier workflow du cache**. Cause : `getWorkflowDetails()` (`workflow-service.js`) compare `w.Code === workflowId` — or `Code`, `Id`, `Name` n'existent pas sur les objets réels (clés minuscules, D5), donc `undefined === undefined` matche. Supprimer les clés mortes de la comparaison et rejeter un `workflow_id` absent avec un message actionnable. - `search_logs` sans terme de recherche renvoie `Search failed: Cannot read properties of undefined (reading 'toLowerCase')` — le contrat d'erreur tient, mais le message viole la convention 4 (actionnable). Garde d'entrée avec le nom du paramètre attendu. --- ## Lot 3 — Ergonomie et documentation ### L3.1 — Bornage des sorties volumineuses - `get_system_parameters` : ajouter `limit` / `offset`, aujourd'hui absents (sortie constatée : 70 000 caractères, rejetée par le client). - `search_logs` : garde-fou de taille. `max_results` existe déjà, mais les `context_lines` multiplient le volume (88 000 caractères pour 50 résultats). - Renvoyer `truncated: true` explicitement plutôt que de laisser le client se faire rejeter. ### L3.2 — Erreurs d'authentification muettes `authenticate()` (`api-service.js`) ré-enveloppe l'erreur axios en `new Error('Authentication failed: ' + error.message)` : le **corps de la réponse du STS est perdu**, alors qu'il contient le diagnostic complet. Preuve (24/08/2026, profil `AD`) : le smoke test affiche seulement `Authentication failed: Request failed with status code 400` ; en rejouant la même requête à la main, le corps était `{"error":"invalid_request","error_description":"Tenant not found"}` — le diagnostic exact, invisible depuis les outils comme depuis le smoke test. Faire remonter `error.response.data` dans le message, comme L1.1 l'a fait pour les outils. Même contrainte : ne jamais logguer les credentials. ### L3.3 — Documentation - DECISIONS.md : **D21** la règle `TableName` (D22, le routage par table explicite, a été livrée avec le lot 1). - CLAUDE.md : corriger la liste d'entités (`Aliases` → `Alias`) et renvoyer vers `get_entity_metadata` comme source de vérité. - `wms://query-examples` : un exemple singulier/pluriel commenté. --- ## Lot 4 — Modèle de données et applications 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`. ### L4.1 — Le modèle Writing est inatteignable `QueryType` est figé à `0` (Reading) en dur dans `api-service.js` (`executeQuery` et `executeScalarQuery`). Or `QueryContextType` a **quatre** valeurs. Testées une à une : | Valeur | Contexte | Résultat sur `LIMAGRAI2512` | |---|---|---| | `0` | Reading | opérationnel (seul utilisé aujourd'hui) | | `1` | Writing | **opérationnel** — `Context.Products` répond | | `2` | DataWarehouse | **non configuré** : `Could not resolve serviceType 'IDataWarehouse…'` | | `3` | Metrics | contexte présent (`ApplicationMetricDataContext`), modèle non exploré | Exposer `query_type` sur les outils de requête, défaut `0`. Attention : D3 reste vrai — en Writing les champs de statut sont des **énumérations**, donc `== "Release"` échoue. La bascule doit être un choix explicite et documenté. Le contexte `Metrics` mérite une exploration à part : c'est probablement là que vivent les données agrégées produites par les jobs `MetricGatherer`. ### L4.2 — Une seule application sur neuf est visible `Application` vient de `WMS_APPLICATION` dans `.env`, **partagé par tous les profils**, sans surcharge par appel ni paramètre d'outil. Le MCP n'interroge donc jamais que `EasyWMS`. `POST /AD/api/Application/GetAll` en déclare **9** : | 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 | | SmartUI, User, WarehouseWebDesigner | 0 | 0–8 | 0 | **CustomApp porte le spécifique client** — ses workflows sont préfixés `CST_` (`CST_SendRejectContainersToPK`, `CST_Task`, `CST_Container`…). C'est précisément ce qu'on cherche en debug, et c'est aujourd'hui invisible. Au total **260 workflows et ~130 queries** hors périmètre. Deux chantiers de difficulté très différentes : **API AD — simple.** L'application est un champ du payload (`[application, tenant, pageSize, offset]`). Vérifié : `["CustomApp", tenant, 5, 0]` sur `/Workflow/GetByApplication` renvoie bien les workflows `CST_`. Il suffit d'un paramètre `application` sur les outils AD et workflow, avec une clé de cache incluant l'application (sinon un cache pollué mélange les applications). **QueryExecute — tranché : le champ `Application` ne partitionne rien.** `Context.AgvTasks` (entité de l'application AGV) répond aussi bien avec `Application: "AGV"` qu'avec `Application: "EasyWMS"`. Le contexte de lecture est **commun au tenant** : toutes les applications y déversent leurs entités. Conséquence pour L2.1 : la table de résolution doit **agréger le Metadata de toutes les applications** (`GET /Metadata/Entities?applicationName=…` par application, 232 + 20 + 6 + …), et non se limiter à `EasyWMS`. Inutile en revanche d'ajouter un paramètre `application` à `QueryExecute` : il ne changerait rien. **Les entités `CustomApp` ne sont interrogeables dans aucun contexte.** Les 11 entités `CST_` ont été testées sous les quatre `QueryType`, au singulier et au pluriel : échec partout, et `Metadata/Entities` comme `Metadata/EntitiesAll` renvoient **0 entité** pour `CustomApp`. Aucune n'est marquée `isDataWarehouse`. Ce sont des définitions EasyBuilder (`FromMetadata: false`) sans projection dans un contexte requêtable. **L'API AD reste donc le seul accès au spécifique client** — ce qui rend le paramètre `application` sur les outils AD et workflow d'autant plus utile. ### L4.3 — Identifier le MCP dans les logs du WMS Les requêtes du MCP apparaissent dans les logs du WMS sous `Execute error. Client: GNA` — le client OAuth partagé — donc indistinguables de celles du vrai client GNA. **La piste `ClientModule` est invalidée** (mesuré le 24/08/2026, lot 1) : le champ est bien accepté par `QueryExecute` (pas d'erreur), mais il est **sans effet observable**. Une requête en échec envoyée avec `ClientModule: "MCP-WMS"` est tracée `Execute error. Client: GNA`, et ni `MCP-WMS` ni `ClientModule` n'apparaissent nulle part dans `ApplicationService.log` ni `HttpResponseTime.log`. Le `Client:` des logs vient du client OAuth, pas du payload — le champ n'a donc **pas** été renseigné. Piste restante (non vérifiée) : un client OAuth dédié au MCP côté EasySTS changerait le `Client:` des logs, mais suppose une configuration côté WMS. ### L4.4 — Historique d'exécution des workflows par API `ApplicationService` expose une API `WorkflowLog` que le MCP n'utilise pas : | Endpoint | Usage | |---|---| | `GET /WorkflowLog/GetInstances?processDefinitionId=&skip=&take=&startDateFrom=&startDateTo=` | instances d'un workflow sur une plage de dates, filtrables par attribut | | `GET /WorkflowLog/GetInstance?processId=` | une instance | | `GET /WorkflowLog/GetLogs?processId=&skip=&take=&logDateFrom=&logDateTo=` | journal d'exécution d'une instance | | `GET /WorkflowLog/Validate?applicationName=&processDefinitionId=` | validation d'une définition | Endpoints joignables et fonctionnels — `Validate` renvoie `{"Success":true,"ErrorMessage":null,"Warnings":[]}`. `GetInstances` répond `[]` sur le workflow testé : à confirmer sur un workflow ayant réellement tourné, la journalisation n'étant pas forcément active partout. **Peut remettre en cause D16** (historique des shipment templates jugé hors de portée faute de logs fichier) : si l'historique d'exécution est disponible par API, la conclusion change. À vérifier avant d'écrire quoi que ce soit. ### L4.5 — Champs de `QueryExecute` inexploités La référence de l'API documente des champs que le MCP n'envoie jamais : | Champ | Intérêt | |---|---| | `Parameters` | requêtes **paramétrées** (dictionnaire `nom -> {TypeName, Value}`) — supprimerait toute concaténation de chaîne dans les filtres, et pourrait débloquer D13 (`Select`) | | `CommandTimeout` | timeout par requête, au lieu du timeout HTTP global de 30 s | | `QueryId` + `POST /QueryCancel` | annulation d'une requête longue | | `POST /QueryExecuteStream` | résultats en flux — piste sérieuse pour L3.1 (sorties volumineuses) | Autres endpoints jamais utilisés, à évaluer : `QueryEvents`, `QueryCommands`, `QueryCorrelationEvents`, `QuerySnapshots` (event sourcing — utile en debug), `GET /Metadata/Commands|Events|Aggregates` et leurs variantes `…All`, `GET /configuration/applications` (liste les applications **avec leur version**, plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et `GET /ready?tenantCode=` (sondes de disponibilité, répondent 200). --- --- ## Écarté | Proposition | Raison | |---|---| | Uniformiser les noms de paramètres (`entity_type` / `query` partout) | Casse les usages existants ; les alias de transition doublent la surface à maintenir. La cause réelle est traitée par L2.2. | | Exposer un `indexStatus` sur `generic_search` | `TotalDocuments: 0` est déjà le signal. Le MCP n'a aucun moyen d'interroger l'état de l'index de recherche. | | Outil dédié `get_query_syntax_help` | L'information doit se trouver dans le message d'erreur, là où elle est lue (L2.1), pas dans un outil qu'il faut penser à appeler. | --- ## Points ouverts (hors lots) - **Profil `AD` : tenant introuvable** (mesuré le 24/08/2026). `npm test -- --all` échoue 0/4 sur ce profil ; le STS de `10.255.255.2` répond `400 {"error":"invalid_request","error_description":"Tenant not found"}` pour le tenant `AD`. L'hôte et le STS fonctionnent (LIMAGRAIN, même hôte, passe 4/4) : c'est la valeur `AD_TENANT` du `.env` qui ne correspond plus à un tenant existant. Correction côté propriétaire du dépôt (mettre à jour ou retirer le profil) — pas un bug du code. Le message opaque du smoke test est traité à part (L3.2). - **`select_expression`** : les projections via le paramètre `Select` provoquent des erreurs de compilation côté serveur (D13). Irritant principal restant. - **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 n'existent pas sur l'hôte joignable (D16).