Files
crowd-funder-for-time-pwa/doc/share-extension-app-group-audit.md
Jose Olarte III 4fc30562fb docs(ios): add App Group configuration audit for share extension
Document App/extension entitlements, bundle IDs, teams, and signing,
flagging a code-vs-entitlement App Group ID mismatch (code uses
group.app.timesafari.share while entitlements grant
group.app.trentlarson.timesafari.share) that breaks shared storage.
2026-06-25 17:48:53 +08:00

9.6 KiB

iOS App Group Configuration Audit

Generated: 2026-06-25 17:31:15 PST

Scope

Static inspection of App Group configuration for the App target and the TimeSafariShareExtension target: entitlements, capabilities, bundle identifiers, Debug/Release build settings, and signing. No code was modified.

Files Inspected

File Role
ios/App/App/App.entitlements App target App Group declaration
ios/App/TimeSafariShareExtension/TimeSafariShareExtension.entitlements Extension App Group declaration
ios/App/App.xcodeproj/project.pbxproj Bundle IDs, teams, signing, entitlement linkage
ios/App/App/SharedImageUtility.swift App Group identifier used by main app code
ios/App/TimeSafariShareExtension/ShareViewController.swift App Group identifier used by extension code

CRITICAL FINDING — Code vs Entitlements App Group Mismatch

The entitlements and the Swift source declare different App Group identifiers:

Location App Group identifier
App.entitlements group.app.trentlarson.timesafari.share
TimeSafariShareExtension.entitlements group.app.trentlarson.timesafari.share
SharedImageUtility.swift (appGroupIdentifier) group.app.timesafari.share
ShareViewController.swift (appGroupIdentifier) group.app.timesafari.share

The runtime code targets group.app.timesafari.share, but neither target is entitled to that group — both entitlements now grant group.app.trentlarson.timesafari.share.

This is an uncommitted change: git diff shows both entitlements were just changed from group.app.timesafari.sharegroup.app.trentlarson.timesafari.share, while the Swift code still uses the old value. Before this edit the code and entitlements matched; after it they do not.

Runtime Consequences

  • FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.app.timesafari.share") returns nil (the app is not entitled to that group). The extension's storeImageData aborts via guard let containerURL → image file is never written; the main app's reads return nil.
  • UserDefaults(suiteName: "group.app.timesafari.share") does not resolve to the shared, entitled suite. Writes fall back to each process's own preferences domain, so the extension's keys (sharedPhotoFilePath, sharedPhotoShareId, shareExtensionLastStart, sharedPhotoReady) are not visible to the main app.

Net effect: the entire share-target handoff via the App Group breaks while this mismatch exists. This is the most likely root cause of "App Group UserDefaults writes failing."

Note: This affects both Debug and Release (the entitlements have no per-configuration variants), not Debug only.


Direct Answers

Do both targets declare the same App Group?

Yes — the two entitlements files match each other. Both App.entitlements and TimeSafariShareExtension.entitlements declare exactly:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.app.trentlarson.timesafari.share</string>
</array>

However, the code does not match the entitlements (see Critical Finding). So "same App Group" is true at the entitlement level, false at the entitlement-vs-code level.

Are there any Debug vs Release differences?

Entitlements / App Group: No. A single entitlements file per target applies to both configurations; the App Group string is identical in Debug and Release (group.app.trentlarson.timesafari.share).

Bundle identifiers: Yes — they differ by configuration:

Target Debug Release
App app.trentlarson.timesafari app.timesafari
Extension app.trentlarson.timesafari.TimeSafariShareExtension app.timesafari.TimeSafariShareExtension

(The Debug bundle IDs were just changed from the app.timesafari* form per git diff.)

Development team: Yes — differs by configuration (see next answer).

In both configurations the extension bundle ID is correctly nested under the app bundle ID, which is required for an app extension.

Are there any team-ID differences that could affect App Group access?

Configuration App team Extension team Match?
Debug 7XVXYPEQYJ 7XVXYPEQYJ same
Release GM3FS5JQPH GM3FS5JQPH same
  • Within each configuration, both targets use the same team — this is the condition required for two targets to share an App Group, and it is satisfied.
  • Across configurations the teams differ (Debug 7XVXYPEQYJ vs Release GM3FS5JQPH). The Debug team was just changed from GM3FS5JQPH per git diff.

