fix(marmot-interop): filter stale welcomes in wait_for_invite
wnd persists pending group invites across daemon restarts, so on any rerun C (and B) already have unaccepted welcomes from previous runs lying around. `wait_for_invite` was pulling `.[0]` out of the invite list, which is the oldest pending — i.e. the stalest — and treating it as the arrival the caller just triggered. In the sanity probe this caused the kind:445 step to send to B's NEW group id while C was accepting a DIFFERENT (old) group, producing a bogus "kind:445 failed — relays may be dropping group messages" with no actual relay issue. Add `snapshot_invites <B|C>` which returns the current gids as CSV, and teach `wait_for_invite` to accept that CSV as an ignore list so it walks past stale welcomes and only returns a freshly-arrived one. Use the new snapshot→wait pattern at every call site: sanity check, Test 02 (B), Test 04 (C), Test 05 (C), Test 08 (C), Test 12 (C). Also log a gid-mismatch warning in the sanity check when B's created group and C's accepted welcome disagree, as an extra guard against the filter being bypassed.
This commit is contained in:
+46
-13
@@ -177,17 +177,45 @@ jq_group_id() {
|
|||||||
|
|
||||||
# ------- polling helpers -----------------------------------------------------
|
# ------- polling helpers -----------------------------------------------------
|
||||||
|
|
||||||
# wait_for_invite <B|C> <timeout-seconds>
|
# Snapshot currently-pending invites on <B|C> as a comma-separated list of
|
||||||
# Echoes the first new group_id that appears in `wn groups invites --json`.
|
# gids. Use this BEFORE triggering a publish that should produce a new
|
||||||
|
# welcome, then pass the result to `wait_for_invite` as the ignore list so
|
||||||
|
# a stale welcome left over from a previous harness run (wnd persists
|
||||||
|
# pending invites across restarts) doesn't register as the arrival we
|
||||||
|
# just triggered.
|
||||||
|
snapshot_invites() {
|
||||||
|
local who="$1" wnfn
|
||||||
|
if [[ "$who" == "B" ]]; then wnfn=wn_b
|
||||||
|
else wnfn=wn_c; fi
|
||||||
|
local raw
|
||||||
|
raw=$("$wnfn" --json groups invites 2>/dev/null || true)
|
||||||
|
# jq_group_id reads one invite at a time, so iterate the array in shell
|
||||||
|
# rather than trying to handle all shapes in a single jq expression.
|
||||||
|
local gids=()
|
||||||
|
while IFS= read -r one; do
|
||||||
|
[[ -z "$one" || "$one" == "null" ]] && continue
|
||||||
|
local g
|
||||||
|
g=$(printf '%s' "$one" | jq_group_id)
|
||||||
|
[[ -n "$g" ]] && gids+=("$g")
|
||||||
|
done < <(printf '%s' "$raw" | jq -c '(.result // .) | .[]?' 2>/dev/null)
|
||||||
|
local IFS=','; printf '%s' "${gids[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# wait_for_invite <B|C> <timeout-seconds> [ignore-csv]
|
||||||
|
# Echoes the first pending group_id that isn't listed in <ignore-csv>
|
||||||
|
# (comma-separated hex gids). Use the ignore list to skip welcomes left
|
||||||
|
# pending by previous harness runs — otherwise `wait_for_invite` can
|
||||||
|
# fire on a stale invite and the caller ends up driving a group the
|
||||||
|
# publisher isn't a member of, which then looks like a kind:445 drop.
|
||||||
# Returns 1 on timeout.
|
# Returns 1 on timeout.
|
||||||
#
|
#
|
||||||
# Also prints a heartbeat every ~10s with:
|
# Also prints a heartbeat every ~10s with:
|
||||||
# - elapsed/remaining time
|
# - elapsed/remaining time
|
||||||
# - current count of pending welcomes (should be 0 until it isn't)
|
# - current count of pending welcomes (total, pre-filter)
|
||||||
# - last relevant line of wnd stderr (grep for welcome/giftwrap/subscribe/error)
|
# - last relevant line of wnd stderr (grep for welcome/giftwrap/subscribe/error)
|
||||||
# so a stalled poll gives the operator something to forward.
|
# so a stalled poll gives the operator something to forward.
|
||||||
wait_for_invite() {
|
wait_for_invite() {
|
||||||
local who="$1" timeout="${2:-60}" deadline gid start last_hb
|
local who="$1" timeout="${2:-60}" ignore_csv="${3:-}" deadline gid start last_hb
|
||||||
local wnfn data_dir
|
local wnfn data_dir
|
||||||
if [[ "$who" == "B" ]]; then wnfn=wn_b; data_dir="$B_DIR"
|
if [[ "$who" == "B" ]]; then wnfn=wn_b; data_dir="$B_DIR"
|
||||||
else wnfn=wn_c; data_dir="$C_DIR"; fi
|
else wnfn=wn_c; data_dir="$C_DIR"; fi
|
||||||
@@ -198,27 +226,32 @@ wait_for_invite() {
|
|||||||
# Post-v0.2 `wn --json groups invites` returns `{"result": [...]}`
|
# Post-v0.2 `wn --json groups invites` returns `{"result": [...]}`
|
||||||
# (older builds returned the bare array). Peel the wrapper when
|
# (older builds returned the bare array). Peel the wrapper when
|
||||||
# present so a pending invite is actually detected.
|
# present so a pending invite is actually detected.
|
||||||
gid=$("$wnfn" --json groups invites 2>/dev/null \
|
local raw
|
||||||
| jq -c '(.result // .) | .[0] // empty' 2>/dev/null | jq_group_id || true)
|
raw=$("$wnfn" --json groups invites 2>/dev/null || true)
|
||||||
if [[ -n "${gid:-}" ]]; then
|
# Walk every pending invite (not just .[0]) so we skip past stales.
|
||||||
printf '%s\n' "$gid"
|
while IFS= read -r one; do
|
||||||
return 0
|
[[ -z "$one" || "$one" == "null" ]] && continue
|
||||||
fi
|
gid=$(printf '%s' "$one" | jq_group_id)
|
||||||
|
[[ -z "$gid" ]] && continue
|
||||||
|
if [[ ",$ignore_csv," != *",$gid,"* ]]; then
|
||||||
|
printf '%s\n' "$gid"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(printf '%s' "$raw" | jq -c '(.result // .) | .[]?' 2>/dev/null)
|
||||||
|
|
||||||
# Heartbeat every ~10s.
|
# Heartbeat every ~10s.
|
||||||
local now=$(date +%s)
|
local now=$(date +%s)
|
||||||
if (( now - last_hb >= 10 )); then
|
if (( now - last_hb >= 10 )); then
|
||||||
local elapsed=$(( now - start )) remaining=$(( deadline - now ))
|
local elapsed=$(( now - start )) remaining=$(( deadline - now ))
|
||||||
local pending
|
local pending
|
||||||
pending=$("$wnfn" --json groups invites 2>/dev/null \
|
pending=$(printf '%s' "$raw" | jq '(.result // .) | length' 2>/dev/null || echo "?")
|
||||||
| jq '(.result // .) | length' 2>/dev/null || echo "?")
|
|
||||||
local recent=""
|
local recent=""
|
||||||
if [[ -f "$data_dir/logs/stderr.log" ]]; then
|
if [[ -f "$data_dir/logs/stderr.log" ]]; then
|
||||||
recent=$(tail -n 200 "$data_dir/logs/stderr.log" 2>/dev/null \
|
recent=$(tail -n 200 "$data_dir/logs/stderr.log" 2>/dev/null \
|
||||||
| grep -iE 'welcome|giftwrap|gift_wrap|mls|subscribe|1059|444|error|warn' \
|
| grep -iE 'welcome|giftwrap|gift_wrap|mls|subscribe|1059|444|error|warn' \
|
||||||
| tail -n 1 || true)
|
| tail -n 1 || true)
|
||||||
fi
|
fi
|
||||||
info "$who wait_for_invite +${elapsed}s (remaining ${remaining}s) pending=$pending tail: ${recent:-<no relevant wnd log yet>}"
|
info "$who wait_for_invite +${elapsed}s (remaining ${remaining}s) pending=$pending ignoring=${ignore_csv:-<none>} tail: ${recent:-<no relevant wnd log yet>}"
|
||||||
last_hb=$now
|
last_hb=$now
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -363,7 +363,15 @@ configure_relays() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
step "sanity-check kinds 10050/1059/445: B creates group + invites C + exchanges a message"
|
step "sanity-check kinds 10050/1059/445: B creates group + invites C + exchanges a message"
|
||||||
local sanity_gid sanity_c_gid sanity_create
|
local sanity_gid sanity_c_gid sanity_create c_ignore
|
||||||
|
# Snapshot C's pending invites before we publish, so wait_for_invite
|
||||||
|
# doesn't fire on a stale welcome left over from a previous run (wnd
|
||||||
|
# persists pending invites across restarts — we saw this cause the
|
||||||
|
# kind:445 sanity probe to send to B's new group while C was still
|
||||||
|
# sitting on an unaccepted welcome from a previous group that B
|
||||||
|
# wasn't a member of anymore).
|
||||||
|
c_ignore=$(snapshot_invites C)
|
||||||
|
[[ -n "$c_ignore" ]] && info "C already has stale invites, ignoring: $c_ignore"
|
||||||
sanity_create=$(wn_b --json groups create "sanity-check" "$C_NPUB" 2>>"$LOG_FILE" || true)
|
sanity_create=$(wn_b --json groups create "sanity-check" "$C_NPUB" 2>>"$LOG_FILE" || true)
|
||||||
sanity_gid=$(printf '%s' "$sanity_create" | jq_group_id)
|
sanity_gid=$(printf '%s' "$sanity_create" | jq_group_id)
|
||||||
printf 'sanity groups create raw: %s\n' "$sanity_create" >>"$LOG_FILE"
|
printf 'sanity groups create raw: %s\n' "$sanity_create" >>"$LOG_FILE"
|
||||||
@@ -371,8 +379,11 @@ configure_relays() {
|
|||||||
warn "sanity group create failed — see $LOG_FILE (stale account state? rerun with 'rm -rf state/')"
|
warn "sanity group create failed — see $LOG_FILE (stale account state? rerun with 'rm -rf state/')"
|
||||||
else
|
else
|
||||||
info "sanity group id: $sanity_gid"
|
info "sanity group id: $sanity_gid"
|
||||||
if sanity_c_gid=$(wait_for_invite C 30); then
|
if sanity_c_gid=$(wait_for_invite C 30 "$c_ignore"); then
|
||||||
info "kind:10050+1059 ok — C received welcome (gid: $sanity_c_gid)"
|
info "kind:10050+1059 ok — C received welcome (gid: $sanity_c_gid)"
|
||||||
|
if [[ "$sanity_c_gid" != "$sanity_gid" ]]; then
|
||||||
|
warn "gid mismatch — C saw $sanity_c_gid, B created $sanity_gid (likely a stale welcome slipped past the filter)"
|
||||||
|
fi
|
||||||
wn_c groups accept "$sanity_c_gid" >/dev/null 2>&1 || true
|
wn_c groups accept "$sanity_c_gid" >/dev/null 2>&1 || true
|
||||||
wn_b messages send "$sanity_gid" "sanity-ping" >/dev/null 2>&1 || true
|
wn_b messages send "$sanity_gid" "sanity-ping" >/dev/null 2>&1 || true
|
||||||
if wait_for_message C "$sanity_c_gid" "sanity-ping" 30; then
|
if wait_for_message C "$sanity_c_gid" "sanity-ping" 30; then
|
||||||
@@ -451,6 +462,12 @@ test_02_amethyst_creates_group() {
|
|||||||
# Amethyst sends it.
|
# Amethyst sends it.
|
||||||
dump_daemon_diagnostics B "pre-invite baseline"
|
dump_daemon_diagnostics B "pre-invite baseline"
|
||||||
|
|
||||||
|
# Snapshot B's already-pending invites so we can tell the new welcome
|
||||||
|
# apart from a leftover from a previous run.
|
||||||
|
local b_ignore
|
||||||
|
b_ignore=$(snapshot_invites B)
|
||||||
|
[[ -n "$b_ignore" ]] && info "B already has stale invites, ignoring: $b_ignore"
|
||||||
|
|
||||||
prompt_human "In Amethyst:
|
prompt_human "In Amethyst:
|
||||||
1. Tap '+' -> Create Group
|
1. Tap '+' -> Create Group
|
||||||
2. Name: Interop-02
|
2. Name: Interop-02
|
||||||
@@ -464,7 +481,7 @@ that's the bug."
|
|||||||
|
|
||||||
step "polling B's daemon for invite (60s, heartbeat every ~10s)"
|
step "polling B's daemon for invite (60s, heartbeat every ~10s)"
|
||||||
local gid
|
local gid
|
||||||
if ! gid=$(wait_for_invite B 60); then
|
if ! gid=$(wait_for_invite B 60 "$b_ignore"); then
|
||||||
fail_msg "no invite arrived at B"
|
fail_msg "no invite arrived at B"
|
||||||
dump_daemon_diagnostics B "post-timeout (test_02)"
|
dump_daemon_diagnostics B "post-timeout (test_02)"
|
||||||
prompt_amethyst_logcat "test_02 timeout"
|
prompt_amethyst_logcat "test_02 timeout"
|
||||||
@@ -603,13 +620,17 @@ test_04_three_member_group() {
|
|||||||
|
|
||||||
dump_daemon_diagnostics C "pre-invite baseline (test_04)"
|
dump_daemon_diagnostics C "pre-invite baseline (test_04)"
|
||||||
|
|
||||||
|
local c_ignore
|
||||||
|
c_ignore=$(snapshot_invites C)
|
||||||
|
[[ -n "$c_ignore" ]] && info "C already has stale invites, ignoring: $c_ignore"
|
||||||
|
|
||||||
prompt_human "In Amethyst, open the group from Test 02 (or 'Interop-04-bootstrap').
|
prompt_human "In Amethyst, open the group from Test 02 (or 'Interop-04-bootstrap').
|
||||||
Group Info -> Add Member -> paste: $C_NPUB
|
Group Info -> Add Member -> paste: $C_NPUB
|
||||||
Confirm the invite is sent."
|
Confirm the invite is sent."
|
||||||
|
|
||||||
step "polling C for invite (60s, heartbeat every ~10s)"
|
step "polling C for invite (60s, heartbeat every ~10s)"
|
||||||
local c_gid
|
local c_gid
|
||||||
if ! c_gid=$(wait_for_invite C 60); then
|
if ! c_gid=$(wait_for_invite C 60 "$c_ignore"); then
|
||||||
fail_msg "C never received invite"
|
fail_msg "C never received invite"
|
||||||
dump_daemon_diagnostics C "post-timeout (test_04)"
|
dump_daemon_diagnostics C "post-timeout (test_04)"
|
||||||
prompt_amethyst_logcat "test_04 timeout"
|
prompt_amethyst_logcat "test_04 timeout"
|
||||||
@@ -642,7 +663,8 @@ test_05_wn_adds_amethyst_existing() {
|
|||||||
banner "Test 05 — wn adds Amethyst to existing B+C group"
|
banner "Test 05 — wn adds Amethyst to existing B+C group"
|
||||||
|
|
||||||
step "B creates group with only C, then adds A"
|
step "B creates group with only C, then adds A"
|
||||||
local out gid
|
local out gid c_ignore
|
||||||
|
c_ignore=$(snapshot_invites C)
|
||||||
out=$(wn_b --json groups create "Interop-05" "$C_NPUB" 2>>"$LOG_FILE")
|
out=$(wn_b --json groups create "Interop-05" "$C_NPUB" 2>>"$LOG_FILE")
|
||||||
printf 'wn groups create raw output: %s\n' "$out" >>"$LOG_FILE"
|
printf 'wn groups create raw output: %s\n' "$out" >>"$LOG_FILE"
|
||||||
gid=$(printf '%s' "$out" | jq_group_id)
|
gid=$(printf '%s' "$out" | jq_group_id)
|
||||||
@@ -652,8 +674,9 @@ test_05_wn_adds_amethyst_existing() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
save_state GROUP_05 "$gid"
|
save_state GROUP_05 "$gid"
|
||||||
if wait_for_invite C 30 >/dev/null; then
|
local c_new_gid
|
||||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
if c_new_gid=$(wait_for_invite C 30 "$c_ignore"); then
|
||||||
|
wn_c groups accept "$c_new_gid" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
step "B adds A to the group"
|
step "B adds A to the group"
|
||||||
@@ -770,9 +793,11 @@ test_08_admin_promote_demote() {
|
|||||||
step "B (creator+admin) adds C so we have 3 members"
|
step "B (creator+admin) adds C so we have 3 members"
|
||||||
wn_c keys publish >/dev/null 2>&1 || true
|
wn_c keys publish >/dev/null 2>&1 || true
|
||||||
sleep 3
|
sleep 3
|
||||||
|
local c_ignore_08 c_new_gid_08
|
||||||
|
c_ignore_08=$(snapshot_invites C)
|
||||||
wn_b groups add-members "$gid" "$C_NPUB" >/dev/null 2>&1 || warn "add-members C returned nonzero"
|
wn_b groups add-members "$gid" "$C_NPUB" >/dev/null 2>&1 || warn "add-members C returned nonzero"
|
||||||
if wait_for_invite C 30 >/dev/null; then
|
if c_new_gid_08=$(wait_for_invite C 30 "$c_ignore_08"); then
|
||||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
wn_c groups accept "$c_new_gid_08" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
step "B promotes A to admin"
|
step "B promotes A to admin"
|
||||||
@@ -946,9 +971,11 @@ test_12_offline_catchup() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
step "B adds C, then sends 3 more messages"
|
step "B adds C, then sends 3 more messages"
|
||||||
|
local c_ignore_12 c_new_gid_12
|
||||||
|
c_ignore_12=$(snapshot_invites C)
|
||||||
wn_b groups add-members "$gid" "$C_NPUB" >/dev/null 2>&1 || true
|
wn_b groups add-members "$gid" "$C_NPUB" >/dev/null 2>&1 || true
|
||||||
if wait_for_invite C 30 >/dev/null; then
|
if c_new_gid_12=$(wait_for_invite C 30 "$c_ignore_12"); then
|
||||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
wn_c groups accept "$c_new_gid_12" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
for i in 6 7 8; do
|
for i in 6 7 8; do
|
||||||
wn_b messages send "$gid" "offline-msg-$i" >/dev/null 2>&1 || true
|
wn_b messages send "$gid" "offline-msg-$i" >/dev/null 2>&1 || true
|
||||||
|
|||||||
Reference in New Issue
Block a user