From 38b2e65362a3b29c5d06dfd615ed1201159a7481 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 15:50:23 +0000 Subject: [PATCH] feat(quartz): expose pending outbox events per relay for auth context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add INostrClient.activeOutboxEvents(url) (backed by PoolEventOutbox.activeOutboxEventsFor) returning the full events still pending delivery to a relay, not just their ids like activeOutboxCache. This lets a host explain *why* a relay is being authenticated with — e.g. a pending kind-1059 gift wrap means we're sending a DM to its recipient — by inspecting kind/tags. Combined with the existing activeRequests(url) filters, it is the generic challenge context the NIP-42 decision hook needs. Updates the INostrClient test fakes. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EZjmYpgHP4pf79Sav5QT8a --- .../desktop/cache/CoordinatorPipelineTest.kt | 2 ++ .../quartz/nip01Core/relay/client/INostrClient.kt | 5 +++++ .../quartz/nip01Core/relay/client/NostrClient.kt | 2 ++ .../relay/client/pool/PoolEventOutbox.kt | 15 +++++++++++++++ .../signer/NostrSignerRemoteIsolationTest.kt | 2 ++ .../signer/RemoteSignerManagerRetryTest.kt | 4 ++++ 6 files changed, 30 insertions(+) diff --git a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt index a43b3758e6..480ea4fe06 100644 --- a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt +++ b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt @@ -127,6 +127,8 @@ class CoordinatorPipelineTest { override fun activeCounts(url: NormalizedRelayUrl): Map> = emptyMap() override fun activeOutboxCache(url: NormalizedRelayUrl): Set = emptySet() + + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = emptyList() } private fun createCoordinator( diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt index 125643864e..35a09a9748 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt @@ -104,6 +104,9 @@ interface INostrClient : AutoCloseable { fun activeCounts(url: NormalizedRelayUrl): Map> fun activeOutboxCache(url: NormalizedRelayUrl): Set + + /** The events still pending delivery to [url] (full events, not just ids). */ + fun activeOutboxEvents(url: NormalizedRelayUrl): List } class EmptyNostrClient : INostrClient { @@ -158,5 +161,7 @@ class EmptyNostrClient : INostrClient { override fun activeOutboxCache(url: NormalizedRelayUrl): Set = emptySet() + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = emptyList() + override fun close() {} } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt index 6516d0cb5b..073575d7e5 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt @@ -369,6 +369,8 @@ class NostrClient( override fun activeOutboxCache(url: NormalizedRelayUrl): Set = eventOutbox.activeOutboxCacheFor(url) + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = eventOutbox.activeOutboxEventsFor(url) + override fun pendingPublishRelaysFor(eventId: HexKey): Set? = eventOutbox.pendingRelaysFor(eventId) override fun getReqFiltersOrNull(subId: String): Map>? = activeRequests.getSubscriptionFiltersOrNull(subId) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt index 0e4506a842..9547e9e453 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt @@ -96,6 +96,21 @@ class PoolEventOutbox { return myEvents } + /** + * The events still pending delivery to [url]. Unlike [activeOutboxCacheFor] (ids only), this + * returns the full events so callers can inspect kind/tags — e.g. to explain *why* a relay is + * being authenticated with (a pending gift wrap => sending a DM to its recipient). + */ + fun activeOutboxEventsFor(url: NormalizedRelayUrl): List { + val myEvents = mutableListOf() + eventOutbox.forEach { (_, outboxCache) -> + if (url in outboxCache.relaysRemaining) { + myEvents.add(outboxCache.event) + } + } + return myEvents + } + /** * Returns the relays that have NOT yet acknowledged [eventId] with an OK, or * null if the event is not currently tracked (never sent or already fully done). diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/NostrSignerRemoteIsolationTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/NostrSignerRemoteIsolationTest.kt index f7213b655f..f86af44b03 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/NostrSignerRemoteIsolationTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/NostrSignerRemoteIsolationTest.kt @@ -104,6 +104,8 @@ private class TrackingNostrClient : INostrClient { override fun activeOutboxCache(url: NormalizedRelayUrl): Set = emptySet() + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = emptyList() + override fun close() {} } diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/RemoteSignerManagerRetryTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/RemoteSignerManagerRetryTest.kt index 3f346531b8..77ca2c5d3c 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/RemoteSignerManagerRetryTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/RemoteSignerManagerRetryTest.kt @@ -431,6 +431,8 @@ private class CapturingNostrClient : INostrClient { override fun activeOutboxCache(url: NormalizedRelayUrl): Set = emptySet() + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = emptyList() + override fun close() {} } @@ -490,5 +492,7 @@ private class CountingNostrClient( override fun activeOutboxCache(url: NormalizedRelayUrl): Set = emptySet() + override fun activeOutboxEvents(url: NormalizedRelayUrl): List = emptyList() + override fun close() {} }