feat: émission d'événements vers le journal du service d'agents #220
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# #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:<vault>/<chemin>`),
|
||||
son nom (libellé) et un lien profond (`/#file=<vault>:<chemin>`) —
|
||||
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=<même valeur>` 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.
|
||||
Reference in New Issue
Block a user