From 5fdb5d73acc9529837cac90d253e9e3bfc0055bb Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:39:17 -0700 Subject: [PATCH] feat(diagnostics): expand client log attribute registry (#649) Adds the attribute keys the Apple and Android clients need to record startup, network, auth, and playback behavior in diagnostics reports. The four playback keys were already being emitted by the Apple client but were never registered here, so they were silently dropped by this collector and hard-rejected by the hosted one. Registering them makes the canonical registry the superset it was always meant to be. position_seconds is deliberately registered as position_ms (integer): attrValueType has only string and integer, and a millisecond ordinal avoids adding a float type for a single key. launch_type is a string for the same reason, rather than a bool. New keys: playback: session_id, play_method, reason, position_ms lifecycle: phase, duration_ms, outcome, reason, launch_type network: outcome, error_code, attempt Purely additive; no existing key changes type or is removed. TestAttrRegistryStaysInSync keeps the Go map and the JSON in lockstep. Co-authored-by: Claude Opus 5 (1M context) --- .../client-diagnostics/v1/attr-registry.json | 48 +++++++++++++++++++ internal/diagnostics/contract/contract.go | 14 +++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/design/schemas/client-diagnostics/v1/attr-registry.json b/docs/design/schemas/client-diagnostics/v1/attr-registry.json index 25b710b7..d22be55b 100644 --- a/docs/design/schemas/client-diagnostics/v1/attr-registry.json +++ b/docs/design/schemas/client-diagnostics/v1/attr-registry.json @@ -39,6 +39,22 @@ "audio_underruns": { "type": "integer", "description": "Audio underrun count." + }, + "session_id": { + "type": "string", + "description": "Playback session identifier for correlation with operational logs." + }, + "play_method": { + "type": "string", + "description": "Selected playback delivery method such as direct, remux, or transcode." + }, + "reason": { + "type": "string", + "description": "Playback route, interruption, or lifecycle reason." + }, + "position_ms": { + "type": "integer", + "description": "Current playback position in milliseconds." } }, "focus": { @@ -67,12 +83,44 @@ "duration_ms": { "type": "integer", "description": "Request duration in milliseconds." + }, + "outcome": { + "type": "string", + "description": "Request outcome such as success, http_error, transport_error, or cancelled." + }, + "error_code": { + "type": "string", + "description": "Stable error classification for a failed request." + }, + "attempt": { + "type": "integer", + "description": "Attempt ordinal for a retried request." } }, "lifecycle": { "state": { "type": "string", "description": "App lifecycle state." + }, + "phase": { + "type": "string", + "description": "Startup or lifecycle phase identifier." + }, + "duration_ms": { + "type": "integer", + "description": "Elapsed duration for the phase in milliseconds." + }, + "outcome": { + "type": "string", + "description": "Result of the phase, such as success, failure, or skipped." + }, + "reason": { + "type": "string", + "description": "Classification for the phase outcome." + }, + "launch_type": { + "type": "string", + "description": "Launch classification, such as cold or warm." } }, "crash": { diff --git a/internal/diagnostics/contract/contract.go b/internal/diagnostics/contract/contract.go index 6709f049..ff0114be 100644 --- a/internal/diagnostics/contract/contract.go +++ b/internal/diagnostics/contract/contract.go @@ -259,6 +259,10 @@ var attrRegistry = map[string]map[string]attrValueType{ "bitrate_kbps": attrInteger, "dropped_frames": attrInteger, "audio_underruns": attrInteger, + "session_id": attrString, + "play_method": attrString, + "reason": attrString, + "position_ms": attrInteger, }, "focus": { "target": attrString, @@ -269,9 +273,17 @@ var attrRegistry = map[string]map[string]attrValueType{ "path": attrString, "status": attrInteger, "duration_ms": attrInteger, + "outcome": attrString, + "error_code": attrString, + "attempt": attrInteger, }, "lifecycle": { - "state": attrString, + "state": attrString, + "phase": attrString, + "duration_ms": attrInteger, + "outcome": attrString, + "reason": attrString, + "launch_type": attrString, }, "crash": { "fingerprint": attrString,