Commit Graph

6 Commits

Author SHA1 Message Date
Claude 9f1b32f40d Add prod nostrnests.com audio transmission diagnostic tests
Four progressively-narrower JVM tests that drive the production
connectNestsSpeaker / connectNestsListener / OkHttpNestsClient /
QuicWebTransportFactory / NestMoqLiteBroadcaster code paths against
the real moq.nostrnests.com + moq-auth.nostrnests.com infrastructure
(no Docker harness, no Nostr events) so we can isolate why audio is
not flowing between two real users:

  1. auth_minting_works_for_publish_and_listen_paths
  2. same_user_speaker_and_listener_round_trip
  3. two_users_speaker_publishes_listener_subscribes
  4. sustained_real_time_cadence_two_users (~2s @ 20ms cadence)

Skipped by default; opt in with -DnestsProd=true and (optionally)
override URLs via -DnestsProdEndpoint=... / -DnestsProdAuth=...
2026-05-01 01:08:14 +00:00
Claude d00e406587 test(audio-rooms): -DnestsInteropExternal bypasses Docker
Adds a "bring your own stack" path to NostrNestsHarness so the interop
tests can run without a Docker daemon. With `-DnestsInteropExternal=true`:

  - Skip the `docker compose up` that builds + boots
    moq-auth + moq-relay
  - Port-probe + /health-check the same 8090 / 4443 endpoints the
    Docker path uses
  - close() is a no-op — the caller owns the lifecycle

Useful in two situations:
  1. Sandboxes / restricted CI without a Docker daemon (just run
     `cargo install moq-relay` + `node moq-auth/dist/index.js`
     yourself, then run gradle with the flag)
  2. Fast iteration — the Docker path takes ~30 s to compile
     moq-relay on first run; with this, you keep both processes
     alive across many test invocations

Forward `nestsInteropExternal` (and `nestsInteropDebug`,
`nestsInteropMoqRev`) through the Test task so the property reaches
test workers; without that, the gate stays off in the worker JVM.

Also: `assertSpeakerReached` / `assertListenerReached` now log a "✘"
checkpoint with the rich state description before calling fail().
JUnit captures the assertion message in a separate section, but the
"standard output" tab is what most people read first when scanning
for a cause — so the chained-cause string now lands in both places.
2026-04-26 20:23:45 +00:00
Claude 76b772ab41 test(audio-rooms): forward -DnestsInterop to test workers
Without this, `-DnestsInterop=true` on the Gradle command line was
only set on the Gradle JVM, not on the test executor JVM that
NostrNestsHarness.isEnabled() reads via System.getProperty. Result:
every interop test silently skipped no matter how the run was
invoked.

Also forwards `nestsInteropRev` (used by the harness to pin the
nostrnests revision in the cache).

Verified with `./gradlew :nestsClient:jvmTest --tests
NostrNestsAuthInteropTest -DnestsInterop=true` that the harness
now actually runs (the run only fails inside the test because the
sandbox blocks outbound traffic to GHCR / Docker Hub — both
registries return 503 at the auth-token endpoint, so we can't
pull strfry / build moq images here. On a network-enabled host
the harness will bring up the stack normally).
2026-04-26 18:28:11 +00:00
Claude 2d541c6fd4 feat(quic): Phase A — module foundations
Create the new :quic Gradle module (KMP, api(project(":quartz"))) and migrate
the QUIC varint codec out of :nestsClient where it was incidentally living.
Add the connection-ID, packet-number-space, and UDP socket primitives that
the rest of the QUIC client will build on.

Layer-by-layer plan in docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md.

- New :quic module wired into settings.gradle, with commonMain + jvmAndroid
  source sets mirroring :quartz's structure.
- Varint moves from com.vitorpamplona.nestsclient.moq to com.vitorpamplona.quic;
  MoqBuffer/MoqCodec updated to import the new path.
- ConnectionId enforces the 0..20 byte length range and ships a randomizer
  backed by Quartz's RandomInstance.
- PacketNumberSpaceState tracks per-space outbound allocation + largest-received
  tracking, and implements the RFC 9000 §A.3 truncated-PN decode formula plus
  the §17.1 minimum encode-length picker.
