zebra-report/test
Russell Ballestrini 0ce1339f8e
zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener)
Fox 2026-06-06: "fedora chrome is flawless besides not able to hear
any mics it was working a few days back and nothing was changed on
the system, only thing we changed was our zebra codes."

The race: on a listener joining a room with existing speakers, the
SFU sub PC ontrack can fire BEFORE the signal-server peer-joined
event populates `members`. handleRemoteSfuTrack already does prefix
resolution at line ~4640:

  let pubHex = pubHex16;
  for (const [, mm] of members){
    if (fh.startsWith(pubHex16)){ pubHex = fh; break; }
  }

When the roster is empty, the loop finds nothing, pubHex stays the
16-char streamID prefix, and sfuStreamsByPubHex.set(pubHex, stream)
caches under that short key. peer-joined arrives later,
flushSfuStreams runs to attach what was cached — but its inner match
was strict ===:

  if (mm.pubkey && hex(unb64(mm.pubkey)) === pubHex){

mm.pubkey decodes to the FULL 64-char hex; pubHex from the cache is
the 16-char prefix; === never matches; listener stays permanently
silent for every speaker who was already in the room.

Pre-cascade this defect was masked: the old guard `!remoteAudio.has(uuid)`
was always true for worklet listeners (remoteAudio is the <audio>
fallback path only), so flushSfuStreams re-attached every cached
stream on every peer-joined — the eventual second ontrack from a
later renegotiation would land with members populated, cache key
became the full pubhex, and === matched. The 2e74b92 fix replaced
the always-true guard with `!listenerAudioNodes.has(uuid)`, which
correctly skipped re-attach but also exposed the strict-equality
matcher in the cold path.

Fix: switch flushSfuStreams' inner match from `=== pubHex` to
`fh.startsWith(pubHex)`. Symmetric with handleRemoteSfuTrack's own
prefix resolution. Works for both cases:

  - cache key is full 64-char pubhex → startsWith with a full string
    requires equality, so behavior is unchanged when ontrack arrived
    after peer-joined (the common case).
  - cache key is 16-char prefix → startsWith matches the first 16
    chars of any member's full pubhex. 64 bits of prefix entropy =
    astronomical collision probability.

Pinned by 29 new assertions in test/listener-audio-attach.test.js,
extracted from the live page so they cannot drift:
  - 22 cover the attach FSM (chain reachability, dedup, in-place
    swap, jbuf race, idempotent re-attach).
  - 7 cover handleRemoteSfuTrack including the failing scenario:
    "ontrack ARRIVES BEFORE peer-joined (member roster empty) →
    cached + audible after flush" — fails pre-fix, passes post-fix.

Makefile gets test-listener-audio + adds it to test-all.
2026-06-06 14:37:18 -04:00
..
functional.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
integration.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
listener-audio-attach.test.js zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
mod-action-serializer.test.js test: pin the mod-action serializer race fix 2026-06-04 10:46:55 -04:00
multi-peer-mesh.test.js zebra-spaces: rebuild <video> on MSID supplant — autoplay grant resets 2026-06-03 16:08:01 -04:00
self-listener-fsm.test.js zebra-spaces: formalize self-listener as FSM — pure spec + observer-driven side effects + 12 unit tests 2026-06-04 13:10:29 -04:00
test.h phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
unit.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
video-track-removal.test.js zebra-spaces: 120s mute window for screen + game (static content), 15s stays for camera 2026-06-03 10:33:28 -04:00
web-protocol.test.js test: node web protocol suite (no devices) + exponential retransmit backoff 2026-05-28 12:44:20 -04:00
zebra-fsm.test.js test/zebra-fsm: MuteFSM transition tests — 14 new cases (102 total) 2026-06-04 14:14:52 -04:00
zebra-spaces.test.js zebra-spaces: JS↔Go protocol parity + crypto tests 2026-05-31 11:06:59 -04:00