package main import ( "context" "log" "os" "os/signal" "syscall" "time" ) // runDaemon mantiene call_monitor vivo como systemd service. Cada `interval` // ejecuta los pasos batch en orden (snapshot + sequences --detect --propose). // Idempotente: cada uno se salta trabajo ya hecho. Errores se loguean y no // abortan el daemon (siguiente tick lo reintenta). func runDaemon(dbPath, registryRoot string, interval time.Duration) { log.Printf("call_monitor daemon: db=%s registry=%s interval=%s", dbPath, registryRoot, interval) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Signal handling — clean exit on SIGTERM/SIGINT. sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) go func() { s := <-sigCh log.Printf("call_monitor daemon: received %s, shutting down", s) cancel() }() // Initial cycle immediately, then on interval. runCycleSafe(dbPath, registryRoot) tick := time.NewTicker(interval) defer tick.Stop() for { select { case <-ctx.Done(): log.Printf("call_monitor daemon: stopped") return case <-tick.C: runCycleSafe(dbPath, registryRoot) } } } func runCycleSafe(dbPath, registryRoot string) { defer func() { if r := recover(); r != nil { log.Printf("call_monitor daemon: cycle panic recovered: %v", r) } }() // Snapshot: registra (function_id, content_hash) en function_versions. log.Printf("call_monitor daemon: cycle start") runSnapshot(dbPath, registryRoot) // Sequences detection: persiste candidatas + proposals new_pipeline. cfg := SequenceConfig{ WindowSecs: 30, LookbackDays: 30, MinOccurrences: 5, MinSessions: 2, MinSuccessRate: 0.9, } runSequences(dbPath, true, false, true, false, registryRoot, cfg) log.Printf("call_monitor daemon: cycle done") }