Un fetch parti AVANT une invalidation (clearCache/invalidateCache, declenchees
par onSwitch a la bascule de profil, D8) terminait APRES elle et ecrivait quand
meme son resultat : le cache repartait peuple avec les donnees de l'ancien
tenant, timestamp neuf, valid: true. Defaut latent avant L6.1 ; deterministe
apres, puisque la promesse en vol survit desormais a l'invalidation.
Compteur de generation par service, incremente a chaque invalidation. Le fetch
capture la generation au depart et, au moment de publier, jette son resultat si
elle a bouge. Surtout : les fonctions de chargement n'ecrivent plus rien en
cache — la publication est un `commit` passe a singleFlight.run, appele
seulement si la generation n'a pas change. La garde devient structurelle, pas
conventionnelle : un chargement ne peut plus publier par inadvertance.
L'invalidation vide aussi la Map des promesses en vol. L'appelant deja en
attente recoit quand meme son resultat — il l'a demande avant la bascule ;
c'est sa mise en cache qui est refusee.
--- Verifications (LIMAGRAIN) ---
Rafale [search_workflows(EasyWMS), switch_wms_profile(EUROTRAFIC)] envoyee d'un
bloc, puis get_application_summary SEQUENTIEL apres les deux reponses.
AVANT la garde (HEAD = L6.1), 3 rejeux — le cache survit a la bascule :
===== RUN 1 =====
search_workflows : success=true count=50
switch_wms_profile : success=true
get_application_summary -> workflowCachesByApplication =
{"EasyWMS":{"cached":true,"count":3944,"timestamp":1787665677594,
"age":0,"valid":true}}
adElementsByApplication = {}
=> caches peuples : 1 (attendu 0)
===== RUN 2 ===== idem, count 3944, timestamp 1787665687744, valid true
===== RUN 3 ===== idem, count 3944, timestamp 1787665703945, valid true
APRES la garde, 3 rejeux — aucun cache peuple :
===== RUN 1 =====
search_workflows : success=true count=50
switch_wms_profile : success=true
get_application_summary -> workflowCachesByApplication = {}
adElementsByApplication = {}
=> caches peuples : 0 (attendu 0)
-- stderr : 1 resultat(s) jete(s) | 2 'Cache cleared'
===== RUN 2 ===== identique : caches peuples 0, 1 resultat jete
===== RUN 3 ===== identique : caches peuples 0, 1 resultat jete
Test direct et deterministe (invalidation declenchee pendant un fetch tenu
ouvert par une porte) :
[Workflow] Cache expired or empty for "TestApp", fetching from API...
--- invalidation PENDANT le fetch (clearCache) ---
[Workflow] Cache cleared
[Workflow] Fetched 1 workflows (total: 1)
[Workflow] Result for "workflows::TestApp" discarded, not cached:
cache invalidated during fetch (generation 0 -> 1)
l'appelant recoit bien son resultat : 1 workflow(s)
cache apres coup : {} (attendu {})
Non-regression L6.1, 3 rejeux de la rafale de 6 (CustomApp) :
== RUN 1 : 6 reponses a 44 | fetch=1 joins=5 caches=1
== RUN 2 : 6 reponses a 44 | fetch=1 joins=5 caches=1
== RUN 3 : 6 reponses a 44 | fetch=1 joins=5 caches=1
Chemin sequentiel nominal, inchange :
[search_workflows] success=true application=EasyWMS count=50 len=14220
[search_workflows] success=true application=EasyWMS count=50 len=14220
fetch=1 cached=1 joins=0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.6 KiB
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 ; ce fichier ne contient que ce qui reste à faire.
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 (reliquat) — Explorer le contexte Metrics
L'exposition de query_type est livrée (D25). Reste l'investigation : le
contexte Metrics (QueryType: 3, ApplicationMetricDataContext) mérite une
exploration à part — c'est probablement là que vivent les données agrégées
produites par les jobs MetricGatherer. Livrable : un rapport, pas du code
(même phase d'investigation que L4.4).
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 long terme pour les sorties volumineuses, au-delà du bornage signalé de D24 |
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).
Lot 6 — Chargements paresseux sous concurrence
Mesuré le 25/08/2026 (révisions des lots 2 et 5) : sans aucune bascule de
profil, une rafale d'appels concurrents pendant un chargement paresseux
produit des résultats faux en silence — search_workflows("CST_", application: "CustomApp") a répondu count: 0 (contre 44 en séquentiel), et
une rafale au lot 2 a déclenché 6 chargements Metadata complets en
parallèle (6 × [EntityResolver] Cache expired or empty, fetching…).
Rejoués en séquentiel, les mêmes appels sont corrects.
Trois défauts structurels dans les services à cache (workflow-service,
ad-service, entity-resolver) :
L6.3 — Ne pas encaisser un vide anormal
fetchAllWorkflows fait response?.entities || [] puis met en cache le
résultat même vide, avec un timestamp valide : toute réponse transitoirement
anormale (forme inattendue sous concurrence) devient un cache vide
empoisonné pour tout le TTL — cause probable du count: 0 mesuré. Une
forme sans entities doit lever ; un entities: [] réel reste cachable
(des applications légitimement vides existent, ex. SmartUI).
É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 de10.255.255.2répond400 {"error":"invalid_request","error_description":"Tenant not found"}pour le tenantAD. L'hôte et le STS fonctionnent (LIMAGRAIN, même hôte, passe 4/4) : c'est la valeurAD_TENANTdu.envqui 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. Depuis L3.2, le corps de la réponse du STS (Tenant not found) remonte dans les erreurs d'outils et du smoke test. - Bascule de profil concurrente aux appels en vol (mesuré le 25/08/2026,
révision du lot 2). Le serveur traite les
tools/callen concurrence : unswitch_wms_profileémis pendant que des requêtes sont en vol les fait partir sur le nouveau profil (observé : une requête destinée à EUROTRAFIC exécutée sur l'hôte10.255.255.2après la bascule suivante). Conséquence du singleton d'état global (D8). À traiter si un cas réel de mélange de profils est observé (piste : sérialiser lestools/callou figer le profil résolu au début de chaque appel). La manifestation « caches » de la même concurrence est traitée par le lot 6 ci-dessus. select_expression: les projections via le paramètreSelectprovoquent 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).