docs(quic-plan): lock in new top-level :quic module (Option A)

Updates the Architecture section to reflect the decision to house the
pure-Kotlin QUIC + HTTP/3 + WebTransport code in a new top-level
Gradle module `:quic`, sibling to `:quartz`, `:commons`, `:nestsClient`.

Key changes:

- Module placement: new KMP module `:quic` with commonMain +
  jvmAndroid + jvmMain + androidMain source sets (mirrors Quartz).
  `:quic` takes `api(project(":quartz"))` so all crypto primitives
  are in-scope without re-export.
- settings.gradle delta spelled out.
- Package layout shifts from
  `nestsClient/src/jvmAndroid/...transport/quic/` to
  `:quic/src/commonMain/com/vitorpamplona/quic/`, with UDP
  socket-specific bits in `jvmAndroid/`.
- Varint.kt migration called out: moves from
  `nestsClient/moq/Varint.kt` to `:quic` at
  `com.vitorpamplona.quic.Varint`. Mechanical, one commit.
- Rename KwikWebTransportFactory stub → QuicWebTransportFactory
  (real), living in `:quic` but implementing `:nestsClient`'s
  existing `WebTransportFactory` interface. AudioRoomConnectionViewModel
  changes one ctor call.
- Rationale section documenting why Option A beats putting it in
  `:nestsClient` (single responsibility / reusability / security
  boundary / test isolation / build graph / charter fit).
- Phase A now explicitly includes the module-creation +
  Varint-migration step; test suite must stay green across the move.

Timeline unchanged (17-19 weeks). Dependency story unchanged
(no external libs; everything piggybacks on Quartz).

https://claude.ai/code/session_013nVLALALKaHVgHm9u5Cg8D
This commit is contained in:
Claude
2026-04-22 13:20:33 +00:00
parent 1ff951dbb4
commit 2b44e9b06b
@@ -78,32 +78,94 @@ the TLS 1.3 client state machine ourselves using those primitives.
## Architecture
```
nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/
└── transport/
├── quic/ ← new: pure-Kotlin QUIC client
│ ├── crypto/ ← BC adapter (TLS over CRYPTO frames)
│ ├── packet/ ← long/short-header encode/decode + protection
│ ├── frame/ ← STREAM, ACK, CRYPTO, MAX_*, CONNECTION_CLOSE …
│ ├── stream/ ← per-stream send/recv buffers, flow control
│ ├── recovery/ ← loss detection, PTO, NewReno
│ ├── connection/ ← QuicConnection state machine
│ └── transport/ ← UDP socket + datagram pump
├── http3/ ← new: HTTP/3 client
│ ├── frame/ ← DATA, HEADERS, SETTINGS, GOAWAY, MAX_PUSH_ID
│ ├── qpack/ ← static + dynamic tables, Huffman
│ └── Http3Connection.kt ← request/response state machine
└── webtransport/ ← new: WT layer
├── ExtendedConnect.kt ← :protocol = webtransport
├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni) prefix bytes
├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843, etc.
└── KotlinWebTransportFactory.kt ← implements existing WebTransportFactory
**Module placement:** a new top-level Gradle module `:quic` sibling to
`:quartz`, `:commons`, `:nestsClient`. It's a KMP module (commonMain + an
`androidMain` + `jvmMain` pair via a shared `jvmAndroid` source set, mirroring
Quartz's pattern) so it's reusable for future non-Nests consumers
(Nostr-over-QUIC relays, desktop audio, iOS later). `:quic` takes
`api(project(":quartz"))` so the crypto primitives are available without
re-exporting. `:nestsClient` drops its `transport/` subpackage and declares
`implementation(project(":quic"))` instead.
**settings.gradle delta:**
```groovy
include ':quartz'
include ':commons'
include ':ammolite'
include ':quic' // NEW — pure-Kotlin QUIC + HTTP/3 + WebTransport
include ':nestsClient' // gains `implementation project(':quic')`
include ':desktopApp'
include ':cli'
```
Naming note: the existing `KwikWebTransportFactory.kt` stub becomes
`KotlinWebTransportFactory.kt` once real. The current ViewModel wiring
(`AudioRoomConnectionViewModel`) imports the class directly, so the rename is
one change with no protocol impact.
**Package layout under `:quic`:**
```
quic/
├── build.gradle.kts ← KMP module, api(project(":quartz"))
└── src/
├── commonMain/kotlin/com/vitorpamplona/quic/
│ ├── Varint.kt ← migrated from nestsClient/moq/Varint.kt
│ ├── crypto/ ← HkdfExpandLabel + thin helpers
│ ├── tls/ ← TLS 1.3 client state machine
│ ├── packet/ ← long/short-header codec + protection
│ ├── frame/ ← CRYPTO, STREAM, ACK, MAX_*, CONNECTION_CLOSE…
│ ├── stream/ ← per-stream buffers + flow control
│ ├── recovery/ ← loss detection + PTO + NewReno
│ ├── connection/ ← QuicConnection state machine
│ ├── http3/ ← RFC 9114 client
│ ├── qpack/ ← RFC 9204 encoder/decoder
│ └── webtransport/
│ ├── ExtendedConnect.kt
│ ├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni)
│ ├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843
│ └── QuicWebTransportFactory.kt ← implements nestsClient's WebTransportFactory
├── jvmAndroid/kotlin/com/vitorpamplona/quic/
│ └── transport/
│ └── UdpSocket.kt ← DatagramChannel + suspend wrapper
├── commonTest/
└── jvmTest/ ← RFC 8448 / RFC 9001 vector tests
```
**Migrations when `:quic` lands:**
- `nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/Varint.kt`
moves to `:quic` at `com.vitorpamplona.quic.Varint`. The existing MoQ codec
(Phase 3c) imports the new path. Mechanical change — one commit.
- The `nestsClient/transport/` package (`WebTransportSession` +
`FakeWebTransport` + `KwikWebTransportFactory` stub) stays in `:nestsClient`
because that's the seam `:nestsClient`'s audio pipeline talks to. The real
implementation `QuicWebTransportFactory` lives in `:quic` and implements
`nestsClient.transport.WebTransportFactory``:nestsClient` swaps its
constructor call from `KwikWebTransportFactory()` to
`QuicWebTransportFactory()`, nothing else changes upstream.
**Why a dedicated module (Option A):**
- **Single responsibility.** `:quic` is a transport library; `:nestsClient`
is an audio-room client. They have nothing conceptually in common beyond
nests happens to run over WebTransport.
- **Reusability.** Future Nostr-over-QUIC relay work, any Kotlin project that
wants MoQ, desktop / iOS targets all pull `:quic` directly. Potentially
extractable to an open-source library later — pure-Kotlin QUIC barely
exists in the JVM ecosystem, so this would be genuinely valuable beyond
Amethyst.
- **Security boundary.** QUIC + TLS is the code you want smallest, isolated,
and auditable without reading audio-room code around it. A dedicated module
enforces the boundary.
- **Test isolation.** `:quic` tests run on `commonTest` / `jvmTest` — fast
feedback. They never touch `:nestsClient`'s fakes.
- **Build graph.** `:quic` has no Android-framework dependencies (no
`MediaCodec`, no `AudioTrack`), so it compiles clean as a pure KMP lib and
targets iOS / desktop trivially when those arrive.
- **Charter.** Quartz is labeled "Nostr protocol only, no UI" in the project's
CLAUDE.md. QUIC isn't Nostr; it doesn't fit Quartz's charter.
Naming note: the existing `KwikWebTransportFactory.kt` stub in `:nestsClient`
is renamed to reference the real implementation `QuicWebTransportFactory` in
`:quic`. `AudioRoomConnectionViewModel` updates its constructor call in one
line, no protocol impact.
## Phase breakdown (one developer, full-time estimates)
@@ -111,11 +173,17 @@ Each phase ends with green commonTest tests that exercise its layer in isolation
plus an integration test against the next layer up.
### Phase A — Foundations (1 week)
- UDP socket abstraction (`DatagramChannel`, suspend wrapper, single-receive loop).
- Create new `:quic` module (KMP, `api(project(":quartz"))`), register in
`settings.gradle`, port `Varint.kt` from `:nestsClient` and update MoQ
imports, add `:nestsClient``implementation(project(":quic"))` wire.
- UDP socket abstraction (`DatagramChannel`, suspend wrapper, single-receive
loop) in `jvmAndroid`.
- Connection ID generation (cryptographically random, 8-byte source IDs).
- Packet number space tracking (Initial, Handshake, Application).
- Largest-acked / next-pn tracking per space.
- **Tests:** varint already done; add CID + packet-number-space unit tests.
- **Tests:** varint tests migrate with the file; add CID + packet-number-space
unit tests. Full MoQ + nestsClient test suite stays green after the
Varint migration.
### Phase B — TLS 1.3 client state machine (3 weeks)