- UdpSocket is an expect class with a connected DatagramChannel actual on
  jvmAndroid using Dispatchers.IO (no Selector — one socket per connection).

All 12 tests pass on jvmTest. RFC 9000 §A.1 varint vectors and §A.3 truncated-PN
vector match bit-for-bit.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 17:19:02 +00:00
Claude c07f7baa14 docs(nestsClient): Phase 3b-2 Kwik integration plan in stub
Phase 3b-2 attempt. The honest version: I do not have network access
to verify Kwik's current Maven coordinates or test against a live
nests server, so committing speculative QUIC + Extended CONNECT code
risks breaking the build for everyone else without giving us
audible-audio-on-device confidence in return.

What I did instead: expanded the docstring on KwikWebTransportFactory
into a full integration playbook that the next person picking this
up can execute directly. It covers:

- Maven coordinates to verify (`tech.kwik:kwik-core` + `tech.kwik:flupke`,
  with `net.luminis.quic:kwik` as the legacy fallback group).
- The exact `gradle/libs.versions.toml` + `nestsClient/build.gradle.kts`
  edits to drop in once coords are confirmed.
- Step-by-step handshake sequence with the right HTTP/3 setting IDs:
  * SETTINGS_ENABLE_CONNECT_PROTOCOL=1 (RFC 8441, 0x08)
  * SETTINGS_ENABLE_WEBTRANSPORT=1 (WT-H3 draft, 0x2b603742)
  * SETTINGS_H3_DATAGRAM=1 (RFC 9297, 0x33)
- Extended CONNECT pseudo-header set, including the legacy
  `sec-webtransport-http3-draft02 = 1` for older server compat.
- WebTransport stream-type prefix bytes (0x41 for client bidi, 0x54
  for client uni) + WT capsule type for graceful close (0x2843).
- Test strategy: validate against local nests-rs first, then the
  production `nostrnests.com`.
- Open questions (Flupke Extended CONNECT maturity, draft churn,
  dev-only self-signed-cert support, Android API surface).

Behavior unchanged: connect() still throws WebTransportException
with Kind.NotImplemented and a message pointing at this docstring.

When the real implementation lands, no upstream caller needs to
change — the AudioRoomConnectionViewModel from Phase 3d-3 will
auto-light up the connection chip from "Failed: NotImplemented" to
"Connected" and start producing audio through the Phase 3d-1
pipeline that's already wired.

https://claude.ai/code/session_013nVLALALKaHVgHm9u5Cg8D
2026-04-22 07:41:56 +00:00
Claude 933b522273 feat(nestsClient): NIP-98 auth + nests room-info client
Phase 3a of the Clubhouse/nests integration. Adds a new KMP module
`nestsClient` (Android + JVM targets) that owns the HTTP control plane
for talking to a nests audio-room backend. No transport/audio yet —
that arrives in 3b with WebTransport + MoQ.

Surface:
- `NestsAuth.header(signer, url, method)` signs a kind 27235 event via
  Quartz's existing HTTPAuthorizationEvent and returns a ready-to-use
  `Authorization: Nostr <base64>` header value.
- `NestsRoomInfo` data class + tolerant JSON parser (ignores unknown
  fields so newer nests server revisions don't break older clients).
- `NestsClient.resolveRoom(serviceBase, roomId, signer)` calls
  `<serviceBase>/<roomId>` with the signed NIP-98 header and returns
  the MoQ endpoint + token the audio layer will need.
- `OkHttpNestsClient` (jvmAndroid) is the default implementation
  shared between Android and desktop JVM.

Tests:
- `NestsRoomInfoTest` (commonTest) covers full/minimal payloads,
  unknown-field tolerance, missing-endpoint rejection, and URL
  construction edge cases.
- `NestsAuthTest` (jvmTest) signs a real 27235 event, decodes the
  base64 back through Quartz's JacksonMapper, and asserts the
  signature verifies and the url+method tags bind correctly.

https://claude.ai/code/session_013nVLALALKaHVgHm9u5Cg8D
2026-04-22 02:58:24 +00:00