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:
@@ -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 |
|
||||
Reference in New Issue
Block a user