APPSTORE.md: submission plan — P0 resolved old blockers, owner/timeline checklist
This commit is contained in:
+97
-72
@@ -1,86 +1,111 @@
|
||||
# FuelBoard App Store Approval Review
|
||||
# FuelBoard App Store Submission Plan
|
||||
|
||||
Review date: 2026-08-11 · Target: App Store submission readiness.
|
||||
This is the companion to SECURITY.md — focuses on App Store *review* gate
|
||||
(guideline compliance), not code security.
|
||||
Updated: 2026-08-16 · Target: safe App Store submission.
|
||||
Companion to SECURITY.md (code security) — this file is the *review-gate*
|
||||
checklist: what must happen, who owns it, and the timeline.
|
||||
|
||||
## Status: NOT SUBMISSION-READY
|
||||
## Status: SUBMITTABLE after the checklist below
|
||||
|
||||
Two blockers remain before a real submission (see below). Everything else
|
||||
checks out for review.
|
||||
The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
|
||||
- ~~LAN relay data source~~ → **app fetches from public HTTPS
|
||||
(raw.githubusercontent.com mirror) with a bundled real 8,022-station dump
|
||||
as the no-network fallback.** No private IP in the consumer path. Reviewers
|
||||
on Apple's network see real data.
|
||||
- ~~Always-location risk~~ → **Always is requested only when the user enables
|
||||
Alerts** (ProximityMonitor escalates WhenInUse → Always on alert opt-in).
|
||||
Onboarding requests WhenInUse only.
|
||||
|
||||
## Blockers
|
||||
## Hard blockers (must happen before ANY submission)
|
||||
|
||||
### 1. Data source — LAN relay (BLOCKER)
|
||||
- The app fetches from `http://192.168.1.131:8788` — a private LAN host. Off
|
||||
the user's network it fails and falls back to sample data. Reviewers on
|
||||
Apple's network will see empty/sample results → 2.1 (performance) rejection,
|
||||
and hardcoded IPs look like test builds.
|
||||
- **Fix**: the govuk API switch (BACKLOG P0). App must talk to a public HTTPS
|
||||
endpoint before submission. The relay (HTTPS domain) or direct gov.uk OAuth
|
||||
both work; the code path is already stubbed.
|
||||
1. **[USER] Apple Developer Program membership** — the app has never been
|
||||
signed (all builds are `CODE_SIGNING_ALLOWED=NO` + ad-hoc re-sign for
|
||||
SideStore). Requires a paid membership ($99/yr, up to 48 h to approve) and
|
||||
a developer account.
|
||||
2. **[USER] Register App ID `com.apt.fuelboard`** in the developer portal,
|
||||
with the **App Group `group.com.apt.fuelboard`** capability enabled (the
|
||||
widget + app-group cache depend on it).
|
||||
3. **[ME] `PrivacyInfo.xcprivacy`** — REQUIRED for all submissions since
|
||||
May 2024; the repo has none. Add to the app target (Xcode can generate;
|
||||
must declare location-data reasons + any required-reason APIs used).
|
||||
4. **[ME] Real Distribution signing** — Distribution certificate + App Store
|
||||
provisioning profile (or Xcode automatic signing once the account is in
|
||||
Xcode); then `xcodebuild archive` (no more `CODE_SIGNING_ALLOWED=NO`).
|
||||
5. **[USER] Privacy policy URL** in App Store Connect — mandatory; the app
|
||||
collects location. A one-page GitHub Pages privacy policy is fine.
|
||||
|
||||
### 2. Always-location justification (BLOCKER without review notes)
|
||||
- `NSLocationAlwaysAndWhenInUseUsageDescription` + `UIBackgroundModes: location`
|
||||
present, but guideline 5.1.1 requires the feature to be obvious AND the app
|
||||
must visibly use background location. The geofenced "cheapest station" alerts
|
||||
satisfy this, but the reviewer must see it:
|
||||
- Provide detailed App Review notes explaining the alert flow (enable Alerts →
|
||||
geofence trigger → notification when approaching the cheapest station).
|
||||
- Ensure the Alerts tab is discoverable and functional in review.
|
||||
- Also: "Always" prompt should only appear after the user opts into alerts
|
||||
(in-progress UX; currently the permission prompt may fire too early).
|
||||
## Required tasks — owner [ME] (technical, do after account exists)
|
||||
|
||||
## Review-relevant configuration — PASS
|
||||
- [ ] PrivacyInfo.xcprivacy (blocker #3 above)
|
||||
- [ ] Distribution archive + TestFlight upload
|
||||
- [ ] Bump version: `CFBundleShortVersionString 1.0.1` / `CFBundleVersion 2`
|
||||
- [ ] OGL attribution + "prices updated twice daily" footnote in
|
||||
Settings → About (2.3.1 hygiene; data is 12 h-cache, UI says "cheapest")
|
||||
- [ ] Out-of-UK empty state: reviewer may not be in the UK → friendlier copy
|
||||
than "No X stations found." (explain UK-only data + browse-all option)
|
||||
- [ ] Optional hardening: gate the dev-only surface (Settings Debug section,
|
||||
`fuelboard.relayFallback`, FuelBeacon, relay IP) behind `#if DEBUG` so
|
||||
the Release binary ships clean — currently Release contains the hidden
|
||||
dev flag + a private IP string (no credentials, but a "test build"
|
||||
smell; not a rejection risk on its own)
|
||||
- [ ] Final pass: `swift test` green (95 tests), archive smoke test on device
|
||||
- [ ] Capture screenshots from simulator (6.7″ 1290×2796 required; 6.5″
|
||||
1242×2688 recommended) for App Store Connect
|
||||
|
||||
- [x] **App icon**: was MISSING entirely (invalid-binary rejection). Fixed —
|
||||
1024×1024 `AppIcon.appiconset` + `ASSETCATALOG_COMPILER_APPICON_NAME`.
|
||||
- [x] **Launch screen**: `UILaunchScreen` empty dict present (iOS 14+ style).
|
||||
- [x] **Device family**: iPhone-only (`TARGETED_DEVICE_FAMILY = 1`) — no iPad
|
||||
multitasking requirements.
|
||||
- [x] **Versioning**: `CFBundleShortVersionString 1.0`, `CFBundleVersion 1`
|
||||
(bump per release).
|
||||
- [x] **Permissions**: only location (WhenInUse + Always), with clear usage
|
||||
strings. No contacts/photos/mic/camera.
|
||||
- [x] **No third-party SDKs** → no SDK privacy manifests or third-party
|
||||
attribution required.
|
||||
- [x] **No account creation / login** → no account-deletion requirement (5.1.1(v)).
|
||||
- [x] **ATS**: no `NSAllowsArbitraryLoads` (raw-IP cleartext is ATS-exempt; once
|
||||
the relay is HTTPS this is moot).
|
||||
- [x] **Export compliance**: no crypto beyond keychain/SecItem → no
|
||||
documentation needed.
|
||||
## Required tasks — owner [USER] (App Store Connect, cannot be done by me)
|
||||
|
||||
## Content / IP review
|
||||
- [ ] Apple Developer Program membership + App ID (blockers #1–#2)
|
||||
- [ ] App Store Connect app record: name, bundle ID `com.apt.fuelboard`,
|
||||
category (Utilities), pricing (Free), availability (UK first?)
|
||||
- [ ] Description, keywords, support URL, marketing URL, privacy policy URL
|
||||
- [ ] Privacy nutrition labels questionnaire (location: yes, linked to user
|
||||
for alerts/nearby; diagnostics: yes — the widget-diag beacon, not
|
||||
linked, not used for tracking; no ATT prompt needed)
|
||||
- [ ] Age rating questionnaire (4+; no objectionable content)
|
||||
- [ ] Export compliance: HTTPS-only + keychain → standard encryption
|
||||
exemption (5A992a), no documentation needed
|
||||
- [ ] Approve the TestFlight build and do a real-device pass before submit
|
||||
- [ ] Final "Submit for Review" click + stand by to answer review questions
|
||||
|
||||
- **Brand logos (Shell, BP, Tesco…)**: bundled from Simple Icons (CC0) +
|
||||
Wikimedia Commons. Nominative use (identifying stations) is standard practice
|
||||
(cf. GasBuddy). Low risk, but keep the sources documented.
|
||||
- **Price data**: gov.uk Fuel Finder data is OGL-licensed — include attribution
|
||||
in the app (About/credits) once live data ships. Sample fallback data must be
|
||||
clearly demo-labelled (or removed for review) so reviewers don't think the
|
||||
prices are fake — 2.3.1 accuracy risk.
|
||||
- **"Cheapest" claims**: data is 12h-old cache. The UI says "cheapest" — fine,
|
||||
but a small "prices from the UK Fuel Finder dataset, updated twice daily"
|
||||
footnote is good hygiene for 2.3.1.
|
||||
|
||||
## Suggested review notes (copy-paste for App Store Connect)
|
||||
## Review notes (copy-paste into App Store Connect)
|
||||
|
||||
> FuelBoard shows the cheapest petrol, diesel, and premium fuel near the user
|
||||
> using the UK government Fuel Finder dataset (OGL). Prices are fetched up to
|
||||
> twice a day; the app filters 5/10/15-mile radii on-device. Location is used
|
||||
> to (a) rank stations by distance and (b) power the optional Alerts feature:
|
||||
> when enabled, the app geofences the cheapest station in the user's chosen
|
||||
> radius and sends a notification when they approach it. Always-location is
|
||||
> only requested when the user enables alerts. The app also provides a
|
||||
> home-screen widget showing the current cheapest station nearby.
|
||||
> using the UK government Fuel Finder dataset (Open Government Licence).
|
||||
> Prices are downloaded from the internet up to twice a day; the app filters
|
||||
> 5/10/15-mile radii on-device and works offline from a bundled snapshot.
|
||||
> Location is used to (a) rank stations by distance and (b) power the
|
||||
> optional Alerts feature: when the user enables alerts, the app geofences
|
||||
> the cheapest station in their radius and sends a notification when they
|
||||
> approach it. Always-location is requested only when the user enables
|
||||
> alerts. The app also provides a home-screen widget showing the current
|
||||
> cheapest station nearby, and a price-history Trends chart for starred
|
||||
> stations. All data is UK-wide (Fuel Finder covers England, Scotland, and
|
||||
> Wales); users outside the UK see an empty state explaining this.
|
||||
|
||||
## Release checklist (before every submission)
|
||||
## Review risks (know before submitting)
|
||||
|
||||
1. [ ] Relay/govuk data source live on a public HTTPS endpoint
|
||||
2. [ ] Always-permission flow tied to explicit user opt-in
|
||||
3. [ ] Bump `CFBundleShortVersionString` / `CFBundleVersion`
|
||||
4. [ ] Sample-data fallback labelled or removed in the release build
|
||||
5. [ ] OGL attribution present in-app
|
||||
6. [ ] `swift test` green (FuelBoardTests, 28 tests)
|
||||
7. [ ] Security audit pass (see SECURITY.md recurring checks)
|
||||
8. [ ] Icon + screenshots for all required sizes in App Store Connect
|
||||
- **Always-location (5.1.1)** — the #1 question. The feature is visible
|
||||
(Alerts tab) and opt-in; the review notes above say so explicitly.
|
||||
- **Reviewer outside the UK** — empty radius; mitigated by the empty-state
|
||||
task above + review notes.
|
||||
- **"Cheapest" claims (2.3.1)** — mitigated by the OGL/footnote task.
|
||||
- **Live Activities** — reviewers may ask why; answer: price alerts at a
|
||||
glance. It is declared in the plist already.
|
||||
- **Brand logos (Shell, BP, Tesco…)** — Simple Icons (CC0) + Wikimedia,
|
||||
nominative use (cf. GasBuddy); low risk, keep sources documented.
|
||||
- **GitHub raw as data source** — public data, no credentials; bundled dump
|
||||
covers any regional GitHub unreachability.
|
||||
|
||||
## Timeline (best case)
|
||||
|
||||
| Day | Owner | Milestone |
|
||||
|-----|-------|-----------|
|
||||
| 0–2 | USER | Developer Program enrollment + App ID + app group capability |
|
||||
| 2–3 | ME | Signing (automatic), PrivacyInfo.xcprivacy, attribution, empty state, version bump |
|
||||
| 3–4 | ME | Archive → TestFlight; screenshots captured |
|
||||
| 4–5 | USER | TestFlight install + real-device pass (onboarding, alerts, widget, offline) |
|
||||
| 5–6 | USER | App Store Connect metadata + labels + age rating + privacy URL |
|
||||
| 6–7 | USER/ME | Submit for review |
|
||||
| 7–14 | Apple | Review (typically 1–3 days, can be a week); answer questions same-day |
|
||||
|
||||
Rollback note: nothing about submission changes the repo's rollback posture
|
||||
(`main` history is intact; release builds are tagged per submission).
|
||||
|
||||
Reference in New Issue
Block a user