#!/usr/bin/env bash
# Prove a channel member past the historical 1,000-row boundary is present in
# relay-served discovery, can write, and survives an authoritative republish.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
: "${DATABASE_URL:?set DATABASE_URL to the isolated relay database}"
: "${BUZZ_RELAY_URL:=http://localhost:3030}"
: "${RELAY_URL:=ws://localhost:3030}"
: "${BUZZ_RELAY_PRIVATE_KEY:=0000000000000000000000000000000000000000000000000000000000000001}"
export BUZZ_RELAY_URL RELAY_URL BUZZ_RELAY_PRIVATE_KEY
unset BUZZ_AUTH_TAG
for binary in buzz buzz-admin; do
resolved="$(command -v "$binary" || true)"
[[ "$resolved" == "$REPO_ROOT/target/release/$binary" ]] || {
echo "error: $binary must resolve to $REPO_ROOT/target/release/$binary (got ${resolved:-not found})" >&2
exit 1
}
done
command -v jq >/dev/null || { echo "error: jq is required" >&2; exit 1; }
command -v psql >/dev/null || { echo "error: psql is required" >&2; exit 1; }
key_field() {
awk -v label="$1" '$1 == label && $2 == "key:" { print $3 }'
}
OWNER_GEN="$(buzz-admin generate-key)"
OWNER_SK="$(printf '%s\n' "$OWNER_GEN" | key_field Secret)"
export BUZZ_PRIVATE_KEY="$OWNER_SK"
CHANNEL="$(buzz channels create \
--name "roster-boundary-$$" --type stream --visibility open | jq -er '.channel_id')"
LATE_GEN="$(buzz-admin generate-key)"
LATE_SK="$(printf '%s\n' "$LATE_GEN" | key_field Secret)"
LATE_PUBKEY="$(printf '%s\n' "$LATE_GEN" | key_field Public)"
# The creator is roster position 1. Add 1,499 fixtures followed by the real
# late identity at position 1,501. A final API-added member forces the relay to
# emit a fresh discovery snapshot through the normal membership-change path.
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
--set=channel="$CHANNEL" --set=late_pubkey="$LATE_PUBKEY" <<'SQL'
WITH target AS (
SELECT community_id, id
FROM channels
WHERE id = :'channel'::uuid
), fixtures AS (
SELECT n, decode(lpad(to_hex(n + 65536), 64, '0'), 'hex') AS pubkey
FROM generate_series(1, 1499) AS n
)
INSERT INTO channel_members (community_id, channel_id, pubkey, role, joined_at)
SELECT target.community_id, target.id, fixtures.pubkey, 'member',
NOW() + fixtures.n * interval '1 millisecond'
FROM target CROSS JOIN fixtures;
INSERT INTO channel_members (community_id, channel_id, pubkey, role, joined_at)
SELECT community_id, id, decode(:'late_pubkey', 'hex'), 'member',
NOW() + interval '2 seconds'
FROM channels
WHERE id = :'channel'::uuid;
SQL
TRIGGER_GEN="$(buzz-admin generate-key)"
TRIGGER_PUBKEY="$(printf '%s\n' "$TRIGGER_GEN" | key_field Public)"
buzz channels add-member --channel "$CHANNEL" --pubkey "$TRIGGER_PUBKEY" --role member >/dev/null
BEFORE="$(buzz channels members --channel "$CHANNEL")"
BEFORE_COUNT="$(jq 'length' <<<"$BEFORE")"
jq -e --arg pk "$LATE_PUBKEY" 'any(.[]; .pubkey == $pk and .role == "member")' \
<<<"$BEFORE" >/dev/null
(( BEFORE_COUNT > 1000 ))
printf 'PASS discovery-before-republish channel=%s members=%s late_pubkey=%s\n' \
"$CHANNEL" "$BEFORE_COUNT" "$LATE_PUBKEY"
export BUZZ_PRIVATE_KEY="$LATE_SK"
ACTION="$(buzz messages send --channel "$CHANNEL" --content "member-1501-action")"
jq -e '.accepted == true' <<<"$ACTION" >/dev/null
ACTION_ID="$(jq -er '.event_id' <<<"$ACTION")"
buzz messages get --channel "$CHANNEL" --limit 10 \
| jq -e --arg id "$ACTION_ID" 'any(.[]; .id == $id and .content == "member-1501-action")' >/dev/null
printf 'PASS late-member-action event_id=%s\n' "$ACTION_ID"
# Targeted roster repair must not replace canonical metadata or admin events.
# Preserve both the event ID and complete tags, including fields this backfill
# command does not know how to rebuild (DM participants, topic, TTL, etc.).
DISCOVERY_BEFORE="$(psql "$DATABASE_URL" -AtX -v ON_ERROR_STOP=1 \
--set=channel="$CHANNEL" <<'SQL'
SELECT jsonb_object_agg(kind::text, jsonb_build_object(
'id', encode(id, 'hex'),
'tags', tags
) ORDER BY kind)::text
FROM events
WHERE channel_id = :'channel'::uuid
AND kind IN (39000, 39001)
AND deleted_at IS NULL;
SQL
)"
jq -e 'has("39000") and has("39001")' <<<"$DISCOVERY_BEFORE" >/dev/null
DATABASE_URL="$DATABASE_URL" RELAY_URL="$RELAY_URL" \
buzz-admin reconcile-channels --channel "$CHANNEL" >/dev/null
DISCOVERY_AFTER="$(psql "$DATABASE_URL" -AtX -v ON_ERROR_STOP=1 \
--set=channel="$CHANNEL" <<'SQL'
SELECT jsonb_object_agg(kind::text, jsonb_build_object(
'id', encode(id, 'hex'),
'tags', tags
) ORDER BY kind)::text
FROM events
WHERE channel_id = :'channel'::uuid
AND kind IN (39000, 39001)
AND deleted_at IS NULL;
SQL
)"
[[ "$DISCOVERY_AFTER" == "$DISCOVERY_BEFORE" ]]
printf 'PASS targeted-repair-preserves-metadata-and-admin-events channel=%s\n' "$CHANNEL"
AFTER="$(buzz channels members --channel "$CHANNEL")"
AFTER_COUNT="$(jq 'length' <<<"$AFTER")"
jq -e --arg pk "$LATE_PUBKEY" 'any(.[]; .pubkey == $pk and .role == "member")' \
<<<"$AFTER" >/dev/null
(( AFTER_COUNT == BEFORE_COUNT ))
printf 'PASS discovery-after-republish channel=%s members=%s late_pubkey=%s\n' \
"$CHANNEL" "$AFTER_COUNT" "$LATE_PUBKEY"