test(nests): T16 Phase 3 — udp-loss-shim + I9 + I5 hot-swap

- **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
This commit is contained in:
Claude
2026-05-06 22:39:33 +00:00
parent 274334d14c
commit a32b6d6248
4 changed files with 361 additions and 29 deletions
+40 -7
View File
@@ -555,7 +555,7 @@ checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4"
dependencies = [
"getrandom 0.3.4",
"libm",
"rand",
"rand 0.9.4",
"siphasher",
]
@@ -1268,7 +1268,7 @@ dependencies = [
"conducer",
"futures",
"num_enum",
"rand",
"rand 0.9.4",
"serde",
"thiserror 2.0.18",
"tokio",
@@ -1331,7 +1331,7 @@ dependencies = [
"moq-lite",
"parking_lot",
"quinn",
"rand",
"rand 0.9.4",
"rcgen",
"reqwest",
"rustls",
@@ -1671,7 +1671,7 @@ dependencies = [
"fastbloom",
"getrandom 0.3.4",
"lru-slab",
"rand",
"rand 0.9.4",
"ring",
"rustc-hash",
"rustls",
@@ -1713,14 +1713,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
"rand_chacha",
"rand_core",
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
@@ -1730,7 +1751,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
@@ -2639,7 +2669,10 @@ version = "0.0.1"
dependencies = [
"anyhow",
"clap",
"rand 0.8.6",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]