Repository navigation
Conversation
Duplicate GENERATION (not just the #899 heal): the ring serves >1 decode of one night and PK=(deviceId,startTs) mints a row per 0x49 onset. @pipiche38 measured two modes over four nights — re-anchor (same hypnogram, two onsets) and partial-drain (a shorter divergent decode with its own anchor). This lands the PURE, testable core of the fix (option 2, the one her data picks 4/4 vs the grid's 1/4): - OuraSessionReconciler (Swift WhoopStore + Kotlin com.noop.oura twin): noonAnchoredSleepDay(startTs, tz) grouping + reconcile(new, existing) that treats overlap-or-gap<60min as the same session and keeps the FULLER decode (the completeness guard that adjudicates the partial-drain mode). - Tests transcribing all four nights + the 08-09 nap: mode-1 collapse, mode-2 guard, the 30-min-grid straddle that proximity still collapses, the fragment ending before midnight that noon-anchor groups (wake-day would not), and the nap that shares the sleep-day yet stays distinct. Kotlin 6/6 green locally; Swift runs under swift-packages. FOLLOW-UP (hardware-gated on @pipiche38's ring, per the issue): wire this into OuraLiveSource.persistHypnogramBurst — read the day's sessions at persist time (the read that also lets the cross-connection check see duplicates a per-connection list cannot) and insert/skip/replace on the decision. That's the write-only-source read dependency; it needs a Q3 hardware run to confirm one row across two connections.
…h platforms) At persist time (SourceCoordinator persist closure, Swift + Kotlin) read the day's already-stored sessions via candidateReadWindow and run the reconciler: insert a distinct session, skip when a fuller same-night row is stored, or delete the earlier/shorter duplicates and upsert the fuller one. The DB read also sees cross-connection duplicates the per-connection diagnostic can't. - Delete uses the tombstone-free row delete on both sides (Swift store.deleteSleepSession; Kotlin repo.deleteSleepSessionRowOnly), matching the #899 heal so a superseded duplicate can't suppress the survivor's re-detection. - A true re-persist (same startTs) is excluded from candidates so it upserts idempotently rather than delete+insert (no churn, keeps userEdited). - Kotlin reconciler timestamps moved Int -> Long (SleepSession.startTs is Long; avoids the 2038 32-bit overflow and matches the Swift 64-bit Int). Kotlin reconciler tests still 6/6 green. Runtime is hardware-gated on @pipiche38's ring (Q3: one row across two connections). Swift app-target validated via the app-build gate; reconciler via swift-packages; Android via local testFullDebugUnitTest.
Owner
Author
|
Closing — superseded. The persist-time reconciler here had a TOCTOU race (non-atomic read-modify-write on the BLE thread) and a Split into:
|
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.
Draft — the pure, testable core of #1284 residual 3 (duplicate generation), landing the design @pipiche38's four-night corpus selected. Wiring into the live path is the hardware-gated follow-up (below), matching this issue's draft → hardware-run cadence.
What this is
OuraSessionReconciler(SwiftWhoopStore+ byte-identical Kotlincom.noop.ouratwin) — pure, no store/BLE:noonAnchoredSleepDay(startTs, tz)— groups a ring's sessions bydate(startTs_local + 12h).reconcile(new, existing)— within a sleep-day, overlap-or-gap<60min ⇒ same session; keep the fuller decode (the completeness guard). Returnsinsert/skip/replace(supersededStartTs).Why this design (from the corpus, not a guess)
Validation
OuraSessionReconcilerTest) — every fixture is one of @pipiche38's nights.WhoopStoreTests, macos-15). Pure package code — no app-target Swift, so no app-build gate needed.Follow-up (needs @pipiche38's ring — Q3)
Wire the reconciler into
OuraLiveSource.persistHypnogramBurst: at persist time read the day's already-stored sessions (noonAnchoredSleepDaywindow) andinsert/skip/replaceon the decision. That read is the same one that lets the cross-connection check see duplicates a per-connection in-memory list can't — so it also closes the #1297 diagnostic blind spot. It's the write-only-source read dependency we discussed; it can't be unit-tested (BLE + store) and needs a hardware run confirming one row across two connections at the correct window.Relates to #1284. Design + measurements: @pipiche38 (thank you).