Trois agents. Trois dépôts. Un périmètre exclusif chacun. Mission : comprendre pourquoi la chaîne de déclencheurs VFG semblait « pas à jour, non fonctionnelle », puis préparer un POC. Les trois rapports étaient solides — et aucun n'a vu le défaut décisif, parce qu'il n'était dans aucun des trois dépôts. Ce billet raconte l'audit, le harnais vfg-gate-trigger/, et les preuves rejouées le 15 septembre 2026.
Contexte : une chaîne qui ment poliment
VFGate est le hub d'événements agents (API :5191, admin :5190). Un relay pousse vers Chronos (:4344). Fil (:4477) lit Chronos en lecture seule.
Le problème structurel, documenté dans l'audit et le README du POC : l'API VFGate ne dit jamais si un événement a été livré. Un POST répond 200 {ok:true} dans quatre situations indiscernables :
| État réel | Ce qui s'est passé | L'API dit |
|---|---|---|
delivered | une sortie a réellement livré | 200 ok:true |
mock-only | send a renvoyé {mock:true} — aucun réseau | 200 ok:true |
no-adapter | adaptateur absent ou sans send | 200 ok:true |
error | send a jeté une exception | 200 ok:true |
Fil ne peut (et ne doit) pas conclure une livraison depuis la seule présence visible. Le POC affiche un verdict déduit de la configuration, et l'annonce comme tel.
Ce que les audits en lecture ont bien vu
Substance toujours vraie :
- Quatre erreurs critiques avalées côté pipeline (adaptateur absent, exception d'envoi, promesse non attendue, exception de traitement) → toujours
ok:truecôté appelant. - Panneau de réglages : confirmation de succès inconditionnelle ; valeur de retour API non lue ; secret vide ignoré.
- Moteur de règles étroit : actions
route/brainseulement ; cascade exclusive ; clé inconnue →true(faute de frappe = règle attrape-tout). - Chaîne de présence câblée : preuve quantitative d'audit — 977 / 1 000 identifiants du journal local retrouvés dans l'agrégateur.
Conclusion trompeuse possible : « la chaîne fonctionne, pas de maillon manquant » — vrai pour le câblage, insuffisant pour l'exécution.
Ce qu'aucun n'a vu : la collision :5191
À l'exécution du POC :
VFGate injoignable sur …:5191/api/vfgate/v1 — HTTP 404
alors que /health répondait 200 une seconde plus tôt. Cause constatée :
127.0.0.1:5191 vite --host 127.0.0.1 --port 5191 --strictPort
0.0.0.0:5191 node src/server.js ← l'API VFGate
Deux processus, deux bindings. L'adresse plus spécifique gagne : 127.0.0.1 atteint Vite, pas l'API. Origine identifiée : RainbowPiano/playwright.capture.config.ts (défaut PLAYWRIGHT_PORT ?? 5191 ; le voisin playwright.config.ts utilise déjà 5183). La SSOT (_ssot/ports.json) attribue 5191 à VFGate API seulement — Vite est un intrus non déclaré. Incident déjà vu au moins deux fois (dont river-matrix).
Le harnais : prédire sans injecter, ou injecter et observer
Emplacement : D:\GitRepos\Chronowflow\vfg-gate-trigger/ (hors src/ Fil ; aucune dépendance npm).
| Mode | Effet |
|---|---|
--dry | N'injecte rien. Lit adaptateurs / routes / règles et prédit. |
--live | Injecte un événement, puis observe. |
--presence | Topic agent.presence : agent.turn.started puis completed (Stop). |
lib.mjs reproduit à l'identique topicMatches et matchCondition du moteur VFGate (marqueurs SOURCE) — y compris les défauts — pour que le dry prédise le comportement réel.
Défauts URL : VFGate :5191 · Chronos :4344 (plus :4340) · Fil :4477.
Faux négatifs corrigés par l'exécution
| Erreur initiale | Correction | Mesure |
|---|---|---|
POC pointait Chronos :4340 (périmé, ADR 0005) | Défaut :4344 | Ingestion 1 558 ms → 17 ms (README, session initiale) |
Audit annonçait {events:[…]} | Forme réelle {count, items} | Faux négatif d'ingestion évité via readChronosItems |
| Masquage secrets « absent du code » | Présent et testé dans main (7f1a18a) | Instance obsolète en mémoire, pas oubli de code |
Preuves rejouées — VERIFICATION.md (2026-09-15 ~16:36–16:38)
Révision dépôt Chronowflow : c662246. Node v22.14.0. Verdict global : PASS.
Amont (prérequis)
| Cible | Résultat |
|---|---|
VFGate /api/vfgate/v1/health | 200 — 6 adaptateurs, 0 règle |
Chronos /healthz | 200 — port 4344 |
Fil /api/chronos | 200 — corps ~1,3 Mo |
--dry
| Champ | Mesure |
|---|---|
| Exit | 0 |
| Config | 6 adaptateurs, 8 routes, 0 règles |
| Routes match | 4 |
| Verdict | mock-only |
| Injection | aucune |
--live --presence (keyword verify-agent-20260915)
| Étape | Résultat |
|---|---|
| Publication | HTTP 200 |
Champ delivery | absent (étape 2 PLAN toujours ouverte) |
processed | false (hors bridge Hero — documenté) |
| Chronos | ingéré après 4 233 ms cette session |
| Fil | session TROUVÉE, state=active |
| Stop | completed publié |
| Verdict routes | no-match (déduit, pas mesure de livraison) |
Latence Chronos variable : 17 ms / 1 558 ms (README) vs 4 233 ms (VERIFICATION) — la chaîne est PASS, la latence n'est pas un SLA figé.
Non exécuté (et non compté)
| Item | Statut |
|---|---|
PLAN A1 (config vide → no-match) | non exécuté |
| PLAN A2 (règle présence → telegram) | non exécuté (0 règle en place) |
chat.inbound live hors présence | non rejoué |
npm test / tsc / eslint / build Fil racine | hors périmètre POC |
Masquage UI 60 s après completed | non attendu dans la fenêtre de vérif |
Limites assumées
- Le dry ne détecte pas les échecs d'exécution d'adaptateur.
- Le verdict live reste déduit, pas une mesure
delivered. - Un dismiss local
fil.agent.dismiss.v1peut masquer la session POC dans le navigateur — invisible depuis le serveur. - Instance VPS
gate.vfg.lol: non testée.
Suite hors Fil (ownership explicite)
- 0.5 — Redémarrer VFGate API depuis
BDDBridgePOCbranchemain(fermer fuites F1/F5 sans patch). - 0.6 — RainbowPiano Playwright
5191→5183: consignié fait dansSTATUS.mdFil (15 septembre) ; non re-prouvé ici par collision Vite. - 0.4 — Rotation humaine des tokens exposés pendant l'audit (aucune valeur ici).
Une phrase pour la suite
La chaîne présence est prouvée bout en bout ; ce qui manque encore, ce n'est pas un câble Fil, c'est qu'un 200 ok:true VFGate reste incapable de dire s'il a vraiment livré.
Canon technique : vfg-gate-trigger/VERIFICATION.md, README.md, AUDIT.md, HANDOFF.md, PLAN.md. Ancien récit unique : vfg-gate-trigger/ARTICLE-BLOG.md (renvoi vers ce billet). Miroir : Chronowflow/docs/devblog/.