storebound/docs/rejections.md
CRT 710c3a53d0 README, docs and site: one story (the whole job, no avoidable rejections, the desktop app, your AI, install)
README is a short front page (about 120 lines) with one real visual per section; every former section moved
unchanged into docs/*.md with a docs/README.md index (journey, rejections, getting-started, commands, growth,
studios, store-art, listing-languages, api, skills, mcp, safety, faq, project). The site tells the same story on one
page: the teaser in the hero, real CLI output as text, the desktop app as a section with light/dark captures of eight
screens and the per-system downloads (desktop/latest.json); /desktop/ redirects to /#download. New capture tools:
tools/site-desktop-shots.ts (fixture-mode desktop frames) and tools/site-term-shots.ts (real CLI output as text and
WebP). tests/docs-links.test.ts checks README, docs and site links resolve.
2026-10-13 03:29:59 +08:00

12 KiB

Catch rejections before you submit

App Store rejections cost days: you wait for review, read one sentence in the Resolution Center, fix it, and wait again. On Google Play a policy problem can mean a blocked update, a removal or a strike on the account. Most of the usual ones are visible in the project long before that. storebound scan reads the project itself, offline and read-only (Xcode project, Info.plist and build settings, entitlements, PrivacyInfo.xcprivacy, Podfile.lock, Package.resolved, Cartfile, Gradle files and version catalogs, AndroidManifest.xml (and the merged manifest of a local build), res/xml and strings.xml, CMake / ndk-build files, Unity ProjectVersion.txt, package.json, Expo app.json, capacitor.config, Cordova config.xml, pubspec, Unity Packages/manifest.json, ProjectSettings and External Dependency Manager files, Godot export presets), plus your config and listing files. It works out the stack and the SDKs (ads, analytics, attribution, crash, sign-in, payments, push, with versions and the file:line they come from) and reports every rejection risk it can prove, each with the guideline, the evidence, the fix, a severity and the skill to open. On the teaser fixture (tests/fixtures/scan/teaser):

$ storebound scan
Rejection risks: 2 blocker, 3 high, 0 medium, 0 low
  1. BLOCKER 2.1(a) · App Store · Sign-in is required but there is no demo account for App Review
       at storebound.config.ts:12  reviewAccess: { signInRequired: true, instructions: 'Sign in with the demo account.' },
       at storebound.config.ts:11  accounts: { enabled: true },
       at Podfile.lock:3  Google Sign-In: GoogleSignIn 7.1.0
  2. BLOCKER 5.1.1(v) · App Store · Accounts can be created but no in-app account deletion was found
       at storebound.config.ts:11  accounts: { enabled: true },
       at Planner/LoginView.swift:11  Button("Create account") { Task { try await AccountApi.signUp() } }
       at Planner/LoginView.swift:17  static func signUp() async throws {
  3. HIGH    4.8 · App Store · Third-party login (Google / Facebook / ...) without Sign in with Apple or another equivalent login option
       at Podfile.lock:3  Google Sign-In: GoogleSignIn 7.1.0
       at Planner/LoginView.swift:9  GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController()) { _, _ in }
  4. HIGH    3.1.1 · App Store · Restorable purchases (non-consumables or subscriptions) but no Restore Purchases path was found
       at storebound.config.ts:16  com.example.planner.pro (non_consumable)
  5. HIGH    5.1.2(i) · App Store · Tracking-capable code without App Tracking Transparency (Google AdMob, IDFA)
       at Podfile.lock:2  Google AdMob: Google-Mobile-Ads-SDK 11.10.0
       at Planner/Ads.swift:7  print(ASIdentifierManager.shared().advertisingIdentifier)

Every finding also has a fix: line and the skill. Below the risks comes the declarations proposal: the Play Data safety and App Store App Privacy answers, App Tracking Transparency and privacy manifest entries the detected SDKs and code imply, each marked detected (file:line) or ask owner, as a diff against dataSafety / appPrivacy in the config. Nothing is written unless you run storebound scan --write (only detected answers; questions stay questions). The MCP tool scan is read-only and cannot write; validate includes the open scan findings; the desktop app shows them on the Checks tab. A finding you decided is fine goes into the config with its reason: scan: { ignore: [{ rule: 'demo-account', reason: '...' }] }; a rule with several checks (e.g. play-metadata) takes match: '<text in the finding title>' to accept just one of them.

rule App Store Google Play what scan proves
demo-account, play-app-access 2.1(a) App access a sign-in (config, auth SDKs, sign-in code) without reviewAccess or without the demo account in .storebound/
account-deletion, play-deletion-url 5.1.1(v) User Data: account deletion sign-up or social login without a deletion path in code or accounts.inApp; no accounts.deletionUrl
login-services 4.8 Google / Facebook / other third-party login without Sign in with Apple or another equivalent option
restore-purchases, external-purchase, play-payments 3.1.1 Payments non-consumables or subscriptions without a restore path; card / PayPal SDKs next to in-app products; "buy on our website" (Play: unless play.billingPrograms lists user choice billing or external offers)
subscription-info 3.1.2(c), 3.1.2 no Terms of Use URL, no Terms link or renewal wording in the description, no legal links or "Manage subscription" in the app
att 5.1.2(i) tracking-capable SDKs or IDFA without ATT; the prompt without its purpose string; the string without a prompt
purpose-strings, unused-permission 5.1.1(ii), 5.1.1(iv), ITMS-90683 a used permission without its NS*UsageDescription; empty or vague strings; declared but unused
privacy-manifest ITMS-91053 / 91061 no PrivacyInfo.xcprivacy, required-reason APIs not declared, SDKs older than their first manifest version
privacy-policy-link, play-privacy-policy 5.1.1(i) User Data: privacy policy no privacy link in the app's sources (Play: every app; medium when it collects data), an http:// link
privacy-labels 5.1.2 User Data: Data safety SDK data types missing from dataSafety; Play also: an answer narrower than the SDK's disclosure (purposes, sharing), SDKs with no dataSafety at all
play-prominent-disclosure User Data: prominent disclosure, Location permissions background location without a disclosure that says "location" and "background"; location, contacts, calendar or phone number access without any disclosure or rationale text in sources or strings.xml
play-background-location, play-photo-video, play-all-files-access, play-accessibility, play-install-packages, play-health-connect, play-full-screen-intent, play-vpn Location permissions, Photo and Video Permissions, All files access, AccessibilityService API, REQUEST_INSTALL_PACKAGES, Health Connect, Full-screen intent, VpnService the restricted permission or service in the manifest (or the merged manifest of a local build), each with the core-use test and the Play Console declaration it needs; isAccessibilityTool lowers the accessibility finding to medium
play-fgs, play-exact-alarm, play-query-all-packages, play-sms-call-log Foreground service types, exact alarms, package visibility, SMS and Call Log foreground services without a type or its permission (and the Console declaration); USE_EXACT_ALARM; QUERY_ALL_PACKAGES; SMS and call log permissions
kids-ads, play-families-ads, play-families-ad-id, play-families-location 1.3 Families a Kids Category age band with third-party ads or analytics SDKs; play.targetAges under 13 with an ad SDK that is not Families self-certified (or older than its certified version), the AD_ID permission in a child-only app (mixed audience: ad / attribution SDKs need child-directed treatment), location in a child-only app
play-ad-id Advertising ID targetSdk 33+ with ad / attribution SDKs and no AD_ID permission (medium when the merged manifest proves it is missing; Google Mobile Ads adds it itself)
play-16kb 16 KB page sizes (updates blocked from 2027-02-01) Unity before 6000.0.38f1 / 2022.3.56f1 / 2021.3.48f1, React Native before 0.77, NDK before r28 for the app's own C/C++ without the 16 KB flags, AGP before 8.5.1 with uncompressed native libraries; else prebuilt .so files to check
play-ads-launch Ads, Better Ads Experiences an interstitial shown from onCreate of the launcher activity (Android native; other engines: review-only)
play-testing-requirement Testing for new personal accounts play.accountType: 'personal' created after 2023-11-13 (12 testers, 14 days of closed testing before production); low when the account type is not set
other-platforms 2.3.10 "Android", "Google Play" and other stores in the App Store text or screenshot captions
metadata-keywords, kids-terms, play-metadata 2.3.7, 2.3.8 Metadata other apps' names, prices and "best / #1" in name, subtitle or keywords; keyword lists; "For Kids" outside the Kids Category. Play title over 30 characters, emoji, ALL CAPS, rank / price / promotion terms; other apps in the title or short description; repeated keywords or keyword lists in the full description
play-impersonation Impersonation a well-known brand in the package name or app name (low: a check, not a verdict)
placeholder-text 2.1(a) Metadata lorem ipsum, TODO, template lines in the listings
beta-wording 2.2 "beta", "demo", "trial version", "test build", "early access" in the app name (config, CFBundleDisplayName) or the App Store name, subtitle, keywords, promotional text and description ("demo account" is fine)
originality (with --originality) 4.3 Repetitive content a near-duplicate of one of your own apps (storebound originality scores: name, listing text, icon and screenshot hashes, project structure)
min-functionality 4.2 Capacitor server.url / Cordova <content src> on a remote site; a few native files around a remote WKWebView
deprecated-api, private-api 2.5.1 (ITMS-90809) UIWebView; App-Prefs: / prefs:root URL schemes
ipv6, ats 2.5.5, App Transport Security IPv4 literals in server URLs; NSAllowsArbitraryLoads
export-compliance export compliance no ITSAppUsesNonExemptEncryption and no apple.usesNonExemptEncryption, or the two disagree (never guessed)
review-prompt 5.6.1, 3.2.2(x) / 5.6.3 a custom "rate us" prompt without the system API; rewards for ratings
play-target-sdk Target API level below API 36 (Wear OS / Automotive 35, TV / XR 34) for new apps and updates since 2026-08-31
play-package-name Play Console upload the reserved com.example prefix

storebound scan --originality [--against <folders>] adds the Guideline 4.3 / Play repetitive content check against your other apps to the same report (folders default to originality.against in the config; storebound originality stays the command with every score and suggestion).

Rules cite Apple's App Review Guidelines and the Play Policy Center / Play Console Help pages they come from (checked 2026-10-11, in the code comments of cli/lib/scan/rules.ts and cli/lib/scan/play-rules.ts); the SDK table with each vendor's data disclosure page and Google's Families self-certified ads SDK list is cli/lib/scan/sdks.ts. Play config the rules read: play.targetAges (Target audience age groups), play.accountType / play.accountCreated, and play.billingPrograms. When the project was built locally, scan also reads the merged manifest under build/intermediates (the permissions libraries add); otherwise it says that it could not. Not a rule because no Play policy page covers it: device admin apps (BIND_DEVICE_ADMIN); not a policy at all: Play Integrity / anti-tamper.

Checked at review, not by scan (no file shows them): crashes and hangs on the reviewer's device (2.1, store-health), links that are dead at review time (validate checks the configured URLs), screenshots and descriptions that do not match the app (2.3, store-screenshots), unfinished UI (4.0), "too simple" and spam / template apps (4.2, 4.3, portfolio-originality), IAPs that fail in the sandbox (2.1(b), store-iap-setup), the content of the privacy policy (privacy-policy), local law (5.2). On Google Play: whether a restricted permission really serves the app's core purpose (the Console declaration and its video decide), the wording and timing of a prominent disclosure, ad placements in Unity, Flutter or React Native code, the Health apps declaration answers, and the alignment inside a prebuilt .so (scan lists them; zipalign -c -P 16 and check_elf_alignment.sh check the built app). The review-rejections skill covers what to do when one of these comes back.


All docs · README