- Add KordantSpamShieldExtension target to project.yml with proper app-extension type, bundle identifier, and deployment target - Create CallKit + App Group entitlements for SpamShield extension - Move SpamDirectoryService to Sources/Shared for cross-target access - Update app-review-checklist with 5 new technical items (total: 121) - Update rejection-risk-mitigation with extension build integration - Add SpamShield extension details to reviewer notes - Mark Task 24 (push deep links) and Task 28 as complete
239 lines
8.3 KiB
Markdown
239 lines
8.3 KiB
Markdown
# Rejection Risk Mitigation
|
|
|
|
> Kordant iOS App — Pre-submission risk analysis
|
|
> Last updated: 2026-06-02
|
|
|
|
---
|
|
|
|
## High-Risk Areas & Mitigation
|
|
|
|
### Guideline 2.1 — App Completeness
|
|
|
|
**Risk**: Rejection for incomplete features or placeholder content
|
|
**Mitigation**:
|
|
- ✅ Removed "Map integration coming soon" from PropertyDetailView
|
|
- ✅ All buttons are functional (including "Upgrade Plan" → opens billing portal)
|
|
- ✅ No disabled UI elements
|
|
- ✅ All navigation paths tested and working
|
|
- ✅ Empty states provide meaningful feedback (not blank screens)
|
|
|
|
**Residual risk**: LOW — All features are complete and functional
|
|
|
|
---
|
|
|
|
### Guideline 3.1.1 — Payment (Subscription Model)
|
|
|
|
**Risk**: Rejection for not using In-App Purchase for digital subscriptions
|
|
**Mitigation**:
|
|
- ✅ Subscriptions are for **access to server-side monitoring services** (dark web scanning, data broker removal)
|
|
- ✅ Services are not digital content consumed within the app
|
|
- ✅ Billing handled via Stripe Customer Portal (web billing)
|
|
- ✅ "Upgrade Plan" button opens the billing portal
|
|
- ✅ Free tier available with limited features
|
|
- ✅ No external links to purchase digital goods within the app
|
|
- ✅ Documented rationale: service-based subscription, not content subscription
|
|
|
|
**Reference**: Apple's guidance distinguishes between:
|
|
- **Digital content** (magazines, music, games) → must use IAP
|
|
- **Service access** (cloud storage, monitoring, real-world services) → web billing acceptable
|
|
|
|
**Residual risk**: LOW-MEDIUM — Service model is clearly distinct from digital content
|
|
|
|
---
|
|
|
|
### Guideline 4.2 — Minimum Functionality
|
|
|
|
**Risk**: Rejection for being a "wrapper" around a website
|
|
**Mitigation**:
|
|
- ✅ Full native SwiftUI application — not a web wrapper
|
|
- ✅ 5+ distinct service modules with native UI
|
|
- ✅ CallKit integration for spam call filtering (native system feature)
|
|
- ✅ Home screen widgets with real-time data
|
|
- ✅ Siri shortcuts for common actions
|
|
- ✅ Push notification deep linking
|
|
- ✅ Offline data caching and sync
|
|
- ✅ Biometric authentication
|
|
- ✅ Native camera integration for document scanning
|
|
- ✅ Native microphone access for voice enrollment
|
|
|
|
**Residual risk**: VERY LOW — App is clearly a substantial native application
|
|
|
|
---
|
|
|
|
### Guideline 5.1.1 — Data Collection and Storage
|
|
|
|
**Risk**: Rejection for inaccurate or missing privacy disclosures
|
|
**Mitigation**:
|
|
- ✅ Privacy manifest (`PrivacyInfo.xcprivacy`) present in main app and widget extension
|
|
- ✅ All data types accurately declared with correct linkage
|
|
- ✅ `NSPrivacyTracking` set to `false` — no cross-app tracking
|
|
- ✅ API access reasons declared (FileTimestamp, DiskSpace, UserDefaults)
|
|
- ✅ ATT prompt shown before any analytics
|
|
- ✅ Analytics respects user consent
|
|
- ✅ No third-party tracking domains
|
|
- ✅ All permission usage descriptions accurate and localized
|
|
|
|
**Residual risk**: LOW — Privacy disclosures are comprehensive and accurate
|
|
|
|
---
|
|
|
|
### Guideline 5.6 — Developer Code of Conduct
|
|
|
|
**Risk**: Rejection for manipulating reviews or ratings
|
|
**Mitigation**:
|
|
- ✅ No review prompts in the app
|
|
- ✅ No SKStoreReviewController usage
|
|
- ✅ No manipulation of app store metadata
|
|
- ✅ No incentivized reviews
|
|
- ✅ No pre-filled review text
|
|
|
|
**Residual risk**: NONE — No review-related code in the app
|
|
|
|
---
|
|
|
|
### Guideline 2.3.1 — Performance — Accurate Metadata
|
|
|
|
**Risk**: Rejection for screenshots not matching the app
|
|
**Mitigation**:
|
|
- ✅ Screenshots captured from actual app builds
|
|
- ✅ All supported device sizes (iPhone SE, 12, 14 Pro, 15 Pro Max)
|
|
- ✅ Screenshots reflect current app state (no outdated UI)
|
|
- ✅ App preview video shows real app functionality
|
|
- ✅ Description accurately reflects app capabilities
|
|
|
|
**Residual risk**: LOW — Ensure screenshots are refreshed before each submission
|
|
|
|
---
|
|
|
|
### Guideline 2.1 — Crash on Launch
|
|
|
|
**Risk**: Rejection if app crashes during review
|
|
**Mitigation**:
|
|
- ✅ Deferred initialization — no blocking work on launch
|
|
- ✅ Graceful error handling for all async operations
|
|
- ✅ Network failure handling with retry
|
|
- ✅ Permission denial handling
|
|
- ✅ Keychain access with error recovery
|
|
- ✅ Security checks run on background thread
|
|
- ✅ Tested on multiple iOS versions (17.0+)
|
|
|
|
**Residual risk**: LOW — Comprehensive error handling throughout
|
|
|
|
---
|
|
|
|
## Medium-Risk Areas
|
|
|
|
### Jailbreak Detection
|
|
|
|
**Risk**: Reviewer's device triggers jailbreak detection (e.g., simulator, development device)
|
|
**Mitigation**:
|
|
- ✅ Graceful degradation — app remains functional in degraded mode
|
|
- ✅ Warning banner is informational, not blocking
|
|
- ✅ Degraded mode reduces features but doesn't prevent usage
|
|
- ✅ Security checks are non-blocking (run on detached tasks)
|
|
|
|
**Note**: Apple reviewers typically use clean devices. Simulator testing shows degraded mode works correctly.
|
|
|
|
---
|
|
|
|
### Call Directory Extension
|
|
|
|
**Risk**: Extension not properly integrated in build
|
|
**Mitigation**:
|
|
- ✅ Extension target added to `project.yml` with proper configuration
|
|
- ✅ Entitlements created (CallKit + App Group) for shared data access
|
|
- ✅ `SpamDirectoryService` moved to `Sources/Shared` for cross-target access
|
|
- ✅ Extension is optional — app works without it
|
|
- ✅ Clear instructions in SpamShield settings
|
|
- ✅ Extension status checked via `CXCallDirectoryManager.getEnabledStatusForExtension`
|
|
- ✅ Graceful fallback when extension is not enabled
|
|
|
|
---
|
|
|
|
### Siri Shortcuts
|
|
|
|
**Risk**: Siri intents not configured on reviewer's device
|
|
**Mitigation**:
|
|
- ✅ Shortcuts are optional — app works without Siri
|
|
- ✅ Intent donations happen automatically after onboarding
|
|
- ✅ Settings screen explains how to enable Siri shortcuts
|
|
|
|
---
|
|
|
|
## Low-Risk Areas
|
|
|
|
### Background Fetch
|
|
|
|
- App uses standard background fetch API
|
|
- No aggressive battery usage
|
|
- Fetch interval respects system scheduling
|
|
|
|
### Push Notifications
|
|
|
|
- Standard UNUserNotificationCenter usage
|
|
- Deep links use standard userInfo payload
|
|
- No critical alert abuse
|
|
|
|
### OAuth Sign-In
|
|
|
|
- Apple Sign-In uses native `AuthenticationServices`
|
|
- Google Sign-In uses official GoogleSignIn-iOS SDK
|
|
- Both flows are standard and well-documented
|
|
|
|
---
|
|
|
|
## Rejection Response Plan
|
|
|
|
If the app is rejected, follow this process:
|
|
|
|
1. **Read the rejection email carefully** — Identify the specific guideline
|
|
2. **Reproduce the issue** — Test on a clean device/simulator
|
|
3. **Fix the issue** — Implement the required change
|
|
4. **Update this document** — Add the new risk and mitigation
|
|
5. **Resubmit with response** — Use App Store Connect messaging to explain the fix
|
|
6. **Target response time**: Within 24 hours of rejection
|
|
|
|
### Common Rejection Scenarios & Quick Fixes
|
|
|
|
| Rejection Reason | Quick Fix |
|
|
|-----------------|-----------|
|
|
| 2.1 — Incomplete app | Remove any remaining placeholder content |
|
|
| 2.1 — Crashes | Check crash logs, fix the specific crash |
|
|
| 3.1.1 — IAP required | Clarify service model in review notes |
|
|
| 4.3 — Spam | Ensure app is unique, not a clone |
|
|
| 5.1.1 — Privacy | Update privacy manifest, add missing declarations |
|
|
| 2.1 — Beta label | Remove any "beta", "test", or "preview" text |
|
|
| 2.1 — Dead links | Verify all external URLs work |
|
|
| 4.2 — Minimum functionality | Emphasize native features in review notes |
|
|
|
|
---
|
|
|
|
## Pre-Submission Checklist
|
|
|
|
Before submitting to App Store Review:
|
|
|
|
- [ ] Build Release configuration
|
|
- [ ] Verify no `#if DEBUG` code paths contain visible content
|
|
- [ ] Verify no `print()` calls remain outside `#Preview` blocks
|
|
- [ ] Verify no force unwraps in production code paths
|
|
- [ ] Test on physical device (not just simulator)
|
|
- [ ] Verify all deep links work
|
|
- [ ] Verify push notifications work
|
|
- [ ] Verify ATT flow works
|
|
- [ ] Verify sign-in flows work (email, Apple, Google)
|
|
- [ ] Verify subscription upgrade button opens billing portal
|
|
- [ ] Verify widgets appear on home screen
|
|
- [ ] Verify Siri shortcuts are available
|
|
- [ ] Verify no console warnings in release build
|
|
- [ ] Verify app icon and launch screen are correct
|
|
- [ ] Verify version number and build number are correct
|
|
- [ ] Verify privacy policy URL works
|
|
- [ ] Verify all screenshots match current app UI
|
|
- [ ] Verify app preview video is current
|
|
- [ ] Verify SpamShield extension target built
|
|
- [ ] Verify SpamShield extension entitlements correct
|
|
- [ ] Upload via Xcode Organizer or Transporter
|
|
- [ ] Fill in App Store Connect metadata
|
|
- [ ] Add review notes and demo account
|
|
- [ ] Submit for review
|