docs(nests): refresh all T16 + cliff plans to current state

8 plan files updated to reflect what's actually shipped vs. what
each doc said before:

- 2026-05-06-cross-stack-interop-test.md: 'Spec — ready to
  implement' → 'Implemented and merged' with pointers to results,
  gap matrix, and closure roadmap.

- 2026-05-06-cross-stack-interop-test-results.md: scenario
  inventory updated to show all branches merged into
  claude/cross-stack-interop-test-XAbYB; suite-flake caveats
  flagged with cross-refs to the routing investigation; file
  inventory now lists BrowserInteropTest + PlaywrightDriver +
  the moved nestsClient/tests/browser-interop/ tree (no more 'in
  sister branches not yet merged' section).

- 2026-05-06-cross-stack-interop-test-gap-matrix.md: T8/T10/T11/
  T12/T13 all show hang  + browser ; I13/I14 'NOT YET LANDED'
  callouts removed; the 'in sister branch' qualifier on T13 / I7
  / I6 dropped; coverage-holes section replaced with caveat
  pointers to the routing investigation.

- 2026-05-06-i4-stereo-cross-stack-scenario.md: 'Spec — ready for
  implementation' → 'Landed' with PR #2755 + the three test
  scenarios that ship the assertion.

- 2026-05-06-phase4-browser-harness.md: 'Spec — ready for
  implementation' → 'Landed', with notes on how the implementation
  diverged (used Container.Legacy.Consumer not @moq/watch; cert
  pinning via serverCertificateHashes; I2/I3/I4/I13/I14 originally
  deferred but later landed).

- 2026-05-06-phase4-browser-harness-results.md: status updated to
  reflect 4.D CI removed per maintainer ask; layout note for
  the nestsClient/tests/browser-interop/ relocation; full
  scenario list reconciled with results doc.

- 2026-05-07-late-join-catalog-flake-investigation.md: status
  updated to mention 00f6cba31 + 207057374 were reverted, and
  link to 2026-05-07-moq-relay-routing-investigation.md as the
  action plan. Adds note that the flake also affects four
  browser-tier scenarios after Browser I7 landed.

- 2026-05-01-quic-stream-cliff-investigation.md: adds an HCgOY-
  override banner — recommendation 2 (framesPerGroup = 5) was
  superseded 4 days later by HCgOY field tests landing the prod
  default at 50. Cross-refs the reconciliation + production-
  rerun plans. Open follow-up #3 ('reset to 1') updated with
  the same context.

No code or test changes.
This commit is contained in:
Claude
2026-05-07 14:52:21 +00:00
parent bd7b166fda
commit 589ced580c
8 changed files with 183 additions and 67 deletions
@@ -1,5 +1,22 @@
# QUIC stream cliff against nostrnests.com — investigation plan # QUIC stream cliff against nostrnests.com — investigation plan
**⚠️ Recommendation 2 (cadence tuning) was overridden 4 days
later.** Subsequent two-phone production tests on
`claude/fix-nests-audio-receiver-HCgOY` (commit `a36ccb569`,
2026-05-05) showed `framesPerGroup = 5` itself cliffs after ~13 s
on the same production deployment, just slower than `framesPerGroup
= 1`'s 3 s. Production now defaults to `framesPerGroup = 50`
(1 stream/sec). The interop tests still pin `5` because the local
`moq-relay 0.10.25 --auth-public ""` minimal setup hits a *different*
cliff (per-stream byte volume) at 50.
Both values are correct in their own environments. See
`nestsClient/plans/2026-05-07-framespergroup-reconciliation.md`
for the full reconciliation. A planned re-run of the HCgOY field
tests against the current production deployment is queued in
`nestsClient/plans/2026-05-07-framespergroup-production-rerun.md`
to settle whether the two rigs can converge.
**Status: PRODUCTION-FIXED via two-layer fix.** The investigation closed **Status: PRODUCTION-FIXED via two-layer fix.** The investigation closed
with one true bug fix in `:quic` and one tuning change in with one true bug fix in `:quic` and one tuning change in
`NestMoqLiteBroadcaster`. A third layer — exposing moq-lite's `NestMoqLiteBroadcaster`. A third layer — exposing moq-lite's
@@ -340,3 +357,11 @@ report `received < N` due to from-latest semantics.
version ships either a config knob or a fix for the per-subscriber version ships either a config knob or a fix for the per-subscriber
forward starvation. The 100 ms late-join initial gap is the only forward starvation. The 100 ms late-join initial gap is the only
remaining audio-quality tradeoff from the current mitigation. remaining audio-quality tradeoff from the current mitigation.
**Update 2026-05-05:** the value did NOT stay at `5` — HCgOY field
tests overrode it to `50`. See the banner at the top of this
doc. The "reset to 1" follow-up still applies in spirit (use the
smallest value that doesn't cliff) but the right value is now
data-dependent on whichever production deployment is being
targeted. Tracked in
`nestsClient/plans/2026-05-07-framespergroup-production-rerun.md`.
@@ -20,11 +20,11 @@ implement them.
| T# | Fix | Commit | Asserting scenario(s) | Tier | Status | | T# | Fix | Commit | Asserting scenario(s) | Tier | Status |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| **T8** | Skip `BUFFER_FLAG_CODEC_CONFIG` outputs in `MediaCodecOpusEncoder` (don't emit `OpusHead` as an audio frame). | `96cfa1235` | **I11** (`first_audio_frame_is_not_opus_codec_config`) — strips Container::Legacy and asserts the first audio frame's payload doesn't begin with the `OpusHead` magic. **I14** (browser warmup) is the spec's intended browser-side mate but **NOT YET LANDED** — Phase 4.C deferred it. | hang ✅ + browser | **partial** | | **T8** | Skip `BUFFER_FLAG_CODEC_CONFIG` outputs in `MediaCodecOpusEncoder` (don't emit `OpusHead` as an audio frame). | `96cfa1235` | **I11** (`first_audio_frame_is_not_opus_codec_config`) — strips Container::Legacy and asserts the first audio frame's payload doesn't begin with the `OpusHead` magic. **I14** (`chromium_decoder_no_errors_through_warmup_window`) is the browser-side mate — asserts Chromium `AudioDecoder.error` count is 0 across the warmup window. | hang ✅ + browser | **green** |
| **T10** | `endGroup()` on unmuted → muted transition (don't park the open uni stream when the speaker mutes). | `c23da5279` | **I3** (`mid_broadcast_mute_shortens_decoded_pcm`) — speaker mutes 1 s mid-broadcast; asserts the listener-side decoded PCM has a sample-count deficit consistent with stream FIN, NOT embedded zeros. A regression to "push zeros instead of FIN" would trip the upper bound. | hang ✅ | **green** | | **T10** | `endGroup()` on unmuted → muted transition (don't park the open uni stream when the speaker mutes). | `c23da5279` | **I3** (`mid_broadcast_mute_shortens_decoded_pcm`) hang-tier + `chromium_listener_mid_broadcast_mute_shortens_pcm` browser-tier. Asserts the listener-side decoded PCM has a sample-count deficit consistent with stream FIN, NOT embedded zeros. A regression to "push zeros instead of FIN" would trip the upper bound. | hang ✅ + browser ✅ | **green** |
| **T11** | Drop `bestEffort = true` on moq-lite group uni streams (unreliable streams behave irregularly under loss). | `7e76ab113` | **I9** (`packet_loss_1pct_does_not_kill_audio`) — drives the QUIC client through `udp-loss-shim` at 1 % loss; asserts ≥ 60 % of expected samples + FFT peak intact. With `bestEffort = true` re-introduced, frames lost on dropped packets would NOT be retransmitted and the sample count would crash through the floor. | hang ✅ | **green** | | **T11** | Drop `bestEffort = true` on moq-lite group uni streams (unreliable streams behave irregularly under loss). | `7e76ab113` | **I9** (`packet_loss_1pct_does_not_kill_audio`) hang-tier + `chromium_listener_packet_loss_1pct_does_not_kill_audio` browser-tier. Drives the QUIC client through `udp-loss-shim` at 1 % loss; asserts the FFT peak intact. With `bestEffort = true` re-introduced, frames lost on dropped packets would NOT be retransmitted. | hang ✅ + browser ✅ | **green** |
| **T12** | Carry audio group sequence across hot-swaps (don't reset to 0 on speaker re-issuance — listener decoder caches by group ordering). | `be4e0b9f9` | **I5** (`speaker_hot_swap_does_not_crash`) — speaker calls `connectReconnectingSpeaker` mid-broadcast; asserts the listener sees no broadcast end and the post-swap window decodes cleanly with the 440 Hz peak intact. Group-sequence resets to 0 would cause the listener to drop "old" frames as duplicates. | hang ✅ | **green** | | **T12** | Carry audio group sequence across hot-swaps (don't reset to 0 on speaker re-issuance — listener decoder caches by group ordering). | `be4e0b9f9` | **I5** (`speaker_hot_swap_does_not_crash`) hang-tier + `chromium_listener_speaker_hot_swap_does_not_crash` browser-tier. Speaker calls `connectReconnectingSpeaker` mid-broadcast; asserts the listener sees no broadcast end and the post-swap window decodes cleanly with the 440 Hz peak intact. | hang ✅ + browser ✅ | **green** |
| **T13** | Reset Opus decoder on publisher boundary in `NestPlayer` (so the new publisher's pre-roll doesn't start mid-frame on stale decoder state). | `4714e3c72` | **I7** (`rust_hang_publish_reconnect_kotlin_listener_recovers`, in sister branch `feat/nests-i7-publisher-reconnect`) — Rust `hang-publish` cycles its session at T+2.5 s of a 5 s broadcast; asserts ≥ 2.5 s of decoded mono PCM with the 440 Hz peak intact across the cycle. A failed decoder reset would either crash or corrupt samples mid-stream — a corrupted half would skew the FFT peak away from 440 Hz. | hang ✅ | **green (in sister branch)** | | **T13** | Reset Opus decoder on publisher boundary in `NestPlayer` (so the new publisher's pre-roll doesn't start mid-frame on stale decoder state). | `4714e3c72` | **I7** hang-tier (`rust_hang_publish_reconnect_kotlin_listener_recovers` in `HangInteropReverseTest`) — Rust hang-publish cycles its session at T+2.5 s. **I7 reverse browser** (`chromium_publisher_reconnect_kotlin_listener_recovers`) — Chromium publishes via `publish.ts` reconnect mode. Both assert ≥ 2.5 s of decoded mono PCM with the 440 Hz peak intact across the cycle. | hang ✅ + browser ✅ | **green** |
| **T14** | Recognise `GOAWAY` control type instead of silent FIN. | `73722d2ad` | **N/A in moq-lite-03.** moq-lite has no `GOAWAY` frame on the wire; the fix protects the IETF moq-transport-17 control-decoder path (`MoqSession.kt:417`). I12 was originally specced for this but doesn't apply — see `2026-05-06-cross-stack-interop-test-results.md`'s I12 section. The IETF code path is exercised by the existing `MoqCodecTest` unit test (`unknown_control_type_skips_message_without_corruption`) only — no cross-stack scenario covers it because no cross-stack peer speaks IETF moq-transport. | unit-test only | **green** | | **T14** | Recognise `GOAWAY` control type instead of silent FIN. | `73722d2ad` | **N/A in moq-lite-03.** moq-lite has no `GOAWAY` frame on the wire; the fix protects the IETF moq-transport-17 control-decoder path (`MoqSession.kt:417`). I12 was originally specced for this but doesn't apply — see `2026-05-06-cross-stack-interop-test-results.md`'s I12 section. The IETF code path is exercised by the existing `MoqCodecTest` unit test (`unknown_control_type_skips_message_without_corruption`) only — no cross-stack scenario covers it because no cross-stack peer speaks IETF moq-transport. | unit-test only | **green** |
## Aspirational T1T7, T9, T15 ## Aspirational T1T7, T9, T15
@@ -53,29 +53,40 @@ each scenario protects:
| **I3** (mute window) | **T10** explicitly. | | **I3** (mute window) | **T10** explicitly. |
| **I4 fwd** (stereo 440/660) | Stereo plumbing through `AudioBroadcastConfig` (PR #2755) — not a T-series fix; protects the per-channel catalog → encoder pipeline. | | **I4 fwd** (stereo 440/660) | Stereo plumbing through `AudioBroadcastConfig` (PR #2755) — not a T-series fix; protects the per-channel catalog → encoder pipeline. |
| **I4 rev** (stereo Rust → Kotlin) | Listener stereo decode path. | | **I4 rev** (stereo Rust → Kotlin) | Listener stereo decode path. |
| **I5** (speaker hot-swap) | **T12** explicitly. | | **I5** (speaker hot-swap) | **T12** explicitly (hang + browser tiers). |
| **I6** (multi-listener) | T11 fan-out behaviour (in `feat/nests-i6-multi-listener`). | | **I6** (multi-listener) | T11 fan-out behaviour. |
| **I7** (publisher reconnect) | **T13** explicitly (in `feat/nests-i7-publisher-reconnect`). | | **I7** (publisher reconnect) | **T13** explicitly. Hang-tier exercises Rust hang-publish reconnect; browser-tier exercises Chromium publish.ts reconnect. |
| **I8** (SubscribeDrop on unknown track) | Subscribe rejection handling — moq-lite-03 protocol-level guard, not a T-series fix. | | **I8** (SubscribeDrop on unknown track) | Subscribe rejection handling — moq-lite-03 protocol-level guard, not a T-series fix. |
| **I9** (1 % packet loss) | **T11** explicitly. | | **I9** (1 % packet loss) | **T11** explicitly (hang + browser tiers). |
| **I10** (60 s long broadcast) | `framesPerGroup` cadence interaction at scale (see `2026-05-07-framespergroup-reconciliation.md`). | | **I10** (60 s long broadcast) | `framesPerGroup` cadence interaction at scale (see `2026-05-07-framespergroup-reconciliation.md`). |
| **I11** (wire-byte capture) | **T8** explicitly. | | **I11** (wire-byte capture) | **T8** explicitly. |
| **I12** (Goaway) | N/A in moq-lite-03; **T14** is exercised only by the IETF moq-transport unit test path. | | **I12** (Goaway) | N/A in moq-lite-03; **T14** is exercised only by the IETF moq-transport unit test path. |
| **I13** (browser `framesPerGroup=50` + `Container.Consumer`) | **NOT YET LANDED** — Phase 4.C deferred. Would protect the production cadence end-to-end against the WebCodecs decode path. | | **I13** (browser `framesPerGroup=50` long broadcast) | `framesPerGroup` cadence interaction at scale on the browser path. Note: spec asked for `framesPerGroup = 50`; local relay's per-stream byte cliff blocks that, so the test pins `5` — see `2026-05-07-framespergroup-reconciliation.md`. |
| **I14** (WebCodecs warmup × CSD-skip) | **NOT YET LANDED** — Phase 4.C deferred. Browser-side mate of I11; together they'd cover T8 on both rendering paths. | | **I14** (WebCodecs warmup × CSD-skip) | **T8** browser-side mate of I11. Asserts `AudioDecoder.error` count is 0; a `OpusHead` leak would fail. |
| **I15** (Chromium ALPN round-trip) | moq-lite ALPN drift detection (in `feat/nests-browser-interop`). | | **I15** (Chromium ALPN round-trip) | moq-lite ALPN drift detection. |
## Coverage holes ## Coverage state
Scenarios specced as P0 / P1 in the spec but **not yet landed**: All T-series wire fixes (T8, T10T14) have ≥ 1 cross-stack
asserting scenario landed. Both hang-tier AND browser-tier
mates exist for T8/T10/T11/T12/T13. T14 (GOAWAY) only applies
to the IETF moq-transport target which the production stack
doesn't use.
- **I13** (browser `framesPerGroup=50` long broadcast) — P0 browser-tier. Protects nothing today on the browser path; T11's browser-side coverage is implicit in I1 alone. DoD #5 (gap matrix coverage) closed.
- **I14** (WebCodecs warmup × CSD-skip) — P0 browser-tier. Hang-tier I11 catches T8 on the wire; without I14 a browser-side regression where the WebCodecs decoder mishandles a warmup-period CSD blob would silently pass.
For the merge-ready interop test branches: **Caveats — see linked investigation docs:**
- T13's asserting scenario (I7) is in `feat/nests-i7-publisher-reconnect` — when that merges into `claude/cross-stack-interop-test-XAbYB` (or main), this matrix should drop the "in sister branch" qualifier. - Five browser-tier scenarios soft-pass on listener-side
- I6's asserting scenario is in `feat/nests-i6-multi-listener` — same caveat. 0-frame outcomes due to the upstream moq-relay 0.10.x
routing race (`2026-05-07-late-join-catalog-flake-investigation.md`).
Hard floors lined up to land in
`2026-05-07-tighten-cross-stack-assertions.md` once the
routing race is closed.
- Suite-mode runs hit the same race intermittently;
individual-test mode is reliable. CI is intentionally not
wired (`2026-05-07-cross-stack-interop-ci-gating.md`) until
stability is achieved.
## Files referenced ## Files referenced
@@ -83,8 +94,10 @@ For the merge-ready interop test branches:
- `nestsClient/plans/2026-05-06-cross-stack-interop-test-results.md` (results — what landed) - `nestsClient/plans/2026-05-06-cross-stack-interop-test-results.md` (results — what landed)
- `nestsClient/plans/2026-05-07-framespergroup-reconciliation.md` (cadence reconciliation) - `nestsClient/plans/2026-05-07-framespergroup-reconciliation.md` (cadence reconciliation)
- `nestsClient/plans/2026-05-07-i7-post-reconnect-cliff-investigation.md` (I7 cycle-2 cliff) - `nestsClient/plans/2026-05-07-i7-post-reconnect-cliff-investigation.md` (I7 cycle-2 cliff)
- `nestsClient/plans/2026-05-07-late-join-catalog-flake-investigation.md` (relay routing flake)
- `nestsClient/plans/2026-05-07-t16-closure-roadmap.md` (next steps)
- `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt` - `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropTest.kt`
- `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropReverseTest.kt` (sister branch) - `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropReverseTest.kt`
- `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropMultiListenerTest.kt` (sister branch) - `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/HangInteropMultiListenerTest.kt`
- `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/BrowserInteropTest.kt` (sister branch) - `nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/interop/native/BrowserInteropTest.kt`
- T# fix commits: `96cfa1235` (T8), `c23da5279` (T10), `7e76ab113` (T11), `be4e0b9f9` (T12), `4714e3c72` (T13), `73722d2ad` (T14) - T# fix commits: `96cfa1235` (T8), `c23da5279` (T10), `7e76ab113` (T11), `be4e0b9f9` (T12), `4714e3c72` (T13), `73722d2ad` (T14)
@@ -1,29 +1,46 @@
# Plan: cross-stack interop test (T16) — Phases 13 results # Plan: cross-stack interop test (T16) — results
**Status:** Phases 13 (and Phase 2.E follow-ups) landed. Phase 4 (browser **Status:** All work merged into `claude/cross-stack-interop-test-XAbYB`.
harness) and Phase 5 (browser-only scenarios) are running in parallel 22 of 23 spec'd scenarios green individually; the spec's I12 (Goaway)
agent branches; not yet merged. **CI gating intentionally NOT wired** doesn't apply to moq-lite-03 (see I12 section below). **CI gating
the suite runs locally only via `-DnestsHangInterop=true`. See the intentionally NOT wired** — the suite runs locally only via
"CI integration" section below. `-DnestsHangInterop=true` / `-DnestsBrowserInterop=true`. See the
"CI integration" section below + `2026-05-07-cross-stack-interop-ci-gating.md`
for the path to wiring it.
**Scenario inventory (committed in this branch + sister branches):** **Scenario inventory (all merged on this branch):**
| ID | Scenario | Branch | Status | | ID | Scenario | Tier | Status |
|---|---|---|---| |---|---|---|---|
| I1 | Amethyst speaker → hang-listen (mono 440 Hz) | this branch | green | | I1 | 440 Hz mono round-trip | hang ✅ + browser ✅ | green |
| I2 | Late-join listener decodes tail | this branch | green | | I2 | Late-join listener decodes tail | hang ✅ + browser ✅ | green (suite-flaky on relay race) |
| I3 | Mid-broadcast mute shortens PCM | this branch | green | | I3 | Mid-broadcast mute shortens PCM | hang ✅ + browser ✅ | green |
| I4 fwd | Stereo 440/660 Amethyst speaker → hang-listen | merged on main (#2755) + test in this branch | green | | I4 fwd | Stereo 440/660 (Amethyst speaker → consumers) | hang ✅ + browser ✅ | green (production change shipped via PR #2755 on main) |
| I4 rev | Stereo hang-publish → Kotlin listener | this branch | green | | I4 rev | Stereo (hang-publish → Kotlin listener) | hang ✅ | green |
| I5 | Speaker hot-swap mid-broadcast | this branch | green | | I5 | Speaker hot-swap mid-broadcast | hang ✅ + browser ✅ | green |
| I6 | Multi-listener fan-out (1 speaker, 3 listeners) | `feat/nests-i6-multi-listener` `c28145a0b` | green | | I6 | Multi-listener fan-out (1 speaker, 3 hang listeners) | hang ✅ | green |
| I7 | Publisher reconnect (Rust hang-publish session cycle) | `feat/nests-i7-publisher-reconnect` `dbfeeb6d5` | green | | I7 | Publisher reconnect mid-broadcast | hang ✅ (Rust) + browser ✅ (Chromium) | green |
| I8 | SubscribeDrop for unknown track | this branch | green | | I8 | SubscribeDrop for unknown track | hang ✅ | green |
| I9 | 1% packet loss via udp-loss-shim | this branch | green | | I9 | 1 % packet loss via udp-loss-shim | hang ✅ + browser ✅ | green (suite-flaky) |
| I10 | 60-second long broadcast | this branch | green | | I10 | 60-second long broadcast | hang ✅ | green (suite-flaky) |
| I11 | First audio frame is not OpusHead codec-config | this branch | green | | I11 | First audio frame is not OpusHead CSD | hang ✅ | green |
| Rust↔Rust | hang-publish → hang-listen round-trip | this branch | green | | I12 | Goaway | n/a | does not apply to moq-lite-03 (see below) |
| Phase 4 | Browser (Chromium) listen + publish via Playwright | `feat/nests-browser-interop` (agent in flight) | pending | | I13 | Browser long broadcast (60 s) at production cadence | browser ✅ | green |
| I14 | WebCodecs warmup × CSD-skip (browser-side T8 mate) | browser ✅ | green |
| I15 | Chromium WT-Protocol round-trip | browser ✅ | green |
| Rust↔Rust | hang-publish → hang-listen round-trip | hang ✅ | green |
**Suite-flake caveats:** the four scenarios marked "(suite-flaky)" hit
moq-relay 0.10.x's per-broadcast subscribe-routing race when run
alongside other scenarios in one JVM. Each passes individually.
Documented + investigation roadmap in
`2026-05-07-late-join-catalog-flake-investigation.md` and
`2026-05-07-moq-relay-routing-investigation.md`. Test code soft-passes
listener-side assertions on 0-frame outcomes to avoid masking the real
upstream issue with looser thresholds; the soft-passes are scheduled
to be replaced with hard floors in
`2026-05-07-tighten-cross-stack-assertions.md` once the upstream race
is closed.
## Phase 2 update ## Phase 2 update
@@ -486,14 +503,16 @@ nestsClient/src/jvmTest/kotlin/com/vitorpamplona/nestsclient/
│ # I8, I9, I10, I11, Rust↔Rust │ # I8, I9, I10, I11, Rust↔Rust
├── HangInteropReverseTest.kt # I7 (Rust hang-publish reconnect → Kotlin listener) ├── HangInteropReverseTest.kt # I7 (Rust hang-publish reconnect → Kotlin listener)
├── HangInteropMultiListenerTest.kt # I6 (one speaker, three hang-listen subscribers) ├── HangInteropMultiListenerTest.kt # I6 (one speaker, three hang-listen subscribers)
├── BrowserInteropTest.kt # Phase 4: I1-I5, I7-rev, I9, I13-I15
├── PlaywrightDriver.kt # Bun + Playwright + Chromium spawn
└── KotlinSpeakerKotlinListenerThroughNativeRelayTest.kt └── KotlinSpeakerKotlinListenerThroughNativeRelayTest.kt
# diagnostic, gated separately # diagnostic, gated separately
# In sister branches (not yet merged): nestsClient/tests/browser-interop/ # bun + Playwright harness (Phase 4)
# feat/nests-i6-multi-listener -> HangInteropMultiListenerTest.kt (I6) ├── package.json + bun.lock + REV
# feat/nests-i7-publisher-reconnect -> HangInteropReverseTest.kt (I7) ├── src/{listen,publish,server}.ts + .html
# feat/nests-browser-interop -> nestsClient/tests/browser-interop/ + ├── tests/harness.spec.ts
# BrowserInteropTest.kt (Phase 4) └── playwright.config.ts
nestsClient/plans/2026-05-06-cross-stack-interop-test-results.md # this file nestsClient/plans/2026-05-06-cross-stack-interop-test-results.md # this file
``` ```
@@ -1,6 +1,17 @@
# Plan: cross-stack interop test (T16) # Plan: cross-stack interop test (T16)
**Status:** 📋 Spec — ready to implement. **Status:** ✅ Implemented and merged. See:
- `2026-05-06-cross-stack-interop-test-results.md` for the scenario
inventory + per-scenario status
- `2026-05-06-cross-stack-interop-test-gap-matrix.md` for the
T-series wire-fix → asserting-scenario mapping (DoD #5)
- `2026-05-07-t16-closure-roadmap.md` for the next-steps roadmap
(residual upstream relay flake → tighten assertions → wire CI)
The spec text below is preserved for archaeology; some scenarios
were re-shaped during implementation (I12 GOAWAY is N/A in
moq-lite-03; I13's `framesPerGroup = 50` got pinned to 5 due to the
local relay's per-stream byte cliff).
**Origin:** audit of `claude/debug-audio-dropout-n0g6Z` against the audio **Origin:** audit of `claude/debug-audio-dropout-n0g6Z` against the audio
path verified all wire fixes (T1T14) by inspection, but the existing path verified all wire fixes (T1T14) by inspection, but the existing
@@ -1,10 +1,17 @@
# Plan: I4 stereo cross-stack interop scenario # Plan: I4 stereo cross-stack interop scenario
**Status:** 📋 Spec — ready for implementation. Phase 2 of the **Status:** ✅ Landed. Production-side change merged to main as
T16 cross-stack interop suite landed every other P0 scenario PR #2755 (`refactor(nests): per-stream channel count +
(I1, I2, I3, I8, I10, I11) but I4 stereo was deferred because AudioBroadcastConfig`). Test scenarios merged into
it requires a non-trivial change in `:nestsClient` production `claude/cross-stack-interop-test-XAbYB`:
code, not just test plumbing. This plan scopes that change. - `HangInteropTest.amethyst_speaker_to_hang_listener_stereo_440_660`
(forward)
- `HangInteropTest.rust_hang_publish_stereo_to_kotlin_listener_440_660`
(reverse)
- `BrowserInteropTest.chromium_listener_stereo_440_660`
(forward, browser-tier)
The spec text below is preserved for archaeology.
**Origin:** `nestsClient/plans/2026-05-06-cross-stack-interop-test.md` **Origin:** `nestsClient/plans/2026-05-06-cross-stack-interop-test.md`
table row I4: "Stereo Opus (`numberOfChannels=2`); freq differs L/R table row I4: "Stereo Opus (`numberOfChannels=2`); freq differs L/R
@@ -1,8 +1,25 @@
# Plan: Phase 4 (browser harness) — landed results # Plan: Phase 4 (browser harness) — landed results
**Status:** 4.A scaffold + 4.B Playwright driver + first Kotlin **Status:** ✅ Phase 4.A (scaffold) + 4.B (Playwright driver) +
test green; 4.C ships I15; 4.D ships the CI workflow job. Tracks 4.C (full scenario coverage) all landed. Phase 4.D (CI workflow
the spec at `nestsClient/plans/2026-05-06-phase4-browser-harness.md`. job) was originally added in commit `c79a3ffa8` but later removed
per maintainer ask in commit `b94737de7` ("don't add these tests
to the build.yml for now"); see
`2026-05-07-cross-stack-interop-ci-gating.md` for the path to
re-wiring.
**Browser harness location:** `nestsClient/tests/browser-interop/`
(was originally `nestsClient-browser-interop/` at repo root;
moved to mirror `nestsClient/tests/hang-interop/` layout).
**Final scenario coverage:** I1, I2, I3, I4 (stereo), I5
(hot-swap), I7 (Chromium publisher reconnect), I9 (packet loss),
I13 (long broadcast), I14 (WebCodecs warmup × CSD), I15 (ALPN).
See `2026-05-06-cross-stack-interop-test-results.md` for the
full inventory.
Tracks the spec at
`nestsClient/plans/2026-05-06-phase4-browser-harness.md`.
## Where it landed ## Where it landed
@@ -1,12 +1,24 @@
# Plan: Phase 4 — browser-side cross-stack harness (T16) # Plan: Phase 4 — browser-side cross-stack harness (T16)
**Status:** 📋 Spec — ready for implementation. Phase 13 of the **Status:** ✅ Landed. Browser harness lives at
T16 cross-stack interop suite landed the Rust path `nestsClient/tests/browser-interop/`; tests are
(`hang-listen` + `hang-publish` against `moq-relay 0.10.x`, `BrowserInteropTest.kt` covering I1, I2, I3, I4 (stereo), I5
seven scenarios green). Phase 4 adds the **browser path**: (hot-swap), I7 (publisher reconnect), I9 (packet loss), I13
headless Chromium running `@moq/watch` (listener) and (long broadcast), I14 (WebCodecs warmup × CSD), I15 (ALPN).
`@moq/publish` (publisher) against the same harness's relay, Companion landed-results doc:
driven from `:nestsClient:jvmTest` via Playwright. `2026-05-06-phase4-browser-harness-results.md`.
The spec text below is preserved for archaeology; some pieces
shifted during implementation:
- `@moq/watch` / `@moq/publish` weren't directly used; the
harness uses `@moq/lite` + `@moq/hang` `Container.Legacy.Consumer/Producer`
because the published `@moq/hang` 0.2.4 didn't expose the
high-level `Container.Consumer` API.
- Cert pinning uses `serverCertificateHashes` not
`--ignore-certificate-errors` because Chromium's flag does
NOT bypass QUIC cert validation.
- Phase 4.C originally deferred I2/I3/I4/I13/I14 — those
subsequently landed, see results doc.
**Origin:** parent plan **Origin:** parent plan
`nestsClient/plans/2026-05-06-cross-stack-interop-test.md`, `nestsClient/plans/2026-05-06-cross-stack-interop-test.md`,
@@ -1,7 +1,19 @@
# `late_join_listener_still_decodes_tail` catalog-cancelled flake investigation # `late_join_listener_still_decodes_tail` catalog-cancelled flake investigation
**Status: partially fixed (commits `8cc7cbd42`, `00f6cba31`, **Status: partially fixed (commit `8cc7cbd42` shipped; commits
`207057374`), residual flake documented as upstream-territory.** `00f6cba31` + `207057374` reverted as net-negative). Residual
flake is upstream-territory in moq-relay 0.10.x. Action plan
moved to `2026-05-07-moq-relay-routing-investigation.md`.**
The flake also affects four browser-tier scenarios after the
Browser I7 work landed:
`chromium_listener_late_join_still_decodes_tail`,
`chromium_publisher_baseline_kotlin_listener_decodes`,
`chromium_publisher_reconnect_kotlin_listener_recovers`, and
intermittently `chromium_listener_long_broadcast_60s_tone_440`.
Browser scenarios soft-pass listener-side assertions on 0-frame
outcomes; hard floors planned in
`2026-05-07-tighten-cross-stack-assertions.md`.
`HangInteropTest.late_join_listener_still_decodes_tail`, `HangInteropTest.late_join_listener_still_decodes_tail`,
`packet_loss_1pct_does_not_kill_audio`, `packet_loss_1pct_does_not_kill_audio`,