feat(cli): drop --data-dir; tests isolate via $HOME override

There is no installed base to protect, so the self-contained
`--data-dir P` escape hatch goes away entirely. amy now has exactly
one layout — the production multi-account one — and tests exercise
that same code path. Drops one global flag, one DataDir construction
mode, and the "tests use a different code path than users" footgun.

Layout (unchanged from the previous commits — just the only mode now):

    ~/.amy/
    ├── current                      # `amy use NAME` marker
    ├── shared/
    │   └── events-store/            # FsEventStore, one per machine
    ├── alice/
    │   ├── identity.json
    │   ├── state.json
    │   ├── aliases.json             # {"alice": "<own npub>"} after init
    │   └── marmot/
    └── bob/ ...

`DataDir.DEFAULT_ROOT` reads `$HOME` directly (falling back to
`user.home`) because JDK 21 resolves `user.home` via getpwuid and
ignores `$HOME` — which would have broken the standard CLI test
isolation pattern of `HOME=/tmp/foo amy …` (the same convention
git, gpg, npm follow).

Test sweep:

- `cli/tests/headless/helpers.sh`, `cli/tests/dm/setup.sh`,
  `cli/tests/cache/cache-headless.sh` wrappers all switch to
  `HOME=$STATE_DIR amy --name X …`.
- `ensure_identity_for` drops its `dir` parameter; the function and
  every harness call site go through `--name` only.
