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:
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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`
|
||||
|
||||
+9
-8
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user