---
title: "Un port occupé, deux serveurs muets : prouver VFGate → Chronos → Fil"
subtitle: "Le POC vfg-gate-trigger : dry vs live, mock-only, Chronos :4344, et ce que trois audits en lecture n'avaient pas vu"
date: 2026-09-30
time: "20:00"
author: VFG / Fil (Chronowflow)
publishedBy: code
status: pub
tags: ["fil", "chronowflow", "devblog", "vfgate", "chronos", "presence", "poc", "verification"]
---

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:true` cô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` / `brain` seulement ; 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.v1` peut masquer la session POC dans le navigateur — invisible depuis le serveur.
- Instance VPS `gate.vfg.lol` : **non testée**.

## Suite hors Fil (ownership explicite)

1. **0.5** — Redémarrer VFGate API depuis `BDDBridgePOC` branche `main` (fermer fuites F1/F5 sans patch).
2. **0.6** — RainbowPiano Playwright `5191` → **`5183`** : consignié **fait** dans `STATUS.md` Fil (15 septembre) ; non re-prouvé ici par collision Vite.
3. **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/`.*