ReviewOS

craft-native/craft

Four modules gate on an Info.plist proxy for a config flag ios_config.zig now reads

#131
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Five modules gate an action on the presence of an Info.plist key, used as a stand-in for a config.enable* flag Zig could not read. Each says so, and each was right when it was written. ios_config.zig reads those flags exactly now, and ios_dispatch.offerToModules refuses a gated action before any module runs — so the proxies are a second, weaker gate sitting behind the real one.

Two of these — the ones that are only stale prose — are also worth fixing: nine module headers still tell the reader their gate "has no Zig mirror" and their actions are "served unconditionally", which has not been true since ios_config.zig landed.

Proxies that are now redundant

modulekeywhat it stands in forthe module's own words
bridge_mobile_contactpicker.zig (:635)NSContactsUsageDescriptionenableContacts"not a precondition of the API … purely as evidence of the flag"
bridge_mobile_calendar.zig (:908)NSCalendarsUsageDescriptionenableCalendarfor deleteCalendarEvent, "only evidence of the flag — which is why it is still checked"
bridge_mobile_motion.zig (:392)NSMotionUsageDescriptionenableMotionSensors"an exact proxy for it"
bridge_mobile_imagepicker.zig (:570)NSPhotoLibraryUsageDescriptionenableCamera || enableVideoRecording"the closest proxy" — explicitly not exact

gateFor maps every one of these actions today: pickContact/getContacts/addContact → .contacts, the three calendar actions → .calendar, startMotionUpdates → .motion_sensors, startListening → .speech_recognition, pickImage/openCamera → .camera.

Why this is not only tidying

The proxy and the flag can disagree, and only one direction is caught:

  • flag off, key present — the real gate refuses first. No divergence.
  • flag on, key absent — the real gate passes and the module refuses anyway. A false refusal, of an action the spec would serve.

For the exact proxies, packages/ios/src/index.ts writes the key iff the flag, so a generated app keeps them in step. A hand-edited Info.plist, or any app not built through the SDK, does not. imagepicker's is not exact even in generated apps: it stands in for a disjunction of two flags with one key.

Not to be touched

The plist checks that are crash guards must stay, and are a different thing entirely — iOS terminates the process when authorization is requested without the key, so these run before the API call and are not gates at all:

  • bridge_mobile_audiorec.zig — NSMicrophoneUsageDescription
  • bridge_mobile_bluetooth.zig — NSBluetoothAlwaysUsageDescription
  • bridge_mobile_speech.zig — both its keys. NSMicrophoneUsageDescription is not an exact proxy anyway (enableSpeechRecognition || enableAudioRecording), and NSSpeechRecognitionUsageDescription is also a precondition: requestAuthorization: without it does not fail, iOS terminates the process. It reads exactly like the four proxies above and is not one — an earlier revision of this issue listed it as redundant, which it is not.
  • bridge_mobile_imagepicker.zig — NSCameraUsageDescription for a .camera source
  • bridge_mobile_location.zig — the two location keys
  • bridge_mobile_calendar.zig — the key on the paths where it is an API precondition, which the module already separates (:902)

Any fix has to keep that distinction, which is why this is an issue rather than a sweep.

The stale headers

Twelve modules claimed a gate has no Zig mirror, no Zig-reachable channel, or that actions are served unconditionally: contactpicker, bgtasks, calendar, filepicker, motion, notifications, notifcancel, securestore, system, location, imagepicker, speech. All were false; the gate table covers every action they name. bridge_mobile_watch.zig makes the same claim and is still true — nothing in gateFor maps sendToWatch, updateWatchContext or isWatchReachable.

The first nine were found by grepping the phrase the first one used; the last three said the same thing in different words and turned up only on a second sweep for the idea rather than the wording. Fixed in #132, which states it once at the enforcement point and leaves each module a pointer.

bridge_mobile_imagepicker.zig was the sharpest of them: it asserted in the present tense that a default-config app hangs the page forever on openCamera and pickImage, which stopped being reachable the moment gateFor mapped those actions.

The same rot in a manifest rather than a header was #134: haptic refused because config.enableHaptics was "not visible to Zig" while gateFor mapped that exact flag. That one is now caught by a build check, because a refusal naming a flag the gate table maps is a contradiction a test can see. These are prose, so nothing catches them.

10 days ago

Implemented and pushed in c66f499. Removed the obsolete contacts, motion, photo-library, and calendar-delete Info.plist proxy gates now that ios_config enforces their real feature flags. The mandatory camera and calendar-access privacy crash guards remain, with regression coverage distinguishing the two classes. Exact-commit CI, SBOM/vulnerability scan, Binary Size, and Benchmark are green.

Sign in to comment on this issue.