The previous trap only fired on the EXIT pseudo-signal, which doesn't
always run cleanly when the script is killed mid-flight — leaving the
nohup'd wnd daemons (and the local relay) alive and still holding the
port. Route SIGINT/SIGTERM/SIGHUP through an explicit `exit`, use a
single idempotent cleanup handler, and preserve the exit code so the
harness still reports failure when killed.
Quartz:
- processWelcome now enforces hash(ratchet_tree) == GroupContext.tree_hash
and verifies the joiner-derived confirmation_tag matches GroupInfo's,
closing the gap where a tampered but-signed GroupInfo could diverge
the joiner's epoch secrets.
- externalJoin does the equivalent tree_hash check.
- Promoted constantTimeEquals to a file-level helper so the companion
object (processWelcome) can use it alongside instance methods.
Tests:
- New MlsGroupNegativeTest exercises every authenticity knob on inbound
commits: tampered confirmation_tag, bit-flipped signature, spoofed
senderLeafIndex, wrong wire_format, empty confirmation_tag, and
tampered/missing membership_tag — each must throw and leave the
receiving group's epoch unchanged (atomic-rollback regression cover).
- Fixed stale jvmAndroidTest references to the removed `selfRemove()`
helper — use `buildSelfRemoveProposalMessage()` (now Pair-returning).
Interop harness:
- Adds tests 14–16 as inverted-role scaffolding (wn drives, amy receives).
Currently recorded as SKIP with explicit notes:
14 / 15 block on filtered-direct-path UpdatePath support in
quartz's applyUpdatePath (RFC 9420 §7.7 path compression).
16 blocks on a createdAt-sorted KeyPackage fetch path — the current
fetchFirst-based fetcher is non-deterministic on relay order.
Preserves the test functions so they start running the moment those
gaps are closed.
Every test now flows through one loopback nostr-rs-relay on
ws://127.0.0.1:8080 — no public relays, no egress. Addresses the
repeated 503s we saw from the sandbox and the kind:445 rejections we
hit on public relays even with internet.
Changes:
- Preflight clones https://github.com/scsibug/nostr-rs-relay into
state-headless/nostr-rs-relay and runs `cargo build --release`
(~3 min first run). Cache hits on subsequent runs.
- New start_local_relay / stop_local_relay functions render a minimal
config.toml each run (binds to 127.0.0.1, no kind blacklist, data
under state-headless/relay) and wait up to 20s for the port.
- configure_relays collapsed to a single-relay path — A/B/C all point
at \$RELAY_URL. Kept publish-lists + key-package publish as smoke
signals for the advertise path.
- Flags pared down: drop --local-relays (always local now), add
--port N for when 8080 is taken. --no-build still honoured.
- trap wires relay shutdown alongside daemon shutdown.
https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
Matches the 13 scenarios in marmot-interop.sh but runs A via the new
`amy` CLI instead of prompting the human. Identities B and C still go
through `wn`/`wnd` from whitenoise-rs.
Layout:
- marmot-interop-headless.sh — top-level entrypoint
- headless/setup.sh — preflight, daemon lifecycle, identities
- headless/helpers.sh — amy wrappers + assertion helpers
- headless/tests-create.sh — tests 01..05
- headless/tests-manage.sh — tests 06, 07, 08, 11
- headless/tests-extras.sh — tests 09, 10, 12, 13
Reuses lib.sh for colours, record_result, wait_for_invite/message,
jq_group_id, and dump_daemon_diagnostics.
Keeps the interactive marmot-interop.sh intact for final UI sign-off.
https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq