whitenoise-rs became a workspace (core `whitenoise` crate at root, wn/wnd moved to
crates/whitenoise-cli) and grew native --discovery-relays / --default-account-relays
flags, which broke the headless harness's patch/build assumptions.
- Repath the mock-keyring patch to crates/whitenoise-cli/src/bin/wnd.rs and match the
new main() (env-gated Whitenoise::initialize_mock_keyring_store(); the fn is pub
under the integration-tests feature).
- Drop the discovery-env / defaults-env patches: start_daemon now passes the native
--discovery-relays / --default-account-relays flags instead.
- Build wn/wnd via `cargo build -p whitenoise-cli --features whitenoise/integration-tests`
(the binaries left the root crate; integration-tests brings in the mock keyring).
- The skip-unprocessable-retry patch still applies (root crate, 19-line offset).
Enables the end-to-end test_17_group_image_commit (amy sets a group icon -> whitenoise
must still process the commit) added in the previous commit to actually run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JL3GXW1fmHa3xWfQjLLqfp
Restructure `amy relay` from verb-first `relay add URL --type T` to noun-first
`relay <noun> <verb>`, matching amy's `marmot group …` / `cashu mint …`
convention. The relay-list type is now a required path segment (no implicit
default), and a bare noun lists that bucket.
NIP-65 (kind:10002) is fronted by two facet-nouns, `outbox` (write) and `inbox`
(read), replacing the `--marker` flag. They edit the single 10002 event and
apply the spec's merge rules:
- outbox add R on a read-only R → both
- inbox add R on a write-only R → both
- outbox remove R on a both-R → read (stays in inbox)
- inbox remove R on a both-R → write (stays in outbox)
- dropping the last facet removes R entirely
`relay nip65` shows the combined view; `nip65 remove`/`clear` edit the whole
event.
Other buckets are noun+verb: `relay dm|key-package|search|private|blocked|
trusted|proxy|indexer|broadcast|feeds <add|remove|set|clear|list>`. `set` needs
≥1 URL; `clear` empties. `relay add|remove URL` (no noun) stays as the
transport fan-out (nip65 both + dm + key-package).
BREAKING (cli --json/args): removes `relay add/remove/set --type T` and
`--marker`; `relay list` overview now keys nip65 as `outbox`/`inbox`/`nip65`
and the DM bucket as `dm` (was `inbox`). In-repo harnesses updated
(cache/dm/marmot setup drop `--type all`; cache T5 asserts `.dm`).
Verified end-to-end: merge semantics, encrypted NIP-51 round-trips, facet
set/clear, fan-out, aliases, and error paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EjHzNewJ2sfBGCcSwe35Mc
Two fixes needed to make the DM harness actually pass end-to-end:
1. Bind the loopback relay to 127.0.0.2 instead of 127.0.0.1.
Quartz's `RelayTag.parse` (called from `ChatMessageRelayListEvent.
relays()`) rejects any URL for which `isLocalHost()` returns true —
the substring check matches `"127.0.0.1"` among others. That means a
kind:10050 event whose `relay` tag reads `ws://127.0.0.1:8090/`
silently parses to an empty relay list, and `RecipientRelayFetcher`
reports `dmInbox == []`. Under strict mode `dm send` then correctly
refuses with `no_dm_relays` — hiding an otherwise-valid test setup.
`127.0.0.2` is the cleanest fix: still pure loopback (no network
traffic, no config needed), not matched by `isLocalHost()`, and
Linux binds any IP in 127.0.0.0/8 without setup. The Marmot harness
is unaffected because its tests never depend on parsing their own
kind:10051/10002 relay tags back out — they hit the bootstrap
fallback path.
marmot/setup.sh's `start_local_relay` now reads `${RELAY_HOST:-
127.0.0.1}` so both harnesses can share it; the DM harness sets
`RELAY_HOST=127.0.0.2` by default and exposes `--host` as an escape
hatch.
2. Replace dm-06 cursor-advance check with `--since` filter check.
The original assertion ("second no-flag `dm list` returns 0") was
wrong for the actual design: `filterGiftWrapsToPubkey` always
subtracts a 2-day lookback from `since` (required to catch NIP-59's
randomised `created_at`), so a repeat no-flag query legitimately
re-pulls everything in the last two days. The cursor advances the
scan window for efficiency, not for idempotency. dm-06 now verifies
`--since` actually filters: query with `--since <newest + 2 days +
1 hour>` — after the filter's lookback subtraction, the effective
floor lands past every message, and the list comes back empty.
Also: added `protoc` to the DM preflight checks so the missing-dep
error comes before 4 failed cargo retries.
Result on this machine: 6/6 pass, two clean runs in a row.
`tools/` is for libraries (arti-build); shell-based interop harnesses
that drive the `amy` binary belong with the CLI module. New layout:
cli/tests/
├── lib.sh # shared logging + result tracking
├── headless/helpers.sh # shared amy_a / amy_json / assertions
├── marmot/ # MLS group-messaging interop (vs whitenoise-rs)
│ ├── marmot-interop.sh
│ ├── marmot-interop-headless.sh
│ ├── setup.sh
│ ├── tests-create.sh
│ ├── tests-manage.sh
│ ├── tests-extras.sh
│ └── patches/
└── dm/ # NIP-17 DM interop (amy ↔ amy)
├── dm-interop-headless.sh
├── setup.sh
└── tests-dm.sh
Per-suite changes:
- Each suite owns its own setup.sh; previously the Marmot setup was at
headless/setup.sh and the DM setup at headless/setup-dm.sh, which
implied shared infra they don't actually share.
- `cli/tests/lib.sh` and `cli/tests/headless/helpers.sh` are the only
shared bits across suites.
- The DM harness still reuses Marmot's `start_local_relay` /
`stop_local_relay` (sourced via `../marmot/setup.sh`) — same relay
binary, no duplication.
- All sourcing paths updated; REPO_ROOT now climbs three levels (was
two when the harness lived under `tools/`); patches/ is now a sibling
of marmot/setup.sh rather than under marmot/headless/.
- `.gitignore` updated to cover both suites' state dirs.
cli/ROADMAP.md and cli/tests/README.md updated to point at the new paths.
No behavioural change — every test runs the same code; only the file
layout and source paths moved.