feat: implementar audit trail con AuditWriter y emision de eventos

Crea shell/audit/ con Writer que escribe eventos de auditoria a archivo
JSONL y opcionalmente a un room Matrix. Integra la emision de eventos
en los puntos clave del runtime:

- message_received: al recibir cualquier evento Matrix (handler.go)
- command_exec: al ejecutar un comando (handler.go)
- tool_exec: al ejecutar una tool (tools/registry.go via AuditFunc callback)
- llm_request / llm_error: al llamar al LLM (llm.go)

El Writer se inicializa en agents/runtime.go si security.audit.enabled=true.
Usa patron de inyeccion de dependencias (MatrixSender como funcion,
AuditFunc como callback) para evitar acoplamiento entre packages.

Incluye tests completos para el Writer: escritura JSONL, filtrado por
Include, modo solo-file, modo solo-room, auto-set de timestamp.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 20:13:21 +00:00
parent 892fe0cb19
commit fb96a79feb
7 changed files with 525 additions and 2 deletions
+16
View File
@@ -12,11 +12,16 @@ import (
"github.com/enmanuel/agents/shell/logger"
)
// AuditFunc is called after each tool execution for audit purposes.
// The registry does not depend on the audit package directly.
type AuditFunc func(toolName string, durationMS int64, err error)
// Registry holds available tools keyed by name.
type Registry struct {
tools map[string]Tool
logger *slog.Logger
rateLimiter *RateLimiter // nil when rate limiting is disabled
auditFn AuditFunc // nil when audit is disabled
}
// NewRegistry creates an empty registry.
@@ -60,6 +65,12 @@ func (r *Registry) SetRateLimiter(rl *RateLimiter) {
r.rateLimiter = rl
}
// SetAuditFunc attaches an audit callback to the registry.
// When set, it is called after each tool execution.
func (r *Registry) SetAuditFunc(fn AuditFunc) {
r.auditFn = fn
}
// ExecuteForRoom is like Execute but checks the per-room rate limit first.
// If the rate limit is exceeded, it returns an error result without executing.
func (r *Registry) ExecuteForRoom(ctx context.Context, name, argsJSON, roomID string) Result {
@@ -99,6 +110,11 @@ func (r *Registry) Execute(ctx context.Context, name string, argsJSON string) Re
r.logger.Info("tool_exec_end", "tool", name, logger.FieldDurationMS, ms)
}
// Audit callback
if r.auditFn != nil {
r.auditFn(name, ms, result.Err)
}
return result
}