diag(oura): raw undecoded history-notification capture → oura-raw-<id…>.jsonl - #937
Conversation
There was a problem hiding this comment.
Makes sense, and the gap is real — the bundle assembler already looks for oura-raw.jsonl, so right now it attaches nothing. Thanks for building the macOS app too; nothing in CI covers that path.
One thing before merge: this file has no size cap. The activity and motion dumps both rotate at 25 MB (#676), and this one needs it more than either — it stores full hex of every notification and deliberately has no dedup, so it grows faster than both. A ~12 MB oura-raw.jsonl already hung the report review sheet once.
Can you lift the rotation from OuraMotionDump? It's about 15 lines and drops straight in. Happy to merge once that's there.
@ryanbr One more thing worth flagging alongside it: #702 (the master "Diagnostics capture" toggle) is arguably just as important here as rotation, for a different reason — rotation bounds the file after it's growing, #702 stops it from being written at all when the user hasn't opted into diagnostics. That matters more for this file than for activityDump/motionDump: it's full undecoded hex of every notification with no dedup, so it's the one that most benefits from not Worth noting: #702 currently wraps activityDump + motionDump only — rawDump didn't exist on main when that PR was built, so it isn't wired into the gate yet. Once #702 merges, OuraRawDump's instantiation in OuraLiveSource.swift:785 will need the same diagnosticsCapture check, or this file stays always-on while its siblings are opt-in. |
…>.jsonl
Adds a RAW capture sidecar complementing the decoded MET/motion dumps: it records
the undecoded history-drain TLV notification bytes exactly as received, so after a
full connect we know precisely which records arrived. A hole in a decoded file can
now be pinned as a decode drop (present in raw, absent in decoded) vs ring-side
(absent in both), instead of being ambiguous.
Motivation: an iOS 9.2.1 field export (real Oura ring, Diagnostics capture on) carried
oura-activity.jsonl but no oura-raw.jsonl — because the raw writer had only ever lived
on a feature branch and was never merged to main. The bundle assembler already lists
oura-raw.jsonl in its attach-if-present set, so on main it silently attaches nothing.
This lands the missing writer.
- OuraRawDumpLine (WhoopStore, pure) + tests: byte-stable {schema,deviceId,utc,iso,hex}
line, contiguous lowercase hex. 4 tests green.
- OuraRawDump (app): append-only writer, no dedup (a re-serve is still evidence the ring
re-sent it; the offline reframer collapses by tag+ring-time).
- OuraLiveSource: rawDump property + tap on both history TLV feed points (secure and
non-secure). Same feedsLive && !deviceId.isEmpty gate as the activity/motion dumps;
the live-HR push is excluded so a night stays bounded.
Diagnostic only, never scored, never read back, safe to delete. Cross-platform: the
decoded Oura sidecars have Android twins, but this raw capture is Apple-only in origin
and is a diagnostic artifact (not stored data / analytics), so it is not parity-gated;
the Android twin is a fast-follow and the only parity-sensitive surface is the pure
OuraRawDumpLine encoder, which will be mirrored byte-for-byte.
Verified: WhoopStore OuraRawDumpLine 4/4 green; Strand macOS app BUILD SUCCEEDED
(app-target Swift — no default CI covers it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AtXcBU1t6Xk1qJhaQEeDx6
ryanbr flagged in review: this sidecar stores full hex of every raw notification with no dedup, so it grows faster than OuraActivityDump or OuraMotionDump (a ~12MB file already hung the report review sheet), yet was the only one of the three with no size cap. Lift the same rotate-past-maxBytes-keep-one-".1" logic used by OuraMotionDump so the corpus stays bounded to ~2x 25MB instead of growing forever.
b6d1d78 to
1f785a7
Compare
ryanbr
left a comment
There was a problem hiding this comment.
Rotation's in and it's the same logic as OuraMotionDump line for line, including the fresh-stat note about
the cached URL size. One commit, nothing else touched.
Worth knowing: the 20 MB figure I mentioned was wrong in the code, not in yours — ReportReviewGate said
20 while both dumps enforce 25. Fixed that in #939, so your 25 now agrees with the comment.
Approving. Still keen on the Android twin when you get to it.
……>.jsonl (ryanbr#937) * diag(oura): raw undecoded history-notification capture → oura-raw-<id>.jsonl Adds a RAW capture sidecar complementing the decoded MET/motion dumps: it records the undecoded history-drain TLV notification bytes exactly as received, so after a full connect we know precisely which records arrived. A hole in a decoded file can now be pinned as a decode drop (present in raw, absent in decoded) vs ring-side (absent in both), instead of being ambiguous. Motivation: an iOS 9.2.1 field export (real Oura ring, Diagnostics capture on) carried oura-activity.jsonl but no oura-raw.jsonl — because the raw writer had only ever lived on a feature branch and was never merged to main. The bundle assembler already lists oura-raw.jsonl in its attach-if-present set, so on main it silently attaches nothing. This lands the missing writer. - OuraRawDumpLine (WhoopStore, pure) + tests: byte-stable {schema,deviceId,utc,iso,hex} line, contiguous lowercase hex. 4 tests green. - OuraRawDump (app): append-only writer, no dedup (a re-serve is still evidence the ring re-sent it; the offline reframer collapses by tag+ring-time). - OuraLiveSource: rawDump property + tap on both history TLV feed points (secure and non-secure). Same feedsLive && !deviceId.isEmpty gate as the activity/motion dumps; the live-HR push is excluded so a night stays bounded. Diagnostic only, never scored, never read back, safe to delete. Cross-platform: the decoded Oura sidecars have Android twins, but this raw capture is Apple-only in origin and is a diagnostic artifact (not stored data / analytics), so it is not parity-gated; the Android twin is a fast-follow and the only parity-sensitive surface is the pure OuraRawDumpLine encoder, which will be mirrored byte-for-byte. Verified: WhoopStore OuraRawDumpLine 4/4 green; Strand macOS app BUILD SUCCEEDED (app-target Swift — no default CI covers it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AtXcBU1t6Xk1qJhaQEeDx6 * diag(oura): rotate oura-raw.jsonl at 25MB like OuraMotionDump ryanbr flagged in review: this sidecar stores full hex of every raw notification with no dedup, so it grows faster than OuraActivityDump or OuraMotionDump (a ~12MB file already hung the report review sheet), yet was the only one of the three with no size cap. Lift the same rotate-past-maxBytes-keep-one-".1" logic used by OuraMotionDump so the corpus stays bounded to ~2x 25MB instead of growing forever. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
……>.jsonl (ryanbr#937) * diag(oura): raw undecoded history-notification capture → oura-raw-<id>.jsonl Adds a RAW capture sidecar complementing the decoded MET/motion dumps: it records the undecoded history-drain TLV notification bytes exactly as received, so after a full connect we know precisely which records arrived. A hole in a decoded file can now be pinned as a decode drop (present in raw, absent in decoded) vs ring-side (absent in both), instead of being ambiguous. Motivation: an iOS 9.2.1 field export (real Oura ring, Diagnostics capture on) carried oura-activity.jsonl but no oura-raw.jsonl — because the raw writer had only ever lived on a feature branch and was never merged to main. The bundle assembler already lists oura-raw.jsonl in its attach-if-present set, so on main it silently attaches nothing. This lands the missing writer. - OuraRawDumpLine (WhoopStore, pure) + tests: byte-stable {schema,deviceId,utc,iso,hex} line, contiguous lowercase hex. 4 tests green. - OuraRawDump (app): append-only writer, no dedup (a re-serve is still evidence the ring re-sent it; the offline reframer collapses by tag+ring-time). - OuraLiveSource: rawDump property + tap on both history TLV feed points (secure and non-secure). Same feedsLive && !deviceId.isEmpty gate as the activity/motion dumps; the live-HR push is excluded so a night stays bounded. Diagnostic only, never scored, never read back, safe to delete. Cross-platform: the decoded Oura sidecars have Android twins, but this raw capture is Apple-only in origin and is a diagnostic artifact (not stored data / analytics), so it is not parity-gated; the Android twin is a fast-follow and the only parity-sensitive surface is the pure OuraRawDumpLine encoder, which will be mirrored byte-for-byte. Verified: WhoopStore OuraRawDumpLine 4/4 green; Strand macOS app BUILD SUCCEEDED (app-target Swift — no default CI covers it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AtXcBU1t6Xk1qJhaQEeDx6 * diag(oura): rotate oura-raw.jsonl at 25MB like OuraMotionDump ryanbr flagged in review: this sidecar stores full hex of every raw notification with no dedup, so it grows faster than OuraActivityDump or OuraMotionDump (a ~12MB file already hung the report review sheet), yet was the only one of the three with no size cap. Lift the same rotate-past-maxBytes-keep-one-".1" logic used by OuraMotionDump so the corpus stays bounded to ~2x 25MB instead of growing forever. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…t lost (#1507) Two reports, one cause. "REST" ellipsised to "R..." under the Today hero, and the three rings did not look the same size. Charge and Effort sized their columns to their own label rows. Rest alone sat in a Box pinned to the ring diameter, because that box anchors the card's source badge - "the badge may grow leftward, but its trailing edge always matches the Rest vessel". So the three columns came out different widths, which reads as rings of different sizes even though one `ring` value feeds all three vessels, and Rest's label had the LEAST room of the three. The tell is that REST is the SHORTEST of the three words: if the labels were simply too long, CHARGE and EFFORT would break first. All three columns now take one shared `col`, derived from the same maxWidth the ring is. The badge keeps the vessel-aligned trailing edge it exists for by insetting exactly the slack the wider column introduces. The label was also paying for a counterweight. #937 wanted the WORD centred on the ring's axis rather than the word-plus-chevron block, and balanced the row with an invisible LEADING twin of the chevron - correct, but it spends a second 14.dp slot plus its gap on nothing. Centring in a Box gets the same axis for free, since the column is symmetric, and the chevron floats at the trailing edge instead of being counterweighted. The word reserves 16.dp each side so it can never run under it. Label room goes from 50.8 to 68.0.dp on a 360.dp screen (+34%), 66.0 to 92.0 on a 432.dp one. The `ring` is capped at the column width and the badge inset clamped at zero. The 90.dp floor exceeds `col` under about 298.dp of hero width - a small phone, a split-screen pane, a foldable's cover display - and an unclamped inset went NEGATIVE there, which Modifier.padding throws on. That would have taken the Today screen down rather than merely looking wrong; it was caught on re-review, having been missed by an arithmetic table that only covered 360-432.dp. Verified against current main rather than the PR's own checks, which predated a dozen merges including one to this same file: trial merge clean, compiles, 4189 tests 0 failures, and #1514's tile caption coexists with this (hero rings and key-metric tiles are different components). NOT verified visually. The truncation is font-scale dependent and the reporter did not confirm their settings, so this may be an incomplete fix rather than a complete one - but it is strictly more room and equal columns, so it cannot regress either symptom. Reported by @mailingjash.
Thanks for approving this PT which will help me to diagnose new tage (such as Spo2) on iOS
Adds a RAW capture sidecar complementing the decoded MET/motion dumps: it records the undecoded history-drain TLV notification bytes exactly as received, so after a full connect we know precisely which records arrived. A hole in a decoded file can now be pinned as a decode drop (present in raw, absent in decoded) vs ring-side (absent in both), instead of being ambiguous.
Motivation: an iOS 9.2.1 field export (real Oura ring, Diagnostics capture on) carried oura-activity.jsonl but no oura-raw.jsonl — because the raw writer had only ever lived on a feature branch and was never merged to main. The bundle assembler already lists oura-raw.jsonl in its attach-if-present set, so on main it silently attaches nothing. This lands the missing writer.
Diagnostic only, never scored, never read back, safe to delete. Cross-platform: the decoded Oura sidecars have Android twins, but this raw capture is Apple-only in origin and is a diagnostic artifact (not stored data / analytics), so it is not parity-gated; the Android twin is a fast-follow and the only parity-sensitive surface is the pure OuraRawDumpLine encoder, which will be mirrored byte-for-byte.
Verified: WhoopStore OuraRawDumpLine 4/4 green; Strand macOS app BUILD SUCCEEDED (app-target Swift — no default CI covers it).