AtlatestRepositorycourier
1
# Sentinel-zero storm — off-device repro + fix (2026-07-03)3
The 2026-07-02 live storm: a count=0 cold-start drain (empty backlog)4
persisted offset=0; a later restart that trusts 0 restores it and the5
normal poll loop issues getUpdates offset=0 -> Telegram re-returns the6
whole backlog -> re-delivered -> leader acks each -> outbound storm.8
Fix: never persist a non-positive offset (0 is Telegram's "send everything"9
sentinel, never a real update_id); an empty-backlog cold start leaves NO10
offset file, so the next start cold-drains instead of restoring 0. load11
also independently rejects <=0. NOTHING below touched real Telegram12
(poller pointed at repro/mock_telegram.py via COURIER_TELEGRAM_API_URL).14
## Harness (repro/repro-sentinel-zero.sh): CONTROL vs FIXED15
```16
=== FIXED (../build/dev/bin/courier) ===17
empty cold start left offset file? (expect no): note file removed by restart drain18
restart message events: 0 (expect 0)19
PASS: fixed binary does not re-deliver20
=== 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
```25
CONTROL = master(6805ad1) + load accepts 0 (models the deployed binary,26
which live-logged 'Restored offset=0'). FIXED = this branch.28
## Manual verification — FIXED RELEASE (native, the deploy target)29
```30
empty cold start (native): Cold start: drained ... count=0 offset=0 persisted=no (empty backlog) -> NO offset file31
restart w/ 2-msg backlog: Cold start: drained ... count=2 offset=3 persisted=yes -> 0 message events32
planted poisoned '0' file: Cold start: drained ... count=2 -> load REJECTED 0, drained, 0 message events33
(native codegen of (> n 0) verified correct; dev and release behave identically)34
```36
## Unit tests37
test/test-poller-offset.sgl adds a 'never persists the sentinel 0' group38
(save 0 -> no file; save 0 clears an existing file; negative -> no file;39
positive still persists). Full suite: 63 passed.