# Voice troubleshooting diagnostics

Document: SPEC-021. Revision: 0.10. Date: 2026-11-04. Lifecycle: Approved for the bounded implementation requested in D-099. D-172–D-180 scope is accepted only as specified below; SPEC-013 records the approved one-message history exception.

## Approved video-delivery successor — D-167

D-167/SPEC-024 distinguishes presentation latency and presentation-sound skew from microphone alignment. A timing counter cannot label a video paused merely because speech arrived sooner. Retain bounded aggregate diagnostics, actual rendered cadence/drop/hold counts and the existing secret/content/address redaction rules. Managed closure evidence and cooperative-client rate reports cannot be labeled independently enforced. See [SPEC-024](024-presentation-delivery.md).


The human requested client/server logging to investigate silent voice disconnects and explicitly authorised deployment and continued testing. R-105 adds an exportable client event journal and structured control/media lifecycle logs. This extends D-084's current-call aggregates; it does not select general analytics, automatic client uploads or recording of content. SPEC-005 and SPEC-008 own their respective component responsibilities.


D-166 adds numeric screenAdaptations, screenEncoderDrops and screenFeedbackLevel (maximum observed receiver level). These aggregate actual encoder reductions, omitted/stale encoding work and receiver adaptation pressure without recording sources, frames or per-frame events. Timing pauses now count transitions into a held-picture state, with authority clearing unchanged.

## Contract

Client events record UTC timestamps, elapsed time, opaque voice/generation IDs, fixed event/stage/state/reason labels, HTTP status, renewal timing and numeric aggregate media counters. Record join/connected/closure, control and relay renewal failures, periodic successful renewal, transport/audio-context changes, online/visibility changes, and scheduling gaps. A failed renewal must identify its stage before cleanup clears the call ID. Client closure records whether it was requested or unexpected. Unknown causes remain unknown. D-138 channel deletion records the fixed server reason `channel_deleted` without channel names, content or membership lists.

Native Qt setup failures retain the observed boundary (`audio`, `prepare`, `connectivity`, `join`, `lease` or `offer`) even before a voice attendance exists. Project exact locally defined failure notices into fixed codes for audio scheduling/device/plugin/start failures, WebRTC plugin/pipeline/channel/negotiation/disconnection failures, ICE gathering/configuration failures, control timeout, forwarding-authority expiry and media-generation expiry. These codes describe observed failures rather than an inferred root cause. Unrecognized text retains the boundary and `unknown` reason without a code or the original text. Reset setup-stage/code state for every explicit attempt; successful rejoining retains earlier journal entries within the existing bound. Raw device/backend exceptions and server messages remain excluded.

Native output timeline recovery records the fixed `audio_resync` event at stage `audio`, the attendance ID when assigned and the absolute observed timeline discrepancy as `durationMS`. Numeric `audioOutputResyncs` counts recoveries and `audioOutputGapMS` retains the largest discrepancy in milliseconds for the current audio graph. They start at zero, reset with that graph and use the existing numeric projection/snapshot bounds. A recovery event does not claim that the CPU, device or network caused the discontinuity and does not indicate a rejoin.

Screen playback uses the same ten-second snapshots and final call-closure record for cumulative numeric counts of verified chunks, assembled video keyframes, assembled/decoded/drawn/late/superseded video frames, and decoder overload/errors. Sender counters record encoded video rejected before queueing and packets discarded after the send deadline. Count transitions into policy, membership-revision, local-call, presenter-authority and timing pauses separately, plus current watching/publishing counts and the maximum observed render-tick duration in milliseconds. `screenVideoGaps` counts observed dependency-chain resets from missing/out-of-order video, separate from decoder overload and authority pauses. These counters distinguish observed paths without attributing an unmeasured cause. No per-frame events, source identifiers, source names or image data are retained. This completes SPEC-023’s pause/drop accounting within the existing diagnostic bounds.

Keep a bounded journal across calls in the running page, independent of the call's aggregate reset. **Download voice log** is available in Connection details and the unexpected-disconnection notice, including after signing out. It exports JSON on explicit user action. Reloading/restarting clears it. No client journal is automatically uploaded, stored in browser persistence, or copied into history/recovery. Exported files remain under the user's control.

Control logs record voice admission and removal, distinguishing explicit leave/cancel/move, attendance expiry, generation expiry and other authority changes. Voice API failures and unusually slow requests record operation, HTTP status and duration. Media logs record generation creation/readiness/shutdown, detected process changes, and failed/slow gateway forwarding. These are observations, not proof that the network or a particular machine caused a disconnect. Failed transaction attempts may be logged before transaction completion; state remains authoritative.

Allowlist application diagnostic fields and values before emission. Application diagnostic records never log audio, message text, pictures, passwords, tokens, keys, raw exceptions, request bodies, SDP, candidate addresses, account names, channel names, or arbitrary URLs. Opaque call/generation IDs permit correlation without becoming credentials. Logging failures must not interrupt voice cleanup, authority checks or deadlines. Preserve SPEC-004/009/017 lease, moderation and manual-rejoin rules.

