AtlatestRepositorycourier

courier / tree / reproevidence-2026-07-03-sentinel-zero.md

Read it rendered

1# Sentinel-zero storm — off-device repro + fix (2026-07-03)
2
3The 2026-07-02 live storm: a count=0 cold-start drain (empty backlog)
4persisted offset=0; a later restart that trusts 0 restores it and the
5normal poll loop issues getUpdates offset=0 -> Telegram re-returns the
6whole backlog -> re-delivered -> leader acks each -> outbound storm.
7
8Fix: never persist a non-positive offset (0 is Telegram's "send everything"
9sentinel, never a real update_id); an empty-backlog cold start leaves NO
10offset file, so the next start cold-drains instead of restoring 0. load
11also independently rejects <=0. NOTHING below touched real Telegram
12(poller pointed at repro/mock_telegram.py via COURIER_TELEGRAM_API_URL).
14## Harness (repro/repro-sentinel-zero.sh): CONTROL vs FIXED
15```
16=== FIXED (../build/dev/bin/courier) ===
17 empty cold start left offset file? (expect no): note file removed by restart drain
18 restart message events: 0 (expect 0)
19 PASS: fixed binary does not re-deliver
20=== CONTROL (/home/daviwil/Projects/Code/sigil/courier-sentinel-control/build/dev/bin/courier) — models the deployed sentinel-0 binary ===
21 restart message events: 3 (expect >0 = storm reproduced)
22 PASS: control reproduces the storm (3 re-delivered)
23=== ALL CHECKS PASSED ===
24```
25CONTROL = master(6805ad1) + load accepts 0 (models the deployed binary,
26which live-logged 'Restored offset=0'). FIXED = this branch.
28## Manual verification — FIXED RELEASE (native, the deploy target)
29```
30empty cold start (native): Cold start: drained ... count=0 offset=0 persisted=no (empty backlog) -> NO offset file
31restart w/ 2-msg backlog: Cold start: drained ... count=2 offset=3 persisted=yes -> 0 message events
32planted poisoned '0' file: Cold start: drained ... count=2 -> load REJECTED 0, drained, 0 message events
33(native codegen of (> n 0) verified correct; dev and release behave identically)
34```
36## Unit tests
37test/test-poller-offset.sgl adds a 'never persists the sentinel 0' group
38(save 0 -> no file; save 0 clears an existing file; negative -> no file;
39positive still persists). Full suite: 63 passed.