Repository navigation
Oura: fix #91 on the clean stack - (lost when refactoring the oura ble stack) - #291
Merged
Merged
Conversation
…, drain to bytes_left==0 The 0x11 GetEvents summary was misread on main (the old ryanbr#91 fix never merged): body[0] is events_received (a batch COUNT, not a status - the 2026-07-11 18:53 drain stopped on events==0 with 400,873 bytes STILL LEFT, abandoning the newest data incl. the night's hypnogram), and bytes[2..5] is bytes_left (a remaining-BYTE count, not a cursor - persisting it and comparing across sessions as clocks minted a phantom "ring-time regression" -> reset-to-0 -> full history re-dump on every connect; both morning/evening logs show it byte-for-byte). Package: - parseGetEventsResponse -> (eventsReceived, bytesLeft, moreData) per open_oura's EventBatchSummary; moreData = bytesLeft > 0. - 3 golden tests from the real captured summaries, incl. the exact events==0/bytes_left==400,873 case that must continue. App (OuraLiveSource): - The drain now runs until bytes_left == 0. The response byte-count is NEVER persisted. - The durable resume point (open_oura nextEventToSync) is the newest STORED history sample's ring-time (maxStoredRingTime), advanced only by samples that resolved a REAL anchored time (incl. drained pending-anchor parks; never wall-clock fallbacks), bounded by a plausibility ceiling, and committed only at drain end when it resolves under the current anchor. - Genuine ring reboot detection: a stored sample OLDER than the seek cursor (sawPreResumeData) -> full pull next connect. - Backstop guards only (a healthy drain ends at 0): bytes_left stall (3 flat summaries) and a 300s wall deadline; a forced stop keeps banked forward progress, never resets to 0. - A persisted cursor above the ceiling (pre-fix garbage) resets to a full pull at load. - Log marker [cursor-fix v1] so the running build is identifiable. swift test: 91/91. macOS app builds (BUILD SUCCEEDED). To validate on-device: connect twice - 1st drain should end "caught up (bytes_left 0)" + "resume cursor advanced", 2nd should fetch only the delta (no phantom regression, no full re-dump), and the previous night's sleep data should finally arrive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bj38eZ4JMejA813XhYvMk6
ryanbr
added a commit
that referenced
this pull request
Jul 12, 2026
…tested core (#291 follow-up) The #91 drain guards + resume-cursor logic lived untested inline in OuraLiveSource (app-target BLE) — which is exactly why it silently regressed when the Oura BLE stack was refactored (#291: "lost when refactoring"). Move the DECISIONS into a pure OuraHistoryDrain in OuraProtocol so they're pinned by unit tests, not just on-device observation: - drain-to-zero + stall guard (bytes_left must shrink; stop after 3 flat summaries) + 300s deadline guard, keeping banked progress on a forced stop - stored-ring-time high-water mark + reboot flag (a real sample older than the seek) - resume-cursor commit (advance only if forward AND resolves; reboot -> 0 full pull) - loaded-cursor plausibility-ceiling sanitize OuraLiveSource keeps ALL I/O — anchor resolution, persistence, logging, the historyCursorAdvanced emit — and now delegates every decision to the drain core. Behaviour-preserving: each site maps 1:1 to the extracted logic (verified by diff). Also de-versions the [cursor-fix v1] log marker to [cursor-fix]. 14 new OuraHistoryDrainTests + the existing 91 pass under swift test (105/105, run on Linux — OuraProtocol is pure). OuraLiveSource compile: app-build.
ryanbr
added a commit
that referenced
this pull request
Jul 12, 2026
…tested core (#291 follow-up) (#306) The #91 drain guards + resume-cursor logic lived untested inline in OuraLiveSource (app-target BLE) — which is exactly why it silently regressed when the Oura BLE stack was refactored (#291: "lost when refactoring"). Move the DECISIONS into a pure OuraHistoryDrain in OuraProtocol so they're pinned by unit tests, not just on-device observation: - drain-to-zero + stall guard (bytes_left must shrink; stop after 3 flat summaries) + 300s deadline guard, keeping banked progress on a forced stop - stored-ring-time high-water mark + reboot flag (a real sample older than the seek) - resume-cursor commit (advance only if forward AND resolves; reboot -> 0 full pull) - loaded-cursor plausibility-ceiling sanitize OuraLiveSource keeps ALL I/O — anchor resolution, persistence, logging, the historyCursorAdvanced emit — and now delegates every decision to the drain core. Behaviour-preserving: each site maps 1:1 to the extracted logic (verified by diff). Also de-versions the [cursor-fix v1] log marker to [cursor-fix]. 14 new OuraHistoryDrainTests + the existing 91 pass under swift test (105/105, run on Linux — OuraProtocol is pure). OuraLiveSource compile: app-build.
pipiche38
added a commit
to pipiche38/noop
that referenced
this pull request
Jul 13, 2026
…t, PR ryanbr#325 review) ryanbr is right that the Android twin exists and must not diverge - it carried every bug Swift fixed this cycle. This ports the complete chain, file by file, with the same fixes, the same comments, and JVM twin tests mirroring the Swift suites (same fixtures/values): - Framing.kt: 0x11 summary corrected to open_oura EventBatchSummary (events_received/progress/bytes_left - the old cursor misread WAS the ryanbr#91 full re-dump loop, still live here); 0x13 SyncTime response parse (deterministic anchor pair); lenient parseRecord + ONE-packet-per-notification reassembler (twin of dae3d7a, the phantom-storm fix - the old buffering loop was still live here too). - Commands.kt: syncTime -> u64 seconds LE + tz half-hours (on-device-proven layout; the open_ring token/counter/0xF6 guess never anchored real hardware). - OuraDriver.kt: continuation fetch at the ADVANCED cursor (max=255, open_oura drain_events; the max=0 "ack" caused the same-window re-serve loop); syncTimeAnchorCandidate (raw-ticks vs seconds x10 disambiguation against the persisted cursor) + adoptSyncTimeAnchor (same 2020-2035 plausibility gate); 0x4B routed to the validated phase decode. - EventTags.kt: SLEEP_SUMMARY_B(0x4B) -> SLEEP_PHASE_B, Tier A (twin of dddbe95 - the divergence flagged in the PR ryanbr#325 review). - OuraEvents.kt: envelopeRingTimestamp (feeds the in-session continuation cursor). - Decoders.kt: decodeGreenIBIAmpCandidate (ringverse p_green_ibi_and_amp @0x503960, Tier-B ryanbr#287, log-only). - NEW OuraHistoryDrain.kt: seen/stored two-cursor drain core (ryanbr#91/ryanbr#291) - guards, progress test, reboot detection, cursor commit rules. - NEW HypnogramAssembler.kt: burst grouping + 30s/code backward time-axis reconstruction. - ble/OuraLiveSource.kt: sync_time now sent at streaming (was never sent!); 0x13 anchor handler; quiet-window batch continuation + no-progress stop; drain-end cursor commit (stored-anchored only) + self-chained passes (reboot/deadline backlog); hypnogram burst persistence with park-until-anchor + honest teardown drop + the 0x49 true-sleep- end refinement; 0x71 fixture-capture log with candidate decode; per-session resets. STORED-DATA PARITY: OURA_SLEEP_PHASE rows now get the same reconstructed time axis on both platforms (per-code envelope-time rows were the old Android behavior - the whole night collapsed onto the write moment). Verified: compileFullDebugKotlin BUILD SUCCESSFUL; testFullDebugUnitTest 2647 tests - all com.noop.oura + OuraStreamMapping suites green incl. 30 new/updated twin tests (OuraHistoryDrainTest, HypnogramAssemblerTest, 0x71 round-trip/gates, 0x13/candidate, one-packet reassembler, continuation-shape pins). The 3 remaining failures (AiCoachContextTest, StandardHrSensorFormatTest x2) fail identically on the untouched tree - pre-existing locale/env issues on this machine, not this change. BLE session choreography is inspection-only (no Android device on hand) - same validation ask as the Swift side had before its on-device passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bj38eZ4JMejA813XhYvMk6
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…, drain to bytes_left==0 (ryanbr#291) The 0x11 GetEvents summary was misread on main (the old ryanbr#91 fix never merged): body[0] is events_received (a batch COUNT, not a status - the 2026-07-11 18:53 drain stopped on events==0 with 400,873 bytes STILL LEFT, abandoning the newest data incl. the night's hypnogram), and bytes[2..5] is bytes_left (a remaining-BYTE count, not a cursor - persisting it and comparing across sessions as clocks minted a phantom "ring-time regression" -> reset-to-0 -> full history re-dump on every connect; both morning/evening logs show it byte-for-byte). Package: - parseGetEventsResponse -> (eventsReceived, bytesLeft, moreData) per open_oura's EventBatchSummary; moreData = bytesLeft > 0. - 3 golden tests from the real captured summaries, incl. the exact events==0/bytes_left==400,873 case that must continue. App (OuraLiveSource): - The drain now runs until bytes_left == 0. The response byte-count is NEVER persisted. - The durable resume point (open_oura nextEventToSync) is the newest STORED history sample's ring-time (maxStoredRingTime), advanced only by samples that resolved a REAL anchored time (incl. drained pending-anchor parks; never wall-clock fallbacks), bounded by a plausibility ceiling, and committed only at drain end when it resolves under the current anchor. - Genuine ring reboot detection: a stored sample OLDER than the seek cursor (sawPreResumeData) -> full pull next connect. - Backstop guards only (a healthy drain ends at 0): bytes_left stall (3 flat summaries) and a 300s wall deadline; a forced stop keeps banked forward progress, never resets to 0. - A persisted cursor above the ceiling (pre-fix garbage) resets to a full pull at load. - Log marker [cursor-fix v1] so the running build is identifiable. swift test: 91/91. macOS app builds (BUILD SUCCEEDED). To validate on-device: connect twice - 1st drain should end "caught up (bytes_left 0)" + "resume cursor advanced", 2nd should fetch only the delta (no phantom regression, no full re-dump), and the previous night's sleep data should finally arrive. Claude-Session: https://claude.ai/code/session_01Bj38eZ4JMejA813XhYvMk6 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…tested core (ryanbr#291 follow-up) (ryanbr#306) The ryanbr#91 drain guards + resume-cursor logic lived untested inline in OuraLiveSource (app-target BLE) — which is exactly why it silently regressed when the Oura BLE stack was refactored (ryanbr#291: "lost when refactoring"). Move the DECISIONS into a pure OuraHistoryDrain in OuraProtocol so they're pinned by unit tests, not just on-device observation: - drain-to-zero + stall guard (bytes_left must shrink; stop after 3 flat summaries) + 300s deadline guard, keeping banked progress on a forced stop - stored-ring-time high-water mark + reboot flag (a real sample older than the seek) - resume-cursor commit (advance only if forward AND resolves; reboot -> 0 full pull) - loaded-cursor plausibility-ceiling sanitize OuraLiveSource keeps ALL I/O — anchor resolution, persistence, logging, the historyCursorAdvanced emit — and now delegates every decision to the drain core. Behaviour-preserving: each site maps 1:1 to the extracted logic (verified by diff). Also de-versions the [cursor-fix v1] log marker to [cursor-fix]. 14 new OuraHistoryDrainTests + the existing 91 pass under swift test (105/105, run on Linux — OuraProtocol is pure). OuraLiveSource compile: app-build.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…, drain to bytes_left==0 (ryanbr#291) The 0x11 GetEvents summary was misread on main (the old ryanbr#91 fix never merged): body[0] is events_received (a batch COUNT, not a status - the 2026-07-11 18:53 drain stopped on events==0 with 400,873 bytes STILL LEFT, abandoning the newest data incl. the night's hypnogram), and bytes[2..5] is bytes_left (a remaining-BYTE count, not a cursor - persisting it and comparing across sessions as clocks minted a phantom "ring-time regression" -> reset-to-0 -> full history re-dump on every connect; both morning/evening logs show it byte-for-byte). Package: - parseGetEventsResponse -> (eventsReceived, bytesLeft, moreData) per open_oura's EventBatchSummary; moreData = bytesLeft > 0. - 3 golden tests from the real captured summaries, incl. the exact events==0/bytes_left==400,873 case that must continue. App (OuraLiveSource): - The drain now runs until bytes_left == 0. The response byte-count is NEVER persisted. - The durable resume point (open_oura nextEventToSync) is the newest STORED history sample's ring-time (maxStoredRingTime), advanced only by samples that resolved a REAL anchored time (incl. drained pending-anchor parks; never wall-clock fallbacks), bounded by a plausibility ceiling, and committed only at drain end when it resolves under the current anchor. - Genuine ring reboot detection: a stored sample OLDER than the seek cursor (sawPreResumeData) -> full pull next connect. - Backstop guards only (a healthy drain ends at 0): bytes_left stall (3 flat summaries) and a 300s wall deadline; a forced stop keeps banked forward progress, never resets to 0. - A persisted cursor above the ceiling (pre-fix garbage) resets to a full pull at load. - Log marker [cursor-fix v1] so the running build is identifiable. swift test: 91/91. macOS app builds (BUILD SUCCEEDED). To validate on-device: connect twice - 1st drain should end "caught up (bytes_left 0)" + "resume cursor advanced", 2nd should fetch only the delta (no phantom regression, no full re-dump), and the previous night's sleep data should finally arrive. Claude-Session: https://claude.ai/code/session_01Bj38eZ4JMejA813XhYvMk6 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…tested core (ryanbr#291 follow-up) (ryanbr#306) The ryanbr#91 drain guards + resume-cursor logic lived untested inline in OuraLiveSource (app-target BLE) — which is exactly why it silently regressed when the Oura BLE stack was refactored (ryanbr#291: "lost when refactoring"). Move the DECISIONS into a pure OuraHistoryDrain in OuraProtocol so they're pinned by unit tests, not just on-device observation: - drain-to-zero + stall guard (bytes_left must shrink; stop after 3 flat summaries) + 300s deadline guard, keeping banked progress on a forced stop - stored-ring-time high-water mark + reboot flag (a real sample older than the seek) - resume-cursor commit (advance only if forward AND resolves; reboot -> 0 full pull) - loaded-cursor plausibility-ceiling sanitize OuraLiveSource keeps ALL I/O — anchor resolution, persistence, logging, the historyCursorAdvanced emit — and now delegates every decision to the drain core. Behaviour-preserving: each site maps 1:1 to the extracted logic (verified by diff). Also de-versions the [cursor-fix v1] log marker to [cursor-fix]. 14 new OuraHistoryDrainTests + the existing 91 pass under swift test (105/105, run on Linux — OuraProtocol is pure). OuraLiveSource compile: app-build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I forgot that fix when refactoring the BLE Oura stack
App (OuraLiveSource):
swift test: 91/91. macOS app builds (BUILD SUCCEEDED). To validate on-device: connect twice - 1st drain should end "caught up (bytes_left 0)" + "resume cursor advanced", 2nd should fetch only the delta (no phantom regression, no full re-dump), and the previous night's sleep data should finally arrive.
Both logs are a textbook pass — the fix works exactly as designed:
1st connect:
2nd connect (2 min later):