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
+16
View File
@@ -824,6 +824,22 @@ which emits the same events live.
it requires intercepting a response — an opencode plugin
(`~/.config/opencode/plugin/`) is the only clean hook.
### S4 — closed, 2026-07-27
`delivery.Fanout.Run` no longer `return`s on the first sender error — a
single ntfy hiccup used to permanently kill the notification goroutine for
the rest of the process (`main.go` only logged the `Run` error, it never
restarted the goroutine). Failed sends now go through an `OnError` hook
(default `log.Printf`), and the loop keeps going to the next sender/event.
Cursor persistence was also added: `SaveCursor` is called every time the
cursor advances, and `main.go` wires it to a `delivery-cursor` file next to
`ORCHESTRA_DATA`, loaded on startup — a restart resumes from the last
delivered event instead of re-notifying the entire log from seq 0. Covered
by `TestFanoutContinuesAfterSendError` (`internal/delivery/delivery_test.go`
— previously the package had zero tests): a failing sender and a healthy
sender both receive the event, the cursor still advances, and `Run` only
exits on context cancellation, never on the send error.
### Design consequences (not yet implemented)
1. **Percentages are a level, not a delta.**