Implications:

  1. The App Group container is namespaced by Team ID at runtime ($(TeamID).group...). A Debug install (team 7XVXYPEQYJ) and a Release install (team GM3FS5JQPH) use different physical containers and cannot share data with each other. This is normal and only matters if you expect data continuity between Debug and Release builds.
  2. With Automatic signing, the App Group group.app.trentlarson.timesafari.share must be registered/enabled for both teams. If it is not provisioned under the Debug team 7XVXYPEQYJ, automatic signing of the Debug build can fail to include the App Group entitlement (or fail to sign), which would also break App Group access in Debug.

Are there signing/entitlement mismatches that could cause App Group UserDefaults writes to fail in Debug builds?

Yes. In order of severity:

  1. (Primary) Code/entitlement group-ID mismatch. Code uses group.app.timesafari.share; entitlements grant group.app.trentlarson.timesafari.share. The code's group is not entitled, so shared UserDefaults/container access fails. Affects Debug and Release.

  2. (Debug-specific risk) App Group provisioning under the Debug team. Debug now signs with team 7XVXYPEQYJ (changed from GM3FS5JQPH). Under Automatic signing, if group.app.trentlarson.timesafari.share is not enabled for team 7XVXYPEQYJ, the Debug build's App Group entitlement may not be granted, causing writes to silently fall back to the local domain.

  3. (Consistency) Bundle-ID change accompanying the team change. Debug bundle IDs changed to app.trentlarson.timesafari*. App Groups don't have to match bundle IDs, so this is not a direct cause, but combined with the new team it means Debug provisioning is a distinct profile/identifier set that must independently carry the App Group capability.

No mismatch was found between the two entitlement files themselves, and no per-configuration entitlement override exists.


Detailed Configuration

Entitlements (identical content in both files)

<key>com.apple.security.application-groups</key>
<array>
    <string>group.app.trentlarson.timesafari.share</string>
</array>

CODE_SIGN_ENTITLEMENTS linkage (both Debug and Release):

Target Entitlements path
App App/App.entitlements
Extension TimeSafariShareExtension/TimeSafariShareExtension.entitlements

Bundle Identifiers, Teams, Signing (project.pbxproj)

Setting App Debug App Release Ext Debug Ext Release
PRODUCT_BUNDLE_IDENTIFIER app.trentlarson.timesafari app.timesafari app.trentlarson.timesafari.TimeSafariShareExtension app.timesafari.TimeSafariShareExtension
DEVELOPMENT_TEAM 7XVXYPEQYJ GM3FS5JQPH 7XVXYPEQYJ GM3FS5JQPH
CODE_SIGN_STYLE Automatic Automatic Automatic Automatic
CODE_SIGN_ENTITLEMENTS App/App.entitlements same TimeSafariShareExtension/...entitlements same
App Group (from entitlements) group.app.trentlarson.timesafari.share same same same

App Group Identifier Used in Code

// SharedImageUtility.swift:13  and  ShareViewController.swift:13
private let appGroupIdentifier = "group.app.timesafari.share"   // ← does NOT match entitlements

Recommendations (no code changed)

  1. Resolve the group-ID mismatch. Either revert the entitlements back to group.app.timesafari.share, or update the two Swift constants to group.app.trentlarson.timesafari.share. Both sides must use one identical string.
  2. Confirm App Group provisioning per team. Ensure group.app.trentlarson.timesafari.share (whichever string is chosen) is enabled for both 7XVXYPEQYJ (Debug) and GM3FS5JQPH (Release) so Automatic signing includes the capability in both configurations.
  3. Decide whether the Debug↔Release team/bundle-ID split is intentional. If cross-config data continuity is ever expected, note that different Team IDs yield different App Group containers.
  4. Verify at runtime using the existing getShareExtensionDiagnostics() / [ShareTarget] logs: after aligning identifiers, shareExtensionLastStart written by the extension should become readable by the main app.

Conclusion

The two entitlement files agree on the App Group (group.app.trentlarson.timesafari.share) and, within each build configuration, both targets share the same Development Team and consistent nested bundle IDs — the structural requirements for App Group sharing are met. The decisive problem is that the Swift code still references the old group group.app.timesafari.share, which no entitlement grants; this breaks both shared UserDefaults and the shared container in all builds. Secondarily, the recent Debug switch to team 7XVXYPEQYJ means the chosen App Group must be provisioned under that team for Debug App Group access to work under Automatic signing.