Telemetry contract: a session the app never saw end
Documents the amber-app change that records a film whose TV is switched off with it on screen. session_summary gains ended (closed | unseen), lastSeenAt and playedOn; a session held on disk and never closed is written once at the next launch, with its one health row; a new error row, session_hold_failed, records a hold that could not be written, released or read. Includes the measurement that ruled out a lifecycle hook: on the kids' box ff3bcf, 13 of 13 films in 14 days left no summary, with no callback at their end and 0 of 3572 api_usage rows delivered late. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
db50af0c69
commit
2e5ae66662
1 changed files with 45 additions and 2 deletions
|
|
@ -22,6 +22,7 @@ older than 14 days nightly.
|
||||||
| `player` | `exo_error` | `{host, anime}` — native player error |
|
| `player` | `exo_error` | `{host, anime}` — native player error |
|
||||||
| `player` | `av_delay_applied` | `{audioMs, host}` — user dialed in an audio offset (the "a track falls behind" signal) |
|
| `player` | `av_delay_applied` | `{audioMs, host}` — user dialed in an audio offset (the "a track falls behind" signal) |
|
||||||
| `session` | `session_summary` | one row per playback — see below. **Written only by `SessionFeedbackService._write`.** |
|
| `session` | `session_summary` | one row per playback — see below. **Written only by `SessionFeedbackService._write`.** |
|
||||||
|
| `error` | `session_hold_failed` | `{op, sessionId}` — an open session could not be held on disk; see [below](#a-session-the-app-never-saw-end-ended-unseen) |
|
||||||
| `qa` | `qa_session` | `{sid, lines[], truncated?}` — the QA decision log for one playback (issue #92), off by default |
|
| `qa` | `qa_session` | `{sid, lines[], truncated?}` — the QA decision log for one playback (issue #92), off by default |
|
||||||
| `source` | `source_select` | one row per source sheet — timings and counts, see below |
|
| `source` | `source_select` | one row per source sheet — timings and counts, see below |
|
||||||
| `source` | `source_pick` | **what was chosen and what it beat** — see below |
|
| `source` | `source_pick` | **what was chosen and what it beat** — see below |
|
||||||
|
|
@ -64,8 +65,10 @@ The pass is arithmetic over a few dozen candidates, once per pick.
|
||||||
|
|
||||||
`{sessionId, title, probeKey, host, durationS, watchedS, positionS, stalls,
|
`{sessionId, title, probeKey, host, durationS, watchedS, positionS, stalls,
|
||||||
droppedFrames, renderedFrames, maxConsecutiveDropped, problemAtS[], promptOutcome,
|
droppedFrames, renderedFrames, maxConsecutiveDropped, problemAtS[], promptOutcome,
|
||||||
rating?}` plus the player's provenance fields (`forcedAudio`, `sideloadedSubs`,
|
rating?, ended, lastSeenAt?, playedOn?}` plus the player's provenance fields
|
||||||
`resumed`, `seeks`, `seekStormMax`, codec/height/bitrate).
|
(`forcedAudio`, `sideloadedSubs`, `resumed`, `seeks`, `seekStormMax`,
|
||||||
|
codec/height/bitrate). `ended` and the two after it are explained in
|
||||||
|
[A session the app never saw end](#a-session-the-app-never-saw-end-ended-unseen).
|
||||||
|
|
||||||
- **`watchedS` is elapsed watch-clock time. `positionS` is where playback got to.**
|
- **`watchedS` is elapsed watch-clock time. `positionS` is where playback got to.**
|
||||||
They differ whenever someone seeks, rewatches, or stares at a spinner — a session
|
They differ whenever someone seeks, rewatches, or stares at a spinner — a session
|
||||||
|
|
@ -81,6 +84,46 @@ this collection computed before that date counts Android sessions twice**, and
|
||||||
rows older than the fix still carry the duplicate. `sessionId:isset = true` is the
|
rows older than the fix still carry the duplicate. `sessionId:isset = true` is the
|
||||||
filter that excludes them.
|
filter that excludes them.
|
||||||
|
|
||||||
|
### A session the app never saw end (`ended: unseen`)
|
||||||
|
|
||||||
|
Added 2026-09-13 (amber-app, first in the build after 1.3.0+38). Until then the
|
||||||
|
summary and the amber-api health row were written only when the player closed, and
|
||||||
|
a box whose television is switched off with the film on screen never closes it.
|
||||||
|
Measured on the kids' box `Xiaomi MIBOX4 · ff3bcf`: **13 of 13 films in 14 days
|
||||||
|
left no summary and no health row.** At the end of every one the 5 s position
|
||||||
|
save ran at full rate up to the last 45 s `api_usage` tick and then stopped, and
|
||||||
|
no row from the hide/pause flush arrived, then or at any later launch (0 of 3572
|
||||||
|
`api_usage` rows were delivered late). That box gives the app no callback, so a
|
||||||
|
write on `AppLifecycleState.paused` would have recovered none of them.
|
||||||
|
|
||||||
|
Now both players hold the open session on disk every 10 s
|
||||||
|
(`<appSupport>/open_sessions/<sessionId>.json`). A normal close writes as before
|
||||||
|
and deletes the copy once the summary is in the telemetry spool. The next launch
|
||||||
|
writes whatever is still held, through the same single writer, with:
|
||||||
|
|
||||||
|
| field | meaning |
|
||||||
|
|---|---|
|
||||||
|
| `ended` | `closed`: the player closed and wrote it. `unseen`: a later launch wrote it from the held copy. Absent on older builds. |
|
||||||
|
| `lastSeenAt` | `unseen` only. When the session was last held, device clock, UTC. The row's own `ts` and `created` are the later launch, often the next day. |
|
||||||
|
| `playedOn` | `unseen` only, and only when it differs: the `appVersion gitSha` that played it, because a row is stamped with the build that sends it. |
|
||||||
|
|
||||||
|
Reading an `unseen` row:
|
||||||
|
|
||||||
|
- The counters stop at `lastSeenAt`, at most 10 s before the end, and
|
||||||
|
`promptOutcome` is `notShown`.
|
||||||
|
- It does not say how the session ended (TV switched off, power cut, crash, killed
|
||||||
|
in the background), only that the app did not see it.
|
||||||
|
- It arrives when the box is next started. A complaint made before that start has
|
||||||
|
no row yet.
|
||||||
|
- Its amber-api health row is sent at the same moment, once. A session that was
|
||||||
|
held and then closed normally sends one health row, at the close.
|
||||||
|
- A session killed within its first 10 s was never held and still leaves nothing.
|
||||||
|
|
||||||
|
`kind: error`, `event: session_hold_failed`, `meta: {op, sessionId}` records a
|
||||||
|
hold that could not be written (`put`), let go of (`release`), read back
|
||||||
|
(`unreadable`, the copy is deleted) or listed (`recover`). One row per session per
|
||||||
|
op, so a full disk is one row, not one per tick.
|
||||||
|
|
||||||
## How Claude queries it
|
## How Claude queries it
|
||||||
Superuser token (same as releases publishing), then filter/sort the collection:
|
Superuser token (same as releases publishing), then filter/sort the collection:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue