Skip to content

Strap log: surface WHERE offloaded rows landed + WHY (stale-RTC "missed sleep", #67)#69

Merged
ryanbr merged 1 commit into
mainfrom
diag-strap-clock-misdate
Jul 8, 2026
Merged

Strap log: surface WHERE offloaded rows landed + WHY (stale-RTC "missed sleep", #67)#69
ryanbr merged 1 commit into
mainfrom
diag-strap-clock-misdate

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Diagnostic instrumentation for #67 ("Missed sleep"). Logging only — no BLE/offload behaviour change.

The bug it exposes

Jenssaibe's strap has a reset RTC (Strap clock: 835d behind wall, newest banked record 2024-03-24). Last night's sleep was banked by the strap with that stale clock, so the offload persisted it under ~2024 — off the recent timeline, so it reads as "missed sleep" (Analytics funnels: no sleep session in the last 14 days).

The root cause: on a reset-RTC strap the offload falls back to an identity clock ref (Backfiller.swift:357, clockRef ?? ClockRef(device: now, wall: now)), which zeroes the offset that FIX #72's correctedWall needs to re-base stale timestamps — so the 2024 stamps are stored verbatim. The log gave no way to see this: it showed the strap was stale and that rows persisted, but not where they landed or what ref decoded them.

What this adds

  • Backfiller — a new session line after the persist summary:
    Backfill: rows landed on 2024-03-24 · clock ref: IDENTITY fallback (GET_CLOCK never correlated) - stale-record correction OFF
    The dates are the actual post-correction landing days (the same night-keys the tally already builds), so a night filed in the past is self-evident. Also emits strap clock Nd behind wall - correction engaged (stale-but-correlated) or clock ref in sync. Captures the decode ref on the first chunk; reset in begin().
  • DebugDataDiagnostics — the existing Strap clock: Nd behind wall line now flags that recent sleep may be filed ~Nd in the past (Missed sleep #67), not just that the alarm is unreliable.
  • Tests — cover the formatter: identity-fallback past-date signature, correlated-stale "correction engaged", in-sync, multi-night UTC range (en_US_POSIX locale so non-Gregorian device calendars don't corrupt the year), and the no-em-dash convention shared with noCursorLine/futureRtcLine.

Scope

This is deliberately just the log. The actual fix (preserve the stale offset for offload re-basing — either capture the pre-SET_CLOCK correlation or derive the offset from GET_DATA_RANGE) touches the BLE clock handshake and needs on-strap validation; it's tracked separately on #67. One instrumented log from Jenssaibe confirms identity-fallback vs post-set-zeroed before we change any BLE behaviour.

Files: Strand/Collect/Backfiller.swift, Strand/BLE/BLEManager.swift, Strand/System/DebugDataDiagnostics.swift, StrandTests/BackfillerSessionTallyTests.swift.

…le-RTC "missed sleep" (#67)

A reset/stale strap RTC banks last night's sleep with a years-old timestamp, so
the offload files it far in the past — off the recent timeline, where it reads as
"missed sleep". The log showed the strap was stale and that rows persisted, but
NOT where those rows landed or what clock reference decoded them, so the misdating
was invisible.

Logging only — no BLE/offload behaviour change:

- Backfiller: new "Backfill: rows landed on <date(s)>" session line, plus the
  effective clock state — "IDENTITY fallback (GET_CLOCK never correlated) -
  stale-record correction OFF", "strap clock Nd behind wall - correction engaged",
  or "clock ref in sync". The dates are the ACTUAL post-correction landing days
  (the same night-keys the persist tally builds), so a night that landed in 2024
  is self-evident. Captures the decode ref on the first chunk; reset in begin().

- DebugDataDiagnostics: the existing "Strap clock: Nd behind wall" line now notes
  recent sleep may be filed ~Nd in the past (#67), not just that the alarm is
  unreliable.

- Tests: cover the new formatter — identity-fallback past-date signature,
  correlated-stale "correction engaged", in-sync, multi-night UTC range, and the
  no-em-dash convention shared with the sibling strap-log lines.

Pairs with the root-cause investigation on #67: on a reset-RTC strap the offload
falls back to an identity clock ref (Backfiller.swift), which zeroes the offset
FIX #72's correctedWall needs, so stale records are stored verbatim. This makes
that failure mode visible before touching the BLE clock handshake.
@ryanbr ryanbr merged commit d31b70f into main Jul 8, 2026
4 checks passed
tanarchytan pushed a commit to tanarchytan/noop that referenced this pull request Jul 8, 2026
The Live Body Console derives every status from activeConnection
(= live.connected && live.bonded). `bonded` carries WHOOP encrypted-bond
semantics and is deliberately never set by the Oura path (ryanbr#69), so a
connected, authenticated, streaming ring fell through every WHOOP branch
to "Radio connected, stream not yet trusted." — even while HR was live.

Add a display-only `ringStreaming` predicate (live.connected &&
live.streamingLiveHR) and route the status layer through it: mode detail,
connection pill, mode badge, mode color, and the Heart rate / Connection
signal-trust tiles now read a ring stream as an active, trusted stream.

activeConnection itself is untouched, so bond-only feature gates (buzz,
alarm, HRV snapshot, encrypted control) still correctly key off the WHOOP
bond. Wear/Worn stats stay gated on activeConnection to avoid a false
"On wrist" until the ring's wear signal is confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tanarchytan pushed a commit to tanarchytan/noop that referenced this pull request Jul 8, 2026
…ecode", not "never" (ryanbr#67 follow-up)

The identity-fallback diagnostic line (ryanbr#69) asserted "(GET_CLOCK never
correlated)", but sessionUsedIdentityRef is captured on the FIRST chunk. In the
normal flow the correlation lands before chunk 1, and in the reset-RTC case it
never lands — both fine. But on a slow link GET_CLOCK can reply late: chunk 1
decodes on the identity fallback, later chunks on the real ref, so "never" is
false. First-chunk capture is still the right choice (it flags that the early
rows landed misdated), only the absolute word overreached — reword to
"(no clock correlation at decode)", true regardless of any later reply. Doc
comment + test assertion updated to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant