cb625a7918
Context.{entity_type} attend le TableName du Metadata, pas le nom
d'entité de l'AD (Container -> Containers, mais Alias -> Alias) : un nom
faux partait en HTTP 500 de compilation LINQ. Nouveau service
entity-resolver.js : table Name|TableName (insensible à la casse) ->
TableName, agrégée sur les applications déployées (via
GET /configuration/applications — les applications sans contexte
requêtable n'y figurent pas et n'apportent 0 entité Metadata), cache TTL
partagé, invalidation par onSwitch (D8). Branché dans wms-query-service
(query/count/schema/search) et call_query_api.
Nom inconnu -> échec avant tout appel réseau de requête, suggestions
proches + renvoi vers get_entity_metadata. Metadata injoignable -> le
nom passe tel quel avec un warning dans la réponse.
Mesures (LIMAGRAIN, via le protocole) :
- query_wms_entities("Container", limit 1) -> succès, 1 ligne, résolu
Containers
- query_wms_entities("Alias") -> succès, invariant (pas de pluriel)
- query_wms_entities("Item") -> "Item" n'existe pas dans le modèle
Reading. Proches : RFMenuItems, Sites. 288 entités disponibles —
aucune ligne [API] POST dans stderr
- count_wms_entities("Product") -> 51160
- get_entity_schema("Container") et call_query_api("Container") : mêmes
résolutions
- 288 TableName distincts sur 5 applications, aucun conflit
Name -> TableName (mesuré le 24/08/2026)
Docs : D21 dans DECISIONS.md ; CLAUDE.md (piège retiré des points
ouverts, liste d'entités corrigée Aliases -> Alias, entity-resolver dans
la structure) ; exemple singulier/pluriel dans wms://query-examples ;
ROADMAP allégée (cause racine + L2.1 + L3.3 livrés).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
240 lines
12 KiB
Markdown
240 lines
12 KiB
Markdown
# 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.
|
||
|
||
---
|
||
|
||
## Lot 2 — Correctif de fond
|
||
|
||
La cause racine commune du lot (résolution `Name` -> `TableName` via l'API
|
||
Metadata) est livrée — la règle et ses mesures vivent en **D21**.
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
## 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 — traitée : la table de résolution (D21) **agrège le Metadata de
|
||
toutes les applications** déployées, et non le seul `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).
|