D-168 adds bounded numeric `screenRecoveryRollbacks` (failed quality trials restored within the current active route/viewer lifetimes) and `screenRecoveryDelayMS` (maximum current retry window, milliseconds, zero with no active controller). These snapshots contain no source identity or media.

## Configuration

D-173 adds a separate statistics display and bounded numeric connection reports to the authenticated selected server. D-200 makes the native Qt display always visible without a checkbox; other clients retain opt-in display. This supersedes the earlier no-automatic-upload rule only for these numeric reports; the event journal, content, addresses and secrets are never included. SPEC-005 owns display preferences and sampling, SPEC-008 owns report limits, expiry and owner-only aggregates. Distinguish measured application request latency and counted application payload bytes from transport-level media measurements; neither is a complete operating-system bandwidth or billing measurement. Missing measurements are unavailable, not zero.


These implementation bounds complete the requested diagnostic feature, not new user preferences: a client journal retains the latest **500 events**, aggregate snapshots every **10 seconds**, successful renewal summaries every **30 seconds**, and scheduling-gap events after an interval exceeds **2.5 seconds**. A server request taking at least **1000 ms** is logged even when successful; routine successful lease polling is otherwise quiet. These fixed constants take effect on loading updated code. No diagnostic payload/configuration is accepted over the application protocol.

The reference control and media Workers enable operator-only persisted Cloudflare console logs with `observability.logs.enabled=true`, `invocation_logs=false`, and `head_sampling_rate=1`. Set `observability.redact_query_string=true` and disable traces. Wrangler also enables Container stdout/stderr collection with this setting; the unchanged native relay emits its existing startup/failure diagnostics, with no additional packet or debug logging enabled. Provider/runtime metadata, retention and ingestion limits remain Cloudflare-managed; no application database log table or external export destination is added. Node adapters emit the same structured events to their operator's console; their log retention is the host's responsibility. Cloudflare limits may reduce completeness despite the configured sampling rate. Deployment documentation records access and troubleshooting steps.

## Testing

Native output-recovery fixtures assert the fixed audio event, attendance correlation and safe finite counter projection without attributing a CPU/device/network cause. Unknown labels and arbitrary payloads are excluded.

D-173 tests validate report field bounds and owner-only per-user aggregation, while browser fixtures distinguish local payload/request measurements from voice-path metrics and reject malformed aggregate values without disrupting chat.

The managed trial adds numeric active-connection snapshots: `screenSFUConnections`, `screenSFURendered`, `screenSFUVideoFrames`, `screenSFUAudioFrames`, `screenSFURejected`, `screenSFUSentBytes`, `screenSFUReceivedBytes`, `screenSFUDecoded` and `screenSFUDropped`. They distinguish authenticated transform progress, actual render callbacks and browser RTP counters. They describe current owned connections, not complete invoice totals. Project only safe finite nonnegative numbers; never export SDP, provider IDs, App Secret, signing material or sender media roots. Browser frame/byte statistics remain client reports and cannot serve as trusted bitrate policing.

Native regression tests inject a setup-time audio failure before attendance, verify the fixed stage/code and complete cleanup, then retry with unrecognized backend text and verify no stale code, secret or arbitrary text is exported. Unit tests cover the exact failure-code projection and rejection of unknown labels.

Unit tests include finite screen-counter projection and rejection of arbitrary screen labels/media payloads. Focused receiver tests distinguish pause causes and verify cumulative backlog/timing counters. Unit tests cover projection/redaction, invalid values, bounded eviction, serialization, failures in the console sink, client cleanup/failure correlation, server expiry/removal reasons, and media gateway lifecycle/failure paths. Browser integration downloads and parses a log after a forced renewal failure and verifies the persistent notice, stage/call correlation and absence of fixture secrets. Build and deployment checks verify logging configuration, persistent Durable Object behavior and version propagation separately from log delivery.

## Acceptance criteria

- A delayed native output callback records an `audio_resync` and bounded counters while retaining the original live attendance; genuine device/control failures still produce the specified unexpected closure.
- A native setup failure before admission exports its observed boundary and an allowlisted code for a recognized local notice; an unknown backend notice exports neither its text nor a guessed code, and the next explicit attempt cannot inherit the prior code.

- Optional connection statistics label their sampling window and measured-payload scope; unavailable latency is not presented as zero. Owner aggregates expose averages/minimum/maximum without account/device identifiers.

- A forced control or relay renewal failure leaves a downloadable client log containing the call ID, stage, elapsed time and subsequent unexpected closure.
- Reload clears the client journal; joining another call retains earlier events up to the 500-event bound.
- Expired server attendance and explicit client departure produce distinct structured events without weakening authority deadlines.
- The deployed control/media Workers emit structured events viewable by the operator; raw request/response invocation logging and traces remain disabled.
- Diagnostic exports/logs contain none of the excluded payloads, secrets or personal labels; unavailable causes are not invented.
