test(interviews): exercise the voice relay against a real WebSocket - #151
Conversation
The relay was covered thoroughly at the policy layer and not at all at the transport. Nothing in the suite opened a socket, so framing, close codes and backpressure rested on the code looking correct — which #138 made concrete by taking websockets from 16.1.1 to 17.1 on the library carrying live interview audio. Nine tests now run the relay against a websockets server on 127.0.0.1, through the real _connect_provider transport including its NoRedirectConnect subclass. No credential, no egress, no opt-in marker, seven seconds. Three of the four failure modes the issue names behave as they should. A whole turn survives the real transport. A provider closing cleanly mid-stream gives the participant a defined end rather than a hang. A participant cannot outrun the ingress allowance. A redirect is refused end to end, and the target server records no request at all, so the API key cannot have been re-sent. Two findings, both pinned as they behave today and filed rather than changed here, because a test and a behaviour change landing together makes it impossible to tell which broke something later. An abnormal close, a dropped connection and a refused redirect are all reported as internal_failure. Only a graceful close reaches run_provider's own RelayPolicyError; every other transport failure raises a websockets exception and falls through to the catch-all, so the more clearly the provider misbehaves, the more confidently the operator is blamed. See #149. A provider that answers setup and then stops reading is not noticed while the participant is speaking. Every watchdog timeout needs silence, a started generation, or an idle phase; a speaking participant keeps refreshing last_voice and none of them applies. At ten times real time this handed 8.5 MB to a socket nobody was draining without send ever blocking. The session still ends at its deadline, which is what the test asserts. See #150. Closes #139 Signed-off-by: L4XB <L4XB@users.noreply.github.com>
|
I have read and agree to the SixSentences CLA v1.0. |
Self-reviewRecorded under the founding-maintainer exception in
The harness.
Determinism. Each test owns its Cost. 7 s of a suite that takes five minutes locally. The deaf-provider test The two pinned defects. One thing I checked and left alone. One thing this pull request confirms about another
ChecksEvery context green, including the API job that ran the new tests, the Merging. |
Summary
Nine tests that run the relay against a
websocketsserver on 127.0.0.1,through the real
_connect_providertransport —NoRedirectConnectsubclassincluded. No provider, no credential, no outbound connection, no opt-in marker.
Seven seconds.
Three of the four failure modes the issue names behave as they should. Two
findings came out of the other one, and out of the close codes. Both are pinned
as they behave today and filed separately.
What the tests establish
test_a_whole_turn_survives_the_real_transportactivityEnd→ transcript + meter → participant stop, over a socket. The provider is configured by the relay, never by the browser.test_a_provider_that_closes_mid_stream_ends_the_participant_sessionreason="connection",relayEnd, and a closed browser socket — not a hang.test_an_unexpected_close_code_still_ends_the_session(1011, 1008)test_a_connection_dropped_without_a_close_frame_ends_the_sessiontest_a_participant_cannot_outrun_the_ingress_allowancetest_a_provider_that_stops_reading_is_only_noticed_at_the_session_deadlinetest_the_api_key_is_never_re_sent_to_a_redirect_targettest_a_provider_frame_above_the_transport_limit_ends_the_sessionThe connector asserts it was handed
PROVIDER_WS_URLbefore substituting thelocal port, so a change that stopped dialling the provider would fail these
tests rather than pass them quietly.
Two findings, filed rather than fixed here
Both are pinned exactly as they behave today, with a comment in the test naming
the issue. Landing a test and a behaviour change together makes it impossible to
tell which broke something later — the project's own argument, from #140.
#149 — a provider dropping the socket is reported as an internal failure
reasondiagnostic_codeconnectionconnectionunavailableinternal_failureunavailableinternal_failureunavailableinternal_failureunavailableinternal_failureOnly a graceful close reaches
run_provider's ownRelayPolicyError("connection"). Everything else raises awebsocketsexception, which is not a
RelayPolicyError, so it lands in the catch-all in_Relay.runthat exists for genuine faults in the deployment. The redactionthat block performs is doing its job; the classification is not. The more
clearly the provider misbehaves, the more confidently an operator is sent
looking for a bug in their own service.
#150 — a provider that stops answering is only noticed at the deadline
A provider that answers
setupCompleteand then stops reading and stopsanswering is not detected while the participant is speaking. Every watchdog
timeout needs silence (
patience_ms), a started generation(
generation_timeout_seconds), a completed one (meter_timeout_seconds) or anidle phase (
silence_timeout_seconds). A speaking participant keeps refreshinglast_voice, so none of them applies and the only limit left is the sessiondeadline.
Nor does the transport push back in time to matter. Measured at ten times real
time while writing this, the relay handed 8.5 MB across twenty-two seconds
into a socket nobody was draining, and
sendnever blocked once. Loopbackbuffers are generous; a thirty-minute session at real time is the same shape,
and the memory sits in a process serving other interviews.
Keepalive does not cover it either:
ping_interval=20, ping_timeout=20provesthe peer process is reachable, because pongs come from the library below the
application — not that anyone is listening.
Behavior and compatibility
Tests only. No source file changed.
COMMUNITY_EXPORT_MANIFEST.jsonrecords thenew file, as any change under
services/api/must.Validation
Notes on making it fast and deterministic:
not depend on how promptly a loaded runner wakes a sleeping task. Forty frames
is 128 kB against 32 kB plus one second of refill; no scheduling delay makes
that fit.
deadline_atto 1.5 s, which is the only way toassert a bounded outcome for a gap whose real bound is
LIVE_SESSION_MAX_MINUTES.close_timeout=1. That is the harness's patience, notthe relay's: a provider that has stopped reading never processes the relay's
close frame, and waiting the library's default ten seconds for a handshake
that cannot complete cost ten seconds per run for nothing.
Async scenarios use
asyncio.runinside synchronous tests, astest_voice_relay.pyalready does, so no test-runner plugin and no newdependency.
Not covered, stated rather than implied
Keepalive timing.
ping_interval=20, ping_timeout=20means exercising it costsat least twenty seconds of wall clock, which is more than the property is worth
in a suite this size — and, per #150, a pong proves less than it appears to.
Review boundaries
127.0.0.1 on a kernel-assigned port and is torn down with the test. The
only credential is the literal string
transport-test-key-not-real. Theredirect test asserts the target recorded no request, which is a
stronger statement than "no key in the headers it received". No
participant content, no fixture derived from real audio.
websocketsis already a runtime dependency; its server side is used here, not added.no research-facing behavior changed. Nothing changed. The two findings are
recorded in voice: a provider dropping the socket is reported as an internal failure of the deployment #149 and voice: a provider that stops answering is only noticed at the session deadline #150 rather than left in a comment.
Source-release hygiene
CHANGELOG.mdis updated for user-visible changes, or the omission is explained above: no source file changed, so there is nothing for a user of a release to read. The two findings are user-relevant and are recorded as issues.Signed-off-bytrailer.CLA.mdand posted its exact acceptance sentence as a standalone pull-request comment.Visual evidence
No UI change.
Closes #139