fix(graperank): don't create the operator master for a read-only follower crawl

`amy graperank followers <observer>` (no --relay) assembles the relay universe
via allKnownRelays, which read the reachability cache through ctx.reachability —
and that lazily derives the monitor key from the operator master, forcing a
passphrase prompt (or failing on a fresh machine) purely to READ kind:30166
records. The follower crawl signs nothing, so this defeats its anonymous path.

Read the reachability snapshot with a throwaway signer instead (snapshot() only
reads; the signer is used solely for writes) — the same trick `graperank status`
already uses to stay side-effect-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xc3Wm4qVCrAGvSAotTUVt4
This commit is contained in:
Claude
2026-07-17 22:57:25 +00:00
parent 331dca79cd
commit 33f4c648e9
@@ -697,7 +697,12 @@ object GrapeRankCommand {
ctx: Context,
args: Args,
): Set<NormalizedRelayUrl> {
val reach = ctx.reachability.snapshot()
// Read the reachability records (kind:30166) with a throwaway signer, NOT
// ctx.reachability — the latter derives the monitor key and would CREATE the
// operator master (a passphrase prompt) purely to read a cache. The follower
// crawl never signs, so keep it truly account-and-key-free. snapshot() only
// reads; the signer is used solely for writes. Same trick as `status`.
val reach = RelayReachabilityStore(store = ctx.store, signer = NostrSignerInternal(KeyPair())).snapshot()
val relays = LinkedHashSet<NormalizedRelayUrl>()
relays.addAll(reach.live)
// Every advertised outbox/inbox relay in the store — the homes of the users