# Plan: tighten cross-stack interop assertions to hard-pass **Status:** specced — pickup ready. **Depends on:** `2026-05-07-moq-relay-routing-investigation.md` must be closed first (the soft-passes exist *because* of that flake; removing them while the flake is unresolved produces fail-flakes, not regression catches). ## Why soft passes exist today Five scenarios currently soft-pass (vacuous-pass on listener-side 0-frame outcomes) to keep the test suite from fail-flaking on the upstream relay-routing race documented in `2026-05-07-late-join-catalog-flake-investigation.md`: | Scenario | File | Soft-pass behavior | |---|---|---| | `chromium_listener_late_join_still_decodes_tail` | `BrowserInteropTest.kt` | `if (pcm.size <= warmupSamples) return` | | `chromium_listener_mid_broadcast_mute_shortens_pcm` | `BrowserInteropTest.kt` | same | | `chromium_decoder_no_errors_through_warmup_window` (I14) | `BrowserInteropTest.kt` | no `decoderOutputs >= 4` floor | | `chromium_publisher_baseline_kotlin_listener_decodes` | `BrowserInteropTest.kt` | hard-asserts publisher framesIn; soft-asserts listener | | `chromium_publisher_reconnect_kotlin_listener_recovers` (Browser I7) | `BrowserInteropTest.kt` | same as baseline | All five have hard assertions on the `framesIn` / publisher-side behavior; the listener-side is what's soft. None of these are reaching their full design intent. ## Soft-pass justification audit (per scenario) Re-read each scenario's kdoc. The soft-pass is honest right now (captured 0 frames means harness flake, not regression). Once the relay-routing race is fixed, the listener side becomes deterministic and the soft-pass is no longer load-bearing — at that point, the soft-pass HIDES regressions (a real T8/T11/T13 break could land in a 0-frame outcome and pass vacuously). ## Tighten plan ### Step 1 — confirm sweep stability After the routing investigation lands, run: ``` for i in 1 2 3 4 5; do echo "=== run $i ===" ./gradlew :nestsClient:jvmTest \ --tests HangInteropTest \ --tests BrowserInteropTest \ -DnestsHangInterop=true \ -DnestsBrowserInterop=true \ --rerun-tasks 2>&1 | grep -E "FAILED]|BUILD" done ``` 5/5 BUILD SUCCESSFUL with 0 `FAILED` lines = stability achieved. ### Step 2 — replace each soft-pass with a hard floor For each scenario in the table above, remove the `if (pcm.size <= warmupSamples) return` short-circuit and replace with a meaningful sample-count floor. Tighten thresholds based on observed steady-state numbers (see each scenario's kdoc for what "steady-state" looks like — most run ≥ 1 s of audio in green-state). Concretely: - **Late-join**: `assertTrue(pcm.size >= ...)` floor. Steady-state captures ~3 s on a 5 s broadcast with 2 s late-join, minus warmup. Threshold: `≥ 1.5 s` — comfortably under the steady-state but well over zero. - **Mute-window**: tighten the upper bound (current 5.5 s) to ~5.0 s. Add a lower bound asserting `≥ 2.5 s` — proves audio arrived AND the muted segment shortened the total. - **I14**: re-add `decoderOutputs >= 4` (3 warmup + ≥ 1 audio). Current absence-only assertion is partial coverage. - **Browser publisher baseline + reconnect**: remove the vacuous-pass branches, add a `≥ 0.5 s of audio after warmup` floor for baseline and `≥ 2.5 s` for reconnect (matches the hang-tier I7 threshold). ### Step 3 — reverify Re-run the 5× sweep. All scenarios must hard-pass 5/5. If a scenario flakes after tightening, the relay-routing investigation isn't fully done and we revert the tightening on that scenario until it is. ### Step 4 — update the gap matrix `nestsClient/plans/2026-05-06-cross-stack-interop-test-gap-matrix.md` currently lists I14 with "browser ⏳" pending; flip to "✅" once its hard floor is in. Same for any I-scenarios that now have hard floors on both tiers. ## Acceptance criteria - All BrowserInteropTest scenarios run with hard sample-count AND FFT-peak assertions (no `return@runBlocking` short-circuits on pcm.size). - All HangInteropTest scenarios already hard-pass — no change needed there. - Gap matrix updated to reflect hard-pass coverage on each T#. - Results plan updated to remove the "soft-pass on flake" language. ## Risk: post-tightening flake If any scenario fail-flakes after tightening, the routing investigation isn't really done. Don't paper over with a wider threshold; that's the same trap as the soft-passes. Either: (a) revert the tightening on that scenario and keep investigating, OR (b) widen the threshold ONLY if the new value still excludes the regression mode the test was designed to catch.