- **udp-loss-shim** body: tokio UDP loopback that drops a
configurable fraction of datagrams. Single-tenant (one client
at a time) — moq-lite is connection-multiplexed by source port,
so 1:1 forwarding is enough.
- **I9** (`packet_loss_1pct_does_not_kill_audio`): routes the
Kotlin speaker through the shim with `--loss-rate 0.01`;
asserts the decoded PCM has ≥ 80% expected sample count and
the 440 Hz tone survives. moq-lite groups are reliable streams
so retransmits absorb the loss.
- **I5** (`speaker_hot_swap_does_not_crash`): drives the
reconnecting-speaker with `tokenRefreshAfterMs = 2_500` to
force a hot-swap mid-broadcast. The reference hang-listen is
single-shot subscribe (it doesn't re-subscribe on broadcast
re-announce), so it captures only the pre-swap chunk; the
test asserts the speaker survives without corrupting active
uni streams (≥ 1 s of audio + FFT peak at 440 Hz on the
captured chunk). The "no audible gap" property the spec
calls for is an Amethyst-listener concern (handles re-announce
transparently); a Phase 3 follow-up would exercise that path
end-to-end through `connectNestsListener`.
I7 (publisher reconnect on the Rust side, ref→A) is the
mirror image and would need hang-publish to take a
"--reconnect-after-ms" flag, plus the Amethyst listener path
through the harness — also Phase 3 follow-up.
https://claude.ai/code/session_01ERJPUYfdLPwZ99pr5EcEcV
Lands the load-bearing infra for the hang/Rust cross-stack interop
test plan (nestsClient/plans/2026-05-06-cross-stack-interop-test.md):
the cargo workspace, Gradle wiring to install moq-relay /
moq-token-cli + build sidecars, the Kotlin harness that boots a
real moq-relay subprocess on a random ephemeral UDP port with
self-signed TLS and unrestricted public auth, plus the signal-domain
PCM assertion library and deterministic sine-wave AudioCapture that
Phase 2 scenarios will assert against.
Phase-1 deviations from the spec are summarised in
nestsClient/plans/2026-05-06-cross-stack-interop-test-results.md
— notably: --auth-public "" instead of the JWT minter (no
security-relevant difference for wire-format scenarios), --tls-generate
instead of in-Kotlin cert generation, cargo-install of upstream
binaries instead of an embedded kixelated/moq checkout, and
hang-listen / hang-publish / udp-loss-shim shipped as Phase-1 stubs
that compile + accept --flags but do nothing. Phase 2 fills those
in (and adds JVM Opus encoder/decoder).
Verified green via:
./gradlew :nestsClient:jvmTest \
--tests "com.vitorpamplona.nestsclient.interop.native.*" \
--tests "com.vitorpamplona.nestsclient.audio.PcmAssertionsTest" \
-DnestsHangInterop=true
Default mode (no flag) cleanly skips the harness-dependent test.
https://claude.ai/code/session_01ERJPUYfdLPwZ99pr5EcEcV