Erreurs classiques

Les fautes typiques des débutants. Pour chacune : le symptôme, la cause, la correction.

1. Un nom d'instance dans un payload

Symptôme

this.broker.publish(this, { message_demande: { ref, texte, pour: "ecran_fil" } });
ERROR (broker): message_demande v1 r0 from ecran_fil invalid — dropped: - pour: field not declared in catalog (R1)

Ou pire, le champ est déclaré au catalogue et « ça marche » : les composants se reconnaissent par nom et le découplage est mort.

Cause

On veut adresser un composant précis. Dans fwtozza on adresse un message, pas un objet ; qui l'écoute ne regarde pas l'émetteur.

Correction

Corréler un échange par un ref de message (UUID). Cibler un client par la clé d'enveloppe id. Les composants sont uniques. Référence.

2. Une Date ou une instance de classe dans un payload

Symptôme

this.broker.publish(this, { message_poste: { message: { …, poste_le: new Date() } } });
ERROR (broker): message_poste v1 r0 from gestion_messages invalid — dropped: - message.poste_le: string expected

Cause

Le payload n'est pas du JSON pur. Même si le contrat disait object, la copie locale et le passage réseau donneraient deux résultats différents.

Correction

new Date().toISOString() avec un pattern, ou un timestamp number. Le composant qui reçoit reconstruit la Date chez lui. Référence.

3. Un abonné qui déclare une révision

Symptôme

receives = [{ name: "message_poste", version: 1, revision: 0 }];

Le broker ignore la révision à la souscription ; mais le code du composant attend la forme r0 et s'étonne de champs en plus.

Cause

Confusion émetteur/abonné. L'émetteur est validé contre sa révision ; l'abonné reçoit toujours la forme canonique, c'est-à-dire la révision courante complétée.

Correction

receives = [{ name, version }], et un handleMessage écrit pour la révision courante. Référence.

4. Un objet non détruit

Symptôme

this.panneau = null; // « je m'en débarrasse »

Le panneau continue de recevoir et de peindre dans un support qui n'est plus affiché. La mémoire grimpe. Deux panneaux reconstruits : « already registered ».

ERROR (broker): panneau is already registered as subscriber of presence_changee ERROR (broker): registration of panneau rejected as a whole — nothing registered (see errors above)

Cause

Le broker garde une référence vers tout abonné. null ne désinscrit rien.

Correction

this.panneau.destroy(); this.panneau = null;. La désinscription est immédiate. Référence.

5. Un membre de oneOf avec default (ou required)

Symptôme

oneOf: ["accord", "refus"],
fields: { accord: { type: "object", required: true, … }, refus: { … } }
ERROR (broker): catalog defect — acces_reponse v1 r0: oneOf member "accord" must not be required

Cause

Un membre de oneOf est exclusif : il ne peut pas être requis. Et un default serait posé par l'upcast, qui ne re-vérifie pas l'invariant : deux membres présents.

Correction

Membres optionnels, sans default. Le oneOf garantit déjà qu'exactement un est là. Référence.

6. Modifier un contrat validé au lieu de le réviser

Symptôme

On ajoute color directement dans la révision 0 de text_posted. En local tout passe. Puis un client qui a l'ancien catalogue se connecte :

ERROR (websocket): contrat incompatible — serveur→client : text_posted v1 r0 — schéma divergent (discipline append-only rompue ?) — socket fermé

Et tous les émetteurs qui n'envoyaient pas color sont maintenant rejetés.

Cause

Une révision est un snapshot gravé. La modifier casse la promesse faite à tous ceux qui l'ont déjà.

Correction

Ajouter une révision 1, snapshot complet, color requis avec default. Les émetteurs en r0 continuent, complétés. Référence.

7. Importer config.js ou lire process.env depuis un composant

Symptôme

import { DB_URL } from "../config.js";
const pool = await createPool(DB_URL);

Aucune erreur du broker. Mais le test du composant exige une base réelle, et le composant ne peut plus être déployé ailleurs sans ce fichier.

Cause

Le composant va chercher ce qu'il devrait recevoir.

Correction

server.js lit l'environnement, ouvre le pool, et le passe : new Board(broker, "board", { pool }). Le test passe un faux pool. Les cinq règles de deps.

8. Un cycle A→B→A

Symptôme

ERROR (broker): delivery budget exhausted (1000 messages in one drain) — probable publish cycle, queue dropped (next: presence_changee -> presence_signalee -> presence_changee -> presence_signalee -> presence_changee ...)

Cause

A publie X dans son handleMessage de Y, B publie Y dans son handleMessage de X, sans condition d'arrêt. Souvent : « je republie mon état à chaque fois que je reçois un état ».

Correction

Ne publier que si l'état a changé (comparer avant), ou revoir le découpage : si A et B se renvoient sans cesse la même donnée, c'est peut-être un seul composant. Le garde-fou DELIVERY_BUDGET transforme le gel en diagnostic, il ne corrige rien. Référence.

9. Oublier les transits à l'audit

Symptôme

ERROR (audit): message_demande is emitted by ecran_fil, has no local subscriber and crosses no channel

alors que le message traverse bel et bien le canal.

Cause

broker.audit() sans [CHANNEL] : le seul abonné local d'un message qui traverse est le canal, qui ne compte pas.

Correction

broker.audit([CHANNEL]), un CHANNEL par canal monté. Référence.

10. Poser allowMessage après un await

Symptôme

Pas de message d'erreur : une trame envoyée par un client malveillant juste après son handshake est publiée avant que la grille existe.

Cause

Une attente asynchrone entre le connected et la pose de la grille.

Correction

Deny-sauf-login synchrone au connected, resserré ensuite. Référence.