Skip to content

voice: a provider dropping the socket is reported as an internal failure of the deployment #149

Description

@L4XB

Problem

Measured against a real local WebSocket while building the transport tests for
#139. How a provider disconnection is diagnosed depends on how politely the
provider disconnects, and the polite case is the only one that gets it right:

What the provider did reason diagnostic_code
close frame, code 1000 connection connection
close frame, code 1011 unavailable internal_failure
close frame, code 1008 unavailable internal_failure
socket aborted, no close frame unavailable internal_failure
handshake answered with a redirect unavailable internal_failure

Only a graceful close reaches run_provider's own
raise RelayPolicyError("connection"). Everything else raises a
websockets exception — ConnectionClosedError, InvalidStatus,
InvalidHandshake — none of which is a RelayPolicyError, so it falls through
to the catch-all in _Relay.run:

except Exception:
    # No provider exception text, payload, URL, or key is logged or
    # returned to the participant. A persistence failure is terminal.
    self.diagnostic_code = "internal_failure"
    self.diagnostic_stage = stage
    self.stop("unavailable")

The redaction that comment describes is doing its job. The classification is
not: internal_failure is what an operator reads when something in their
deployment is broken, and it is being used for the most clearly provider-side
failure there is — the upstream dropping the socket.

Why it matters

diagnostic_code and diagnostic_stage exist so an operator can tell what went
wrong without seeing participant content. An interview that ends because Google
closed the socket with 1011 currently tells them to go looking for a bug in
their own service. The cases where they would be right — a persistence failure,
a programming error — are indistinguishable from the cases where they are not.

The more clearly a provider misbehaves, the more confidently this blames the
operator.

What to decide

  1. Should a transport failure be connection / connection, matching the
    graceful close? That is the answer the table above suggests, and it costs one
    except clause in run_provider around the async with and the async for.
  2. reason is the harder half. It is not only a diagnostic: it reaches the
    participant in relayEnd and is persisted with the session. Moving abnormal
    closes from unavailable to connection changes a stored value and whatever
    reads it. Worth checking what distinguishes the two for a reader before
    changing either.
  3. Is a redirect refusal the same class? It is a security control doing its job
    rather than a fault — arguably its own code, so an operator can tell a
    blocked redirect from a dropped connection.

Not urgent, and not invisible

Nothing about the participant's experience is wrong today: every case above ends
the session promptly, sends relayEnd, and closes the browser socket. Those
properties are asserted in services/api/tests/test_voice_relay_transport.py.
The same tests pin the current diagnostics, with a comment naming them as the
subject of this issue, so whoever fixes this will find the assertions to update
rather than a surprise.

Acceptance criteria

  • A provider transport failure is distinguishable from a failure inside the
    deployment, by the diagnostic alone
  • reason either stays as it is with a stated rationale, or changes with a
    note on what reads the stored value
  • A redirect refusal is classified deliberately, not by falling through
  • The transport tests are updated in the same change, not left asserting the
    old classification

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:interviewsInterview, transcript, voice, and consent boundariesarea:self-hostingCommunity deployment, upgrades, backup, and recoverytype:hardeningCorrectness, resilience, and adversarial-input hardening

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions