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.
This commit is contained in:
155
doc/share-extension-app-group-audit.md
Normal file
155
doc/share-extension-app-group-audit.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# 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.share` → `group.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:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<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)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<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
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// 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.
|
||||||
Reference in New Issue
Block a user