package main import ( "bufio" "context" "fmt" "strings" "sync" "time" ) // StdinApprover asks at the terminal. It fails safe: anything but y/yes, // including no input at all, is a no. type StdinApprover struct { mu sync.Mutex in *bufio.Reader } func (s *StdinApprover) Approve(ctx context.Context, a Approval) string { s.mu.Lock() defer s.mu.Unlock() prompt := " Accept this answer?" if a.Target == "triage" { prompt = " Approve triage?" } if confirm(s.in, prompt) { return "approved" } return "rejected" } // confirm fails safe: anything but y/yes, including no input at all, is a no. func confirm(in *bufio.Reader, prompt string) bool { fmt.Print(prompt + " [y/N] ") line, _ := in.ReadString('\n') a := strings.ToLower(strings.TrimSpace(line)) if line == "" { fmt.Println() } return a == "y" || a == "yes" } // WebApprover blocks an item's goroutine until POST /api/verdicts answers it, // or the timeout passes, which counts as a rejection. type WebApprover struct { Timeout time.Duration mu sync.Mutex pending map[string]chan bool // key: item_id|target } func NewWebApprover(timeout time.Duration) *WebApprover { return &WebApprover{Timeout: timeout, pending: map[string]chan bool{}} } func approvalKey(itemID, target string) string { return itemID + "|" + target } func (w *WebApprover) Approve(ctx context.Context, a Approval) string { key := approvalKey(a.ItemID, a.Target) ch := make(chan bool, 1) w.mu.Lock() w.pending[key] = ch w.mu.Unlock() defer func() { w.mu.Lock() delete(w.pending, key) w.mu.Unlock() }() timer := time.NewTimer(w.Timeout) defer timer.Stop() select { case ok := <-ch: if ok { return "approved" } return "rejected" case <-timer.C: return "timeout" case <-ctx.Done(): return "rejected" } } // Resolve delivers a human's verdict. It reports false if nothing is waiting. func (w *WebApprover) Resolve(itemID, target string, approve bool) bool { key := approvalKey(itemID, target) w.mu.Lock() defer w.mu.Unlock() ch, ok := w.pending[key] if !ok { return false } delete(w.pending, key) // one verdict per question ch <- approve return true }