Les micromondes — le câblage
Deuxième des trois articles issus de l'audit à quatre agents. Celui-ci parle plomberie : qu'est-ce qui peut se brancher, dans quel ordre, et pour quel effort. Il contient un POC déjà exécuté dont le résultat a surpris l'auteur.
TL;DR
- Le moteur de règles de la passerelle supporte cinq conditions et deux actions, il est branché dans le pipeline — et la production porte zéro règle.
- Dix chaînes classées par effort, dont sept ne demandent aucune ligne de code : juste une règle, une route ou une variable.
- POC mesuré : la boucle
trigger → règle → condition → action → conséquencefonctionne. Trois passages, trois résultats distincts. - Le POC a révélé deux pièges de routage qui auraient coûté cher en production.
- Et il m'a fait commettre ma quatrième erreur de mesure de la journée — racontée plus bas.
1. Le chiffre qui dit tout
Le moteur de règles de VFGate — packages/core/src/rule-engine.js — supporte cinq types de condition (payloadContains, type, adapter, payloadPhase, eventType) et deux types d'action (route, brain). Il est appelé à chaque événement par le pipeline. Et les deux stores mesurés — local et production — portent :
"rules": []
Le moteur existe, il fonctionne, personne ne l'utilise.
C'est le levier le moins coûteux de tout l'écosystème, pour trois raisons mesurées :
- Une règle est du JSON, pas du code.
POST /api/vfgate/v1/rules— zéro redéploiement, zéro test à écrire. - La passerelle route vers tout le monde. Le moteur de Hero ne sait faire que de l'OSC ; VFGate atteint n'importe quel adaptateur.
- Hero reste atteignable depuis VFGate — donc les deux moteurs se composent.
2. Les dix chaînes les plus courtes
Classement par effort croissant. « 0 ligne » signifie : une règle, une route, ou une variable — jamais du code produit.
| # | Chaîne | Ce qui la déclenche | Ce qui l'exécute | Lignes |
|---|---|---|---|---|
| 1 | message entrant → lumières | route existante | OSC vers le logiciel de spectacle | 0 |
| 2 | message entrant → le monde RPG | route existante | ingestion HTTP | 0 |
| 3 | idée capturée → le hub | relais existant | webhook | 0 |
| 4 | mission terminée → XP | gestionnaire existant | écriture de statistique | 0 |
| 5 | règle d'un monde → lumière | API de règles | OSC + flux temps réel | 0 |
| 6 | règle de la passerelle : le texte contient un mot → OSC | API de règles | action de routage | 0 (JSON) |
| 7 | pression → XP d'inactivité | émetteur existant | ingestion | 0 |
| 8 | wizz → message privé | route existante | ingress | 1 (variable) |
| 9 | statistique → Telegram | route à créer | envoi | 1 (JSON) |
| 10 | message sortant → lumière | file existante | envoi adaptateur | 2 |
Les chaînes 1 à 7 ne demandent AUCUN code. C'est le chemin le plus court vers une boucle complète visible.
3. Le POC, mesuré
Le protocole, en clair : un récepteur OSC de trente lignes écoute en UDP et journalise chaque datagramme. Une règle est créée par l'API. Un événement est publié.
La règle :
{
"triggerTopic": "chat.inbound",
"condition": { "payloadContains": "matrice" },
"actions": [{ "type": "route", "targetAdapter": "stack-osc" }]
}
Les trois passages :
| Règle | Texte publié | Datagrammes reçus | Adresse OSC |
|---|---|---|---|
| activée | contient « matrice » | 1 | /vfgate/out |
| activée | sans le mot | 0 | — |
| désactivée | « route seule » | 1 | /matrice/chat/in |
Ce que ça démontre :
- Passages 1 vs 2 — la condition discrimine. Même sujet, même cible, un seul mot change, et l'OSC part dans un cas et pas dans l'autre. Le moteur de règles fonctionne.
- Passage 3 — la route fonctionne aussi, indépendamment de la règle, et elle applique son gabarit de transformation : l'adresse devient
/matrice/chat/in. - Les deux chemins n'émettent pas la même adresse : la règle envoie vers une adresse par défaut, la route applique son gabarit. Deux chemins, deux comportements.
4. Les deux pièges découverts
Ils sont importants, parce qu'ils sont silencieux.
Piège 1 — le reroutage des adresses. La fonction qui choisit la cible (resolveOscTarget) envoie toute adresse commençant par /matrice/ vers la configuration courante — pas vers la sortie déclarée dans l'adaptateur. Une règle qui vise une adresse de ce préfixe part donc ailleurs que prévu. Sur le store de production, cette cible est une adresse de réseau privé : un test local ne verrait jamais partir le datagramme.
Piège 2 — le gabarit n'est pas appliqué par la règle. Une route peut transformer le message (son transform.template) ; une règle ne le fait pas. Le même événement produit donc deux adresses différentes selon qu'il passe par l'un ou l'autre.
Et une limite structurelle : la fonction d'évaluation des conditions est une cascade if / return. Une seule condition effective par règle — on ne peut pas combiner « c'est ce type d'événement » ET « le texte contient ce mot ». La règle reste grossièrement expressive ; l'affiner demande de toucher le moteur.
5. La quatrième erreur de mesure de la journée
Je dois raconter celle-ci, parce qu'elle est plus instructive que le résultat.
En isolant la route de la règle, j'ai obtenu zéro datagramme sur mon récepteur local. J'ai écrit : « la route ne tire pas ».
C'était faux. La route tirait. Elle envoyait vers l'adresse de réseau privé du store de production — c'est-à-dire ailleurs que là où j'écoutais.
J'ai conclu d'un « 0 » qu'une fonctionnalité était cassée, alors que la cause était entièrement de mon côté. Le même jour, plus tôt, j'avais déjà commis trois fois la même faute sous d'autres formes :
- un comptage grossier de « 409 » dans des journaux donnait 40 occurrences ; le comptage précis par code HTTP en donne 1 — les durées en millisecondes contenaient le motif ;
- j'ai déclaré un dépôt « privé et non cloné » alors qu'il était cloné localement ;
- j'ai déclaré un script « inexistant » alors qu'il existait — dans un autre clone du même dépôt.
Le fil commun est unique : dans chaque cas, j'ai conclu sur un signe au lieu d'aller regarder. Un motif dans une ligne de log, un fichier absent d'un dossier, un silence sur un port d'écoute.
La leçon du 30 septembre disait : « un retour vide n'est pas une réponse ». C'est exactement ce que j'ai refait — quatre fois. Ce qui a fonctionné, à chaque fois, n'a pas été une relecture attentive : c'était une mesure de plus.
6. Les trois POC à lancer
POC 1 — le bus fait bouger la lumière (0 ligne). Publier un message entrant, vérifier le datagramme. C'est la validation la plus rapide que le transport fonctionne.
POC 2 — la règle existe vraiment (1 règle JSON). ✔ fait, mesuré, documenté ci-dessus.
POC 3 — la boucle complète (variables d'environnement). Une mission terminée dans la Matrice monte le XP du monde RPG, qui émet un événement, qui devient une lumière. La chaîne la plus longue qui ne demande aucun code.
7. Les frictions qui gênent le câblage
Sept obstacles mesurés, chacun corrigeable en une ligne :
| Friction | Correction |
|---|---|
| Deux stores selon le répertoire de lancement | figer le chemin du store en variable d'environnement |
| Une seule condition effective par règle | refonte de l'évaluateur en conjonction de prédicats |
| L'envoi sortant sans authentification ni idempotence | lire le jeton et la clé transmis — un consommateur les envoie déjà |
| Des routes mortes (les adaptateurs émettent un autre sujet) | renommer les sujets déclarés |
| Journal d'audit sans rotation | poser une rotation |
| Un champ déclaré mais jamais implémenté | l'implémenter ou le retirer du schéma |
| Deux services sur le même port | réallouer |
Deuxième de trois articles. Le premier posait la doctrine : ce qu'est un monde, et ce qu'il a le droit de faire. Le troisième traitera du SSOT : pourquoi un manifeste peut mentir par structure, et pourquoi les liens entre projets ne se tissent pas tout seuls.