From 5f651ce4b3e81354d0b04f72e6a17027d8deec28 Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Thu, 9 Apr 2026 00:46:45 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20a=C3=B1adir=20issue=200035=20=E2=80=94?= =?UTF-8?q?=20audit=20trail=20+=20comando=20!metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activar AuditCfg existente en schema.go (nunca implementada) para escribir eventos auditables a JSONL y opcionalmente a room Matrix. Añadir comando !metrics que agrega datos del log del día actual (tokens, latencia, errores) usando los helpers de query existentes. --- dev/issues/0035-audit-trail-metrics.md | 177 +++++++++++++++++++++++++ dev/issues/README.md | 1 + 2 files changed, 178 insertions(+) create mode 100644 dev/issues/0035-audit-trail-metrics.md diff --git a/dev/issues/0035-audit-trail-metrics.md b/dev/issues/0035-audit-trail-metrics.md new file mode 100644 index 0000000..e92e224 --- /dev/null +++ b/dev/issues/0035-audit-trail-metrics.md @@ -0,0 +1,177 @@ +# 0035 — Observabilidad activa: audit trail + comando !metrics + +**Estado:** pendiente + +## Objetivo + +Activar la infraestructura de auditoría (`AuditCfg`) que ya está definida en `internal/config/schema.go` pero nunca implementada, y añadir un comando `!metrics` que agregue datos del log del día actual. Ambas features usan infraestructura existente (JSONL logs, config schema, command system) sin dependencias nuevas. + +## Contexto + +- `AuditCfg` lleva definida desde el schema original pero el código nunca la consume: no hay writer, no hay emisión de eventos, no hay integración con el runtime. +- Los logs JSONL ya contienen métricas útiles (`duration_ms`, `tokens_used`, `tool_exec_*`, `command_received`) pero no hay forma de consultarlas sin parsear archivos manualmente. +- `shell/logger/query.go` ya tiene helpers para leer y filtrar logs por fecha/campo. +- El command system (`!status`, `!info`) ya existe y es extensible via built-in. + +## Arquitectura + +### Fase 1: Audit trail + +``` +shell/audit/ NEW — audit event writer (archivo JSONL + opcionalmente room Matrix) +shell/audit/writer.go NEW — AuditWriter: escribe eventos a archivo y/o room +``` + +Integración en el runtime existente: +``` +agents/handler.go MOD — emitir eventos audit en puntos clave +agents/runtime.go MOD — inicializar AuditWriter si cfg.Security.Audit.Enabled +tools/registry.go MOD — emitir evento audit en tool_exec +``` + +**Pure core / impure shell:** +- No se añade nada a `pkg/` — los eventos audit son side effects puros (escritura a archivo/Matrix) +- `shell/audit/` es 100% impuro: escribe a disco y opcionalmente envía mensajes Matrix + +### Fase 2: Comando !metrics + +``` +agents/commands.go MOD — añadir cmdMetrics como built-in +pkg/command/builtins.go MOD — añadir spec de !metrics +``` + +El comando lee los JSONL del día actual usando `shell/logger/query.go` (ya existente) y calcula agregados en memoria. No persiste nada, no crea tablas, no necesita SQLite. + +## Tareas + +### Fase 1 — Audit trail + +- [ ] **1.1** Crear `shell/audit/writer.go` con `AuditWriter` struct: + - Constructor `New(cfg AuditCfg, matrixSender func(roomID, msg string), logger *slog.Logger) *AuditWriter` + - Método `Emit(event AuditEvent)` que escribe a `LogFile` (JSONL append) y opcionalmente envía a `LogToRoom` (room Matrix) + - `AuditEvent` struct: `{ Time, AgentID, EventType, SenderID, RoomID, Detail string }` + - Filtrado por `Include` (lista de event types a auditar; vacío = todos) + - Event types iniciales: `command_exec`, `tool_exec`, `llm_request`, `llm_error`, `message_received` + +- [ ] **1.2** Crear `shell/audit/writer_test.go`: + - Test de escritura a archivo (verificar formato JSONL) + - Test de filtrado por `Include` (solo emite los tipos configurados) + - Test con `LogFile` vacío (no escribe a archivo, solo room) + - Test con `LogToRoom` vacío (solo escribe a archivo) + +- [ ] **1.3** Integrar `AuditWriter` en `agents/runtime.go`: + - En `New()`: si `cfg.Security.Audit.Enabled`, crear `AuditWriter` y guardarlo en el struct `Agent` + - Pasar `matrixSender` como closure que usa el cliente Matrix del agente + - Si audit no está habilitado, `AuditWriter` es nil (los call sites hacen nil-check) + +- [ ] **1.4** Emitir eventos en los puntos clave: + - `agents/handler.go` → `message_received` (sender, room, is_dm) + - `agents/handler.go` → `command_exec` (command name, sender) + - `tools/registry.go` → `tool_exec` (tool name, duration, success/error) + - `shell/llm/` → `llm_request` (provider, model, tokens) y `llm_error` (provider, error) + +### Fase 2 — Comando !metrics + +- [ ] **2.1** Añadir spec en `pkg/command/builtins.go`: + ```go + {Name: "metrics", Description: "Métricas agregadas del día actual", Usage: "!metrics"} + ``` + +- [ ] **2.2** Implementar `cmdMetrics` en `agents/commands.go`: + - Leer logs del día actual con `logger.ReadDayLogs(logDir, agentID, time.Now())` + - Calcular: total mensajes recibidos, comandos ejecutados, llamadas LLM (count + tokens totales + latencia media), llamadas a tools (count + errores), errores totales + - Formatear como markdown table para Matrix + - Ejemplo de output: + ``` + **Métricas de hoy (2026-04-09):** + + | Métrica | Valor | + |---------|-------| + | Mensajes recibidos | 42 | + | Comandos ejecutados | 15 | + | Llamadas LLM | 27 | + | Tokens totales | 45,230 | + | Latencia LLM media | 1,250 ms | + | Tool calls | 8 | + | Tool errors | 1 | + | Errores totales | 2 | + | Uptime | 6h 30m | + ``` + +- [ ] **2.3** El handler necesita acceso al `logDir` del agente — pasar via config o campo en Agent struct (ya existe `a.cfg` con el agent ID, solo falta saber el baseDir de logs) + +### Fase 3 — Tests y cleanup + +- [ ] **3.1** Tests para `cmdMetrics`: crear logs JSONL de ejemplo en tmpdir, verificar que los agregados son correctos +- [ ] **3.2** Test de integración: `AuditWriter` + handler emite eventos reales a archivo temporal +- [ ] **3.3** Documentar en `docs/security.md` la sección de audit trail (config YAML de ejemplo) + +## Ejemplo de uso + +### Audit trail + +Config en `agents/asistente-2/config.yaml`: +```yaml +security: + audit: + enabled: true + log_file: "logs/asistente-2/audit.jsonl" + log_to_room: "!audit-room:matrix-af2f3d.organic-machine.com" + include: + - command_exec + - tool_exec + - llm_error +``` + +Resultado en `audit.jsonl`: +```json +{"time":"2026-04-09T10:30:00Z","agent_id":"asistente-2","event":"command_exec","sender":"@user:matrix","room":"!abc:matrix","detail":"!status"} +{"time":"2026-04-09T10:30:05Z","agent_id":"asistente-2","event":"tool_exec","sender":"@user:matrix","room":"!abc:matrix","detail":"http_get duration=350ms ok"} +``` + +### Comando !metrics + +``` +Usuario: !metrics +Bot: +**Métricas de hoy (2026-04-09):** + +| Métrica | Valor | +|---------|-------| +| Mensajes recibidos | 42 | +| Comandos ejecutados | 15 | +| Llamadas LLM | 27 | +| Tokens totales | 45,230 | +| Latencia LLM media | 1,250 ms | +| Tool calls | 8 | +| Tool errors | 1 | +| Errores totales | 2 | +| Uptime | 6h 30m | +``` + +## Decisiones de diseño + +1. **Audit separado de logs normales**: los logs de runtime son para debugging (alto volumen, retención corta). El audit trail es para compliance/revisión (eventos selectivos, retención configurable independiente). + +2. **Sin SQLite para métricas**: el comando `!metrics` calcula en memoria leyendo JSONL del día. Con los volúmenes actuales (~cientos de eventos/día por agente), esto es instantáneo. Si escala, se puede cachear o migrar a SQLite en un issue futuro. + +3. **AuditWriter acepta matrixSender como función**: evita acoplar `shell/audit/` con el cliente Matrix directamente. Sigue el patrón de inyección de dependencias del proyecto. + +4. **Include como allowlist**: lista vacía = auditar todo. Esto es deny-by-default invertido (opt-in por tipo de evento) para evitar audit logs gigantes. + +## Prerequisitos + +Ninguno. Todo usa infraestructura existente: +- `AuditCfg` en `internal/config/schema.go` +- `shell/logger/query.go` para leer JSONL +- `pkg/command/builtins.go` para registrar `!metrics` +- `agents/commands.go` para implementar el handler + +## Riesgos + +| Riesgo | Mitigación | +|--------|------------| +| Audit file crece sin límite | Usar el mismo `DailyRotatingWriter` de `shell/logger/` o rotación externa (logrotate) | +| `LogToRoom` falla (room no existe) | Log warning y continuar — audit a archivo no debe fallar por Matrix | +| `!metrics` lento con logs muy grandes | Los JSONL se rotan a 50MB max. Un día normal tiene KB-pocos MB. Aceptable. | +| Audit de `message_received` loguea contenido sensible | El `Detail` solo incluye metadata (sender, room, is_dm), nunca el body del mensaje | diff --git a/dev/issues/README.md b/dev/issues/README.md index 6e7b3f5..c104de5 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -45,3 +45,4 @@ afectados y notas de implementacion. | 32 | E2E: verificar skill /create-agent | [0032-e2e-create-agent-skill.md](0032-e2e-create-agent-skill.md) | pendiente | | 33 | Comandos de robots sin prefijo ! | [0033-bot-commands-no-prefix.md](0033-bot-commands-no-prefix.md) | pendiente | | 34 | E2E: verificar skill /create-bot | [0034-e2e-create-bot-skill.md](0034-e2e-create-bot-skill.md) | pendiente | +| 35 | Audit trail + comando !metrics | [0035-audit-trail-metrics.md](0035-audit-trail-metrics.md) | pendiente |