- `A_DIR`/`B_DIR`/`D_DIR` get repointed at `$STATE_DIR/.amy/X`. The
  one consumer (`cache-headless.sh`'s T6 `relays.json` check) still
  works since it's just a path probe.
- `cli/tests/dm/tests-dm.sh` ghost identities get their own
  short-lived `$HOME` so they don't pollute the main test root.

Cache-test T4 inverts: pre-shared-store, "B has not seen A → first
profile show is a relay miss, second is a cache hit" tested
per-account caching. With one shared events-store, B's first lookup
of A is already a cache hit because A wrote kind:0 there during
bootstrap. T4 now asserts that — drops the stale "second lookup hits
cache" half. T7 (no-identity maintenance verbs) gets a fresh fake
`$HOME` plus a throwaway `--name` so the empty store has no inherited
events.

Docs (README + DEVELOPMENT) rewritten to match — quick-start now uses
`amy --name alice create`, the on-disk-layout section shows the new
tree, and the global-flags table replaces `--data-dir` with `--name`
plus the new `amy use` verb.
This commit is contained in:
Claude
2026-04-25 15:07:31 +00:00
parent 46bdd59ead
commit 99586fbe7e
12 changed files with 240 additions and 200 deletions
+7 -5
View File
@@ -15,8 +15,10 @@ REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
STATE_DIR="$SCRIPT_DIR/state-dm-headless"
LOG_DIR="$STATE_DIR/logs"
A_DIR="$STATE_DIR/A"
D_DIR="$STATE_DIR/D"
# Per-account dirs under the same fake $HOME=$STATE_DIR so amy treats
# this as one user with two accounts (production layout).
A_DIR="$STATE_DIR/.amy/A"
D_DIR="$STATE_DIR/.amy/D"
RUN_TS="$(date +%Y%m%d-%H%M%S)"
LOG_FILE="$LOG_DIR/run-$RUN_TS.log"
@@ -57,7 +59,7 @@ while [[ $# -gt 0 ]]; do
shift
done
mkdir -p "$STATE_DIR" "$LOG_DIR" "$A_DIR" "$D_DIR"
mkdir -p "$STATE_DIR" "$LOG_DIR"
: >"$LOG_FILE"
: >"$RESULTS_FILE"
@@ -94,8 +96,8 @@ trap 'exit 129' HUP
banner "Amethyst NIP-17 DM headless interop ($RUN_TS)"
preflight_dm
start_local_relay
ensure_identity_for A "$A_DIR"
ensure_identity_for D "$D_DIR"
ensure_identity_for A
ensure_identity_for D
configure_relays_dm
test_01_dm_text_round_trip
+12 -9
View File
@@ -69,21 +69,24 @@ preflight_dm() {
}
# --- amy identity wrappers ---------------------------------------------------
# Two identities: A (sender) and D (recipient). We reuse A_DIR for parity
# with the existing harness files; D_DIR is new.
# Two identities: A (sender) and D (recipient), both inside the same
# $STATE_DIR/.amy tree. They share $STATE_DIR/.amy/shared/events-store/
# — the same code path real users hit with multiple accounts. The
# enclosing harness sets STATE_DIR to a fresh tempdir per run, so amy
# sees a virgin home each time.
#
# `--secret-backend=plaintext` keeps these throwaway interop runs headless —
# the default `auto` would try the OS keychain (not available in CI) and then
# ask for a NIP-49 passphrase. Plaintext still writes 0600-owner-only.
amy_a() { "$AMY_BIN" --data-dir "$A_DIR" --secret-backend plaintext --json "$@"; }
amy_d() { "$AMY_BIN" --data-dir "$D_DIR" --secret-backend plaintext --json "$@"; }
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --name A --secret-backend plaintext --json "$@"; }
amy_d() { HOME="$STATE_DIR" "$AMY_BIN" --name D --secret-backend plaintext --json "$@"; }
# --- identity bootstrap ------------------------------------------------------
ensure_identity_for() {
local who="$1" dir="$2"
step "initialising Identity $who (amy at $dir)"
local who="$1"
step "initialising Identity $who (amy at \$HOME=$STATE_DIR --name $who)"
local out
out=$("$AMY_BIN" --data-dir "$dir" --secret-backend plaintext --json init) || {
out=$(HOME="$STATE_DIR" "$AMY_BIN" --name "$who" --secret-backend plaintext --json init) || {
fail_msg "amy init failed for $who: $out"; exit 1
}
local npub hex
@@ -103,8 +106,8 @@ ensure_identity_for() {
# so the DM strict-relay routing has something to resolve to.
configure_relays_dm() {
banner "Configuring relays → $RELAY_URL"
"$AMY_BIN" --data-dir "$A_DIR" relay add "$RELAY_URL" --type all >/dev/null
"$AMY_BIN" --data-dir "$D_DIR" relay add "$RELAY_URL" --type all >/dev/null
amy_a relay add "$RELAY_URL" --type all >/dev/null
amy_d relay add "$RELAY_URL" --type all >/dev/null
step "publishing A's NIP-65 + kind:10050 lists"
amy_a relay publish-lists >>"$LOG_FILE" 2>&1 \
+20 -17
View File
@@ -12,22 +12,23 @@
# dm-05 file message reference mode round-trip (kind:15)
# dm-06 cursor advance (subsequent no-flag `dm list` is empty)
# Wrap amy_json around either data-dir so the per-test code stays tight.
# `--json` opts into amy's machine-readable contract; assertions below
# parse with jq.
# Wrap amy_json around either account so the per-test code stays tight.
# Both share $HOME=$STATE_DIR (set by the harness); --name picks the
# account inside it. `--json` opts into amy's machine-readable contract;
# assertions below parse with jq.
amy_json_for() {
local dir="$1"; shift
local account="$1"; shift
local out
if ! out=$("$AMY_BIN" --data-dir "$dir" --json "$@" 2>>"$LOG_FILE"); then
fail_msg "amy --data-dir $dir $*: exit $? (see $LOG_FILE)"
if ! out=$(HOME="$STATE_DIR" "$AMY_BIN" --name "$account" --secret-backend plaintext --json "$@" 2>>"$LOG_FILE"); then
fail_msg "amy --name $account $*: exit $? (see $LOG_FILE)"
printf '%s\n' "$out" >>"$LOG_FILE"
return 1
fi
printf '%s' "$out"
}
amy_json_a() { amy_json_for "$A_DIR" "$@"; }
amy_json_d() { amy_json_for "$D_DIR" "$@"; }
amy_json_a() { amy_json_for A "$@"; }
amy_json_d() { amy_json_for D "$@"; }
test_01_dm_text_round_trip() {
banner "DM-01 — text round-trip A↔D (kind:14)"
@@ -91,19 +92,21 @@ test_03_dm_send_rejects_no_inbox() {
local id="dm-03 strict no_dm_relays"
# Generate a throwaway identity but do NOT publish its kind:10050.
local tmpdir; tmpdir=$(mktemp -d "${STATE_DIR}/ghost.XXXXXX")
# The ghost lives in its own fake $HOME so it doesn't pollute the
# test's main STATE_DIR with a third account.
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
local ghost_out ghost_npub
ghost_out=$("$AMY_BIN" --data-dir "$tmpdir" --secret-backend plaintext --json init) || {
record_result "$id" fail "ghost init failed"; rm -rf "$tmpdir"; return
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --name ghost --secret-backend plaintext --json init) || {
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
}
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
info "ghost: $ghost_npub (no relays advertised)"
# A sends without --allow-fallback; amy should refuse.
local raw rc
raw=$("$AMY_BIN" --data-dir "$A_DIR" --json dm send "$ghost_npub" "should be rejected" 2>&1)
raw=$(HOME="$STATE_DIR" "$AMY_BIN" --name A --secret-backend plaintext --json dm send "$ghost_npub" "should be rejected" 2>&1)
rc=$?
rm -rf "$tmpdir"
rm -rf "$ghost_home"
if [[ "$rc" -ne 0 ]] && printf '%s' "$raw" | grep -q '"error":"no_dm_relays"'; then
info "amy refused with no_dm_relays as expected (exit $rc)"
record_result "$id" pass
@@ -121,13 +124,13 @@ test_04_dm_send_allow_fallback() {
# fall through kind:10050 → NIP-65 read → bootstrap. Our bootstrap set
# always includes the loopback relay via the shared RelayConfig, so the
# publish should succeed even though the ghost has no 10050.
local tmpdir; tmpdir=$(mktemp -d "${STATE_DIR}/ghost.XXXXXX")
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
local ghost_out ghost_npub
ghost_out=$("$AMY_BIN" --data-dir "$tmpdir" --secret-backend plaintext --json init) || {
record_result "$id" fail "ghost init failed"; rm -rf "$tmpdir"; return
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --name ghost --secret-backend plaintext --json init) || {
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
}
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
rm -rf "$tmpdir"
rm -rf "$ghost_home"
local out source
out=$(amy_json_a dm send "$ghost_npub" "hi via fallback" --allow-fallback) || {