242b0c0f1c6c94e82cf09a888c202fa5247cc729
28 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
242b0c0f1c |
L6.3 : une reponse hors enveloppe leve, au lieu de se faire passer pour vide
Les services lisaient `response?.entities || []` sur les reponses de l'API AD
(enveloppe { entities: [...] }, D4). Toute reponse d'une AUTRE forme — corps
vide, objet d'erreur, champ absent — devenait donc un tableau vide,
indistinguable d'une page finale legitime, et etait mise en cache avec un
timestamp valide : un cache vide empoisonne pour tout le TTL, sans le moindre
message. C'est la cause probable du `count: 0` mesure sous rafale, et le mode
d'echec le plus couteux du lot, parce qu'il se lit comme une reponse.
Le contrat est porte par src/services/ad-envelope.js pour les trois sites
(Workflow/GetByApplication, Application/GetAll, <Type>/GetByApplication) :
`{ entities: [...] }`, `[]` reel compris, est rendu tel quel ; toute autre
forme leve. entity-resolver etait deja conforme — il leve deja si
/configuration/applications ou le Metadata ne rendent aucune entite.
Volontairement sans retry ni logique de resilience : le but est de rendre
l'anomalie visible et non persistante. La rattraper la rendrait invisible,
c'est-a-dire exactement le defaut corrige.
--- Verifications (LIMAGRAIN) ---
Vide LEGITIME — search_workflows sur SmartUI (0 workflow, D26) :
search_workflows(SmartUI) : success=true application=SmartUI count=0
isError=false
error : (aucune)
hint : Aucun workflow trouve dans l'application "SmartUI" — c'est la
SEULE interrogee, les autres ne le sont jamais implicitement.
Le specifique client (prefixe CST_) vit dans "CustomApp" : [...]
cache pose ? workflowCachesByApplication =
{"SmartUI":{"cached":true,"count":0,"timestamp":1787665868988,
"age":0,"valid":true}}
stderr : No more workflows to fetch / Successfully cached 0 workflows
Forme SANS `entities` — non declenchable a la demande contre le vrai WMS,
couverte par un test direct (apiService.post substitue, renvoie {}) :
--- workflow-service fetchAllWorkflows("EasyWMS") avec une reponse {} ---
erreur levee : Failed to fetch workflows for application "EasyWMS":
Reponse inattendue de l'API AD sur Workflow/GetByApplication
(application "EasyWMS", offset 0) : un objet vide, au lieu de
l'enveloppe attendue { entities: [...] }. Rien n'a ete mis en cache —
relancez l'appel. Si l'erreur persiste, l'API AD est en defaut [...]
cache : {} (attendu {})
--- workflow-service fetchApplications() avec une reponse {} ---
erreur levee : Reponse inattendue de l'API AD sur Application/GetAll : [...]
cache : {} (attendu {})
--- ad-service getElements("Command") avec une reponse {} ---
erreur levee : Failed to fetch Command for application "EasyWMS": [...]
cache : {} (attendu {})
--- puis une reponse normale : le refetch repart (rien de coince) ---
1 workflow(s), cache : {"EasyWMS":{"cached":true,"count":1,...}}
Cas nominaux du helper (unitaire) : { entities: [] } et { entities: [1,2] }
passent ; {}, null, undefined, [], { error }, "texte" levent tous.
--- Non-regression, rafales rejouees 3 fois ---
L6.1 rafale workflow RUN 1/2/3 : 6/6 a count=44 | fetch=1 joins=5
L6.1 rafale resolver RUN 1/2/3 : 6/6 succes | chargements=1 joins=5 GET=5
L6.1 rafale mixte RUN 1/2/3 : CustomApp@44=3 EasyWMS@50=3 |
fetch=2 joins=4
L6.2 bascule RUN 1/2/3 : caches peuples : 0 (attendu 0)
sequentiel nominal count=50 puis count=50 | fetch=1 cached=1 joins=0
Baseline finale : tools/list 23, resources/list 6 ; npm test 4/4, exit 0.
ROADMAP : lot 6 retire. Le point ouvert « bascule de profil concurrente aux
appels en vol » reste — D27 borne les chargements paresseux, pas le routage
d'une requete deja partie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
097b76c7ef |
L6.2 : garde de generation contre les ecritures post-invalidation
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>
|
||
|
|
b7151b3bc7 |
L6.1 : dedupliquer les chargements paresseux en vol (single-flight)
Le serveur traite les tools/call en concurrence. Les trois services a cache chargent paresseusement sans se coordonner : le premier appelant qui trouve le cache invalide lance le fetch, et tous ceux qui arrivent pendant ce fetch le trouvent *encore* invalide et lancent le leur. Une rafale de 6 appels identiques declenchait donc 6 chargements complets pour une seule cle. Ce n'est pas qu'un gaspillage : la duplication surcharge l'API AD au point de la faire echouer. Rafale mixte de 14 appels, avant correction — les 4 appels EasyWMS (~4000 workflows) reviennent en erreur, les memes passent en sequentiel : [Workflow] Error fetching workflows for "EasyWMS": POST https://10.255.255.2/AD/api/Workflow/GetByApplication failed (HTTP 500) fetching from API: 8 | EntityResolver Cache expired or empty: 3 Motif commun extrait dans src/services/single-flight.js — une Map de promesses, pas de dependance externe. Une cle par entree de cache (workflows::<app>, applications, <app>::<type>, metadata) : deux cles distinctes se chargent toujours en parallele, aucun prechargement (D26 intact). La promesse est retiree au reglement, succes *ou* echec, pour qu'un fetch en erreur ne reste pas coince. Le log de fetch reste l'observable (un par chargement reel) ; les appelants joints emettent une ligne distincte "Fetch already in flight ... joining it". --- Verifications (LIMAGRAIN), rafales rejouees 3 fois --- Phase 0, reproduction avant correction : 6 x search_workflows CustomApp -> count 44 x6, 'fetching from API' : 6 6 x query_wms_entities Container -> 6 succes, 'EntityResolver] Cache expired or empty' : 6 Rafale de 6 search_workflows {"query":"CST_","application":"CustomApp"} : ===== RUN 1 ===== ===== RUN 2 ===== ===== RUN 3 ===== id 10 success=true application=CustomApp count=44 (idem RUN 2 et RUN 3, id 11 success=true application=CustomApp count=44 les 6 reponses a 44) id 12 success=true application=CustomApp count=44 id 13 success=true application=CustomApp count=44 id 14 success=true application=CustomApp count=44 id 15 success=true application=CustomApp count=44 -- 'fetching from API' : 1 | 'joining it' : 5 [RUN 1] -- 'fetching from API' : 1 | 'joining it' : 5 [RUN 2] -- 'fetching from API' : 1 | 'joining it' : 5 [RUN 3] Rafale de 6 query_wms_entities {"entity_type":"Container","limit":1} : RUN 1/2/3 : id 10..15 success=true count=1 (6/6) -- 'EntityResolver] Cache expired or empty' : 1 | joins : 5 | GET Metadata/Entities : 5 [identique RUN 1, RUN 2, RUN 3] (avant : 6 chargements, soit 30 GET Metadata) Rafale mixte EasyWMS + CustomApp (3 + 3) — un fetch par application : RUN 1/2/3 : CustomApp count=44 x3, EasyWMS count=50 x3 [Workflow] Cache expired or empty for "CustomApp", fetching from API... [Workflow] Cache expired or empty for "EasyWMS", fetching from API... total fetch=2 joins=4 [identique RUN 1, RUN 2, RUN 3] Plus aucun HTTP 500 : un seul fetch EasyWMS concurrent au lieu de 4. Chemin sequentiel nominal, strictement inchange (driver sequentiel) : [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 Liberation de la Map sur echec (test direct, apiService.post substitue : echoue au 1er appel, reussit ensuite) : [Workflow] Cache expired or empty for "TestApp", fetching from API... [Workflow] Fetch already in flight for "workflows::TestApp", joining it (x2) --- rafale de 3 sur un fetch en echec : appelant 0/1/2: rejected - Failed to fetch workflows ... panne reseau simulee appels reseau reels: 1 (attendu 1 : les 3 partagent le meme fetch) cache pose ? {} (attendu {} : rien en cache sur echec) --- appel suivant (la Map doit avoir ete liberee) : resultat: 1 workflow(s), appels reseau cumules: 2 Baseline : tools/list 23, resources/list 6 ; npm test 4/4 exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8a631f5ea4 |
Passation lot 6 : chargements paresseux sous concurrence (D27 réservée)
Le point ouvert concurrence est scindé : la manifestation caches devient le lot 6 (L6.1 single-flight par clé, L6.2 garde de génération contre les écritures post-invalidation, L6.3 refus d'encaisser un vide anormal — response?.entities || [] transforme une réponse transitoirement anormale en cache vide empoisonné pour tout le TTL, cause probable du count 0 mesuré au lot 5). La manifestation bascule de profil reste en point ouvert, hors périmètre du lot. Preuves dans la passation : 6 chargements Metadata parallèles pour une rafale de 6 appels (lot 2), count 0 contre 44 en séquentiel (lot 5), avec la commande de rafale reproductible et l'exigence de rejouer chaque vérification de concurrence trois fois. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1851b38c62 |
Révision lot 5 : validé ; course de chargement paresseux consignée
Les vérifications des quatre blocs rejouées via le protocole passent toutes : fenêtre verbatim (23 117 chars par défaut, tranches 20000+20000+20000+11512 = 71 512 reconstituant le blob à l'octet près), plafond de volume (24 432 / 22 992 / 738 sur les trois outils, réponses sous plafond identiques), champ tool présent sur les 15 enveloppes locales (grep 15/15), découvrabilité (hint nommant CustomApp sur résultat vide, aucun hint parasite). Baseline 23/6, npm test 4/4 exit 0. Découverte de révision : sans bascule de profil, une rafale concurrente pendant le chargement paresseux du cache workflow renvoie des résultats faux en silence (count 0 sur CustomApp contre 44 en séquentiel) — les fetchs en vol ne sont pas dédupliqués. Ajouté au point ouvert concurrence avec la piste de correction. handoff-lot5.md supprimé (livré et révisé). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
660c62c32c |
L5.4 : rendre les autres applications visibles depuis les recherches
Cas reel du 25/08/2026 : une session Cowork cherchant des workflows CST_ sans
passer application="CustomApp" a conclu que l'AD n'en contenait aucun -- alors
que CustomApp en porte 153. Le parametre application existait bien (D26) et
etait documente ; ce qui manquait, c'est que RIEN dans la reponse ne disait
qu'on n'avait regarde qu'une application sur neuf. Un defaut silencieux se lit
comme une exhaustivite.
search_workflows et search_ad_elements rappellent desormais TOUJOURS
l'application effectivement interrogee (plus seulement quand le parametre a ete
passe), et ajoutent un hint quand la recherche revient vide.
Seuil a 0 resultat, pas "peu" : toute valeur non nulle produirait un hint
parasite sur une recherche legitimement etroite, et le mode d'echec observe est
bien le zero pris pour une absence.
Le hint nomme les autres applications depuis la liste allegee DEJA en cache ;
sans elle il reste generique et renvoie vers list_workflow_categories. Aucun
appel reseau n'est fait pour construire un hint -- ce serait exactement le
prechargement que D26 interdit.
Verifie en execution (protocole, LIMAGRAIN) :
search_workflows {"query":"CST_"} 404 chars
application: "EasyWMS", count: 0, hint nommant CustomApp et renvoyant
vers list_workflow_categories
search_workflows {"query":"CST_"} apres
list_workflow_categories 461 chars
meme hint, enrichi de la liste en cache : Common, Notifications, SmartUI,
WarehouseWebDesigner, GalileoFaults, CustomApp, User, AGV
search_workflows {"query":"CST_","application":"CustomApp"}
count: 44, application: "CustomApp" -- dont CST_SendRejectContainersToPK
search_workflows {"query":"stacker"} 14 192 -> 14 220 chars
count: 50 inchange, application: "EasyWMS" presente, aucun hint parasite
search_ad_elements Command "CST_" 457 chars
application: "EasyWMS", count: 0, meme hint
stderr : aucun fetch d'une application non demandee. Seules EasyWMS et
CustomApp sont chargees, chacune sur demande explicite.
Rebouclage complet apres modification des schemas : 23 outils, 6 resources,
les 23 noms de tools/list atteignent leur module (aucun "Unknown tool" ;
execute_command verifie statiquement, non appele car il ecrit dans le WMS),
D23 rejette toujours un parametre inconnu sur les quatre schemas modifies,
npm test 4/4 en code 0.
Lot 5 retire de la ROADMAP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a1acb781b0 |
L5.3 : champ tool dans toutes les enveloppes d'erreur locales
Convention 3 impose { success: false, error, tool }, mais le champ tool n'etait
ajoute que par le wrapper de src/index.js. Les enveloppes construites
localement dans src/tools/ ne le portaient pas : call_query_api avec
query_type: 7 repondait { success: false, error: "query_type invalide : ..." },
sans tool. Le contrat etait donc respecte ou non selon le chemin d'erreur --
alors qu'il est lu par Claude, pas par un humain.
Balayage des 8 modules : 15 enveloppes success:false au total, 11 y ont gagne
le champ tool (ad-tools, config-tools, wms-query-tools et workflow-tools
l'avaient deja via le catch de leur executeTool). Les champs additionnels sont
conserves et passent apres tool : warning de resolution d'api-tools,
availableCount + hint de get_entity_metadata, listes de profils de
profile-tools.
Grep de controle -- 15 enveloppes, 0 sans tool :
src/tools/ad-tools.js:140 tool: name
src/tools/api-tools.js:170 tool: 'call_query_api'
src/tools/api-tools.js:217 tool: 'execute_command'
src/tools/config-tools.js:179 tool: 'get_system_parameters'
src/tools/log-tools.js:116 tool: 'list_log_files'
src/tools/log-tools.js:157 tool: 'read_recent_logs'
src/tools/log-tools.js:227 tool: 'search_logs'
src/tools/metadata-tools.js:110 tool: 'get_entity_metadata'
src/tools/metadata-tools.js:154 tool: 'get_entity_metadata'
src/tools/metadata-tools.js:197 tool: 'generic_search'
src/tools/profile-tools.js:111 tool: 'get_current_wms_profile'
src/tools/profile-tools.js:129 tool: 'switch_wms_profile'
src/tools/profile-tools.js:159 tool: 'switch_wms_profile'
src/tools/wms-query-tools.js:169 tool: name
src/tools/workflow-tools.js:117 tool: name
Verifie en execution (protocole, LIMAGRAIN sauf mention) -- 11 enveloppes
declenchees, toutes avec tool :
call_query_api query_type: 7 -> tool: call_query_api
get_entity_metadata entite inconnue -> tool + availableCount + hint
query_wms_entities entite inconnue -> tool: query_wms_entities
get_workflow_details id inconnu -> tool: get_workflow_details
get_ad_elements type inconnu -> tool: get_ad_elements
switch_wms_profile profil inconnu -> tool + profiles
read_recent_logs fichier inexistant -> tool: read_recent_logs
list_log_files / search_logs /
read_recent_logs sur EUROTRAFIC -> tool, garde SaaS D9 (sans reseau)
Trois catch restent couverts statiquement, faute de declencheur : celui
d'execute_command (interdit d'appel, il ecrit dans le WMS), celui de
generic_search (l'API tolere categorie inexistante comme limit negative :
elle repond success), et celui de get_current_wms_profile (inatteignable tant
qu'un profil par defaut se charge au demarrage).
Baseline preservee : 23 outils, 6 resources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
54a6849563 |
L5.2 : plafonne la taille de reponse des trois outils de requete
Aucune borne de VOLUME n'existait sur query_wms_entities, call_query_api et
search_wms_data -- seulement une borne de LIGNES (MAX_QUERY_ROWS). Le modele
Writing serialise l'agregat complet (navigations, $id) : une seule ligne
Products y pese 95 288 caracteres, la ou la meme ligne Reading en fait ~4 500.
Toutes ces reponses depassaient le seuil de rejet du client MCP (D24), qui
renvoie un echec opaque plutot qu'un resultat partiel.
Plafond commun MAX_QUERY_RESPONSE_CHARS (defaut 25 000, meme ordre de grandeur
que MAX_LOG_SEARCH_CHARS), applique par src/services/response-limit.js : on
ecarte des LIGNES ENTIERES, jamais coupees au milieu, et on signale avec le
vocabulaire D24 (truncated / returned / omitted / hint). Le helper est partage
parce que les trois outils partagent le meme mecanisme -- ce que les mecanismes
de get_system_parameters et search_logs, eux, ne font pas. La recherche
dichotomique evite 200 reconstructions d'une charge utile de ~1 Mo.
Mesures avant/apres (protocole, LIMAGRAIN, longueur de content[0].text) :
query_wms_entities Products limit 200 957 234 -> 24 432
count 200, returned 5, omitted 195, truncated
search_wms_data "PAL" 847 543 -> 22 992
totalFound 150, returned 4, omitted 146 ; reparti en tourniquet :
Products 2, Containers 1, Tasks 1 -- sans quoi Products, en tete,
consommerait tout le budget et les deux autres reviendraient a zero
resultat sans que rien ne le dise
call_query_api Products query_type 1 95 288 -> 738
cas limite : returned 0, omitted 1, truncated, hint expliquant le
volume Writing et renvoyant vers Reading
Sous le plafond, rien ne change -- verifie identique OCTET POUR OCTET contre
la version precedente :
query_wms_entities Container limit 1 4 476 -> 4 476
call_query_api Products limit 2 9 671 -> 9 671
get_entity_schema Container 11 172 -> 11 172
search_wms_data sous plafond 11 767 -> 11 767
count_wms_entities Products 120 -> 120 (non concerne)
MAX_QUERY_ROWS et les limites par defaut des outils sont inchanges : le
correctif est le bornage signale, pas une reduction silencieuse. L'avertissement
de volume rejoint la description du parametre query_type (D25) de
query_wms_entities et call_query_api, pas celle de count_wms_entities dont la
reponse est un scalaire.
Baseline preservee : 23 outils, 6 resources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
6f54d765c4 |
L5.1 : fenetre verbatim sur le blob data de get_workflow_details
La reponse embarquait la definition EasyBuilder complete, au-dela du seuil de
rejet du client MCP (~70 000 caracteres, D24). Deux parametres de fenetre au
schema (D23) : max_data_chars (defaut 20 000) et data_offset (defaut 0). Les
metadonnees du workflow restent completes dans chaque tranche ; seul `data`
est fenetre, et dataTotalChars est porte par toute reponse.
La tranche est verbatim -- decoupe de chaine, rien d'autre. Ne jamais resumer
ni parser ce blob : la concatenation des tranches doit reconstituer la
definition a l'octet pres. Verifie : 20 000 + 20 000 + 20 000 + 11 512 =
71 512, concatenation identique au blob d'origine (premiers et derniers
caracteres compris).
Mesures avant/apres (protocole, LIMAGRAIN, longueur de content[0].text) :
StackerCrane_LocationIsAccessibleByExtractor_PR 79 092 -> 23 117
(blob data : 71 512, desormais annonce par dataTotalChars)
CST_SendRejectContainersToPK (CustomApp) 101 816 -> 23 023
(blob data : 92 362)
StackerCrane_LoadMovementForOutboundTask_PR 10 587 -> 10 652
(blob de 9 013 : sous le defaut, objet workflow identique a l'octet pres,
ni truncated ni hint -- les +65 caracteres sont les trois champs de
fenetre, contrat "total toujours porte" de D24)
Gardes de valeur dans le code de l'outil, pas dans le wrapper (D23 ne valide
que les noms) : data_offset: -5 et max_data_chars: 1.5 echouent avant tout
appel reseau avec un message nommant l'attendu.
Baseline preservee : 23 outils, 6 resources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5ec2990347 |
L5.4 : découvrabilité des applications dans les réponses de recherche
Cas réel du 25/08/2026 : une session Cowork cherchant des workflows CST_ sans passer application=CustomApp a conclu à tort à leur absence de l'AD. Vérifié en révision : CST_PickingTasksSequencing_PR et CST_ChooseDestinationFromPS existent bien dans CustomApp sur LIMAGRAI2512. Le correctif (rappel de l'application interrogée + hint sur résultat maigre, depuis la liste Application/GetAll déjà en cache) rejoint le lot 5, pas encore lancé. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
90b2c89ff1 |
Passation lot 5 : clore la famille D24 (fenêtre workflow, garde requêtes, champ tool)
Deux mesures nouvelles du 25/08/2026 aggravent le dossier : une requête Reading ordinaire query_wms_entities(Products, limit 200) pèse 957 234 caractères et search_wms_data(PAL) 847 543 — le dépassement du seuil de rejet client (~70 000, D24) ne se limite donc pas au Writing ni aux workflows. Les points ouverts correspondants deviennent un lot 5 planifié dans la ROADMAP (L5.1 fenêtre verbatim sur le blob data, L5.2 garde de taille commune aux trois outils de requête avec le cas limite d'une ligne seule au-dessus du plafond, L5.3 balayage du champ tool). Pas de nouveau numéro de décision : le lot étend D24. D27 réservable si besoin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b941f367ae |
Révision lot 4 : validé ; ligne Writing énorme et champ tool absent consignés
Les vérifications attendues des trois blocs rejouées via le protocole passent : api://catalog assaini (plus de QueryType 1, d'Aliases ni de suffixe d'assembly), query_type opérationnel (Writing 1 ligne, Metrics en erreur parlante, 7 rejeté localement sans aucun trafic réseau, warning de résolution conservé jusque dans l'échec WMS), caches par application étanches (4012/153, retour en cache hit, invalidation complète à la bascule). Baseline 23/6, npm test 4/4 exit 0, diffs propres. Deux découvertes consignées en points ouverts : une ligne Products en Writing pèse 95 288 caractères (famille D24), et les catch locaux d'api-tools omettent le champ tool du contrat (antérieur au lot). Leçon de révision : mon appel search_workflows(search_term=...) des lots précédents était silencieusement ignoré pré-D23 — le paramètre s'appelle query. La validation D23 a transformé mon erreur en signal. handoff-lot4.md supprimé (livré et révisé). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
92de85cf53 |
L4.2 : paramètre application sur les outils AD et workflow (D26)
L'application venait de WMS_APPLICATION (partagée par tous les profils) : le MCP n'interrogeait que EasyWMS, alors que CustomApp porte le spécifique client (153 workflows CST_ sur ce tenant) et que 9 applications sont déclarées par Application/GetAll. Paramètre application (défaut : l'application du profil, 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 par (application, type), workflow-service par application — sans quoi un appel CustomApp polluerait le cache EasyWMS. L'invalidation reste l'abonnement onSwitch (D8), le chargement reste paresseux (aucun préchargement des 9 applications, D10). list_workflow_categories s'adosse à Application/GetAll (liste allégée en cache : le blob data de chaque application pèse ~100 Ko) ; get_application_summary regroupe par application et ne détaille que les entrées en cache (D24), workflows compris. Acté en D26 ; CLAUDE.md mis à jour (Caches, AD), L4.2 retiré de la ROADMAP. Vérifications rejouées via le protocole (LIMAGRAIN / LIMAGRAI2512), requêtes séquentielles : - search_workflows(CST_, application:CustomApp) -> 5 objets peuplés dont CST_SendRejectContainersToPK ([Workflow] Successfully cached 153 workflows for "CustomApp"). - get_ad_elements(Workflow, application:CustomApp) -> count 153, éléments CST_* ([AD] Successfully cached 153 CustomApp::Workflow). - Séquence EasyWMS -> CustomApp -> EasyWMS sur search_workflows : 4012 vs 153, retour en cache hit ([Workflow] Using cached data for "EasyWMS"), aucune pollution ; get_application_summary montre les deux caches (workflowCachesByApplication EasyWMS 4012 / CustomApp 153). - Sans paramètre application -> comportement inchangé (W1 = W3). - switch_wms_profile EUROTRAFIC puis retour -> [Workflow] Cache cleared, [AD] All caches invalidated, [EntityResolver] Cache cleared ; summary vide. - list_workflow_categories -> 9 applications, comptes réels des applications chargées. Baseline : tools/list 23 (les 23 noms répondent), resources 6, rejet D23 d'un paramètre inconnu OK (search_workflows/applikation), npm test 4/4 exit 0. Anomalie hors périmètre consignée dans ROADMAP.md : get_workflow_details peut dépasser le seuil de rejet client (~101 800 caractères mesurés sur CST_SendRejectContainersToPK), comportement antérieur au lot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
706e628715 |
L4.1 : expose query_type sur les outils de requête (D25)
QueryType était figé à 0 en dur dans executeQuery/executeScalarQuery : le modèle Writing, opérationnel et mesuré, était inatteignable. Paramètre query_type (défaut 0) sur call_query_api, query_wms_entities, count_wms_entities — get_entity_schema et search_wms_data restent des raccourcis Reading. D3 reste la règle par défaut : la bascule est un opt-in, avertie dans les descriptions d'outils (statuts en énumérations en Writing). Garde de valeur assertValidQueryType dans le code (le wrapper D23 ne valide pas les valeurs), avant tout réseau. En query_type != 0, un nom inconnu du Metadata Reading passe tel quel avec warning (allowUnknown du resolver) — conservé aussi dans la réponse d'erreur si le WMS échoue ensuite. Acté en D25 ; CLAUDE.md nuancé, L4.1 retiré de la ROADMAP (reliquat : exploration Metrics, rapport à part). Vérifications rejouées via le protocole (LIMAGRAIN / LIMAGRAI2512) : - call_query_api(Products, query_type:1, limit:1) -> success, 1 ligne, queryType:1 dans la réponse. - call_query_api(Products, query_type:3) -> erreur structurée contenant 'ApplicationMetricDataContext' ne contient pas de définition pour 'Products'. - call_query_api(Products, query_type:7) -> erreur locale nommant les 4 contextes, aucun [API] POST/GET dans stderr. - query_wms_entities(Container, limit:1) sans query_type -> comportement inchangé (resolvedTableName Containers, Reading, pas de champ queryType). - call_query_api(FooBar123, query_type:1) -> transmis tel quel (payload QueryType:1 Expression Context.FooBar123...), erreur WMS ApplicationWritingRepository + warning de résolution dans la réponse. - count_wms_entities(Product, query_type:1) -> count 51145 (~51160). Baseline : tools/list 23, resources 6, rejet D23 d'un paramètre inconnu OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
88dab289cc |
L4.0 (suite) : retire L4.0 de la ROADMAP, oublié au commit précédent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
35b53dbef5 |
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> |
||
|
|
b37c2ac251 |
L3.1c : acte le contrat de troncature en D24, retire le lot 3 de la ROADMAP
Les deux bornages (L3.1a pagination, L3.1b plafond de volume) partagent le même vocabulaire de signal — truncated présent uniquement quand la réponse est coupée, hint actionnable, returned vs total avant coupe — mais gardent des implémentations locales : paginer et plafonner un volume sont deux mécanismes distincts, un helper commun forcerait une abstraction qu'ils n'ont pas. D24 consigne ce contrat, les garde-fous de cadrage (défauts inchangés, mesure protocolaire qui fait foi) et le changement de sens de totalParameters. ROADMAP : L3.1 livré, le lot 3 devenait vide — section retirée ; la référence à L3.1 dans L4.5 (QueryExecuteStream) renvoie désormais à D24. read_recent_logs est laissé tel quel : sans helper partagé, rien de gratuit à lui apporter (L3.1c). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
37c68a4d9a |
Révision lot 2 : validé ; log du resolver corrigé, concurrence des appels consignée
Les vérifications attendues des quatre correctifs ont été rejouées via le protocole et passent toutes : résolution Container/Alias/Item (échec avant tout POST, 6 GET seulement), rejet des paramètres inconnus et requis manquants, garde-fous get_workflow_details/search_logs, corps STS dans les erreurs d'auth sans fuite de credential. Contrôle latéral séquentiel : la bascule EUROTRAFIC reconstruit bien la table par tenant (280 entités, 7 applications, contre 288/5 sur LIMAGRAIN). Trivialité corrigée : tableNames.size (undefined sur un tableau) -> .length dans le log de chargement du resolver. Découverte de révision consignée en point ouvert : le serveur traite les tools/call en concurrence, une bascule de profil pendant des appels en vol les fait partir sur le nouveau profil. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
808586e615 |
L3.2 : fait remonter statut et corps de la réponse STS dans les erreurs d'auth
authenticate() ré-enveloppait l'erreur axios en "Authentication failed:
Request failed with status code 400" et jetait le corps de la réponse du
STS, qui contenait le diagnostic exact ({"error":"invalid_request",
"error_description":"Tenant not found"} sur le profil AD).
Réutilise _enrichHttpError (L1.1) dans authenticate() et
refreshOAuthToken(), avec payload volontairement omis : il contient les
credentials (password grant) ou le refresh_token ; l'helper n'inclut
jamais les headers. Le chemin "token trop vieux -> password grant" de
refreshOAuthToken() sort du try : un échec d'authenticate() y était
rattrapé pour... rappeler authenticate() à l'identique ; il propage
désormais son erreur enrichie directement.
Mesure (via le protocole) : switch_wms_profile("AD") puis
count_wms_entities("Products") ->
Count failed for Products: Authentication failed: POST
https://10.255.255.2/EasySTS/OAuth/Token failed (HTTP 400): Request
failed with status code 400
Response body: {"error":"invalid_request","error_description":"Tenant
not found"}
Aucun credential ni header dans la réponse (vérifié : pas de
password/Basic/Bearer/payload).
ROADMAP : L3.2 retirée, point ouvert "profil AD" mis à jour.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
97ab56f928 |
L2.3 : garde-fous get_workflow_details et search_logs
Deux anomalies préexistantes au lot 1, mesurées le 24/08/2026 :
- get_workflow_details({}) renvoyait success: true avec le premier
workflow du cache : getWorkflowDetails() comparait w.Id/w.Code/w.Name,
clés qui n'existent pas sur les objets AD réels (minuscules, D5) —
undefined === undefined matchait. Clés mortes supprimées (id et name
seuls existent, les parseInt sur des GUID étaient morts aussi), garde
d'entrée rejetant workflow_id absent avec renvoi vers search_workflows.
- search_logs({}) plantait en "Cannot read properties of undefined
(reading 'toLowerCase')" : garde d'entrée nommant "keyword" avec un
exemple d'appel.
Le wrapper D23 rejette déjà ces appels via required — les gardes côté
code restent, la validation SDK n'étant pas garantie pour les appelants
directs des services.
Mesures :
- via le protocole, les deux appels {} -> "Paramètre(s) requis
manquant(s) pour ... " (wrapper D23)
- gardes appelées en direct (sans wrapper) :
getWorkflowDetails(undefined) jette "workflow_id est requis (id ou nom
exact du workflow)..." ; search_logs({}) répond success: false avec le
message nommant keyword
- get_workflow_details avec un id réel (2e workflow du cache, pas le
premier) -> objet brut complet ($id, validFrom, ..., data), id/name
conformes à la recherche
ROADMAP : L2.3 retirée, lot 2 soldé.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
5386f54922 |
L2.2 : rejette les paramètres inconnus et les requis manquants (D23)
Mesure V1 (24/08/2026) : le SDK MCP ignore additionalProperties: false —
read_recent_logs({lines: 5}) avec la clause sur le schéma répondait
success: true, returnedLines: 100 (retombée silencieuse sur le défaut).
La validation vit donc dans le wrapper tools/call de src/index.js,
pilotée par les schémas de la table de routage (D22) : paramètre inconnu
ou requis manquant -> erreur structurée nommant le fautif et les
paramètres valides, avant tout dispatch.
Les 23 schémas portent additionalProperties: false — inerte côté SDK,
mais c'est le contrat que lisent les clients. Pas de renommage de
paramètres (écarté, cf. ROADMAP).
Mesures (via le protocole) :
- read_recent_logs({"lines": 5}) -> "Paramètre(s) inconnu(s) pour
read_recent_logs : lines. Paramètres valides : count, log_file."
- read_recent_logs({"count": 5}) -> succès, returnedLines: 5
- boucle sur 22 outils avec {} (execute_command vérifié statiquement) :
22/22 répondent, aucun Unknown tool, les 11 outils à paramètres requis
échouent avec le message actionnable
- handshake : 23 outils, 6 resources
Docs : D23 dans DECISIONS.md, L2.2 retirée de la ROADMAP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
cb625a7918 |
L2.1 : résout entity_type via l'API Metadata (D21)
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>
|
||
|
|
8c5792da52 |
Révision lot 1 : validé ; passation lot 2 (L2.1-L2.3 + L3.2), L2.3 consigné
Lot 1 révisé selon la grille : vérifications L1.1/L1.2/L1.3 rejouées en
protocole (22 outils routés + execute_command en statique), diffs lus,
baseline 23/6 et npm test 4/4 confirmés. Deux anomalies préexistantes
découvertes en bouclant sur les outils avec arguments vides :
get_workflow_details({}) renvoie le premier workflow du cache (clés
mortes Id/Code/Name dans la comparaison), search_logs({}) échoue en
TypeError non actionnable — consignées en L2.3.
handoff-lot1.md supprimé (livré), handoff-lot2.md rédigé : D21 et D23
réservés, profil AD signalé cassé (ne pas réinvestiguer), vérifications
attendues par correctif.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
52b5f90521 |
Roadmap : profil AD en échec (tenant introuvable), erreurs d'auth muettes (L3.2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e5614f3b60 |
Roadmap : retire le lot 1 livré, invalide la piste ClientModule (L4.3)
Le lot 1 (erreurs HTTP détaillées, routage par table, projections des workflows) est livré et vérifié contre le WMS réel — il sort de la roadmap. D22 étant écrite, L3.2 est ajusté en conséquence. L4.3 est corrigé d'après mesure : le champ ClientModule de QueryExecute est accepté mais sans effet observable dans les logs du WMS — une requête en échec envoyée avec ClientModule: "MCP-WMS" reste tracée « Execute error. Client: GNA », et la chaîne n'apparaît dans aucun log. Le champ n'a donc pas été renseigné. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
86923542fa |
Roadmap et décisions : exploitation de la référence API du service
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> |
||
|
|
7621b87c49 |
Roadmap : ajoute le lot 4 (modèle de données et applications)
Deux angles morts mesurés sur le tenant LIMAGRAI2512. L4.1 — QueryType est figé à 0 (Reading) en dur dans api-service.js : le modèle Writing est inatteignable. Ce n'est pas une limite de l'API, QueryType 1 répond correctement sur Context.Products — il manque le paramètre. D3 reste vrai en revanche : en Writing les statuts sont des énumérations, donc le défaut doit rester 0. L4.2 — Application vient de WMS_APPLICATION, partagé par tous les profils, sans surcharge possible. Le MCP n'interroge que EasyWMS alors que /AD/api/Application/GetAll en déclare 9. CustomApp porte le spécifique client (153 workflows, 54 queries, 11 entités préfixés CST_) et est entièrement invisible ; avec AGV, Notifications, GalileoFaults et Common, ce sont 260 workflows hors périmètre. Côté API AD le correctif est simple, l'application n'étant qu'un champ du payload — vérifié, ["CustomApp", tenant, 5, 0] renvoie bien les workflows CST_. Il faudra en revanche indexer les caches par application. Côté QueryExecute c'est non résolu : passer Application "CustomApp" ne change pas le contexte de lecture, les entités CST_ ne répondent ni au singulier ni au pluriel et aucune n'apparaît dans les 232 entités du Metadata EasyWMS. Elles sont définies dans EasyBuilder (FromMetadata: false). Consigné comme question ouverte, sans solution promise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1a1b9ebe03 |
Roadmap : lots de correction issus du diagnostic du 24/08
Ajoute ROADMAP.md et le relie depuis README.md et CLAUDE.md.
Origine : un rapport d'usage d'une session Cowork sur le profil LIMAGRAIN a
signalé 8 anomalies. Vérification faite contre le WMS réel, 4 bugs sont
confirmés et reproduits, dont un non signalé par le rapport.
Cause racine commune : entity_type est interpolé dans Context.{entity_type}
sans aucune validation, alors que le nom attendu est le TableName de l'API
Metadata et non le nom d'entité de l'AD. Container -> Containers, mais
Alias -> Alias : c'est un mapping, pas une règle de pluralisation. L'API
Metadata connaît 232 entités là où le MCP en expose 12 en dur, et la liste
documentée était fausse (Aliases n'existe pas).
Lot 1 (déblocage) : corps des erreurs HTTP remonté, routage des outils par
table explicite, projections de champs des workflows.
Lot 2 (fond) : résolution des entités via l'API Metadata, rejet des
paramètres inconnus.
Lot 3 : bornage des sorties volumineuses, documentation.
Trois propositions du rapport sont explicitement écartées, avec leur raison :
uniformisation des noms de paramètres, indexStatus sur generic_search, outil
dédié d'aide à la syntaxe.
CLAUDE.md : la section « Points ouverts » renvoie désormais vers la roadmap et
avertit des deux pièges non encore corrigés.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|