86923542fa
Source : la page d'aide générée https://<host>/ApplicationService/Help, qui documente des champs et des endpoints que le MCP n'utilise pas. Toutes les affirmations ci-dessous ont été testées contre LIMAGRAI2512. D3 corrigé — QueryContextType a quatre valeurs, pas deux : Reading 0 (ApplicationReadingContext), Writing 1 (ApplicationWritingRepository), DataWarehouse 2 (non configuré sur ce tenant : IDataWarehouse non résolu), Metrics 3 (ApplicationMetricDataContext, présent, modèle non exploré). Le message d'erreur nomme le contexte, ce qui donne un moyen rapide de savoir quel QueryType a servi. L4.1 étendu aux quatre contextes. L4.2 tranché sur son point dur : le champ Application ne partitionne pas le contexte de lecture. Context.AgvTasks répond aussi bien avec Application AGV qu'avec EasyWMS — le contexte est commun au tenant. La table de résolution du lot 2 devra donc agréger le Metadata de toutes les applications, mais un paramètre application sur QueryExecute serait inutile. Les entités CustomApp restent inatteignables sous les quatre QueryType, au singulier comme au pluriel, et Metadata renvoie 0 entité pour cette application : ce sont des définitions EasyBuilder sans projection requêtable. L'API AD est le seul accès au spécifique client. Trois chantiers ajoutés : - L4.3 ClientModule, non renseigné, d'où des requêtes du MCP journalisées sous « Client: GNA » et indistinguables du vrai client GNA. - L4.4 API WorkflowLog (GetInstances, GetLogs, Validate), joignable et fonctionnelle, susceptible de remettre en cause D16. - L4.5 champs inexploités de QueryExecute : Parameters (requêtes paramétrées, piste pour D13), CommandTimeout, QueryId + QueryCancel, QueryExecuteStream (piste pour L3.1), plus les endpoints event sourcing et les sondes healthcheck/ready. CLAUDE.md pointe désormais vers la page d'aide comme source de vérité. MONITORING.md documente healthcheck/ready comme sonde légère. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
353 lines
14 KiB
Markdown
353 lines
14 KiB
Markdown
# Décisions d'architecture et pièges vérifiés
|
|
|
|
Ce fichier consigne **pourquoi** le code est écrit comme il l'est. Chaque entrée
|
|
décrit une décision prise ou un piège constaté **sur un WMS réel** — pas une
|
|
supposition. Avant de « corriger » un comportement qui paraît étrange, cherchez-le
|
|
ici : il est probablement volontaire.
|
|
|
|
Convention : une décision reste dans le fichier même si elle est révisée ; on
|
|
ajoute alors une entrée `Révisée le …` plutôt que de réécrire l'histoire.
|
|
|
|
---
|
|
|
|
## D1 — 100 % API, aucun accès Oracle direct
|
|
|
|
**Décision.** Toutes les données transitent par les API REST du WMS. Aucune
|
|
connexion base de données.
|
|
|
|
**Pourquoi.** Le serveur MCP doit fonctionner depuis un poste ou une VM sans
|
|
credentials Oracle, sans client Oracle installé, et sans risque d'écriture
|
|
directe en base. L'API impose en prime les règles métier et les droits du
|
|
compte utilisé.
|
|
|
|
**Conséquence.** Les fichiers `src/services/oracle-service.js`,
|
|
`src/resources/database.js` et `src/tools/database-tools.js` ont été supprimés
|
|
(ils étaient de toute façon morts : non branchés, `oracledb` n'était même pas
|
|
une dépendance). **Ne pas les réintroduire.** Si une donnée n'est pas
|
|
atteignable par API, elle est hors périmètre — voir D16.
|
|
|
|
---
|
|
|
|
## D2 — OAuth : `tenant_code` est obligatoire
|
|
|
|
**Piège.** L'endpoint `/EasySTS/OAuth/Token` répond `400 Bad Request` si le
|
|
paramètre `tenant_code` est absent, sans message explicite.
|
|
|
|
**Solution.** Le corps du grant `password` contient toujours les quatre
|
|
paramètres :
|
|
|
|
```
|
|
grant_type=password&tenant_code=<TENANT>&username=<USER>&password=<PASS>
|
|
```
|
|
|
|
Voir `src/services/api-service.js`, méthode `authenticate()`.
|
|
|
|
---
|
|
|
|
## D3 — `QueryType: 0` (Reading), pas 1
|
|
|
|
**Piège.** `QueryType` (type `QueryContextType`) sélectionne le contexte de
|
|
données interrogé. Il a **quatre** valeurs, pas deux — vérifiées une à une sur
|
|
le tenant `LIMAGRAI2512` :
|
|
|
|
| Valeur | Contexte | Statut sur ce tenant |
|
|
|---|---|---|
|
|
| `0` | **Reading** — `ApplicationReadingContext` | opérationnel, champs de statut en **chaînes** (`"Release"`) |
|
|
| `1` | Writing — `ApplicationWritingRepository` | opérationnel, champs de statut en **énumérations** |
|
|
| `2` | DataWarehouse | **non configuré** : `Could not resolve serviceType 'IDataWarehouse…'` |
|
|
| `3` | Metrics — `ApplicationMetricDataContext` | contexte présent, modèle de données non exploré |
|
|
|
|
Les comparaisons de statut par chaîne — de loin le cas le plus courant en
|
|
debug — **échouent** en `QueryType: 1`. Le code force donc `0` dans
|
|
`executeQuery()` et `executeScalarQuery()`.
|
|
|
|
Le message d'erreur nomme le contexte (`ApplicationReadingContext`,
|
|
`ApplicationWritingRepository`, …) : c'est le moyen le plus rapide de savoir
|
|
quel `QueryType` a réellement été utilisé.
|
|
|
|
**Attention.** D'anciens exemples (dont le PHP de référence) utilisent `1`. Ne
|
|
les recopiez pas.
|
|
|
|
---
|
|
|
|
## D4 — Les API AD renvoient `{ entities: [...] }`, pas un tableau
|
|
|
|
**Piège.** `POST /AD/api/{Type}/GetByApplication` renvoie un objet enveloppe. Un
|
|
code qui traite la réponse comme un tableau obtient silencieusement
|
|
`0 élément` — le symptôme historique était « Successfully cached 0 workflows ».
|
|
|
|
**Solution.** Toujours extraire : `response?.entities || []`.
|
|
|
|
---
|
|
|
|
## D5 — Les propriétés arrivent en minuscules *ou* en majuscules
|
|
|
|
**Piège.** Selon le type d'élément et la version du WMS, l'API renvoie `name`
|
|
ou `Name`, `id` ou `Id`.
|
|
|
|
**Solution.** Systématiquement `const name = w.name || w.Name || ''` avant tout
|
|
filtrage ou tri. Une recherche qui « ne trouve pas » un élément qui existe est
|
|
presque toujours ce bug.
|
|
|
|
---
|
|
|
|
## D6 — dotenv doit écrire sur stderr
|
|
|
|
**Piège.** dotenv affiche une bannière de version sur **stdout**. Or le
|
|
protocole MCP réserve stdout au JSON : Claude Desktop échoue alors avec
|
|
`Unexpected token 'd', "[dotenv@17."... is not valid JSON`.
|
|
|
|
**Solution.** `src/index.js` détourne `process.stdout.write` vers stderr le
|
|
temps du chargement de dotenv, puis le restaure.
|
|
|
|
**Règle générale.** Dans tout le projet, on log avec `console.error()`.
|
|
**Jamais** `console.log()`.
|
|
|
|
---
|
|
|
|
## D7 — Le `.env` est lu à côté de l'exécutable quand le serveur est packagé
|
|
|
|
**Décision.** `src/index.js` résout le chemin du `.env` selon le contexte :
|
|
|
|
| Contexte | Chemin du `.env` |
|
|
|---|---|
|
|
| Sources (`npm start`) | racine du projet |
|
|
| Exécutable pkg (`process.pkg`) | dossier de `process.execPath` |
|
|
|
|
**Pourquoi.** Avec un chemin statique, pkg **embarque le `.env` dans le
|
|
snapshot** de l'exe : les credentials sont figés dans le binaire et
|
|
reconfigurer un déploiement impose un rebuild. Le chemin dynamique via
|
|
`process.execPath` empêche pkg de le détecter, donc rien n'est embarqué, et
|
|
`dist/.env` devient le fichier de configuration du déploiement.
|
|
|
|
**Vérification.** Sans `.env` à côté de l'exe, le serveur démarre en
|
|
avertissant `WMS_PROFILES is empty` — preuve qu'aucune valeur n'est embarquée.
|
|
|
|
---
|
|
|
|
## D8 — Multi-profils au runtime plutôt qu'un serveur MCP par WMS
|
|
|
|
**Décision.** Un seul serveur MCP dessert plusieurs backends WMS ; Claude bascule
|
|
avec `switch_wms_profile`.
|
|
|
|
**Pourquoi.** L'alternative — une entrée par client dans
|
|
`claude_desktop_config.json` — multiplie les processus, les jeux de credentials
|
|
et les caches, pour un usage où l'on ne consulte qu'un WMS à la fois.
|
|
|
|
**Conséquence.** L'état actif est **global au processus**. Un changement de
|
|
profil doit invalider tout ce qui dépend du tenant. Les services s'abonnent via
|
|
`profileManager.onSwitch()` :
|
|
|
|
| Service | Réaction au switch |
|
|
|---|---|
|
|
| `api-service` | `resetToken()` — le token OAuth appartient au tenant précédent |
|
|
| `workflow-service` | `clearCache()` |
|
|
| `ad-service` | `invalidateCache()` — tous les types |
|
|
|
|
**Ne jamais** appeler ces invalidations à la main depuis un autre module :
|
|
l'abonnement suffit, et le doublon masquerait un oubli d'abonnement.
|
|
|
|
**Sans profil actif** (`DEFAULT_WMS_PROFILE` absent ou invalide), `getCurrent()`
|
|
lève une erreur qui **énumère les profils disponibles**. C'est intentionnel :
|
|
Claude lit ce message et enchaîne sur `switch_wms_profile` au lieu d'échouer.
|
|
|
|
---
|
|
|
|
## D9 — Profils SaaS : accès aux logs refusé, pas silencieux
|
|
|
|
**Décision.** Quand `<PROFIL>_SAAS=true`, `read_recent_logs`, `search_logs` et
|
|
`list_log_files` **lèvent une erreur explicite** renvoyant vers les outils API.
|
|
|
|
**Pourquoi.** Le WMS est hébergé dans le cloud Mecalux : le partage
|
|
`\\<host>\inetpub\logs\...` n'est pas joignable. Retourner « 0 fichier » ferait
|
|
croire à une absence d'erreurs dans les logs, ce qui est un faux négatif
|
|
dangereux en diagnostic. Voir [docs/logs.md](docs/logs.md).
|
|
|
|
---
|
|
|
|
## D10 — Chargement paresseux + cache 1 h
|
|
|
|
**Décision.** Workflows et éléments AD ne sont **pas** chargés au démarrage,
|
|
mais à la première requête qui les concerne, puis mis en cache
|
|
(`WORKFLOW_CACHE_TTL`, 3 600 000 ms par défaut).
|
|
|
|
**Pourquoi.** L'ensemble représente ~38 800 éléments dont 29 374 `Resource` :
|
|
tout charger au boot ferait échouer le handshake MCP par timeout, pour des
|
|
données souvent inutiles à la session.
|
|
|
|
**Pagination.** La taille de page est réglée **par type** dans
|
|
`AD_ELEMENT_TYPES` (`src/services/ad-service.js`) : `View: 200`,
|
|
`Workflow: 5000`, `Resource: 15000`, tout le reste `100000` (soit une seule
|
|
page). Ces valeurs viennent de l'observation des timeouts serveur — les
|
|
augmenter à l'aveugle fait échouer les types lourds.
|
|
|
|
---
|
|
|
|
## D11 — Les paramètres système : `Parameter` + `ParamValue`, fusionnés en JS
|
|
|
|
**Piège.** Le modèle Reading ne contient **pas** d'entité
|
|
`CommandParameterData`. La configuration se lit dans deux entités :
|
|
|
|
| Entité | Contenu |
|
|
|---|---|
|
|
| `Parameter` | définition + `DefaultValue` |
|
|
| `ParamValue` | surcharges par entrepôt, liées par `ParameterId` |
|
|
|
|
**Décision.** `get_system_parameters` charge les deux intégralement (~200 et
|
|
~50 lignes) et fait la fusion **côté JavaScript**, en exposant la *valeur
|
|
effective* par entrepôt (surcharge si présente, défaut sinon).
|
|
|
|
**Pourquoi côté JS.** Les filtres (`warehouse`, `param_class`, `search`,
|
|
`only_overridden`) sont appliqués en JS pour éviter toute concaténation de
|
|
chaîne LINQ à partir d'entrées utilisateur — pas d'injection possible, et pas
|
|
de dépendance aux limites du traducteur LINQ (D12).
|
|
|
|
Fichier : `src/tools/config-tools.js`.
|
|
|
|
---
|
|
|
|
## D12 — Les dates relatives ne sont pas traduisibles en LINQ
|
|
|
|
**Piège vérifié en production.** `DateTime.Now`, `DateTime.Today` et
|
|
`AddDays()` ne sont **pas** traduits par le moteur de requêtes : la requête
|
|
échoue à la compilation.
|
|
|
|
**Solution.** Toujours une date littérale :
|
|
|
|
```csharp
|
|
Context.OutboundOrders.Where(z => z.CreationDate > new DateTime(2026, 8, 1))
|
|
```
|
|
|
|
C'est à l'appelant (donc à Claude) de calculer la date avant d'écrire la
|
|
requête.
|
|
|
|
---
|
|
|
|
## D13 — `select_expression` reste instable
|
|
|
|
**État.** Les projections passées via le paramètre API `Select` déclenchent des
|
|
erreurs de compilation côté serveur.
|
|
|
|
**Contournement actuel.** Interroger les lignes complètes et filtrer les
|
|
colonnes côté client.
|
|
|
|
**Non résolu.** C'est le principal point ouvert du projet. Toute tentative de
|
|
correction doit être validée sur un vrai WMS avant d'être documentée ici.
|
|
|
|
---
|
|
|
|
## D14 — `executeCommand` : pas de suffixe d'assembly
|
|
|
|
**Piège.** Ajouter `, Mecalux.ITSW.EasyWMS.Modules.Contracts` au nom de commande
|
|
provoque une `FileLoadException`.
|
|
|
|
**Solution.** Utiliser le `command_name` **tel quel** :
|
|
l'`InternalCommandName` fourni par l'AD contient déjà le nom pleinement
|
|
qualifié correct.
|
|
|
|
---
|
|
|
|
## D15 — Validation TLS désactivée
|
|
|
|
**Décision.** `httpsAgent: new https.Agent({ rejectUnauthorized: false })`.
|
|
|
|
**Pourquoi.** Les WMS on-premise sont exposés en HTTPS avec un certificat
|
|
auto-signé sur une IP privée.
|
|
|
|
**Limite assumée.** Acceptable sur réseau interne ou via VPN. Sur un profil
|
|
SaaS joint par Internet, cela supprime la protection contre l'interception —
|
|
à revoir si l'outil sort du cadre du diagnostic interne.
|
|
|
|
---
|
|
|
|
## D16 — Historique des shipment templates : hors périmètre
|
|
|
|
**Constat.** L'entité Reading `ShipmentTemplate` n'expose que la **dernière**
|
|
exécution (`LastExecuteDate`, `Status`, `IsEnabled`).
|
|
|
|
L'historique complet n'existe que dans les logs texte
|
|
`ApplyShipmentTemplates`, **absents de l'hôte joignable** (`10.255.255.2`) :
|
|
ils résident sur les serveurs de production / ETL des clients.
|
|
|
|
**Décision.** Aucun outil d'analyse de ces logs n'a été construit — il n'aurait
|
|
rien à lire. Les recettes purement API sont dans la resource
|
|
`wms://query-examples`.
|
|
|
|
---
|
|
|
|
## D17 — `WorkflowAction` et `WritingModel` retirés de la liste AD
|
|
|
|
**Constat.** Les endpoints `/AD/api/WorkflowAction/GetByApplication` et
|
|
`/AD/api/WritingModel/GetByApplication` répondent `404 Not Found`.
|
|
|
|
**Décision.** Ces deux types sont sortis de `AD_ELEMENT_TYPES` : il en reste
|
|
**20**, dont 3 valides mais vides (`Dashboard`, `TimelineTemplate`, `Toggle`).
|
|
Détail de la campagne de validation :
|
|
[docs/ad-api-validation.md](docs/ad-api-validation.md).
|
|
|
|
---
|
|
|
|
## D18 — Build : `@yao-pkg/pkg` ciblant node22
|
|
|
|
**Décision.** Le build utilise `@yao-pkg/pkg` (fork maintenu de `pkg`, archivé
|
|
depuis) avec la cible **`node22-win-x64`**.
|
|
|
|
**Pourquoi cette cible.** `node20-win-x64` n'a pas de binaire prébuilt
|
|
disponible : pkg bascule alors sur une compilation de Node depuis les sources,
|
|
qui échoue faute de `vcbuild.bat` (toolchain MSVC absente).
|
|
|
|
**Avertissements normaux au build.** `Cannot find module
|
|
'@modelcontextprotocol/sdk/server/index.js'` et `Entry 'main' not found` :
|
|
pkg ne sait pas résoudre statiquement la table `exports` du SDK. L'exécutable
|
|
produit **fonctionne** — vérifié en démarrant l'exe. Ne pas chercher à
|
|
« corriger » ces avertissements.
|
|
|
|
---
|
|
|
|
## D19 — La resource `docs://` a été supprimée
|
|
|
|
**Constat.** `src/resources/documentation.js` (193 lignes) exposait un index des
|
|
`.md` de `docs/`, mais n'a **jamais été branché** dans `src/index.js` : le
|
|
handler `resources/list` ne l'incluait pas et `resources/read` ne routait aucune
|
|
URI `docs://`. `docs/README.md` promettait pourtant la fonctionnalité aux
|
|
utilisateurs.
|
|
|
|
**Décision.** Fichier supprimé, `docs/README.md` corrigé. `docs/` reste un
|
|
dossier de référence pour les humains et pour un agent qui lit le dépôt — pas
|
|
une resource MCP.
|
|
|
|
**Si on veut la fonctionnalité un jour**, il faut la brancher réellement (2
|
|
lignes dans `src/index.js`) *et* décider de son sort dans l'exécutable pkg, qui
|
|
n'embarque pas `docs/`.
|
|
|
|
---
|
|
|
|
## D20 — Purge du dépôt (2026-08-24)
|
|
|
|
Supprimés lors du nettoyage :
|
|
|
|
| Élément | Raison |
|
|
|---|---|
|
|
| 5 `.md` dupliqués à la racine | copies md5-identiques de `docs/api/` et `docs/entities/` |
|
|
| `JANITOR_main.js`, `JANITOR_entities.json` | application Electron sans lien avec le MCP |
|
|
| `temp/*.json` | dumps de workflows versionnés par accident (`temp/` désormais ignoré) |
|
|
| `claude_desktop_config_ssh.json` | **mots de passe en clair** + variables `ORACLE_*` de l'architecture supprimée (D1) |
|
|
| `IMPLEMENTATION_SUMMARY.md` | doublon d'`AD_API_TEST_RESULTS.md`, déplacé en `docs/ad-api-validation.md` |
|
|
| `src/resources/documentation.js` | code mort (D19) |
|
|
| `src/config/constants.js` (83 l.) | module entier inutilisé : `require` présent dans `wms-query-service.js`, mais **aucune** de ses constantes n'était lue |
|
|
| `log-service.js` : `findRecentErrors`, `readFullLog`, `getLogStats` (~100 l.) | exportées, jamais appelées — aucun outil MCP ne les exposait |
|
|
| `RESOURCE_URIS.WORKFLOWS_CATEGORIES` | URI déclarée, jamais servie |
|
|
| `LOG_FILE_PATTERN` | lue depuis `.env`, jamais utilisée (le scan filtre sur `.log` en dur) |
|
|
|
|
Les trois fonctions de `log-service.js` étaient fonctionnelles ; si l'une d'elles
|
|
redevient utile (`findRecentErrors` en particulier), la reprendre depuis le
|
|
commit `b59cbb3` et **l'exposer réellement** comme outil MCP plutôt que de la
|
|
laisser inatteignable.
|
|
|
|
⚠️ **Credentials à faire tourner.** `claude_desktop_config_ssh.json` et
|
|
l'ancienne version de `test-ad-api.ps1` contenaient des mots de passe en clair.
|
|
Le fichier est retiré du répertoire de travail, **mais il reste dans
|
|
l'historique git** (commit `b59cbb3`). Considérez ces mots de passe comme
|
|
compromis et changez-les ; à défaut, réécrivez l'historique avant toute
|
|
publication du dépôt.
|