From 5d406c599889ced4ed2e5acf0b567fb6010c25d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 14 May 2026 23:02:52 +0000 Subject: [PATCH] fix(nests): address self-audit findings on the interop transport fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuicWebTransportFactory: re-throw CancellationException from the bounded handshake instead of wrapping it in WebTransportException, so a parent-scope cancellation no longer breaks structured concurrency. Mirrors the existing handling on the post-CONNECT path. - NostrNestsHarness: correct two misleading comments — the dev endpoint's IPv4 guarantee comes from QuicWebTransportFactory's resolve step, not from `localhost` itself; and DEFAULT_REVISION is documented as tracking `main` (a known drift risk against the pinned moq-relay), not pinned. https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM --- .../transport/QuicWebTransportFactory.kt | 8 ++++++++ .../nestsclient/interop/NostrNestsHarness.kt | 20 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/transport/QuicWebTransportFactory.kt b/nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/transport/QuicWebTransportFactory.kt index 1a1229a0b..eddd12a80 100644 --- a/nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/transport/QuicWebTransportFactory.kt +++ b/nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/transport/QuicWebTransportFactory.kt @@ -143,6 +143,14 @@ class QuicWebTransportFactory( conn.awaitHandshake() true } + } catch (ce: kotlinx.coroutines.CancellationException) { + // A parent-scope cancellation propagates out of withTimeoutOrNull + // (it only swallows its OWN TimeoutCancellationException). Wrapping + // it in HandshakeFailed would break structured concurrency — close + // the driver but re-throw the cancellation untouched. Mirrors the + // CancellationException handling on the post-CONNECT path below. + driver.close() + throw ce } catch (t: Throwable) { driver.close() throw WebTransportException( diff --git a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/NostrNestsHarness.kt b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/NostrNestsHarness.kt index c155e0c1c..5aae2cabc 100644 --- a/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/NostrNestsHarness.kt +++ b/nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/NostrNestsHarness.kt @@ -86,9 +86,17 @@ class NostrNestsHarness private constructor( private fun isExternal(): Boolean = System.getProperty(EXTERNAL_PROPERTY) == "true" /** - * Pin the nostrnests revision to a known-good SHA. Bump deliberately - * — drift on `main` should not silently change interop expectations. - * Override at runtime via `-DnestsInteropRev=`. + * The `nostrnests/nests` revision the harness checks out — this is + * the repo that supplies the `moq-auth` token-minting sidecar. + * + * NOT currently pinned: it tracks `main`. That's a known drift risk + * — `moq-auth`'s JWT format must stay compatible with the + * `moq-relay` version pinned in [DEFAULT_MOQ_REVISION] + * (`moq-relay-v0.10.10`). If nostrnests advances `moq-auth` past + * what 0.10.10 accepts, the pair desyncs again and the relay + * rejects every connection with `failed to decode the token`. Pin + * this to a SHA known-compatible with 0.10.10 once one is + * identified. Override at runtime via `-DnestsInteropRev=`. */ const val DEFAULT_REVISION = "main" @@ -222,7 +230,11 @@ class NostrNestsHarness private constructor( // there, and a strict relay (rustls) rejects an IP-literal SNI // and then has no SNI to select its dev cert on — the handshake // stalls. `localhost` is a valid hostname and matches the dev - // cert moq-relay generates. Resolves to 127.0.0.1 regardless. + // cert moq-relay generates. The UDP socket itself still goes to + // IPv4 loopback: QuicWebTransportFactory resolves the host with + // an IPv4 preference (`localhost` often resolves to ::1 first, + // and Docker's IPv6 UDP forwarding blackholes), while keeping + // `localhost` as the SNI name. moqEndpoint = "https://localhost:$MOQ_HOST_PORT/", ) }