docs(quic+nestsClient): post-implementation status + audio-rooms completion plan

Two new module-local plan docs (per CLAUDE.md's "plans live in the owning
module" rule) and a sweep of stale inline phase references.

quic/plans/2026-04-26-quic-stack-status.md:
  Post-mortem of the original docs/plans/2026-04-22 plan. Documents
  what shipped vs what was estimated, the actual package layout (~8.5k
  LoC, 39 test files, 5 audit rounds), the crypto delegation surface
  (Quartz only — no BouncyCastle, no JNI), interop verification status
  (aioquic + picoquic; nests not yet), and known deferred items
  (STREAM retransmit, Initial-key discard, etc.).

nestsClient/plans/2026-04-26-audio-rooms-completion.md:
  Punch list to ship audio rooms end-to-end:
    M1 Listener wire-up in Amethyst UI
    M2 Multi-speaker audience UX
    M3 Foreground service for backgrounded playback
    M4 Manual interop pass against nostrnests.com
    M5 MoQ publisher path (ANNOUNCE / TrackPublisher)
    M6 Capture → encode → publish pipeline
    M7 NestsSpeaker API
    M8 App polish (reconnect, leave cleanup)
    M9 Foreground service for speakers
  ~6 weeks for full audio rooms; ~2 weeks for listener-only MVP.

Inline doc cleanup:
  * Removed "Phase 3a/3c-1/3c-2/3c-3" / "Phase B/C/D-K/L" references
    from active code; replaced with "today" or pointers to the
    completion plan
  * Removed "Kwik-based stub" references; QuicWebTransportFactory and
    surrounding docs now describe :quic as the production path
  * TlsClient header reflects non-null certificateValidator + the
    JdkCertificateValidator / PermissiveCertificateValidator split
  * SendBuffer header documents the best-effort no-retransmit mode
    explicitly (was hidden behind a "Phase L will fix this" note)
  * MoqMessage / MoqObject / MoqSession reflect listener-side as
    shipped + publisher-side as Phase M5

CLAUDE.md:
  * Module list now includes :quic and :nestsClient (was 5 modules,
    now 7)
  * Architecture diagram + sharing philosophy explain what each new
    module owns

No production behaviour changes; doc + comment-only edits. Tests green.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
This commit is contained in:
Claude
2026-04-26 01:38:48 +00:00
parent 7f05fd6e2a
commit 4338e5e6c4
14 changed files with 529 additions and 45 deletions
@@ -23,12 +23,15 @@ package com.vitorpamplona.nestsclient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
/**
* High-level entry point for talking to a nests-compatible audio-room backend.
* HTTP control-plane entry point for talking to a nests-compatible
* audio-room backend. Resolves a room's MoQ endpoint + bearer token via
* NIP-98 auth — that's the only HTTP step before the WebTransport / MoQ
* session takes over.
*
* Phase 3a only exposes the HTTP control plane — resolving a room's MoQ
* endpoint + token via NIP-98 auth. Phase 3b will add the WebTransport/MoQ
* transport on top, keeping this interface stable so audio-room callers only
* depend on [resolveRoom] for the control-plane step.
* The full connect orchestration (HTTP → WebTransport → MoQ → audio) lives
* in [NestsListener] / `connectNestsListener`; this interface stays
* narrowly focused on the control plane so testing the audio path doesn't
* require an HTTP fake.
*/
interface NestsClient {
/**
@@ -74,7 +74,7 @@ sealed class NestsListenerState {
/** Calling `<service>/<roomId>` to obtain the MoQ endpoint + token. */
ResolvingRoom,
/** Opening the WebTransport (Kwik QUIC + Extended CONNECT). */
/** Opening the WebTransport ([:quic] + Extended CONNECT). */
OpeningTransport,
/** Running the MoQ CLIENT_SETUP / SERVER_SETUP exchange. */
@@ -27,8 +27,11 @@ package com.vitorpamplona.nestsclient.moq
*
* message_type (varint) | message_length (varint) | payload...
*
* This phase (3c-1) covers only the setup handshake. SUBSCRIBE / ANNOUNCE /
* OBJECT messages arrive in Phase 3c-2.
* Listener-side messages (CLIENT/SERVER_SETUP, SUBSCRIBE, SUBSCRIBE_OK /
* SUBSCRIBE_ERROR, UNSUBSCRIBE) are implemented. Publisher-side messages
* (ANNOUNCE / ANNOUNCE_OK / SUBSCRIBE-receiving / SUBSCRIBE_DONE) are not
* yet implemented; see `nestsClient/plans/2026-04-26-audio-rooms-completion.md`
* (Phase M5).
*/
sealed class MoqMessage {
abstract val type: MoqMessageType
@@ -164,7 +167,7 @@ enum class SubscribeFilter(
/**
* SUBSCRIBE (0x03): client asks a publisher to forward objects belonging to a
* (namespace, track) pair. Phase 3c-2 supports only the LatestGroup /
* (namespace, track) pair. Today the codec supports only the LatestGroup /
* LatestObject filters — absolute-range variants add extra wire fields the
* codec will grow in a follow-up if nests ever needs them.
*/
@@ -182,7 +185,7 @@ data class Subscribe(
init {
require(filter == SubscribeFilter.LatestGroup || filter == SubscribeFilter.LatestObject) {
"Phase 3c-2 only supports LatestGroup / LatestObject filters, got $filter"
"only LatestGroup / LatestObject filters supported, got $filter"
}
require(subscriberPriority in 0..255) { "subscriber_priority must fit in a byte" }
require(groupOrder in 0..255) { "group_order must fit in a byte" }
@@ -32,7 +32,9 @@ package com.vitorpamplona.nestsclient.moq
* 2. STREAM_HEADER_SUBGROUP — multiple objects per uni stream, reliable.
* 3. FETCH_HEADER — historical objects over a bidi stream.
*
* Phase 3c-2 covers only (1). Stream-delivered objects arrive in Phase 3c-3.
* Today the listener path implements only (1) — OBJECT_DATAGRAM — which is
* what nests uses for live audio. (2) and (3) are reserved for future
* stream-delivered media; see the audio-rooms completion plan.
*/
data class MoqObject(
val trackAlias: Long,
@@ -345,9 +345,11 @@ class MoqSession private constructor(
}
else -> {
// Other control messages (SETUP echoes, future ANNOUNCE/etc.)
// are silently dropped at this layer; Phase 3c-3 only needs the
// subscribe lifecycle.
// Other control messages (echoed SETUP, future ANNOUNCE +
// SUBSCRIBE-receiving for the publisher path, etc.) are
// silently dropped — the listener path only needs the
// subscribe lifecycle. Publisher-side routing is Phase M5
// in nestsClient/plans/2026-04-26-audio-rooms-completion.md.
}
}
}
@@ -32,8 +32,8 @@ import kotlinx.coroutines.sync.withLock
*
* A pair of fakes is connected via [pair] — anything written on one side is
* delivered on the other. This deliberately simulates *success* semantics
* only (no packet loss, no congestion); the real Kwik-backed transport will
* exercise those codepaths separately.
* only (no packet loss, no congestion); the real `:quic`-backed transport
* exercises those codepaths via its own pipe + interop tests.
*
* [incomingDatagrams] and [FakeBidiStream.incoming] use [receiveAsFlow]
* semantics: a `take(1)` / `first()` followed by a long-running `collect`
@@ -26,13 +26,14 @@ import kotlinx.coroutines.flow.Flow
* Platform-agnostic WebTransport session, as produced by a successful Extended
* CONNECT (RFC 9220) handshake.
*
* The MoQ layer (Phase 3c) talks to this interface; the real Kwik-based
* implementation sits behind [WebTransportFactory] in jvmAndroid. Keeping this
* abstract lets us:
* - unit-test the MoQ framing layer with an in-memory fake,
* - swap transport implementations (Cronet on Android, a browser-backed
* WebView bridge as a contingency, Kwik on JVM desktop) without touching
* audio/UI code.
* The MoQ layer talks to this interface; the production implementation is
* [com.vitorpamplona.nestsclient.transport.QuicWebTransportFactory] which
* sits on top of the pure-Kotlin `:quic` stack. Keeping this abstract lets
* us:
* - unit-test the MoQ framing layer with [FakeWebTransport],
* - swap transport implementations (a different QUIC backend, or a
* browser-backed bridge as a contingency) without touching audio/UI
* code.
*
* Lifecycle: the session is opened via [WebTransportFactory.connect] and must
* be closed with [close] to release the underlying QUIC connection.
@@ -91,7 +92,7 @@ interface WebTransportWriteStream {
*
* [authority] is the `host:port` of the WT server, [path] is the URL path
* (nests defaults to `/moq`), and [bearerToken] is typically the token
* returned by the `/api/v1/nests/<roomId>` HTTP call in Phase 3a.
* returned by the `/api/v1/nests/<roomId>` HTTP call (see [NestsClient]).
*/
interface WebTransportFactory {
suspend fun connect(