fix(delivery): stop killing the fanout goroutine on a single send error

Closes S4 (AUDIT.md): Fanout.Run returned on the first sender error,
permanently ending notifications for the process lifetime after one ntfy
hiccup. Failed sends now go through an OnError hook and the loop
continues. Also persists the delivery cursor to a file next to
ORCHESTRA_DATA so a restart resumes from the last delivered event instead
of re-notifying the entire log from seq 0. Adds the package's first test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
kami
2026-07-27 23:19:15 +04:00
parent 972845bd98
commit 1f46a34afb
5 changed files with 150 additions and 5 deletions
+17 -1
View File
@@ -852,8 +852,24 @@ func main() {
senders = append(senders, delivery.Ntfy{Topic: topic, Token: os.Getenv("ORCHESTRA_NTFY_TOKEN"), URL: os.Getenv("ORCHESTRA_NTFY_URL")})
}
if len(senders) > 0 {
cursorPath := filepath.Join(dir, "delivery-cursor")
var startCursor uint64
if b, err := os.ReadFile(cursorPath); err == nil {
if v, err := strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64); err == nil {
startCursor = v
}
}
go func() {
err := (&delivery.Fanout{Senders: senders}).Run(context.Background(), s.Events)
fanout := &delivery.Fanout{
Senders: senders,
Cursor: startCursor,
SaveCursor: func(cursor uint64) {
if err := os.WriteFile(cursorPath, []byte(strconv.FormatUint(cursor, 10)), 0o644); err != nil {
log.Printf("delivery cursor persist: %v", err)
}
},
}
err := fanout.Run(context.Background(), s.Events)
if err != nil {
log.Printf("delivery fanout: %v", err)
}