blog.vfg.lol

La passerelle se présente : VFGate, palier delta 7

Ce que la porte transporte réellement, ce qu'elle ne transporte pas encore — et sept défauts qu'on nomme au lieu de les taire.

VFGate est la porte par laquelle toutes les applications de l'écosystème se parlent. Elle normalise le chat, le spectacle et le web en un seul format, puis elle achemine. Une phrase suffit à la définir : les applications ne se connaissent pas, elles connaissent VFGate.

Aujourd'hui, cette porte est un POC avancé qui fonctionne. Ce palier, nommé delta 7, ne corrige rien : il mesure et il nomme. C'est un livrable de documentation, et son intérêt tient entièrement dans la liste des défauts qu'il publie.

TL;DR

  • VFGate est la passerelle : 7 paquets, 12 types d'adaptateurs, un contrat unique (le VME).
  • Mesuré le 30 septembre : 195 tests passent, la production répond 401 (fermée par défaut, c'est conforme), et le jeu Jah est en ligne en 4.0.0-alpha.13.
  • Sept défauts sont documentés, aucun n'est corrigé — dont un qui produit des doublons en silence et un journal d'audit de 174 Mo sans rotation.
  • Un chiffre ne suffit pas : un 401 prouve que la porte est là, pas ce qu'elle transporte.

1. Le problème que résout une passerelle

Trois familles d'applications coexistent ici. Celles qui parlent (Telegram, Discord, Matrix, un flux RSS), celles qui pilotent le réel (OSC vers les lumières, ArtNet, MIDI), et celles qui déclarent (un webhook générique). Sans passerelle, chacune devrait connaître le protocole de toutes les autres : six formats pour six applications, et quinze quand on en ajoute une septième.

La passerelle fait le contraire. Il n'existe plus qu'un seul format — le VME, VFGate Message Envelope — et chaque application ne connaît que celui-là. Ajouter un service, c'est écrire un adaptateur, pas modifier les douze autres.

Le VME porte sept blocs : un identifiant, une source, une cible, un type, une charge utile, des métadonnées (dont l'horodatage et l'original brut), et un routage (le fil et la priorité). Trois d'entre eux sont obligatoires à l'intérieur même des sous-blocs : source.adapter, target.adapter et meta.at.

Un détail mérite d'être dit, parce qu'il explique beaucoup de choses ensuite : le contrat n'impose aucune liste de types d'événements. type est une chaîne libre. C'est un choix d'extensibilité — et c'est aussi la porte par laquelle un adaptateur peut publier un événement que personne n'écoute, sans qu'aucune erreur ne se lève.

2. Ce qui tourne

Sept paquets, dans un monorepo npm :

PaquetRôle
corebus d'événements, routeur, moteur de règles, store, audit, configuration
schemavalidation du VME et des schémas d'adaptateur et de route
apiFastify — REST, WebSocket, bridge
adminl'interface d'administration
adapters-chatTelegram, Discord, Matrix, RSS, IRC, WhatsApp
adapters-showOSC, ArtNet, MIDI
adapters-webwebhook générique, ponts vers Hero

Douze types d'adaptateurs sont enregistrés. Le chemin d'un événement est toujours le même : un adaptateur produit un VME, le bus l'audite et le journalise, le routeur cherche une route correspondante, le moteur de règles évalue ses conditions, et les abonnés configurés reçoivent le tout par webhook. Un seul point, dans tout le code, effectue la livraison sortante : c'est lui qui appelle l'envoi de l'adaptateur et qui met à jour son état de santé.

3. Ce que la mesure dit de la production

Une porte qui répond n'est pas une porte qui transporte. Je l'ai appris à mes dépens en écrivant ce palier : mes premières sondes sur la production sont revenues vides, et j'ai failli conclure que le service était mort. La cause n'était pas le service, c'était mon instrument.

La bonne question n'est pas « est-ce que ça répond », c'est « qu'est-ce que ça répond ». Un HTTP 401 veut dire que le service est vivant et que l'accès est fermé — ce qui est exactement la règle du dépôt. Une réponse vide veut dire que mon outil a échoué à lire le corps. Confondre les deux, c'est fabriquer un faux constat.

MesureRésultat au 30 septembre
Tests locaux195 sur 195, zéro échec (rejoué, pas recopié)
VFGate production401 — vivant, accès fermé par défaut
Adaptateurs en production401
Jah Messenger{"status":"ready","version":"4.0.0-alpha.13"}

Il reste une vraie inconnue, et je préfère l'écrire que la combler par une supposition : ce que la production a réellement chargé. Un relevé antérieur, lors d'une rotation de jeton, avait observé une liste d'adaptateurs vide — une API vivante avec zéro adaptateur actif, donc aucune boucle complète possible. Ce point demande un accès au serveur, pas une requête HTTP.

4. Les sept défauts, nommés

C'est la partie utile de ce palier. Aucun n'est corrigé ici : documenter n'est pas réparer, et chacun mérite son correctif avec son test. Ils sont classés par coût réel, pas par élégance.

D'abord le plus coûteux : l'envoi sortant n'est ni authentifié ni idempotent. La route qui envoie un message vers un adaptateur n'exige aucun jeton et ignore la clé d'idempotence que le consommateur lui transmet pourtant. Or Jah Messenger, de son côté, envoie bien cette clé et rejoue ses échecs depuis une file locale — avec un délai doublé à chaque tentative, un plafond d'une heure et vingt essais. Un rejeu après une réponse perdue produit donc un doublon silencieux. C'est le défaut le plus dangereux de la liste parce qu'il est invisible en test et ne se manifeste qu'en production, sur une liaison instable. Deux documents, deux équipes, et un contrat qui n'a jamais été vérifié des deux côtés.

Ensuite, un chemin qui pointe vers une route qui n'existe pas. Le pont vers le jeu construit une adresse /api/bridge/v1/telegram/ingest — cette route n'existe pas chez le jeu, qui n'expose que /api/bridge/v1/ingest. Un 404 garanti, donc. Sauf que le chemin n'est jamais emprunté par le pipeline réel, parce que la condition pour l'atteindre demande un type d'événement qu'aucune route ne produit. L'impact est aujourd'hui nul, la bombe est posée pour demain.

Puis le journal d'audit, qui n'a pas de rotation. 182 740 270 octets, soit environ 174 Mo, et le mot « rotation » est absent du module d'audit. Un éditeur de texte ne peut plus l'ouvrir. La croissance n'est pas bornée, et le fichier contient encore des contenus de conversation réels du mois de juin.

Puis deux configurations pour un seul service. Le dossier de données est calculé depuis le répertoire de lancement. Lancé depuis un endroit, le service lit un store peuplé de six adaptateurs et huit routes ; lancé depuis un autre, il lit un store vide. C'est la cause la plus probable des « ma configuration a disparu », et le premier piège à connaître avant de déboguer ce dépôt.

Et trois défauts de moindre ampleur : une fonction d'analyse des statistiques définie mais jamais appelée ; une garde anti-boucle qui retourne « succès » et que le pipeline enregistre comme une santé normale — un échec de livraison y serait donc invisible ; et, enfin, le démarrage d'un poller par adaptateur actif, ce qui signifie que deux adaptateurs Telegram partageant un jeton lancent deux écoutes concurrentes. C'est la cause exacte de l'erreur 409 de l'API Telegram. Le dépôt connaît le risque et l'écrit dans un commentaire ; rien ne l'empêche. L'invariant qui manque tient en quatre mots : un jeton, un poller.

5. Ce que ce palier apprend sur la méthode

Deux erreurs commises en écrivant ces lignes valent d'être racontées, parce qu'elles sont plus instructives que le résultat.

La première : j'ai affirmé qu'un dépôt était privé et qu'un script n'existait pas. Les deux étaient faux, et les deux venaient de la même cause — je regardais un autre clone du même dépôt. Un dossier portait l'ancien outillage, un autre le nouveau. La même faute que celle que je venais de documenter en section 4 quatrième point, commise sur moi-même : conclure sur un signe au lieu d'aller regarder.

La seconde : mes premières sondes de production sont revenues vides, et la conclusion naturelle était « c'est mort ». Mesurer le code de statut a suffi à renverser le constat — c'était un 401, c'est-à-dire la porte qui fonctionne comme prévu.

La leçon n'est pas « il faut être plus prudent ». Une prudence vague n'empêche rien. La leçon est opérationnelle : un retour vide n'est pas une réponse. C'est un silence, et un silence peut signifier au moins trois choses — l'outil est périmé, la cible est fermée, ou le chemin est faux. Les distinguer demande une mesure de plus, jamais une déduction de plus.

C'est aussi ce qui justifie la liste des sept défauts. Un document de présentation qui se contenterait de dire « la passerelle fonctionne » serait confortable et faux. Le travail réel d'un palier, c'est de nommer ce qui ne va pas, avec le fichier et la ligne, pour que la prochaine session n'ait pas à le redécouvrir.

6. Ce qui vient ensuite

Sept actions sont rangées, dans l'ordre de ce qu'elles rapportent :

  1. mesurer le store et les adaptateurs depuis le serveur — le 401 ne dit pas ce qui est chargé ;
  2. corriger le chemin mort, qui transforme un 404 opaque en message utile ;
  3. porter l'authentification et l'idempotence sur l'envoi sortant ;
  4. poser une rotation sur le journal d'audit ;
  5. unifier la configuration, ou la rendre explicite ;
  6. faire porter l'invariant « un jeton, un poller » par le code plutôt que par la prose ;
  7. retirer le code mort et rendre visible ce que la garde anti-boucle masque.

Aucune de ces sept actions n'est difficile. Toutes étaient invisibles depuis l'intérieur du système, et c'est exactement ce qui les rendait coûteuses.


Palier delta 7, 30 septembre 2026. Les chiffres viennent d'une réexécution des tests et de sondes HTTP datées ; les défauts, de la lecture des fichiers avec leur numéro de ligne. Le document complet vit dans le dépôt, sous docs/RELEASE-delta7.md, et porte un tag : v0.1.0-delta7.

source markdown

Concepts de cet article

VFGategardeVFG Hero

Partage le plus de concepts avec