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` | `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`.** |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
|
@ -64,8 +65,10 @@ The pass is arithmetic over a few dozen candidates, once per pick.
|
|||
|
||||
`{sessionId, title, probeKey, host, durationS, watchedS, positionS, stalls,
|
||||
droppedFrames, renderedFrames, maxConsecutiveDropped, problemAtS[], promptOutcome,
|
||||
rating?}` plus the player's provenance fields (`forcedAudio`, `sideloadedSubs`,
|
||||
`resumed`, `seeks`, `seekStormMax`, codec/height/bitrate).
|
||||
rating?, ended, lastSeenAt?, playedOn?}` plus the player's provenance fields
|
||||
(`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.**
|
||||
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
|
||||
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
|
||||
Superuser token (same as releases publishing), then filter/sort the collection:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue