Политики сторов для OTA-обновлений
OTA-обновления — не серая зона. Оба магазина говорят о них прямо, оба их разрешают, и границу оба проводят в одном и том же месте: заменять интерпретируемый код можно; заменять нативный код нельзя, и нельзя менять то, чем приложение является.
Эта страница цитирует сами правила, указывает, в каком документе живёт каждое из них, и превращает их в чек-лист, который можно прогнать перед выкаткой. Она написана по официальным источникам, ссылки на которые даны ниже, — а не по пересказам, потому что нумерация, которую все повторяют по памяти, устарела примерно на десятилетие. Это рекомендации, а не юридическая консультация.
Правило, которое все называют «3.3.2», и где оно на самом деле живёт
Почти любое обсуждение OTA в React Native ссылается на «guideline 3.3.2 от Apple». Этот номер — из iOS Developer Program License Agreement образца 2010 года, и правило там больше не находится. Сегодня значение имеют два документа, и ни в одном из них номер не 3.3.2:
- Apple Developer Program License Agreement — договор, который вы приняли как разработчик. Правило — §3.3.1(B), «Executable Code». (§3.3.2 в действующем договоре — это «Regulatory Compliance» про допуски FDA/FAA/FTC, к обновлениям отношения не имеет.)
- App Store Review Guidelines — то, что проверяет ревьюер. Правило — §2.5.2.
Сославшись на правильный документ, вы сильно сократите переписку с ревью-поддержкой.
Что на самом деле написано в лицензионном договоре Apple
Из Apple Developer Program License Agreement, §3.3.1(B) Executable Code (версия LYL255 от 18 августа 2026 года):
Except as set forth in the next paragraph, an Application may not download or install executable code. Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application (b) does not bypass signing, sandbox, or other security features of the OS; and (c) for Applications distributed on the App Store, does not create a store or storefront for other Applications.
В переводе: приложение не может скачивать или устанавливать исполняемый код, за исключением случая из следующего абзаца. Интерпретируемый код скачивать можно — при условии, что он: (a) не меняет основное назначение приложения, добавляя возможности, несовместимые с заявленным и рекламируемым назначением; (b) не обходит подпись, песочницу и другие механизмы безопасности ОС; и (c) для приложений, распространяемых в App Store, не создаёт магазин или витрину для других приложений.
Прочитайте внимательно — это разрешительнее, чем принято думать:
- Скачивание интерпретируемого кода разрешено прямо. JavaScript-бандл — интерпретируемый код. Ни спрашивать разрешения, ни раскрывать механизм на ревью, ни ограничиваться исправлением багов не требуется.
- Три условия — это весь тест. Не «обновление должно быть незначительным». Не «обновление не должно добавлять функциональность». Новая возможность, укладывающаяся в заявленное назначение приложения, проходит пункт (a) без вопросов.
- Пункт (b) — причина, по которой важна подпись кода и по которой нельзя выпускать механизм, скачивающий и выполняющий нативный код.
- Пункт (c) нацелен на магазины приложений внутри приложений. Обычные продукты к нему даже не приближаются.
Действующая редакция всегда лежит на developer.apple.com/support/terms — читайте §3.3.1(B) там, а не доверяйте цитате, включая эту, через год.
Что говорят App Store Review Guidelines
Из App Store Review Guidelines, §2.5.2:
Apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or execute code which introduces or changes features or functionality of the app, including other apps. Educational apps designed to teach, develop, or allow students to test executable code may, in limited circumstances, download code provided that such code is not used for other purposes. Such apps must make the source code provided by the app completely viewable and editable by the user.
В переводе: приложения должны быть самодостаточны внутри своего бандла, не могут читать или писать данные вне отведённого контейнера и не могут скачивать, устанавливать или выполнять код, который добавляет или меняет возможности приложения, включая другие приложения. Образовательные приложения, обучающие программированию, в ограниченных случаях могут скачивать код при условии, что он не используется для других целей и его исходный текст полностью доступен пользователю для просмотра и правки.
Взятая отдельно, формулировка «добавляет или меняет возможности» читается как полный запрет — именно эта фраза и отпугивает команды от OTA целиком. На практике её читают вместе с прямым разрешением на интерпретируемый код из лицензионного договора, а App Store уже десятилетие держит приложения на React Native, Cordova и Flutter с OTA-обновлениями, причём EAS Update и CodePush работают совершенно открыто. Действующее ограничение остаётся прежним — §3.3.1(B): не выходить за рамки заявленного назначения приложения.
Консервативное прочтение обоих документов, на которое и стоит проектировать: OTA-обновление должно быть таким, какое вы спокойно выпустили бы обычным релизом в стор. Если вы не подали бы это на ревью — не выкатывайте.
Что говорит Google Play
Применимая политика — Device and Network Abuse, раздел Full Policy:
An app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. Likewise, an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play. This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser).
В переводе: приложение, распространяемое через Google Play, не может изменять, заменять или обновлять себя способом, отличным от механизма обновлений Google Play. Точно так же оно не может скачивать исполняемый код (например, файлы dex, JAR, .so) из источников, отличных от Google Play. Это ограничение не распространяется на код, выполняемый в виртуальной машине или интерпретаторе, если тот даёт лишь опосредованный доступ к Android API (например, JavaScript в webview или браузере).
Последнее предложение и есть то исключение, на котором стоит OTA: JavaScript, выполняемый в JS-движке и получающий опосредованный доступ к Android API через мост React Native, — не тот «исполняемый код», который ограничивает политика. А вот .dex, .jar и .so — именно он; их по воздуху не отправляют никогда.
Есть и вторая формулировка, добавленная в октябре 2021 года, и она применима напрямую:
Apps or third-party code, like SDKs, with interpreted languages (JavaScript, Python, Lua, etc.) loaded at run time (for example, not packaged with the app) must not allow potential violations of Google Play policies.
В переводе: приложения и сторонний код (например, SDK) с интерпретируемыми языками (JavaScript, Python, Lua и т. д.), загружаемыми во время выполнения, то есть не упакованными вместе с приложением, не должны допускать нарушений политик Google Play.
Смысл: OTA-обновление наследует все политики Play, которым подчинён бинарник. Ваш конвейер обновлений — не дырка в правилах: если доставленный бандл нарушал бы политику, значит нарушили её вы. Практически — никакого нового сбора данных, не описанного в форме Data safety; никакого поведения рекламы, противоречащего вашей декларации; никакого контента вне возрастного рейтинга.
Что менять можно, а что нельзя
Otapush доставляет JavaScript-бандл и его ассеты. Эта граница — не ограничение продукта, а ровно та граница, которую проводят правила магазинов.
Безопасно менять по воздуху:
- JavaScript-бандл: бизнес-логику, экраны, навигацию, состояние, вызовы API, исправления багов.
- Ассеты, на которые ссылается бандл: изображения, шрифты, JSON, файлы Lottie.
- Тексты и локализацию, стили и вёрстку, фича-флаги и удалённую конфигурацию, читаемую из JS.
- Новые экраны и новые возможности в рамках заявленного назначения приложения.
Никогда по воздуху — только релизом в стор:
- Нативный код. Любые правки на Swift, Objective-C, Kotlin, Java или C++; новый нативный модуль; новый сторонний SDK с нативной частью; обновление Expo SDK. Otapush этого не доставит, и никакой OTA-механизм не имеет права.
- Нативная конфигурация. Разрешения, entitlements, записи в
Info.plistиAndroidManifest.xml, URL-схемы, фоновые режимы, иконки и splash-экраны — всё это зашивается на этапе сборки. - Метаданные в сторе. Название, описание, скриншоты, возрастной рейтинг, privacy-метки и декларация Data safety. Они живут в App Store Connect и Play Console, а не в вашем бандле.
- Основное назначение приложения. Калькулятор, ставший мессенджером, нарушает §3.3.1(B)(a) у Apple независимо от того, пришло изменение по воздуху или релизом, — но по воздуху это ещё и выглядит как обход ревью.
- Новый сбор данных или новое использование разрешений. Если обновление начинает читать геолокацию, контакты или микрофон, сначала должны измениться ваши декларации, а это подача в стор.
Версия рантайма — ваш механизм принуждения
Правило «никакого нативного кода» держится не на дисциплине. Otapush сверяет runtimeVersion точно: обновление, опубликованное для 1.0.0, никогда не будет отдано бинарнику 1.1.0 — и 0.9.0 тоже. Поднимайте версию рантайма всякий раз, когда меняется нативная часть, и JavaScript, рассчитывающий на нативный модуль, просто не сможет добраться до бинарника, в котором этого модуля нет. Как выводится версия рантайма и когда её поднимать — в настройке Expo.
Чек-лист безопасной выкатки
Прогоняйте перед каждой публикацией OTA. Он короткий намеренно.
- Нативная часть не тронута? Ни одной зависимости с нативной частью не добавлено и не обновлено, Expo SDK не поднят, config-plugin не изменён. Если что-то из этого сдвинулось — это релиз в стор, и версия рантайма обязана измениться вместе с ним.
- Вы подали бы это на ревью? Если честный ответ — «нет, поэтому и выкатываем по воздуху», остановитесь.
- Декларации о приватности всё ещё верны? Новая конечная точка, новое событие аналитики, новый сигнал устройства — проверьте App Privacy и форму Data safety до публикации, а не после.
- Прогнали через staging? Опубликуйте в
staging, поставьте staging-сборку на реальное устройство каждой платформы и запустите её дважды (первый запуск скачивает, второй выполняет новый бандл). Симуляторы не воспроизводят путь скачивания так, как это делают устройства. - Продвигайте, а не публикуйте заново. Перенесите в
prodровно тот артефакт, который тестировали, командойotapush promote— пересборка под прод означает выкатку того, что вы никогда не проверяли. См. справочник CLI. - Сообщение написано. Одна строка на публикацию: что изменилось и зачем. Во время инцидента в три часа ночи это единственное, что делает список обновлений читаемым.
- Наблюдаете. После выкатки посмотрите статистику приложения: счётчик
checkпродолжает расти, за ним подтягиваетсяinstall. Если проверки растут, а установки стоят — устройства скачивают и не могут запуститься: откатывайтесь немедленно, разбирайтесь потом. Учтите, что счётчикfailureсегодня не заполняется (см. Протокол), поэтому настоящий сигнал — застывшийinstallплюс ваша система сбора крашей. - Откат отрепетирован. Знайте заранее:
otapush rollbackвозвращает канал на предыдущее обновление, а откат единственного обновления канала отправляет все устройства обратно на бандл, вшитый в бинарник.
Если приложение отклонили
Отклонение из-за механизма обновлений — редкость и обычно оно предметное. Что действительно работает:
- Прочитайте, какое правило процитировано. Ссылка на §2.5.2 у Apple или на Device and Network Abuse у Google — это про механизм; всё остальное (приватность, контент, платежи) — про то, что делает приложение, и OTA-конвейер тут ни при чём.
- Прямо опишите, что вы доставляете. В Review Notes: приложение скачивает только JavaScript-бандл и его ассеты, интерпретируемые рантаймом React Native, уже находящимся в бинарнике; исполняемый нативный код не скачивается; сервер обновлений — ваш собственный. Это та же архитектура, что у EAS Update и CodePush, и ревьюер может проверить это самостоятельно.
- Ссылайтесь на пункт договора, а не на фольклор. §3.3.1(B) разрешает скачивать интерпретируемый код при трёх условиях — перечислите, как вы выполняете каждое: обновление не меняет заявленное назначение приложения, не обходит подпись и песочницу ОС, не создаёт витрину.
- В Google Play назовите исключение про интерпретатор. Процитируйте предложение о коде, выполняемом в виртуальной машине или интерпретаторе, и укажите, что
.dex,.jarи.soне скачиваются никогда. - Если отклонили из-за содержимого обновления — чините содержимое. Отклонили не механизм, и спор о нём стоит вам ещё одного цикла ревью.
- Никогда не утверждайте, что стор не видит OTA-обновлений. Это неправда, и именно этот аргумент превращает рядовое отклонение в проблему с аккаунтом разработчика.
Кто за что отвечает
С Otapush конвейер ваш: ваш сервер, ваши ключи, ваш аудит. Это помогает на комплаенс-проверке — вы можете показать, какой именно бандл, какому устройству и когда был отдан, — и это же означает, что соблюдение политик магазинов целиком на вас. Otapush не может заглянуть внутрь вашего бандла, как и любой другой сервер обновлений.
Вопросы и ответы
OTA-обновления нарушают правила App Store?
Нет. Apple Developer Program License Agreement, §3.3.1(B), прямо разрешает скачивание интерпретируемого кода при трёх условиях: обновление не меняет заявленное основное назначение приложения, не обходит подпись и песочницу ОС и не создаёт витрину для других приложений. JavaScript-бандл, доставленный Otapush, — интерпретируемый код.
Могут ли отклонить приложение за собственный сервер обновлений?
За сам сервер — нет. Правила ограничивают, что вы скачиваете и что оно делает (интерпретируемый код в рамках заявленного назначения), а не то, кто его хостит. С точки зрения текста договора самостоятельный хостинг ничем не отличается от EAS Update или CodePush.
Можно ли добавлять новую функциональность по воздуху или только чинить баги?
Функциональность добавлять можно, если она укладывается в заявленное и рекламируемое назначение приложения. «OTA — только для багфиксов» — широко повторяемое правило, которого нет ни в одной из двух политик. Тест проваливает другое: изменение, после которого приложение перестаёт быть тем, что проверяли на ревью.
Google Play это разрешает?
Да. Device and Network Abuse запрещает приложению обновлять себя мимо механизма Play и скачивать исполняемый код, а затем делает исключение для «кода, выполняемого в виртуальной машине или интерпретаторе», — это ровно то, как JavaScript выполняется под React Native. Скачивание файлов .dex, .jar и .so остаётся под запретом.
Нужно ли раскрывать OTA-обновления в privacy-метках?
Сам механизм раскрытием не является. А вот то, что из-за него передаётся, — может: Otapush записывает идентификатор устройства на каждый протокольный запрос для учёта MAU — monthly active users, то есть уникальных устройств за календарный месяц. Если этот идентификатор поставляет ваше приложение и считает его персональным, декларируйте соответственно. Гораздо важнее другое: обновление никогда не должно начинать собирать данные, которых нет в ваших действующих декларациях.
Что делать, если OTA-обновление сломало приложение у всех?
Откатываться, и это одна команда. otapush rollback возвращает канал на предыдущее обновление; устройства подхватывают его при следующей проверке. Если сломанное обновление было первым в канале, Otapush отправит директиву rollBackToEmbedded, и устройства вернутся на бандл, вшитый в бинарник. Ни один из этих путей не требует стора.
Может ли OTA-обновление сменить иконку, название или разрешения?
Нет. Всё три либо вшиты в бинарник, либо лежат в метаданных стора. Иконки и splash-экраны — ресурсы этапа сборки, разрешения живут в Info.plist и AndroidManifest.xml, название — метаданные стора. Каждое требует новой подачи.
Требуют ли магазины подписи обновлений?
Ни один не требует. Делать её всё равно стоит: подпись превращает пункт (b) из §3.3.1(B) Apple — не обходить механизмы безопасности ОС — из утверждения в доказуемый факт, а ещё не даёт злоумышленнику, захватившему ваш сервер обновлений, выполнить код внутри вашего приложения. Otapush генерирует пару ключей за вас и подписывает по умолчанию; см. Протокол.
Влияет ли использование OTA на сроки ревью?
Нет. Ревью проверяет бинарник, который вы подали. Обновления, выкаченные потом, в очередь на проверку не встают, — именно поэтому правила и ограничивают их содержимое.