Patrol logic is now handled by mol-witness-patrol molecule executed by Claude. Removed ~985 lines of Go code: - run(), checkAndProcess(), healthCheck() - handleStuckPolecat(), getNudgeCount(), recordNudge(), clearNudgeCount() - processShutdownRequests(), verifyPolecatState() - autoSpawnForReadyWork(), getActivePolecatCount(), isAlreadySpawned(), spawnPolecat() - Plus all helper functions Kept: NewManager(), stateFile(), loadState(), saveState(), Status(), Start(), Stop() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
152 lines
3.0 KiB
Go
152 lines
3.0 KiB
Go
package witness
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/steveyegge/gastown/internal/rig"
|
|
)
|
|
|
|
// Common errors
|
|
var (
|
|
ErrNotRunning = errors.New("witness not running")
|
|
ErrAlreadyRunning = errors.New("witness already running")
|
|
)
|
|
|
|
// Manager handles witness lifecycle and monitoring operations.
|
|
type Manager struct {
|
|
rig *rig.Rig
|
|
workDir string
|
|
}
|
|
|
|
// NewManager creates a new witness manager for a rig.
|
|
func NewManager(r *rig.Rig) *Manager {
|
|
return &Manager{
|
|
rig: r,
|
|
workDir: r.Path,
|
|
}
|
|
}
|
|
|
|
// stateFile returns the path to the witness state file.
|
|
func (m *Manager) stateFile() string {
|
|
return filepath.Join(m.rig.Path, ".runtime", "witness.json")
|
|
}
|
|
|
|
// loadState loads witness state from disk.
|
|
func (m *Manager) loadState() (*Witness, error) {
|
|
data, err := os.ReadFile(m.stateFile())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &Witness{
|
|
RigName: m.rig.Name,
|
|
State: StateStopped,
|
|
}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var w Witness
|
|
if err := json.Unmarshal(data, &w); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &w, nil
|
|
}
|
|
|
|
// saveState persists witness state to disk.
|
|
func (m *Manager) saveState(w *Witness) error {
|
|
dir := filepath.Dir(m.stateFile())
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.MarshalIndent(w, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(m.stateFile(), data, 0644)
|
|
}
|
|
|
|
// Status returns the current witness status.
|
|
func (m *Manager) Status() (*Witness, error) {
|
|
w, err := m.loadState()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If running, verify process is still alive
|
|
if w.State == StateRunning && w.PID > 0 {
|
|
if !processExists(w.PID) {
|
|
w.State = StateStopped
|
|
w.PID = 0
|
|
_ = m.saveState(w)
|
|
}
|
|
}
|
|
|
|
// Update monitored polecats list
|
|
w.MonitoredPolecats = m.rig.Polecats
|
|
|
|
return w, nil
|
|
}
|
|
|
|
// Start starts the witness (marks it as running).
|
|
// Patrol logic is now handled by mol-witness-patrol molecule executed by Claude.
|
|
func (m *Manager) Start() error {
|
|
w, err := m.loadState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if w.State == StateRunning && w.PID > 0 && processExists(w.PID) {
|
|
return ErrAlreadyRunning
|
|
}
|
|
|
|
now := time.Now()
|
|
w.State = StateRunning
|
|
w.StartedAt = &now
|
|
w.PID = os.Getpid()
|
|
w.MonitoredPolecats = m.rig.Polecats
|
|
|
|
return m.saveState(w)
|
|
}
|
|
|
|
// Stop stops the witness.
|
|
func (m *Manager) Stop() error {
|
|
w, err := m.loadState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if w.State != StateRunning {
|
|
return ErrNotRunning
|
|
}
|
|
|
|
// If we have a PID, try to stop it gracefully
|
|
if w.PID > 0 && w.PID != os.Getpid() {
|
|
// Send SIGTERM
|
|
if proc, err := os.FindProcess(w.PID); err == nil {
|
|
_ = proc.Signal(os.Interrupt)
|
|
}
|
|
}
|
|
|
|
w.State = StateStopped
|
|
w.PID = 0
|
|
|
|
return m.saveState(w)
|
|
}
|
|
|
|
// processExists checks if a process with the given PID exists.
|
|
func processExists(pid int) bool {
|
|
proc, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// On Unix, FindProcess always succeeds; signal 0 tests existence
|
|
err = proc.Signal(nil)
|
|
return err == nil
|
|
}
|