mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
The harness had never actually been run. It now builds MDK 0.9.20 —
against the same OpenMLS fork rev our vector generator pins — boots a
local relay, brings up both wnd daemons and amy, and executes all 17
scenarios. They all still fail, downstream of MDK not finding A's
KeyPackage, but "it runs" is the difference between having an interop
signal and not having one.
Four environment blockers stood between preflight and a run: protoc is
now a build prerequisite; MDK 0.9.x needs WN_ALLOW_LOOPBACK_RELAYS=1
before it will accept a ws:// loopback relay at all; it refuses to create
its socket unless the parent directory is 0700; and `wn --json whoami`
moved to {"ok":true,"result":{"accounts":[…]}}, which the harness's
extractor probed right past.
Two defects in our own code came out of it.
`amy relay add` reported success from its DECISION to write rather than
from the store's answer, so a rejected or no-op write printed
`added: yes` and the caller only discovered otherwise much later.
The more consequential one: we read our OWN relay lists back through the
local-network filter. That filter is correct for someone else's list — it
is attacker-supplied input, and it is also what exempts a relay from Tor
— but applied to a list we published ourselves it made a deliberately
configured local relay look like no configuration at all. The publisher
then fell back to a default set, and the harness sent A's KeyPackage to
five PUBLIC relays instead of its loopback, which is the exact opposite
of what a "nothing leaves the machine" harness is for. `allRelays()` now
exists for reading back our own lists; the KeyPackage publish goes only
to the configured relay.
Test 01 is still blocked on a narrower puzzle: the kind-10051 list
persists under `relay key-package set` but not under `relay add`, while
kind 10050 works through the identical code path. That is a storage/CLI
thread, not a protocol one, and it needs its own pass.
Separately, and not a bug on either side: MDK accepts ws:// only for a
loopback host while quartz strips exactly those hosts from relay lists.
No address satisfies both, so a loopback-relay harness cannot pass until
one side moves — and changing a Tor-adjacent privacy guard is a
maintainer call, not one to make in passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
149 lines
4.9 KiB
Bash
Executable File
149 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# marmot-interop-headless.sh — zero-prompt, zero-internet interop harness.
|
|
#
|
|
# Drives Identity A via the `amy` CLI (./gradlew :cli:installDist) and
|
|
# Identities B/C via MDK's `wn`/`wnd`. Spins up a local
|
|
# nostr-rs-relay on ws://127.0.0.1:$RELAY_PORT so nothing ever leaves the
|
|
# machine. Matches the 13 test scenarios in marmot-interop.sh but without
|
|
# any human prompts — all checks run to completion and the exit code
|
|
# reflects pass/fail totals.
|
|
#
|
|
# Usage: ./marmot-interop-headless.sh [--port N] [--no-build]
|
|
#
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
|
|
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
|
STATE_DIR="$SCRIPT_DIR/state-headless"
|
|
LOG_DIR="$STATE_DIR/logs"
|
|
# A is the amy account inside the fake $HOME=$STATE_DIR layout. B and
|
|
# C are wnd (whitenoise) state dirs — different binary, separate
|
|
# convention, so they stay as plain $STATE_DIR siblings.
|
|
A_DIR="$STATE_DIR/.amy/A"
|
|
B_DIR="$STATE_DIR/B"
|
|
C_DIR="$STATE_DIR/C"
|
|
B_SOCKET="$B_DIR/wnd.sock"
|
|
C_SOCKET="$C_DIR/wnd.sock"
|
|
|
|
RUN_TS="$(date +%Y%m%d-%H%M%S)"
|
|
LOG_FILE="$LOG_DIR/run-$RUN_TS.log"
|
|
RESULTS_FILE="$STATE_DIR/results-$RUN_TS.tsv"
|
|
|
|
WN_REPO="${WN_REPO:-$SCRIPT_DIR/state/mdk}"
|
|
WN_BIN="$WN_REPO/target/release/wn"
|
|
WND_BIN="$WN_REPO/target/release/wnd"
|
|
AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy"
|
|
|
|
# Local relay wiring — cloned + built during preflight, started on
|
|
# $RELAY_PORT. The harness never touches the public internet for test
|
|
# traffic; wn/wnd/amy all point at this one loopback endpoint.
|
|
#
|
|
# Bind to 127.0.0.2 rather than 127.0.0.1: Quartz's RelayUrlNormalizer
|
|
# strips literal 127.0.0.1 / localhost / 192.168.* out of NIP-17 inbox
|
|
# (kind:10050) and KeyPackage (kind:10051) relay-list events as a
|
|
# privacy guard, which would silently leave the harness publishing to
|
|
# Amethyst's public defaults instead of the loopback. 127.0.0.2 is
|
|
# still pure loopback (no network traffic) but isn't on the strip list.
|
|
RELAY_HOST="${RELAY_HOST:-127.0.0.2}"
|
|
RELAY_REPO="${RELAY_REPO:-$STATE_DIR/nostr-rs-relay}"
|
|
RELAY_BIN="$RELAY_REPO/target/release/nostr-rs-relay"
|
|
RELAY_DATA="$STATE_DIR/relay"
|
|
RELAY_PORT="${RELAY_PORT:-8080}"
|
|
RELAY_URL="ws://$RELAY_HOST:$RELAY_PORT"
|
|
NO_BUILD=0
|
|
|
|
# Required as of MDK 0.9.x. `validate_relay_url` accepts `wss://`
|
|
# unconditionally but `ws://` only for a loopback host AND only behind this
|
|
# explicit opt-in; without it wnd refuses the harness relay with "invalid relay
|
|
# URL" and exits before creating its socket. 127.0.0.2 is inside 127.0.0.0/8 and
|
|
# already passes MDK's own loopback test, so the env var is the gate, not the
|
|
# address. Exported once here so `wn` and `wnd` both inherit it — `wn` runs the
|
|
# same validation on any relay argument.
|
|
export WN_ALLOW_LOOPBACK_RELAYS=1
|
|
|
|
A_NPUB=""
|
|
A_HEX=""
|
|
B_NPUB=""
|
|
B_HEX=""
|
|
C_NPUB=""
|
|
C_HEX=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--port) RELAY_PORT="$2"; RELAY_URL="ws://$RELAY_HOST:$RELAY_PORT"; shift ;;
|
|
--host) RELAY_HOST="$2"; RELAY_URL="ws://$RELAY_HOST:$RELAY_PORT"; shift ;;
|
|
--no-build) NO_BUILD=1 ;;
|
|
-h|--help)
|
|
sed -n '3,14p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
|
|
exit 0 ;;
|
|
*) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
mkdir -p "$STATE_DIR" "$LOG_DIR" "$B_DIR/logs" "$C_DIR/logs"
|
|
: >"$LOG_FILE"
|
|
: >"$RESULTS_FILE"
|
|
|
|
# Reuse colours / logging / dump_daemon_diagnostics from the interactive harness.
|
|
# shellcheck source=../lib.sh
|
|
source "$TESTS_DIR/lib.sh"
|
|
|
|
# shellcheck source=setup.sh
|
|
source "$SCRIPT_DIR/setup.sh"
|
|
# shellcheck source=../headless/helpers.sh
|
|
source "$TESTS_DIR/headless/helpers.sh"
|
|
# shellcheck source=tests-create.sh
|
|
source "$SCRIPT_DIR/tests-create.sh"
|
|
# shellcheck source=tests-manage.sh
|
|
source "$SCRIPT_DIR/tests-manage.sh"
|
|
# shellcheck source=tests-extras.sh
|
|
source "$SCRIPT_DIR/tests-extras.sh"
|
|
|
|
# Make sure Ctrl+C / SIGTERM / SIGHUP all run the full cleanup path —
|
|
# otherwise wnd is nohup'd and keeps running after the script dies,
|
|
# and the next run's `ss` check then complains the port is in use.
|
|
# Trap INT/TERM/HUP forces `exit`, which triggers the EXIT handler once.
|
|
cleanup() {
|
|
local rc=$?
|
|
trap - EXIT INT TERM HUP
|
|
stop_daemons
|
|
stop_local_relay
|
|
print_summary
|
|
exit "$rc"
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
trap 'exit 129' HUP
|
|
|
|
banner "Marmot headless interop harness ($RUN_TS)"
|
|
preflight
|
|
start_local_relay
|
|
start_daemon B "$B_DIR" "$B_SOCKET"
|
|
start_daemon C "$C_DIR" "$C_SOCKET"
|
|
ensure_identity_a
|
|
ensure_identity B
|
|
ensure_identity C
|
|
configure_relays
|
|
|
|
test_01_keypackage_discovery
|
|
test_02_a_creates_group
|
|
test_03_b_creates_group
|
|
test_04_three_member_group
|
|
test_05_b_adds_a_existing
|
|
test_06_member_removal
|
|
test_07_metadata_rename
|
|
test_08_admin_promote_demote
|
|
test_17_group_image_commit
|
|
test_09_reply_react_unreact
|
|
test_10_concurrent_commits
|
|
test_11_leave_group
|
|
test_12_offline_catchup
|
|
test_13_keypackage_rotation
|
|
test_14_wn_removes_a
|
|
test_15_wn_member_leaves
|
|
test_16_wn_keypackage_rotation
|