docs: cerrar issue 0035

Mueve el issue a dev/issues/completed/ tras implementar todas las tareas:
audit trail con AuditWriter y comando !metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 20:13:43 +00:00
parent 05668e398f
commit 056dd3c73a
@@ -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 |