AtlatestRepositorycourier

courier / tree / reprorepro-send-duplication.sh

1#!/usr/bin/env bash
2# 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 the
6# leader's MCP client gives up on a slow send and SIGKILLs+restarts+re-issues
7# 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.
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-call
15# timeout is shorter (or a human /mcp's because it "looks hung"), it kills
16# courier and re-issues the send. Each attempt reaches Telegram before the
17# kill. The pre-fix in-memory dedup was wiped on every restart, so it could
18# not suppress the retry; the fix persists the dedup to disk and records a
19# send BEFORE delivery, so a restarted courier suppresses the re-issue.
21# Usage:
22# ./repro-send-duplication.sh
23# COURIER_CONTROL_BIN=/path/to/prefix/courier ./repro-send-duplication.sh
24set -u
26HERE="$(cd "$(dirname "$0")" && pwd)"
27BIN="${COURIER_BIN:-$HERE/../build/dev/bin/courier}"
28PORT="${PORT:-8611}"
29PY="${PYTHON:-python3}"
30RUNDIR="$HERE/run"
31FAILED=0
33if [ ! -x "$BIN" ]; then
34 echo "FATAL: courier binary not found at $BIN (run 'sigil build' first)"
35 exit 2
36fi
38start_mock() {
39 rm -rf "$RUNDIR"; mkdir -p "$RUNDIR"
40 "$PY" "$HERE/mock_telegram.py" "$PORT" "$RUNDIR/mocklog" 2>/dev/null &
41 MOCK=$!
42 sleep 1
44stop_mock() { kill "$MOCK" 2>/dev/null; wait "$MOCK" 2>/dev/null; sleep 0.2; }
46# run_case <label> <bin> <expected-deliveries> <driver-args...>
47# Honours DEDUP_WINDOW: when set, exports COURIER_SEND_DEDUP_WINDOW for the
48# driver's couriers. Used by the positive control below.
49run_case() {
50 local label="$1" bin="$2" expect="$3"; shift 3
51 start_mock
52 COURIER_SEND_DEDUP_WINDOW="${DEDUP_WINDOW:-}" \
53 "$PY" "$HERE/send_driver.py" --bin "$bin" \
54 --api-url "http://127.0.0.1:$PORT" \
55 --stats-url "http://127.0.0.1:$PORT/_stats" \
56 --mode-endpoint "http://127.0.0.1:$PORT/_mode" \
57 --logdir "$RUNDIR" "$@" >/dev/null 2>&1
58 local got
59 got="$(grep -c sendMessage "$RUNDIR/mocklog/sends.log" 2>/dev/null || echo 0)"
60 stop_mock
61 if [ "$got" = "$expect" ]; then
62 echo " PASS $label: $got delivery(ies) (expected $expect)"
63 else
64 echo " FAIL $label: $got delivery(ies) (expected $expect)"
65 FAILED=1
66 fi
69echo "=== FIXED binary: $BIN ==="
70run_case "baseline ok -> 1" "$BIN" 1 --mode ok --tool-timeout 30 --retries 0 --text baseline
71run_case "reset (ack lost) -> 1" "$BIN" 1 --mode reset --tool-timeout 30 --retries 0 --text reset
72run_case "STORM x3 -> 1 (exactly-once)" "$BIN" 1 --mode delay:8 --tool-timeout 3 --retries 3 --text storm3
73run_case "STORM x5 -> 1 (exactly-once)" "$BIN" 1 --mode delay:8 --tool-timeout 2 --retries 5 --text storm5
75# ---------------------------------------------------------------------------
76# POSITIVE CONTROL (always runs).
78# The four cases above are all assertions that something did NOT happen --
79# no duplicate delivery -- and an absence proves nothing until the instrument
80# has been shown to detect the thing when it IS present. A count of 1 is
81# returned just as cheerfully by a courier that never started as by a working
82# one. (Measured 2026-08-04: a control binary built from the pre-dedup commit
83# reported 0 deliveries because it could not run at all -- SGB v10 bundle
84# against a v9 runtime -- and 0 would have read as a very convincing result.)
86# So neuter an INPUT and nothing else: keep the same binary, the same driver,
87# the same scoring, and shrink only the dedup WINDOW so the recorded key
88# expires between storm attempts. The suppression should disappear and the
89# same run should deliver once per attempt.
90# ---------------------------------------------------------------------------
91echo "=== POSITIVE CONTROL: same binary, dedup window neutered to 1s ==="
92DEDUP_WINDOW=1 run_case "window=1s STORM x3 -> 4 (counter CAN see duplicates)" \
93 "$BIN" 4 --mode delay:8 --tool-timeout 3 --retries 3 --text storm-window-control
95if [ -n "${COURIER_CONTROL_BIN:-}" ] && [ -x "${COURIER_CONTROL_BIN}" ]; then
96 echo "=== CONTROL (pre-fix) binary: $COURIER_CONTROL_BIN ==="
97 # The pre-fix binary re-delivers once per attempt: 1 logical send + 3
98 # retries = 4 deliveries. This is the storm the fix removes.
99 run_case "STORM x3 -> 4 (bug present)" "$COURIER_CONTROL_BIN" 4 \
100 --mode delay:8 --tool-timeout 3 --retries 3 --text storm3
101else
102 echo "=== CONTROL skipped (set COURIER_CONTROL_BIN to a pre-fix courier) ==="
103fi
105rm -rf "$RUNDIR"
106if [ "$FAILED" = 0 ]; then
107 echo "ALL CHECKS PASSED"
108 exit 0
109else
110 echo "SOME CHECKS FAILED"
111 exit 1
112fi