Недействительный манифест конфиденциальности App Store Connect: как исправить в 2026?

Сначала найдите Bundle, указанный в письме App Store Connect, внутри финального xcarchive; не добавляйте вслепую общую декларацию в главный App и не удаляйте файлы зависимостей. После проверки формата, значений, Required Reason API, источника SDK и расположения ресурса создайте новый Archive, подпишите его заново и выполните реальную загрузку.

Этот порядок подходит, если вы получили ITMS-91056 или похожее сообщение и должны вернуть сборку в TestFlight или App Store. Он особенно важен для проектов со Swift Package Manager, CocoaPods, динамическими Framework и бинарными XCFramework, а также для удалённой сборки на Mac или в CI.

01 Ошибка начинается с пути, а не с файла в исходниках

Сообщение App Store Connect — это первая запись в журнале доказательств. Сохраните его до любых изменений. В нём ищите:

  • код ошибки;
  • имя файла;
  • полный или частичный путь Bundle;
  • название Target или встроенного Framework;
  • указание на недопустимое значение, отсутствие декларации или проблему подписи.

Apple описывает отдельные сценарии диагностики недействительного манифеста в технической заметке TN3181. Не смешивайте четыре разных случая:

  1. PrivacyInfo.xcprivacy существует, но имеет неверную структуру или значение;
  2. нужный Bundle не содержит манифест;
  3. код или зависимость использует Required Reason API без подходящей причины;
  4. SDK имеет проблему с поставкой, бинарным содержимым или подписью.

Сценарий «в исходниках всё выглядит правильно, но App Store Connect указывает на вложенный Framework» встречается именно потому, что проверяется не рабочая копия файла, а содержимое отправленного продукта. Поэтому исходный каталог проекта — только гипотеза. Главным объектом становится Archive, созданный для конкретной отправки.

Проверьте архив в копии, не меняя оригинал:

cp -R "/ПУТЬ/К_ПРОЕКТУ.xcarchive" "/ПУТЬ/К_КОПИИ_АРХИВА.xcarchive"
find "/ПУТЬ/К_КОПИИ_АРХИВА.xcarchive" \
  -name "PrivacyInfo.xcprivacy" -print

Путь намеренно оставлен шаблонным. Не подставляйте в статью или журнал имя проекта, Bundle ID, учётную запись и внутреннее имя SDK.

02 Формат plist отделяется от соответствия правилам

PrivacyInfo.xcprivacy — это plist, но успешный синтаксический разбор не доказывает, что декларация принята App Store Connect. На первом проходе проверьте корневой объект, вложенные словари, массивы, типы значений и неожиданные поля.

plutil -lint \
  "/ПУТЬ/К_КОПИИ_АРХИВА.xcarchive/Products/Applications/ПРИЛОЖЕНИЕ.app/PrivacyInfo.xcprivacy"

plutil -p \
  "/ПУТЬ/К_КОПИИ_АРХИВА.xcarchive/Products/Applications/ПРИЛОЖЕНИЕ.app/PrivacyInfo.xcprivacy"

Первая команда отвечает только на вопрос: можно ли прочитать plist. Вторая помогает увидеть фактические типы и вложенность. Для проверки структуры и размещения используйте также инструкцию Apple по добавлению манифеста.

Отдельно проверьте:

  • корневой объект — словарь, а не массив или строка;
  • массивы действительно содержат массивы, а не одну строку;
  • словари не заменены текстовыми значениями;
  • логические значения имеют тип Boolean;
  • пустые массивы не появились из-за переменной сборки;
  • в файле нет ключей, которые ваш инструмент добавил автоматически;
  • значения перечислений соответствуют официальным допустимым вариантам.

Не редактируйте файл прямо внутри единственного рабочего Archive. Даже исправление одного символа превращает старый продукт в изменённый пакет, а подпись перестаёт описывать его содержимое. Работайте с исходником или зависимостью, затем собирайте новый архив. Если временная правка Archive неизбежна, зафиксируйте старый хеш, изменение и процедуру повторной подписи.

Важно: plutil -lint подтверждает корректность синтаксиса. Он не подтверждает, что выбранная причина использования API соответствует функции приложения и что App Store Connect видит именно этот файл.

03 Required Reason API проверяется по фактическому вызову

Декларация должна описывать реальное использование API, а не подбираться по принципу «какое значение быстрее пропустит загрузку». Apple объясняет формат и допустимые причины в руководстве по добавлению записей Required Reason API и в справочнике описания использования Required Reason API.

Соберите доказательства по каждому подозрительному API:

  1. найдите вызов в собственном исходном коде;
  2. проверьте зависимости и их версии;
  3. определите, какой Bundle содержит бинарный код;
  4. сопоставьте API-категорию с функциональностью;
  5. выберите только официально разрешённую причину, которая описывает фактическое назначение;
  6. зафиксируйте путь к манифесту и Target, где размещена запись.

Для первичного поиска можно использовать поиск по исходникам и отчётам сборки:

grep -R "название_категории_API" \
  "/ПУТЬ/К_ИСХОДНИКАМ" \
  --exclude-dir=.git

Поиск не является доказательством сам по себе. Вызов может находиться в бинарной библиотеке, быть обёрнутым макросом или приходить из условной конфигурации. Сверяйте результат с зависимостями и финальным отчётом конфиденциальности.

Манифест главного приложения также не заменяет манифест SDK, который самостоятельно использует API. Если вызов принадлежит Framework или XCFramework, ответственность за его декларацию и поставку обычно нужно обсуждать с поддерживающим SDK разработчиком. Подмена причины без связи с функцией создаёт новую проблему: сборка может пройти техническую загрузку, но описание использования будет недостоверным.

04 Источник SDK определяет дальнейшее действие

Одинаковое сообщение требует разных решений в зависимости от способа доставки зависимости. Сначала определите источник, затем выбирайте обновление, замену или временную изоляцию.

Swift Package Manager

Проверьте Package.resolved, версию пакета и ресурсы, объявленные в Package.swift. Убедитесь, что PrivacyInfo.xcprivacy включён как ресурс нужного продукта, а не остался в репозитории без попадания в собранный Bundle. После обновления зафиксируйте изменённую версию и повторите чистую сборку.

CocoaPods

Проверьте Podspec, скрипты копирования ресурсов и итоговый Framework в Archive. Файл в каталоге Pods ещё не означает, что он попал в конечный продукт. Если манифест исправлен в новой версии Pod, обновите зависимость через lock-файл, а не только заменой файла в локальной папке.

Динамический Framework

Найдите Framework внутри Products/Applications/ПРИЛОЖЕНИЕ.app/Frameworks/. Затем проверьте его собственный манифест и подпись. Нельзя автоматически считать, что манифест главного App распространяется на вложенный Bundle.

Бинарный XCFramework

Определите, какой slice выбран для текущей платформы и архитектуры. Проверяйте именно попавший в Archive бинарный продукт. Если поставщик не выпускает исправление, запросите версию с корректным манифестом и подходящей подписью. Долгая ручная правка бинарного SDK повышает стоимость каждого обновления и усложняет воспроизводимость сборки.

Требования Apple к отдельным сторонним SDK, включая случаи, когда нужны манифест и подпись, перечислены на странице Third-party SDK requirements. Это особенно важно для SDK из официального перечня: проверяйте не только наличие файла, но и происхождение бинарного содержимого.

05 Bundle и Target проверяются по конечной структуре

Файл в навигаторе Xcode может быть включён в проект, но не включён в правильный Target. Обратная ситуация тоже возможна: скрипт сборки копирует вторую версию манифеста в продукт, и именно она становится причиной отказа.

Проверьте отдельно:

  • основной iOS App;
  • Extension;
  • Framework;
  • App Clip;
  • Mac Catalyst-продукт;
  • macOS Bundle;
  • вложенные продукты, указанные в сообщении App Store Connect.

Для основного продукта типичный путь внутри Archive выглядит так:

ПРИЛОЖЕНИЕ.xcarchive/
└── Products/
    └── Applications/
        └── ПРИЛОЖЕНИЕ.app/
            └── PrivacyInfo.xcprivacy

Это не универсальный путь для каждого Bundle. Вложенный Framework имеет собственный каталог, а Extension обычно лежит внутри приложения. Поэтому используйте find, затем сопоставляйте найденный путь с сообщением об ошибке.

Проверьте в Xcode:

  • Target Membership;
  • Copy Bundle Resources;
  • ресурсные настройки Swift Package;
  • фазы копирования;
  • скрипты, которые меняют Archive после сборки;
  • конфигурацию Release, а не только Debug.

Для отчёта конфиденциальности полезно сопоставить фактическую структуру Archive с документацией Apple о privacy reports. Отчёт помогает увидеть заявленные категории, но не отменяет ручную проверку ответственного Bundle и подписи.

Условия выбора следующего действия

  • Если путь из письма указывает на основной App, а файл отсутствует — исправьте включение ресурса в этот Target и создайте новый Archive.
  • Если путь указывает на Framework или Extension — не заменяйте его манифест декларацией главного приложения; проверьте отдельный Bundle.
  • Если plutil завершается ошибкой — сначала исправьте структуру plist, не переходя к выбору причины API.
  • Если синтаксис корректен, но App Store Connect отклоняет значение — сверяйте ключ, тип, допустимое значение и фактический вызов с документацией Apple.
  • Если проблема пришла из SDK — сначала обновите или замените зависимость; ручная правка допустима только как временная операция с повторной подписью.
  • Если Archive корректен, но загрузка не завершилась — отделите проблему манифеста от подписи, авторизации и обработки загрузки.

06 FAQ: отдельные случаи, которые часто смешивают

Как действовать при ITMS-91056?

Не удаляйте все манифесты и не добавляйте общий файл в главный Target. Сначала откройте письмо, скопируйте Bundle-путь и найдите этот объект в Archive. Затем определите, относится ли сообщение к формату, ключу, API, SDK или отсутствующему ресурсу. После исправления нужен новый Archive, новая подпись и новая загрузка.

Где искать PrivacyInfo.xcprivacy в xcarchive?

Ищите файл во всех конечных Bundle внутри Products, а не только в папке проекта. Основной App обычно находится в Products/Applications, а Framework и Extension — внутри его структуры. Если найдено несколько копий, сопоставьте каждую с Target и путём из письма. Именно эта связь показывает, какой файл реально анализировал App Store Connect.

Можно ли вручную исправить манифест SDK?

Только как временную техническую меру. Изменение упакованного Framework или Archive влияет на кодовую подпись. После этого необходима повторная подпись всех затронутых объектов по корректной процедуре. Для постоянной поставки лучше получить исправленную версию SDK, зафиксировать её в lock-файле и удалить ручную модификацию из процесса.

Почему локальная проверка проходит, а загрузка отклоняется?

Потому что локальная проверка могла затронуть исходный файл, а не финальный Bundle. Кроме того, plutil оценивает синтаксис, но не смысл разрешённой причины, наличие обязательных ключей, соответствие SDK или подпись бинарного продукта. Поэтому локальный успех — промежуточный статус, а не разрешение на отправку.

Нужно ли проверять Extension отдельно?

Да. Каждый самостоятельный продукт может иметь собственный код, ресурсы и вызовы Required Reason API. Проверьте Extension, Framework, App Clip и macOS-вариант отдельно, даже если главный App уже содержит корректный PrivacyInfo.xcprivacy. Манифест одного Bundle не должен использоваться как доказательство корректности другого.

07 Финальная приёмка разделяется на несколько статусов

Исправление считается завершённым не после сохранения файла, а после прохождения всей цепочки. Для каждого запуска храните commit, lock-файлы, параметры схемы, журнал сборки, Archive и отчёт конфиденциальности. Это особенно важно, если сборка выполняется через удалённый Mac или CI.

Этап проверки Что проверяется Критерий перехода
Исходники Target Membership, зависимости, манифесты Исправлен источник проблемы, версия SDK зафиксирована
Локальный plist Синтаксис и типы значений plutil читает файл без ошибки
Archive Конечные Bundle и пути Ответственный файл находится в нужном продукте
Подпись App, Framework, Extension и вложенные бинарные объекты Подпись соответствует изменённому содержимому
Загрузка Переданный продукт и сообщение App Store Connect Пакет принят для обработки
Обработка Финальный статус в App Store Connect Ошибка манифеста не повторяется

Для чистого запуска не переиспользуйте старый Archive. В Xcode используйте новую сборку Release и новый номер версии или сборки согласно вашей схеме релиза. После экспорта проверьте тот продукт, который фактически отправляется. Документ Apple о распространении приложения для бета-тестирования и релизов помогает разделить этапы Archive, экспорта и загрузки.

Ваша ситуация Основной кандидат на проблему Следующее действие
Ошибка указывает на App Файл отсутствует или неверен в главном Target Проверить ресурс и структуру plist в App
Ошибка указывает на Framework SDK поставил неверную декларацию Найти источник Framework и проверить версию
plutil не проходит Синтаксис, тип или повреждение plist Исправить исходный файл, затем пересобрать
plutil проходит, но App Store Connect отказывает Недопустимое значение или неправильный Bundle Сверить семантику, путь и финальный Archive
После правки появилась ошибка подписи Изменён упакованный продукт Вернуться к исходникам или выполнить корректную повторную подпись
Ошибка исчезла локально, но повторилась в CI Разные зависимости или скрипты сборки Сравнить lock-файл, окружение и журналы

Не считайте повторную загрузку того же продукта диагностикой. Она не меняет содержимое и не показывает, исправлен ли источник проблемы. Сначала меняется код или зависимость, затем выполняется новая сборка, после чего снова проверяются Bundle, подпись и отправленный файл.

Вариант исправления Когда выбирать Что сохранить как доказательство
Обновление SDK Поставщик выпустил версию с корректным манифестом Версию, lock-файл и журнал сборки
Исправление собственного Target Ошибка в включении ресурса или собственном plist Diff проекта и путь файла в Archive
Временная правка Archive Нужно подтвердить гипотезу до обновления зависимости Копию Archive, список изменений и протокол повторной подписи
Замена SDK Поставщик не исправляет проблему или декларация не соответствует функции Причину замены, новую зависимость и результат загрузки
Перенос сборки на воспроизводимый Mac Локальная и CI-среда дают разные продукты Версию Xcode, зависимости, логи и Archive

Для небольшой команды удалённый Mac полезен не как замена проверкам, а как сохраняемая среда воспроизводимой публикации. Если вы не хотите покупать отдельный Mac только для Archive и повторных загрузок, можно рассмотреть аренду Mac для удалённой сборки. Перед этим оцените требования к физическим устройствам, локальному USB-доступу и постоянной нагрузке: аренда не устранит ошибку в манифесте автоматически.

Текущая схема на личном Windows или Linux-компьютере часто приводит к ручному копированию Archive, разным версиям зависимостей и отсутствию полного журнала отправки. Общий CI без закреплённого Mac-окружения добавляет ещё одну переменную: скрипт может собрать не тот Bundle или подтянуть другой lock-файл. В такой ситуации CALMVPS разумно использовать для чистой архивации и повторной проверки, если вам нужны сохраняемые логи, доступ к реальному macOS и возможность повторить тот же выпуск без покупки отдельного устройства. В русскоязычном разделе CALMVPS заранее проверьте подходящий способ подключения и ограничения среды.

Перед запуском ещё одной отправки отметьте все пункты:

  • [ ] сохранено исходное письмо App Store Connect;
  • [ ] записан точный Bundle-путь и код ошибки;
  • [ ] проверен финальный xcarchive, а не только каталог исходников;
  • [ ] отдельно проверены App, Extension, Framework и другие Bundle;
  • [ ] plutil использован только для синтаксической проверки;
  • [ ] причины Required Reason API сопоставлены с реальными вызовами;
  • [ ] источник SDK и версия зафиксированы;
  • [ ] не изменён единственный рабочий Archive;
  • [ ] после изменения создан новый Archive;
  • [ ] затронутые продукты подписаны заново;
  • [ ] сохранены lock-файлы, журналы и отчёт конфиденциальности;
  • [ ] выполнена настоящая загрузка, а не повторная отправка старого файла;
  • [ ] дождались завершения обработки в App Store Connect.

Если все пункты отмечены, ошибка «недействительный манифест конфиденциальности» перестаёт быть поиском случайного файла. У вас появляется проверяемая цепочка: сообщение — Bundle — структура — смысл — источник SDK — подпись — результат загрузки.