From 06dd8a919c2a206ad23f0cebc9c84ea57047350a Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Sat, 15 Aug 2026 14:57:20 +0100 Subject: [PATCH] =?UTF-8?q?APPSTORE.md:=20submission=20plan=20=E2=80=94=20?= =?UTF-8?q?P0=20resolved=20old=20blockers,=20owner/timeline=20checklist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APPSTORE.md | 169 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 72 deletions(-) diff --git a/APPSTORE.md b/APPSTORE.md index 086bd84..6ee13b5 100644 --- a/APPSTORE.md +++ b/APPSTORE.md @@ -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).