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:
| constant | value | correct |
|---|---|---|
CBManagerState unknown…poweredOn | 0–5 | ✓ |
LAPolicyDeviceOwnerAuthenticationWithBiometrics | 1 | ✓ |
kLAErrorUserCancel | −2 | ✓ |
kPDFDisplaySinglePageContinuous | 1 | ✓ |
kPDFDisplayDirectionVertical | 0 | ✓ |
UIModalPresentationFullScreen | 0 | ✓ |
UIButtonTypeSystem | 1 | ✓ |
UIControlEventTouchUpInside | 1<<6 | ✓ |
AVAudioQualityHigh | 0x60 | ✓ |
HKStatisticsOptionCumulativeSum | 1<<4 | ✗ → fixed |
Only the HealthKit one was wrong.
Other decisions
- Type identifiers go through
dlsym, not spelling. EveryHKQuantityTypeIdentifierhappens to equal its own name today; that is not documented and not API. A hand-spelled one that stopped matching would makequantityTypeForIdentifier:answer nil — a refusal blamed on the page'styperather 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? Doublesilently 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.