AtlatestRepositorycourier
1
#!/usr/bin/env bash2
# Off-device regression harness for the courier SEND-PATH DUPLICATION storm.3
#4
# Proves, entirely against a local mock (NOTHING reaches real Telegram),5
# that one logical `send-message` is delivered EXACTLY ONCE even when the6
# leader's MCP client gives up on a slow send and SIGKILLs+restarts+re-issues7
# it -- the loop that spammed David with many copies of the same message.8
#9
# * FIXED binary (this worktree): storm -> 1 delivery.10
# * optional CONTROL binary (pre-fix master, set COURIER_CONTROL_BIN):11
# storm -> N>1 deliveries, demonstrating the bug the fix removes.12
#13
# Mechanism of the storm (see src/courier/dedup.sgl):14
# the sendMessage ack read can take up to 25s; if the client's tool-call15
# timeout is shorter (or a human /mcp's because it "looks hung"), it kills16
# courier and re-issues the send. Each attempt reaches Telegram before the17
# kill. The pre-fix in-memory dedup was wiped on every restart, so it could18
# not suppress the retry; the fix persists the dedup to disk and records a19
# send BEFORE delivery, so a restarted courier suppresses the re-issue.20
#21
# Usage:22
# ./repro-send-duplication.sh23
# COURIER_CONTROL_BIN=/path/to/prefix/courier ./repro-send-duplication.sh24
set -u26
HERE="$(cd "$(dirname "$0")" && pwd)"27
BIN="${COURIER_BIN:-$HERE/../build/dev/bin/courier}"28
PORT="${PORT:-8611}"29
PY="${PYTHON:-python3}"30
RUNDIR="$HERE/run"31
FAILED=033
if [ ! -x "$BIN" ]; then34
echo "FATAL: courier binary not found at $BIN (run 'sigil build' first)"35
exit 236
fi38
start_mock() {39
rm -rf "$RUNDIR"; mkdir -p "$RUNDIR"40
"$PY" "$HERE/mock_telegram.py" "$PORT" "$RUNDIR/mocklog" 2>/dev/null &41
MOCK=$!42
sleep 143
}44
stop_mock() { kill "$MOCK" 2>/dev/null; wait "$MOCK" 2>/dev/null; sleep 0.2; }46
# run_case <label> <bin> <expected-deliveries> <driver-args...>47
run_case() {48
local label="$1" bin="$2" expect="$3"; shift 349
start_mock50
"$PY" "$HERE/send_driver.py" --bin "$bin" \51
--api-url "http://127.0.0.1:$PORT" \52
--stats-url "http://127.0.0.1:$PORT/_stats" \53
--mode-endpoint "http://127.0.0.1:$PORT/_mode" \54
--logdir "$RUNDIR" "$@" >/dev/null 2>&155
local got56
got="$(grep -c sendMessage "$RUNDIR/mocklog/sends.log" 2>/dev/null || echo 0)"57
stop_mock58
if [ "$got" = "$expect" ]; then59
echo " PASS $label: $got delivery(ies) (expected $expect)"60
else61
echo " FAIL $label: $got delivery(ies) (expected $expect)"62
FAILED=163
fi64
}66
echo "=== FIXED binary: $BIN ==="67
run_case "baseline ok -> 1" "$BIN" 1 --mode ok --tool-timeout 30 --retries 0 --text baseline68
run_case "reset (ack lost) -> 1" "$BIN" 1 --mode reset --tool-timeout 30 --retries 0 --text reset69
run_case "STORM x3 -> 1 (exactly-once)" "$BIN" 1 --mode delay:8 --tool-timeout 3 --retries 3 --text storm370
run_case "STORM x5 -> 1 (exactly-once)" "$BIN" 1 --mode delay:8 --tool-timeout 2 --retries 5 --text storm572
if [ -n "${COURIER_CONTROL_BIN:-}" ] && [ -x "${COURIER_CONTROL_BIN}" ]; then73
echo "=== CONTROL (pre-fix) binary: $COURIER_CONTROL_BIN ==="74
# The pre-fix binary re-delivers once per attempt: 1 logical send + 375
# retries = 4 deliveries. This is the storm the fix removes.76
run_case "STORM x3 -> 4 (bug present)" "$COURIER_CONTROL_BIN" 4 \77
--mode delay:8 --tool-timeout 3 --retries 3 --text storm378
else79
echo "=== CONTROL skipped (set COURIER_CONTROL_BIN to a pre-fix courier) ==="80
fi82
rm -rf "$RUNDIR"83
if [ "$FAILED" = 0 ]; then84
echo "ALL CHECKS PASSED"85
exit 086
else87
echo "SOME CHECKS FAILED"88
exit 189
fi