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 -----------------------------------------------------
|
||||
|
||||
# wait_for_invite <B|C> <timeout-seconds>
|
||||
# Echoes the first new group_id that appears in `wn groups invites --json`.
|
||||
# Snapshot currently-pending invites on <B|C> as a comma-separated list of
|
||||
# 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.
|
||||
#
|
||||
# Also prints a heartbeat every ~10s with:
|
||||
# - 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)
|
||||
# so a stalled poll gives the operator something to forward.
|
||||
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
|
||||
if [[ "$who" == "B" ]]; then wnfn=wn_b; data_dir="$B_DIR"
|
||||
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": [...]}`
|
||||
# (older builds returned the bare array). Peel the wrapper when
|
||||
# present so a pending invite is actually detected.
|
||||
gid=$("$wnfn" --json groups invites 2>/dev/null \
|
||||
| jq -c '(.result // .) | .[0] // empty' 2>/dev/null | jq_group_id || true)
|
||||
if [[ -n "${gid:-}" ]]; then
|
||||
printf '%s\n' "$gid"
|
||||
return 0
|
||||
fi
|
||||
local raw
|
||||
raw=$("$wnfn" --json groups invites 2>/dev/null || true)
|
||||
# Walk every pending invite (not just .[0]) so we skip past stales.
|
||||
while IFS= read -r one; do
|
||||
[[ -z "$one" || "$one" == "null" ]] && continue
|
||||
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.
|
||||
local now=$(date +%s)
|
||||
if (( now - last_hb >= 10 )); then
|
||||
local elapsed=$(( now - start )) remaining=$(( deadline - now ))
|
||||
local pending
|
||||
pending=$("$wnfn" --json groups invites 2>/dev/null \
|
||||
| jq '(.result // .) | length' 2>/dev/null || echo "?")
|
||||
pending=$(printf '%s' "$raw" | jq '(.result // .) | length' 2>/dev/null || echo "?")
|
||||
local recent=""
|
||||
if [[ -f "$data_dir/logs/stderr.log" ]]; then
|
||||
recent=$(tail -n 200 "$data_dir/logs/stderr.log" 2>/dev/null \
|
||||
| grep -iE 'welcome|giftwrap|gift_wrap|mls|subscribe|1059|444|error|warn' \
|
||||
| tail -n 1 || true)
|
||||
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
|
||||
fi
|
||||
|
||||
|
||||
@@ -363,7 +363,15 @@ configure_relays() {
|
||||
fi
|
||||
|
||||
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_gid=$(printf '%s' "$sanity_create" | jq_group_id)
|
||||
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/')"
|
||||
else
|
||||
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)"
|
||||
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_b messages send "$sanity_gid" "sanity-ping" >/dev/null 2>&1 || true
|
||||
if wait_for_message C "$sanity_c_gid" "sanity-ping" 30; then
|
||||
@@ -451,6 +462,12 @@ test_02_amethyst_creates_group() {
|
||||
# Amethyst sends it.
|
||||
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:
|
||||
1. Tap '+' -> Create Group
|
||||
2. Name: Interop-02
|
||||
@@ -464,7 +481,7 @@ that's the bug."
|
||||
|
||||
step "polling B's daemon for invite (60s, heartbeat every ~10s)"
|
||||
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"
|
||||
dump_daemon_diagnostics B "post-timeout (test_02)"
|
||||
prompt_amethyst_logcat "test_02 timeout"
|
||||
@@ -603,13 +620,17 @@ test_04_three_member_group() {
|
||||
|
||||
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').
|
||||
Group Info -> Add Member -> paste: $C_NPUB
|
||||
Confirm the invite is sent."
|
||||
|
||||
step "polling C for invite (60s, heartbeat every ~10s)"
|
||||
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"
|
||||
dump_daemon_diagnostics C "post-timeout (test_04)"
|
||||
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"
|
||||
|
||||
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")
|
||||
printf 'wn groups create raw output: %s\n' "$out" >>"$LOG_FILE"
|
||||
gid=$(printf '%s' "$out" | jq_group_id)
|
||||
@@ -652,8 +674,9 @@ test_05_wn_adds_amethyst_existing() {
|
||||
return
|
||||
fi
|
||||
save_state GROUP_05 "$gid"
|
||||
if wait_for_invite C 30 >/dev/null; then
|
||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
||||
local c_new_gid
|
||||
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
|
||||
|
||||
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"
|
||||
wn_c keys publish >/dev/null 2>&1 || true
|
||||
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"
|
||||
if wait_for_invite C 30 >/dev/null; then
|
||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
||||
if c_new_gid_08=$(wait_for_invite C 30 "$c_ignore_08"); then
|
||||
wn_c groups accept "$c_new_gid_08" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
step "B promotes A to admin"
|
||||
@@ -946,9 +971,11 @@ test_12_offline_catchup() {
|
||||
done
|
||||
|
||||
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
|
||||
if wait_for_invite C 30 >/dev/null; then
|
||||
wn_c groups accept "$gid" >/dev/null 2>&1 || true
|
||||
if c_new_gid_12=$(wait_for_invite C 30 "$c_ignore_12"); then
|
||||
wn_c groups accept "$c_new_gid_12" >/dev/null 2>&1 || true
|
||||
fi
|
||||
for i in 6 7 8; do
|
||||
wn_b messages send "$gid" "offline-msg-$i" >/dev/null 2>&1 || true
|
||||
|
||||
Reference in New Issue
Block a user