# #220 — Fusion : émission d'événements vers le journal du service d'agents **Contexte.** Le Lot 2 du programme Fusion (carte fusion-trois-apps #18) dote le service d'agents d'un **journal d'activité** cross-apps : chaque application y émet ses mutations significatives au contrat **ACC-0.2** — enveloppe `{id, type, occurred_at, actor, domain, source_app, object{ref, label, url}}` signée **HMAC-SHA256** du corps brut (en-tête `X-Draco-Signature`, clé propre à chaque application), que le service expose dans la vue Journal de sa console (agent-commun 0.11.0). TikTak (0.59.0) et Kanbante (0.28.0) émettent déjà ; ObsiGate est la troisième source, après Flowdeck. **Principe directeur : référence, jamais contenu.** L'enveloppe ne contient que la référence de la note (`fichier:/`), son nom (libellé) et un lien profond (`/#file=:`) — jamais son contenu. ## Garde-fou central : jamais le watcher ObsiGate surveille les vaults par un **watcher** de système de fichiers (`backend/watcher.py`) qui réindexe chaque changement disque — le vault principal compte **222 393 fichiers**. Brancher l'émission sur le watcher inonderait le journal (et confondrait les gestes d'Obsidian, de la synchro de fichiers ou d'un éditeur externe avec des opérations faites *dans* ObsiGate). L'émission n'a donc lieu que pour les **opérations explicites via l'API web**, à deux points d'accroche, et nulle part ailleurs : | Source | Point d'accroche | Acteur | |---|---|---| | Interface web / clients REST | `backend/routers/files_write.py` | `user` | | Serveur MCP d'ObsiGate (outils) | `backend/tools/service.py`, mode `mcp` | `agent:mcp` | | Assistant intégré (outils, mode `in_app`) | `backend/tools/service.py` | `user` | Conséquences assumées : - une note créée/modifiée **hors API** (Obsidian, éditeur externe, copie de fichiers) n'apparaît **pas** au journal — c'est voulu ; - les écritures internes qui passent par la couche mutations sans passer par ces points (planificateur, crawler, fusion de doublons, restauration de sauvegarde) n'émettent pas non plus ; - le test `test_watcher_et_indexation_sans_evenement` verrouille le garde-fou : écriture disque directe + gestionnaires du watcher + `update_single_file` de l'indexeur → **zéro émission**. ## Catalogue d'événements émis | Type | Déclencheur | |---|---| | `obsigate.note.created` | `POST /api/file/{vault}` ; outil `create_file` | | `obsigate.note.edited` | `PUT /api/file/{vault}/save` (+ variantes xlsx/csv : cellules, structure, style) ; outils `edit_file`, `append_to_file` | | `obsigate.note.moved` | `PATCH /api/file/{vault}` (renommage), `POST /api/move/{vault}` (**fichiers seulement**) ; outils `rename_file`, `move_path` (fichiers seulement) — la référence pointe sur le **nouveau** chemin | | `obsigate.note.deleted` | `DELETE /api/file/{vault}` ; outil `delete_file` | Hors catalogue (non émis, volontairement) : dossiers (création, renommage, déplacement, suppression — le catalogue est `note.*`), téléversement par lots (`batch-upload`), duplication, restauration de sauvegarde, `replace_in_files` (multi-fichiers). ## Module `backend/agent_journal.py` - **Activation** : `AGENT_EVENEMENTS_CLE` (clé propre à ObsiGate, jamais en dur). Sans clé, le module est **inactif** : aucun appel, aucun bruit. URL surchargée par `AGENT_EVENEMENTS_URL` (défaut `https://agent.dracodev.net/api/v1/evenements`). - **Discipline « au mieux »** (identique à TikTak/Kanbante) : envoi dans un fil d'arrière-plan démon via httpx, délai maximal 2 s, exceptions avalées avec une ligne de journal local — une panne du service d'agents ne casse ni ne ralentit jamais le métier (prouvé par `test_recepteur_en_panne_ne_casse_rien`). - **Domaine** : ObsiGate ne modélise pas encore le domaine d'un vault (la question Q-A du programme — les domaines sont-ils des vaults distincts ? — est toujours ouverte). La correspondance peut être déclarée par `AGENT_EVENEMENTS_DOMAINES` (JSON `{"NomDuVault": "famille"}`) ; repli `pro`, qui est aussi le défaut du journal du service. ## Activation en production (gestes de Bruno) 1. Générer une clé propre à ObsiGate (`openssl rand -hex 32`). 2. Service d'agents : ajouter l'entrée `obsigate` à `AGENT_EVENEMENTS_CLES` (JSON des clés par application), recréer le conteneur. 3. ObsiGate : poser `AGENT_EVENEMENTS_CLE=` dans son environnement et **redéployer** (Docker). Tant que la clé n'est pas posée des deux côtés, ObsiGate n'émet rien — c'est le comportement voulu. ## Tests `tests/test_agent_journal.py` (7 tests) : enveloppe et signature revérifiées comme le fait le service ; cycle de vie complet en REST (créer → éditer → renommer → déplacer → supprimer, acteur `user`) ; chemin outils/MCP (`create/edit/rename/delete`, acteur `agent:mcp`) ; inactivité sans clé ; garde-fou watcher/indexation ; panne du récepteur sans effet métier ; correspondance de domaines.