M9 — Device diagnostics & debug reporting (connectivity + UPnP desync) #104

Merged
bvandeusen merged 5 commits from dev into main 2026-06-29 19:24:16 -04:00
Owner

Toggleable per-account device diagnostics: remotely enable debug mode on an account → its phone(s) stream a timeseries of device state → admin timestamp view with copy/export for handing slices off for analysis. Targets two unsolved problem classes: roaming/poor-data recovery and locked-phone UPnP/Sonos desync (prime suspect: Doze killing the 1Hz poll loop).

Server

  • Migration 0036: diagnostic_events + users.debug_mode_enabled.
  • POST /api/diagnostics (gated batch ingest, no-op 204 when off), GET /api/admin/diagnostics (account/device/kind/time-window filters, export-sized paging), /diagnostics/devices, PUT /api/admin/users/{id}/debug-mode. debug_mode_enabled exposed on /api/me.
  • 30-day retention via the existing gc worker.

Android

  • DiagnosticsReporter taps connectivity, server-health, UPnP drops/player-state/route, power (Doze/battery-opt/screen), app fg/bg, plus a heartbeat snapshotting Sonos-vs-local position.
  • DiagnosticsUploader drains a Room ring buffer on tick / health-recovery / sign-in — deliberately NOT the MutationQueue so events survive the dead zone being debugged.
  • Remote gate via /api/me refresh; per-device OFF switch in Settings. Room schema 6→7.

Web admin

  • /admin/diagnostics: account+device+kind+time-window → chronological timeline, remote debug-mode toggle, Copy-JSON / Download-NDJSON export.

Scribe milestone M9 (#119). All three CI lanes green on dev (server 1671, android 1679, web 1684).

🤖 Generated with Claude Code

Toggleable per-account device diagnostics: remotely enable debug mode on an account → its phone(s) stream a timeseries of device state → admin timestamp view with copy/export for handing slices off for analysis. Targets two unsolved problem classes: roaming/poor-data recovery and locked-phone UPnP/Sonos desync (prime suspect: Doze killing the 1Hz poll loop). ## Server - Migration 0036: `diagnostic_events` + `users.debug_mode_enabled`. - `POST /api/diagnostics` (gated batch ingest, no-op 204 when off), `GET /api/admin/diagnostics` (account/device/kind/time-window filters, export-sized paging), `/diagnostics/devices`, `PUT /api/admin/users/{id}/debug-mode`. `debug_mode_enabled` exposed on `/api/me`. - 30-day retention via the existing gc worker. ## Android - `DiagnosticsReporter` taps connectivity, server-health, UPnP drops/player-state/route, power (Doze/battery-opt/screen), app fg/bg, plus a heartbeat snapshotting Sonos-vs-local position. - `DiagnosticsUploader` drains a Room ring buffer on tick / health-recovery / sign-in — deliberately NOT the MutationQueue so events survive the dead zone being debugged. - Remote gate via `/api/me` refresh; per-device OFF switch in Settings. Room schema 6→7. ## Web admin - `/admin/diagnostics`: account+device+kind+time-window → chronological timeline, remote debug-mode toggle, Copy-JSON / Download-NDJSON export. Scribe milestone M9 (#119). All three CI lanes green on dev (server 1671, android 1679, web 1684). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 5 commits 2026-06-29 19:24:07 -04:00
feat(server/diagnostics): device debug-reporting ingest + admin timeline + retention (M9)
test-go / test (push) Successful in 37s
test-go / integration (push) Successful in 4m44s
4ed831d9c3
New diagnostic_events table + per-account users.debug_mode_enabled flag.
When an account's flag is on, its client(s) POST a batch timeseries of
connectivity / UPnP-sync / power / lifecycle events to /api/diagnostics
(no-op 204 when off, kind whitelist mirrors the CHECK constraint).

Admin surface: GET /api/admin/diagnostics (optional account/device/kind/
time-window filters, RFC3339-or-epoch-ms, export-sized paging) + a
/diagnostics/devices overview + PUT /api/admin/users/{id}/debug-mode to
flip an account remotely while a bug is live. debug_mode_enabled is now
exposed on /api/me (client gate) and the admin user views.

Retention: a 30-day gc-worker sweep (GcPruneDiagnostics), keyed on the
server clock so a skewed device clock can't keep rows alive.

Refs Scribe M9 (#119), tasks #1172 #1173.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
feat(android+web/diagnostics): on-device debug reporter + admin timeline (M9)
test-web / test (push) Failing after 11s
android / Build + lint + test (push) Failing after 1m19s
4d42e298dd
Android: a gated DiagnosticsReporter taps connectivity, server-health,
UPnP drops/player-state/route, power (Doze/battery-opt/screen), and
app fg/bg, plus a heartbeat snapshotting Sonos-vs-local position — the
locked-phone desync signal. Events buffer in a Room ring buffer
(deliberately NOT the MutationQueue: high-volume best-effort telemetry
that must survive the dead zone being debugged) and DiagnosticsUploader
drains them on a tick / health-recovery / sign-in.

Gating: the account flag (users.debug_mode_enabled) reaches the device
via a new /api/me refresh in AuthController; a per-device local OFF
switch lives in Settings. Reporter runs only when enabled && !optOut;
disabling drops the unsent buffer.

Web admin: /admin/diagnostics — pick account+device+kind+time-window,
see a chronological timeline, flip an account's debug mode remotely, and
Copy-JSON / Download-NDJSON the slice for analysis.

Room schema 6→7 (new diagnostic_events table + auth_session.diagnosticsOptOut;
pre-v1 destructive fallback).

Refs Scribe M9 (#119), tasks #1174 #1175 #1176 #1177.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
fix(android/diagnostics): keep uploader drain within ReturnCount gate
android / Build + lint + test (push) Failing after 2m28s
8a58f07237
drainSafe/drain each had 3 returns (detekt ReturnCount ≤ 2). Collapse the
guard clauses and convert drain's loop to a `more` flag — same behavior,
zero/two returns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
fix(diagnostics): StateFlow distinctUntilChanged build error + AdminUser test fixtures
test-web / test (push) Failing after 33s
android / Build + lint + test (push) Successful in 3m29s
bffa5b28bd
- DiagnosticsReporter.collectServerHealth: drop distinctUntilChanged() on
  networkStatus.state (StateFlow is already distinct; the deprecation
  warning is a hard error under allWarningsAsErrors).
- web users.test.ts: add debug_mode_enabled to the alice/bob AdminUser
  fixtures now that the field is required on the type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
bvandeusen merged commit 23a82fb38d into main 2026-06-29 19:24:16 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#104