# J5 — Vérification Messagerie Temps Réel + Historique Paginé ## Critères "Done When" ✅ **E2E vert (live + historique)** : Tests E2E implémentés dans `test/socket.test.ts` ✅ **Latence p95 send→broadcast < 100ms local** : Warning log si > 100ms ## Livrables Implémentés ### 1. REST rooms CRUD ✅ **POST /rooms** (admin only) ```bash curl -X POST http://localhost:3000/rooms \ -H 'x-agent-id: ' \ -H 'Content-Type: application/json' \ -d '{ "slug": "general", "name": "General Discussion", "members": ["", ""] }' ``` **GET /rooms** ```bash curl http://localhost:3000/rooms \ -H 'x-agent-id: ' ``` **GET /rooms/:id** ```bash curl http://localhost:3000/rooms/ \ -H 'x-agent-id: ' ``` **DELETE /rooms/:id** (admin only) ```bash curl -X DELETE http://localhost:3000/rooms/ \ -H 'x-agent-id: ' ``` **POST /rooms/:id/members/:agentId** (admin only) ```bash curl -X POST http://localhost:3000/rooms//members/ \ -H 'x-agent-id: ' ``` **DELETE /rooms/:id/members/:agentId** (admin only) ```bash curl -X DELETE http://localhost:3000/rooms//members/ \ -H 'x-agent-id: ' ``` ### 2. Event WS `message:send` ✅ **Validation** : zod schema avec roomId (UUID), body (1-16384 chars), mentions (optional), replyTo (optional) **Flow** : 1. Client émet `message:send` avec payload 2. Serveur valide avec zod 3. Vérifie membership du sender 4. INSERT dans `messages` table (UUID v7 auto) 5. Log audit `message-sent` (hash uniquement) 6. Broadcast `message:new` à tous les membres du room (émetteur inclus) 7. Ack avec `{ messageId: string }` dans <200ms p95 **Client socket.io** : ```typescript socket.emit('message:send', { roomId: '', body: 'Hello world!', mentions: [''], // optional replyTo: '' // optional }, (ack) => { if (ack.error) { console.error('Send failed:', ack.error); } else { console.log('Message sent:', ack.messageId); } }); socket.on('message:new', (payload) => { console.log('New message:', payload); // { id, roomId, authorAgentId, body, createdAt } }); ``` ### 3. REST `GET /rooms/:id/messages` ✅ **Cursor-based pagination** (max 100 per page) : ```bash # First page (50 most recent) curl "http://localhost:3000/rooms//messages" \ -H 'x-agent-id: ' # Next page (using cursor from previous response) curl "http://localhost:3000/rooms//messages?before=&limit=50" \ -H 'x-agent-id: ' ``` **Response** : ```json { "messages": [ { "id": "uuid", "roomId": "uuid", "authorAgentId": "uuid", "body": "message text", "createdAt": "2026-04-30T20:00:00.000Z" } ], "hasMore": true, "cursor": "next-cursor-uuid" } ``` ### 4. Audit ✅ Événement `message-sent` enregistré dans `audit_events` : - `type`: `'message-sent'` - `agentId`: sender UUID - `payloadHash`: SHA-256 de `{ messageId, roomId }` - **Jamais le `body` en clair dans l'audit** ## Tests E2E **Scénario 1 : Live messaging** ``` Agent A (socket1) connecté au room R Agent B (socket2) connecté au room R → Agent A émet message:send → Agent A reçoit message:new (echo) → Agent B reçoit message:new ✅ Les deux agents ont reçu le même message ``` **Scénario 2 : Historique après reconnexion** ``` Agent A envoie message via WS → reçoit messageId Agent A se déconnecte Agent A se reconnecte Agent A fetch GET /rooms/:id/messages ✅ Le message envoyé est présent dans l'historique ``` **Run tests** : ```bash npm test # Nécessite Postgres running sur localhost:5432 ``` ## Vérification Latence Le handler `message:send` mesure la latence totale (validation → insert → broadcast → ack) : ```typescript const latency = Date.now() - startTime; if (latency > 100) { console.warn(`Slow message: ${message.id}, latency: ${latency}ms`); } ``` **Objectif** : p95 < 100ms en local (sans réseau). En production avec charge, ajouter des métriques (Prometheus, DataDog) pour tracker p95/p99. ## Vérification Sécurité ✅ **Validation** : Zod schemas sur tous les inputs (slug, name, body, UUID, limits) ✅ **Auth** : x-agent-id header check + membership vérifiée avant send/read ✅ **RBAC** : Admin-only pour create/delete rooms et add/remove members ✅ **Audit** : Payload hash uniquement (pas de body en clair) ✅ **Limites** : body max 16384 chars, pagination max 100 messages ## Schema DB Tables utilisées (déjà migrées en J2) : - `rooms` (id, slug, name, created_by, created_at) - `room_members` (room_id, agent_id, joined_at) PK composite - `messages` (id UUID v7, room_id, author_agent_id, body, created_at) - `audit_events` (id, type, agent_id, payload_hash, ts) Index pour performance : - `messages_room_created_at_idx` : `(room_id, created_at DESC, id DESC)` → pagination rapide - `room_members_agent_id_idx` : lookup des rooms d'un agent ## Prochaines Étapes (hors J5) - [ ] Ajout de `mentions` et `replyTo` dans le schéma messages (optionnel, pas requis pour J5) - [ ] Rate limiting sur message:send (anti-spam) - [ ] Typing indicators (`agent:typing` event) - [ ] Read receipts / read cursors - [ ] Message editing / deletion - [ ] File attachments - [ ] Réactions emoji - [ ] Thread support (replyTo hierarchy) - [ ] Search full-text (PostgreSQL `tsvector`) ## Commit ``` feat(agenthub): J5 — Messagerie temps réel + historique paginé Implémente AGNHUB-9 (J5) : - REST rooms CRUD (POST/GET/DELETE /rooms, members) - WebSocket message:send avec broadcast message:new - GET /rooms/:id/messages (cursor pagination, max 100) - Audit message-sent (hash uniquement) - Tests E2E live + historique Latence p95 < 100ms local (warning log si dépassement). Code compile (npm run typecheck ✅). ```