ReviewOS

also looking at this

craft-native/craft

iOS: the health query pair — ratchet 31 → 29

#99
Merged glennmichael123 wants to merge feat/ios-health into main
8 files +922 -4
10 days ago

requestHealthAuthorization and getHealthData.

Why two, when the last two pairs had to move whole

saveHealthWorkout stays with the shim, and the split is safe for the opposite reason openPDF/closePDF and the audio pair could not be split. Those shared module state — a controller one stored and the other dismissed, a recorder one built and the other stopped — so splitting left half a state machine in each language.

Nothing flows between these three. The only thing they share is an HKHealthStore, which is a stateless handle: authorization lives in the system database, not in the object, and Apple supports more than one store per process. What saveHealthWorkout costs is a third asynchronous stage — HKWorkout construction, then save:withCompletion:, then an HKWorkoutRouteBuilder chain for the optional locations array, each able to fail after the previous succeeded. That is a round of its own.

A constant I guessed, and a test that pinned the guess

statistics_option_cumulative_sum was 1 << 2 on the first attempt. That is HKStatisticsOptionDiscreteMin, which HealthKit rejects for a cumulative type like step count by raising inside +[HKStatistics _validateOptions:forDataType:]. The fixture caught it as a process abort:

3   HealthKit  -[HKQuantityType(_HKStatisticsSampleTypeConfigurationProviding) supportsStatisticOptions:]
4   HealthKit  +[HKStatistics _validateOptions:forDataType:]
5   HealthKit  -[HKStatisticsQuery queue_validate]

That was luck. Had the wrong bit been a legal option for the type, the query would have computed a minimum and reported it as a sum — a plausible number, silently wrong.

The test I had written asserted 4, and passed, because it pinned the same guess the code made. A constant test is only worth having when its value came from somewhere other than the code it checks. Both constants now cite their SDK headers (HKStatistics.h:42, HKQuery.h:50), and the test additionally rules out the three neighbouring option bits a slip would land on.

On the back of that I re-verified every constant used across the modules written this session against the simulator SDK headers:

constantvaluecorrect
CBManagerState unknown…poweredOn0–5✓
LAPolicyDeviceOwnerAuthenticationWithBiometrics1✓
kLAErrorUserCancel−2✓
kPDFDisplaySinglePageContinuous1✓
kPDFDisplayDirectionVertical0✓
UIModalPresentationFullScreen0✓
UIButtonTypeSystem1✓
UIControlEventTouchUpInside1<<6✓
AVAudioQualityHigh0x60✓
HKStatisticsOptionCumulativeSum1<<4✗ → fixed

Only the HealthKit one was wrong.

Other decisions

  • Type identifiers go through dlsym, not spelling. Every HKQuantityTypeIdentifier happens to equal its own name today; that is not documented and not API. A hand-spelled one that stopped matching would make quantityTypeForIdentifier: answer nil — a refusal blamed on the page's type rather than on this file.
  • Dates are milliseconds, because Swift divides by 1000, so the conversion is wire format. A mistyped date is refused where as? Double silently substitutes the default seven-day window — a wrong number rather than a missing one.
  • A window with no samples is zero, not an error, carrying Swift's ?? 0.

Verification

zig build test green (153/153), zig fmt --check, build-ios and build-ios-simulator clean, pickier . 0 errors.

37 fixture assertions pass on a simulator, one new:

ok: health query answered rather than hanging (i=66 seen 1x)

getHealthData is exercised and must answer — the spec's arm has no else, so an unauthorised HealthKit left the promise pending. requestHealthAuthorization is not exercised: it presents a system sheet and simctl has no HealthKit privacy service. That is the same wall scheduleNotification hits, and the one the microphone service let the audio pair through last round.

8 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.