Skip to content

Preserve legacy RR-derived vitals and remove accidental scaffolding - #2141

Merged
ryanbr merged 5 commits into
ryanbr:mainfrom
bhelm:feature/rr-followup-cleanup
Sep 12, 2026
Merged

ryanbr merged 5 commits into
ryanbr:mainfrom
bhelm:feature/rr-followup-cleanup

Conversation

@bhelm

@bhelm bhelm commented Sep 12, 2026

Copy link
Copy Markdown

Summary

Follow-up to #2115 and #2123.

The exact-window legacy WHOOP 5 snapshot now preserves the remaining stored nightly values that are exclusively derived from R-R:

  • respiratory rate (respRateBpm)
  • SDNN (avgSdnn)

They use the same existing guard as HRV/Recovery and are released by the same labelled-transport boundary, including when the labelled transport is too sparse to score. No R-R rows are converted or relabelled, and the general upsert behavior is unchanged. A review of every stored DailyMetric field found no other value exclusively derived from nightly R-R.

This PR also removes the repository scaffolding accidentally included in #2115:

  • all three okf/ files
  • all eight openspec/ files, including openspec/config.yaml, the German archived change, and the published spec

The product implementation, Swift/Kotlin tests, cross-platform source contract, and maintainer-derived parity data from #2115 remain.

Verification

  • Red before fix: cross-platform source contract failed on both Swift and Kotlin for the missing fields.
  • Red before fix: Android upgrade/restore lifecycle assertion failed for respiratory rate.
  • Green after fix: cross-platform source contract 2/2.
  • Green after fix: Android WHOOP 5 lifecycle/store class 19/19.
  • Integrated rerun of the same checks passed.
  • git ls-files okf openspec is empty.
  • Two independent final reviews found no P0, P1, or P2 issues.

Test boundary

The current Linux environment cannot compile the Swift app/tests, so GitHub CI must provide the Apple build and test evidence. This widening has not yet been exercised with a real v11.5 .noopbak on physical WHOOP 5 hardware; it extends the same preservation mechanism already hardware-tested for HRV/Recovery.

@ryanbr

ryanbr commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Thanks @bhelm. Reviewed on a checkout rather than from the description, and the substance holds up.

The red check is not yours

parity-governance fails here, and it fails identically on main. Verified in a pristine clone, since a local .build/ directory inflates this scanner and my first attempt reported two failures where a clean tree reports one:

AssertionError: {... 'functions': 4308, 'sha256': 'b1257bab...'} != {... 'functions': 4310, 'sha256': '4e1aaa36...'}

The twin map's authority pins 4308 functions; the current derivation gives 4310. Same numbers and same hashes on main, on this branch, and at 0b0d206 before today's merges.

