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 ## Architecture
``` **Module placement:** a new top-level Gradle module `:quic` sibling to
nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/ `:quartz`, `:commons`, `:nestsClient`. It's a KMP module (commonMain + an
└── transport/ `androidMain` + `jvmMain` pair via a shared `jvmAndroid` source set, mirroring
├── quic/ ← new: pure-Kotlin QUIC client Quartz's pattern) so it's reusable for future non-Nests consumers
│ ├── crypto/ ← BC adapter (TLS over CRYPTO frames) (Nostr-over-QUIC relays, desktop audio, iOS later). `:quic` takes
│ ├── packet/ ← long/short-header encode/decode + protection `api(project(":quartz"))` so the crypto primitives are available without
│ ├── frame/ ← STREAM, ACK, CRYPTO, MAX_*, CONNECTION_CLOSE … re-exporting. `:nestsClient` drops its `transport/` subpackage and declares
│ ├── stream/ ← per-stream send/recv buffers, flow control `implementation(project(":quic"))` instead.
│ ├── recovery/ ← loss detection, PTO, NewReno
│ ├── connection/ ← QuicConnection state machine **settings.gradle delta:**
│ └── transport/ ← UDP socket + datagram pump
├── http3/ ← new: HTTP/3 client ```groovy
│ ├── frame/ ← DATA, HEADERS, SETTINGS, GOAWAY, MAX_PUSH_ID include ':quartz'
│ ├── qpack/ ← static + dynamic tables, Huffman include ':commons'
│ └── Http3Connection.kt ← request/response state machine include ':ammolite'
└── webtransport/ ← new: WT layer include ':quic' // NEW — pure-Kotlin QUIC + HTTP/3 + WebTransport
├── ExtendedConnect.kt ← :protocol = webtransport include ':nestsClient' // gains `implementation project(':quic')`
├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni) prefix bytes include ':desktopApp'
├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843, etc. include ':cli'
└── KotlinWebTransportFactory.kt ← implements existing WebTransportFactory
``` ```
Naming note: the existing `KwikWebTransportFactory.kt` stub becomes **Package layout under `:quic`:**
`KotlinWebTransportFactory.kt` once real. The current ViewModel wiring
(`AudioRoomConnectionViewModel`) imports the class directly, so the rename is ```
one change with no protocol impact. 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) ## 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. plus an integration test against the next layer up.
### Phase A — Foundations (1 week) ### 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). - Connection ID generation (cryptographically random, 8-byte source IDs).
- Packet number space tracking (Initial, Handshake, Application). - Packet number space tracking (Initial, Handshake, Application).
- Largest-acked / next-pn tracking per space. - 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) ### Phase B — TLS 1.3 client state machine (3 weeks)