335a337283
Lite-03 audit L1: completes the moq-lite version surface so the
session speaks the wire-format version the server picks via
WebTransport ALPN negotiation. Pre-fix the codec hardcoded Lite-03;
advertising `moq-lite-04` was a footgun because a Lite-04-preferring
relay would desync on the very first Announce exchange.
Verified diff between Lite-03 and Lite-04 by WebFetching kixelated's
Rust reference (`rs/moq-lite/src/lite/{announce,subscribe,probe}.rs`,
`rs/moq-lite/src/version.rs`, `rs/moq-lite/src/model/origin.rs`):
exactly three fields differ; everything else is byte-identical.
- `Announce.hops`: Lite-03 wire = single varint count (the spec
explicitly fills with `Origin::UNKNOWN` placeholders on decode);
Lite-04 wire = `varint(count) + count × varint(originId)` (the
`OriginList`). MAX_HOPS = 32.
- `AnnouncePlease.excludeHop`: Lite-03 absent; Lite-04 single
varint after `prefix`. Sentinel `0` = no exclusion.
- `Probe.rtt`: Lite-03 absent; Lite-04 single varint after
`bitrate`. Sentinel `0` = unknown (decoded as null). Outgoing
`Some(0)` is clamped to `Some(1)` to avoid colliding with the
sentinel — mirrors kixelated's `encode_msg` clamp.
New surface:
- `MoqLiteVersion` enum (LITE_03, LITE_04) with `fromAlpn` lookup.
- `MoqLiteCodec.{encode,decode}{AnnouncePlease,Announce,Probe}`
take `version: MoqLiteVersion = LITE_03` parameter, branch on
it. Default LITE_03 keeps existing call sites compiling.
- `MoqLiteSession.client(transport, scope, version)` carries the
version through every codec invocation.
- `MoqLiteAnnouncePlease.excludeHop: Long = 0L` (default).
- `MoqLiteAnnounce.hops: List<Long>` (was `Long`) — list of
origin IDs, bounded to MAX_HOPS=32. Existing call sites
migrate `hops = 0L` → `hops = emptyList()`, `hops = 7L` →
`hops = List(7) { 0L }`.
- `MoqLiteProbe.rtt: Long? = null` (default).
Negotiation:
- `WebTransportSession.negotiatedSubProtocol: String?` exposes
the server's `wt-protocol` selection. `QuicWebTransportFactory`
parses the response HEADERS, extracts the SF-string from
`wt-protocol`, and threads it into `QuicWebTransportSession`.
`FakeWebTransport.pair(negotiatedSubProtocol = …)` lets tests
drive the same path.
- Default advertised list now `[moq-lite-04, moq-lite-03]` (was
`[moq-lite-03]`). Lite-04 sits first to match kixelated's
preference; servers that don't support it fall back to
Lite-03 cleanly.
- `connectNestsListener` / `connectNestsSpeaker` resolve the
version via `resolveMoqLiteVersion(webTransport.negotiatedSubProtocol)`
and pass it to `MoqLiteSession.client(...)`. Falls back to
Lite-03 when the server doesn't echo `wt-protocol` (older
deployments) or echoes something unrecognised (forward-compat).
- New `MOQ_LITE_04_VERSION = 0x6D71_6C04L` synthetic version
code; `versionCode(version)` mapping function.
Regression tests (all green):
- `MoqLiteCodecTest.announcePlease_lite04_round_trips_excludeHop`
- `MoqLiteCodecTest.announcePlease_lite03_omits_excludeHop_on_wire`
- `MoqLiteCodecTest.announce_lite04_round_trips_full_origin_list`
- `MoqLiteCodecTest.announce_lite03_drops_origin_ids_keeps_count`
- `MoqLiteCodecTest.announce_decoder_rejects_oversize_hop_count`
(MAX_HOPS bounds check)
- `MoqLiteCodecTest.probe_lite04_round_trips_rtt`
- `MoqLiteCodecTest.probe_lite04_clamps_some_zero_to_one_to_avoid_unknown_sentinel`
- `MoqLiteCodecTest.probe_lite04_decodes_zero_rtt_as_null`
- `MoqLiteCodecTest.probe_lite03_wire_omits_rtt`
- `MoqLiteSessionTest.lite04_announce_round_trips_full_origin_list_through_session`
(end-to-end Lite-04 session)
Audit doc: nestsClient/plans/2026-05-09-moq-lite-rfc-compliance.md (L1).
https://claude.ai/code/session_012TGfo99Ugz7fcCPv85a8Tq