apple resubmit: REJECTED review submission items are marked resolved before the submit (PATCH reviewSubmissionItems resolved: true; the version item by default, IAP / subscription items with --resolve-iaps), plan table 'mark resolved' rows; on 409 'not ready yet' the items are re-read and a REJECTED item out of scope stops the run at once naming it and how to resolve it, backoff only when none is left; an empty READY_FOR_REVIEW draft is never canceled (409 'not in cancellable state'): left as is, no cancel command, apple status labels it harmless and treats the cancel 409 as such; fake ASC blocks submit on REJECTED items and refuses to cancel empty drafts. Pitfall 30

This commit is contained in:
CRTkafa 2026-10-12 10:45:05 +08:00
parent 0da5ee9d01
commit 1724296e59
17 changed files with 291 additions and 78 deletions

View file

@ -12,7 +12,7 @@ Specs: Apple [App Store Connect API 4.5.1](https://developer.apple.com/sample-co
| API | operations | GET | other | wrapped by a command | owner only |
|---|---:|---:|---:|---:|---:|
| App Store Connect API 4.5.1 | 1303 | 826 | 477 | 198 | 166 |
| App Store Connect API 4.5.1 | 1303 | 826 | 477 | 199 | 166 |
| Google Play Android Developer API v3 (revision 20261008) | 145 | 45 | 100 | 33 | 49 |
| Google Play Developer Reporting API v1beta1 (revision 20261008) | 25 | 15 | 10 | 6 | 0 |
@ -1306,7 +1306,7 @@ Specs: Apple [App Store Connect API 4.5.1](https://developer.apple.com/sample-co
| **reviewSubmissionItems** (3) | | | | |
| `reviewSubmissionItems_createInstance` | POST | `/v1/reviewSubmissionItems` | write (dry by default) | apple/submission, lib/cpp-events, lib/review-submit |
| `reviewSubmissionItems_deleteInstance` | DELETE | `/v1/reviewSubmissionItems/{id}` | owner only (typed confirm) | |
| `reviewSubmissionItems_updateInstance` | PATCH | `/v1/reviewSubmissionItems/{id}` | write (dry by default) | |
| `reviewSubmissionItems_updateInstance` | PATCH | `/v1/reviewSubmissionItems/{id}` | write (dry by default) | apple/submission |
| **reviewSubmissions** (6) | | | | |
| `reviewSubmissions_createInstance` | POST | `/v1/reviewSubmissions` | owner only (typed confirm) | apple/cpp, apple/events, apple/submission, lib/review-submit |
| `reviewSubmissions_getCollection` | GET | `/v1/reviewSubmissions` | read | apple/events, apple/iap-submit, apple/status, apple/submission |

View file

@ -55,7 +55,7 @@ first. No write path sends the same values again on a second run.
| `apple cpp create` | page name; locale | page: findOrCreate + journal; locales from a fresh read, 409 = exists | ok T |
| `apple cpp upload` / `apple ppo upload` / `apple screenshots` | display type; position + MD5 | set: findOrCreate (unique, 409 = found); screenshots: matching prefix kept, rest replaced in order | ok T (twice, crash on the 3rd file) |
| `apple cpp submit` / `apple events submit` / `apple iap-submit` | the app's draft submission; version id per item | draft reused or created once (journal); an item in the draft is not added again | ok T (iap-submit) (owner: submit asks) |
| `apple iap-submit` / `apple resubmit` | the app's open review submission (draft or UNRESOLVED_ISSUES); version id per item | the open submission reused, never a second one; only missing items added; a draft the run created and could not use (409 ITEM_PART_OF_ANOTHER_SUBMISSION) canceled; a re-run after the submit sends nothing | ok T (resubmit) (owner: --yes) |
| `apple iap-submit` / `apple resubmit` | the app's open review submission (draft or UNRESOLVED_ISSUES); version id per item | the open submission reused, never a second one; only missing items added; a draft the run created and could not use (409 ITEM_PART_OF_ANOTHER_SUBMISSION) left as is (an empty draft is not cancellable, harmless, reused later); REJECTED items in scope marked resolved (`resolved: true` is idempotent); a re-run after the submit sends nothing | ok T (resubmit) (owner: --yes) |
| `apple events create` | reference name; locale | event: findOrCreate + journal; localizations planned from a fresh read | ok T |
| `apple events upload` | asset type per locale (size + delivered) | kept when the same size is delivered, else replaced | ok |
| `apple ppo create` | test name (open tests only); treatment name; locale | findOrCreate + journal for test and treatments | ok T |

View file

@ -9,7 +9,7 @@ fails without the fix.
| # | date | lesson | guard |
|---|---|---|---|
| 30 | 2026-10 | Resubmitting after an Invalid Binary rejection: `apple iap-submit --with-version --yes` created a second, empty draft next to the rejected submission (UNRESOLVED_ISSUES, holding the REJECTED version and 7 IAP versions) and stopped with "the store says it already exists but does not list it yet (lists lag)". The answer was really 409 `ITEM_PART_OF_ANOTHER_SUBMISSION` naming the rejected submission; and right after a new build is attached Apple answers the submit with 409 "Version is not ready to be submitted yet". | `chooseSubmission` lists the open submissions (READY_FOR_REVIEW, UNRESOLVED_ISSUES, WAITING_FOR_REVIEW, IN_REVIEW) and reuses one, never creating a second; `isConflict` excludes the code and `addItem` names the other submission, a draft the run created is canceled and the named one used; `submitWithRetry` (15 s, 30 s, 60 s, 2 min, heartbeat, 10 min limit); new `apple resubmit --build <n>` (build by number, plan table, `--yes`); `apple status` shows the UNRESOLVED_ISSUES items and the command. `tests/resubmit.test.ts`. |
| 30 | 2026-10 | Resubmitting after an Invalid Binary rejection: `apple iap-submit --with-version --yes` created a second, empty draft next to the rejected submission (UNRESOLVED_ISSUES, holding the REJECTED version and 7 IAP versions) and stopped with "the store says it already exists but does not list it yet (lists lag)". The answer was really 409 `ITEM_PART_OF_ANOTHER_SUBMISSION` naming the rejected submission; and right after a new build is attached Apple answers the submit with 409 "Version is not ready to be submitted yet". That 409 lasted 25 minutes with a VALID, APP_STORE_ELIGIBLE build attached and the console's Resubmit / Add for Review disabled: the appStoreVersion item was still REJECTED. `PATCH /v1/reviewSubmissionItems/{id}` `{"data":{"type":"reviewSubmissionItems","id":...,"attributes":{"resolved":true}}}` → 200, item READY_FOR_REVIEW, then the submit passed. An empty READY_FOR_REVIEW draft cannot be canceled (409 "Resource is not in cancellable state"). | `chooseSubmission` lists the open submissions (READY_FOR_REVIEW, UNRESOLVED_ISSUES, WAITING_FOR_REVIEW, IN_REVIEW) and reuses one, never creating a second; `isConflict` excludes the code and `addItem` names the other submission, a draft the run created is left as is (not cancellable, harmless) and the named one used; REJECTED items are marked resolved before the submit (`resolveRejected`: the version item in `apple resubmit`, IAP / subscription items with `--resolve-iaps`); `submitWithRetry` re-reads the items on "not ready", stops at once naming a REJECTED item out of scope and how to resolve it, and backs off (15 s, 30 s, 60 s, 2 min, heartbeat, 10 min limit) only when none is left; `apple status` labels an empty draft harmless and treats its cancel 409 as such; new `apple resubmit --build <n>` (build by number, plan table, `--yes`); `apple status` shows the UNRESOLVED_ISSUES items and the command. `tests/resubmit.test.ts`. |
| 29 | 2026-10 | Apple refused build 1 as Invalid Binary one minute after submission: "ITMS-91064: Invalid tracking information - A PrivacyInfo.xcprivacy file contains invalid tracking information at the following path: "PrivacyInfo.xcprivacy". NSPrivacyTracking must be true if NSPrivacyTrackingDomains isn't empty. Keys and values in your app's privacy manifests must be valid." The app's own manifest had NSPrivacyTracking true with an EMPTY NSPrivacyTrackingDomains, as storebound recommended from `appPrivacy.tracking: true` although the tracking came from an ad SDK (LevelPlay) whose own manifest declared it. Apple's wording names the other direction (domains listed, tracking false); both are invalid. After the fix (app manifest false, App Privacy still "used for tracking") `check` wrongly demanded the app manifest say true. | Rule (`trackingProblem` / `trackingIssue` / `itms91064` in `cli/lib/privacy-manifest.ts`): a manifest's NSPrivacyTracking and NSPrivacyTrackingDomains agree, in the app's and every SDK's; `check`, `scan` (blocker) and `privacy-manifest` (blocker) fail on either mismatch with the exact ITMS text and never suggest true without domains. App Privacy tracking no longer needs the app manifest's true: `check` looks for SDK manifests (Pods, frameworks, SwiftPM checkouts, Carthage, Capacitor plugins, the last built IPA; `cli/lib/privacy-files.ts`) that declare tracking (info), known tracking SDKs in the dependencies (info), else warns. `privacy-manifest --propose` and the scan proposal write true only with `appPrivacy.trackingDomains` (the app's own domains), else false. `apple upload` reads every PrivacyInfo.xcprivacy in the IPA (app root, Frameworks, bundles; XML or binary) and stops before any network call; `apple build` checks the exported IPA. `tests/privacy-tracking.test.ts`. |
| 28 | 2026-10 | First App Store release with IAPs: the console route failed an agent (the IAP page's "Add for Review" menu ignored scripted clicks; the version page no longer has an "In-App Purchases and Subscriptions" section), while `apple iap-submit --with-version --yes` added the IAPs to the draft submission that held 1.0.0 and submitted them first try. App Privacy's Publish opens a "Publish Your App Privacy Responses?" dialog and leaves a "Published … ago by …" line. | appstore-first-release and store-iap-setup point to `apple iap-submit --with-version` (the owner's yes: it submits), never the console. `console verify apple-app-privacy` reads the "Published …" line as the published signal; the plan names the dialog. `tests/console-play-plans.test.ts`. |
| 27 | 2026-10 | A real Play first release (an agent with a browser tool) found the steps no API does: the App category stayed "Not selected" (`play listing` cannot set it: edits.details has no category field); production had 0 countries (edits.countryAvailability is read-only); App content had ten cards with their own pages; the IARC questionnaire is one long page whose Yes answers reveal follow-ups, with an IARC Terms of Use agreement on page 1 and a final "Save" (not "Submit") on the Summary; Play's material checkboxes, like its radios, ignore native input; long evaluate calls timed out at 45 s; "Send changes for review" ignored a JS click; managed publishing is off by default, so approved means live. | Plans `play-store-settings`, `play-app-content` (+ one per card), `play-production-countries`, `play-production-release`; config `play.category` (Play's lists), `play.countries`, `play.managedPublishing`, `console.contentRating.details`, `console.appContent`, `social`, `products[].randomRewards`; every ask-owner answer in one batch; `check` warns without a category and counts the open App content answers; `validate` reports the category reminder and "production has 0 countries"; `play status` and `play release` say what is console-only; the IARC Terms box is never-press; material checkboxes get the inner [role=checkbox] click (plan and `console fill`); every plan ships `inPageLoop`. `tests/console-play-plans.test.ts`. |