It surfaced on your PR only because the gate is path-filtered to Tools/parity_* and Tools/tests/**, and you touched the contract test. The two functions were added by something merged after the authority was last re-derived, by a PR that touched none of those paths, so the gate never ran on it. That is the failure mode the path filter has, and it lands on whoever next edits those files.

Nothing for you to do. I will re-derive the authority on main, and this should go green on a rebase.

Verified independently

  • "no other stored field is exclusively derived from nightly R-R" holds. Walking DailyMetric: avgHrv and recovery were already covered, respRateBpm and avgSdnn are the two you add, and every remaining field traces to HR, optical, thermistor, accelerometer or the hypnogram.
  • The scaffolding is genuinely unreferenced. okf/ and openspec/ are 11 files on main and appear in no workflow, tool, doc or CONTRIBUTING entry. Removing them is right.
  • Tools/tests/test_rr_legacy_preservation_contract.py passes 2/2 here.
  • The parity ledger's own findings check is clean on your branch: counts unchanged.
  • Swift and Kotlin move together, same guard, same fresh.avgHrv == nil precondition, so the four cells are released by one boundary rather than drifting apart.

One nuance, not a blocker

respRateBpm is not always R-R-derived: AnalyticsEngine prefers a device-measured rate when the night has one, today the Oura ring's 0x6A breath. Within this gate's scope the claim is safe, since the snapshot only applies where the day's owner is a strict WHOOP 5 R-R source and a WHOOP 5 carries no raw respiration ADC, so RSA is the only producer.

Worth a sentence in the snapshot's doc all the same. A later reader who widens the gate past WHOOP 5, or a day whose stored respiration arrived from another brand under the computed id, would meet a preserved value that was never R-R-derived. Preservation never fabricates, so the risk is a stale value rather than a wrong one, but the reasoning is currently only true because of a device fact stated elsewhere.

Still a draft

Leaving it to you to mark ready. The hardware boundary you name is the right one: this widens a mechanism already hardware-tested for HRV and Recovery, and respiratory rate was the specific gap reported in #2123.

@ryanbr
ryanbr marked this pull request as ready for review September 12, 2026 12:25
@ryanbr
ryanbr merged commit 44434b1 into ryanbr:main Sep 12, 2026
8 checks passed
ryanbr added a commit that referenced this pull request Sep 12, 2026
The suite is path filtered to Tools/ on both triggers, but one of its
tests is not about the scanner at all: the repository acceptance asserts
the checked-in authority still reproduces from the CURRENT product
source. Product changes are what invalidates it, and product changes are
exactly what both filters exclude. So main could carry stale authority
indefinitely with nothing red, until some later PR touched Tools/ and
inherited the whole accumulated drift.

Four staleness events now, three of them landing on an outside
contributor's PR that caused none of it. Tonight was #2141: the same 4308
against 4310 and the same hashes failing on main, on that branch, and at
0b0d206 before any of today's merges.

A daily run on main finds it within a day, where re-deriving is a
two-minute maintainer action.

Deliberately a schedule rather than an unfiltered push. My first attempt
removed the push filter, and the suite's own
test_core_tools_filter_covers_every_governance_tool_path caught it: that
test pins BOTH filters and forbids product globs, so not running on
product source is a deliberate invariant rather than an oversight. A
periodic check keeps it true. Both path lists are therefore untouched and
that test passes unchanged.

This does NOT make drift repairable. --refresh-derived still fails
"migration required" because the checked-in authority cannot be
reproduced from the base, so the only route remains deleting both
snapshots and re-bootstrapping, which no contributor can do. All this
does is make the maintainer the one who finds out.
ryanbr added a commit that referenced this pull request Sep 12, 2026
…2142)

The suite is path filtered to Tools/ on both triggers, but one of its
tests is not about the scanner at all: the repository acceptance asserts
the checked-in authority still reproduces from the CURRENT product
source. Product changes are what invalidates it, and product changes are
exactly what both filters exclude. So main could carry stale authority
indefinitely with nothing red, until some later PR touched Tools/ and
inherited the whole accumulated drift.

Four staleness events, three of them landing on an outside contributor's
PR that caused none of it. The last was #2141: the same 4308 against 4310
and the same hashes failing on main, on that branch, and at 0b0d206
before any of that day's merges.

A daily run on main finds it within a day, where re-deriving is a
two-minute maintainer action.

Deliberately a schedule rather than an unfiltered push. The first attempt
removed the push filter and the suite's own
test_core_tools_filter_covers_every_governance_tool_path rejected it:
that test pins BOTH filters and forbids product globs, so not running on
product source is a deliberate invariant rather than an oversight. Both
path lists are therefore untouched and that test passes unchanged.

This does NOT make drift repairable. --refresh-derived still fails
"migration required" because the checked-in authority cannot be
reproduced from the base, so the only route remains deleting both
snapshots and re-bootstrapping, which no contributor can do. All this
does is make the maintainer the one who finds out.
@bhelm
bhelm deleted the feature/rr-followup-cleanup branch September 15, 2026 22:35
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.

2 participants