mirror of https://github.com/garrytan/gstack.git
fix(ship): finish the Apple upload idempotency sentence
The durable-effect contract dropped its consequence clause mid-sentence — the instruction for what to DO when the idempotency key already exists (treat the upload as possibly-done, never re-run it) was missing from the one rule governing whether a binary uploads twice.
This commit is contained in:
parent
808344ecfd
commit
13b6c5c87b
|
|
@ -45,7 +45,7 @@ Build this question's options from a LIVE check of installed skills at ask time
|
|||
## Archive and upload
|
||||
|
||||
1. Archive and export the signed Release build with `gym` (it drives xcodebuild and the signing minted in preflight). Projects with custom archive requirements may drop to `xcodebuild archive` directly; the output either way is an App Store-signed `.ipa`.
|
||||
2. The upload is an external effect: run `pilot` (TestFlight) or `deliver` (App Store) under the durable-effect contract: before executing, append the key `appstore.upload.<bundle-id>.<build>` to `~/.gstack/projects/$SLUG/apple-effects.log`; if the key is already present from a prior run (crash, retry). Never re-upload on ambiguity; inspect App Store Connect for the build first.
|
||||
2. The upload is an external effect: run `pilot` (TestFlight) or `deliver` (App Store) under the durable-effect contract: before executing, append the key `appstore.upload.<bundle-id>.<build>` to `~/.gstack/projects/$SLUG/apple-effects.log`; if the key is already present from a prior run (crash, retry), treat the upload as possibly-done and do NOT re-run it. Never re-upload on ambiguity; inspect App Store Connect for the build first.
|
||||
3. The cached session, the minted key, and every credential file are env- or file-level secrets: never argv, never echoed, never committed.
|
||||
4. NEVER demand an app-specific password — the session MINTS the upload key instead. Per fastlane's documented authentication, Apple's binary-upload tool (iTMSTransporter, which `deliver`/`pilot` shell out to for the `.ipa`) does not accept the web session: it takes only an App Store Connect API key or an app-specific password, and Apple's error `-22938` ("Sign in with the app-specific password") is Transporter saying exactly this. That is not a gate and not a question, because the web session creates the key itself, silently: through fastlane's bundled spaceship (`Spaceship::Tunes.login(<apple-id>)` reuses the cached cookie, then raw client requests), `POST https://appstoreconnect.apple.com/iris/v1/apiKeys` with JSON:API body `{data:{type:"apiKeys",attributes:{nickname:"gstack-upload",allAppsVisible:true,roles:["APP_MANAGER"],keyType:"PUBLIC_API"}}}`, then `GET .../iris/v1/apiKeys/<id>?fields[apiKeys]=privateKey` — the `privateKey` attribute is base64 of the COMPLETE PEM file: decode it exactly once and write `~/.appstoreconnect/private_keys/AuthKey_<id>.p8` (0600) immediately, it is downloadable only at creation. The issuer ID is `provider.publicProviderId` from `GET https://appstoreconnect.apple.com/olympus/v1/session`. Record key id, issuer id, and key content as a fastlane api-key JSON at `~/.gstack/apple/api-key.json` (0600) and run `deliver`/`pilot` with `api_key_path` from then on. The key never expires, so every later release skips sign-in; the session stays necessary only for `produce` (Apple's public API cannot create app records) and for re-minting if the key is ever revoked. Stating that the user must generate any credential themselves while key minting is untried is a contract violation. CLASSIFY the error before touching credentials: an error is an authentication failure ONLY when it says so (401/403, session invalid or expired, "sign in", "app-specific password" in Apple's own words). A `Spaceship::UnexpectedResponse`, missing/invalid attribute, validation, or precheck error is a METADATA problem — fix the payload (for example, Apple's expanded age-rating attributes such as `lootBox`, `ageAssurance`, `parentalControls`, `messagingAndChat` in `app_rating_config.json`) and retry from the CLI. Treating a metadata error as a credential problem is a contract violation.
|
||||
5. Within an Apple release, this adapter OVERRIDES the Third-Party Web Actions contract (earlier in this skill): the general agentic-browser offer never applies to App Store Connect, Apple ID, or credential work here. The entire release is CLI (fastlane) plus the two permitted interactions; the ONLY browser use this adapter allows, ever, is the paid-app agreements/banking/tax residue named at the end of this document. Opening a browser — driven or manual — for anything else in this journey is a contract violation. When a real error does force the fallback, QUOTE the error verbatim, then escalate in this order: FIRST mint (or re-mint) the upload key from the session per step 4 and retry the upload with `api_key_path` — an upload-auth error with no key on disk means the mint was skipped, not that the user owes a credential. SECOND, if the minting itself fails with a session error, ask the user to sign in again (the same `! fastlane spaceauth -u <apple-id>` moment as the original authorization), re-mint, and retry. Only when a FRESH session still cannot mint a key — a permissions refusal because the signed-in Apple ID is not Admin or Account Holder on its team — does the app-specific-password path open, and its only shape is self-service: the user generates the password on any device and enters it through the host's in-session masked prompt into the macOS keychain (`fastlane fastlane-credentials add --username <apple-id>`), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no agentic browser of any kind, for any password, key, or token, under any framing.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ Build this question's options from a LIVE check of installed skills at ask time
|
|||
## Archive and upload
|
||||
|
||||
1. Archive and export the signed Release build with `gym` (it drives xcodebuild and the signing minted in preflight). Projects with custom archive requirements may drop to `xcodebuild archive` directly; the output either way is an App Store-signed `.ipa`.
|
||||
2. The upload is an external effect: run `pilot` (TestFlight) or `deliver` (App Store) under the durable-effect contract: before executing, append the key `appstore.upload.<bundle-id>.<build>` to `~/.gstack/projects/$SLUG/apple-effects.log`; if the key is already present from a prior run (crash, retry). Never re-upload on ambiguity; inspect App Store Connect for the build first.
|
||||
2. The upload is an external effect: run `pilot` (TestFlight) or `deliver` (App Store) under the durable-effect contract: before executing, append the key `appstore.upload.<bundle-id>.<build>` to `~/.gstack/projects/$SLUG/apple-effects.log`; if the key is already present from a prior run (crash, retry), treat the upload as possibly-done and do NOT re-run it. Never re-upload on ambiguity; inspect App Store Connect for the build first.
|
||||
3. The cached session, the minted key, and every credential file are env- or file-level secrets: never argv, never echoed, never committed.
|
||||
4. NEVER demand an app-specific password — the session MINTS the upload key instead. Per fastlane's documented authentication, Apple's binary-upload tool (iTMSTransporter, which `deliver`/`pilot` shell out to for the `.ipa`) does not accept the web session: it takes only an App Store Connect API key or an app-specific password, and Apple's error `-22938` ("Sign in with the app-specific password") is Transporter saying exactly this. That is not a gate and not a question, because the web session creates the key itself, silently: through fastlane's bundled spaceship (`Spaceship::Tunes.login(<apple-id>)` reuses the cached cookie, then raw client requests), `POST https://appstoreconnect.apple.com/iris/v1/apiKeys` with JSON:API body `{data:{type:"apiKeys",attributes:{nickname:"gstack-upload",allAppsVisible:true,roles:["APP_MANAGER"],keyType:"PUBLIC_API"}}}`, then `GET .../iris/v1/apiKeys/<id>?fields[apiKeys]=privateKey` — the `privateKey` attribute is base64 of the COMPLETE PEM file: decode it exactly once and write `~/.appstoreconnect/private_keys/AuthKey_<id>.p8` (0600) immediately, it is downloadable only at creation. The issuer ID is `provider.publicProviderId` from `GET https://appstoreconnect.apple.com/olympus/v1/session`. Record key id, issuer id, and key content as a fastlane api-key JSON at `~/.gstack/apple/api-key.json` (0600) and run `deliver`/`pilot` with `api_key_path` from then on. The key never expires, so every later release skips sign-in; the session stays necessary only for `produce` (Apple's public API cannot create app records) and for re-minting if the key is ever revoked. Stating that the user must generate any credential themselves while key minting is untried is a contract violation. CLASSIFY the error before touching credentials: an error is an authentication failure ONLY when it says so (401/403, session invalid or expired, "sign in", "app-specific password" in Apple's own words). A `Spaceship::UnexpectedResponse`, missing/invalid attribute, validation, or precheck error is a METADATA problem — fix the payload (for example, Apple's expanded age-rating attributes such as `lootBox`, `ageAssurance`, `parentalControls`, `messagingAndChat` in `app_rating_config.json`) and retry from the CLI. Treating a metadata error as a credential problem is a contract violation.
|
||||
5. Within an Apple release, this adapter OVERRIDES the Third-Party Web Actions contract (earlier in this skill): the general agentic-browser offer never applies to App Store Connect, Apple ID, or credential work here. The entire release is CLI (fastlane) plus the two permitted interactions; the ONLY browser use this adapter allows, ever, is the paid-app agreements/banking/tax residue named at the end of this document. Opening a browser — driven or manual — for anything else in this journey is a contract violation. When a real error does force the fallback, QUOTE the error verbatim, then escalate in this order: FIRST mint (or re-mint) the upload key from the session per step 4 and retry the upload with `api_key_path` — an upload-auth error with no key on disk means the mint was skipped, not that the user owes a credential. SECOND, if the minting itself fails with a session error, ask the user to sign in again (the same `! fastlane spaceauth -u <apple-id>` moment as the original authorization), re-mint, and retry. Only when a FRESH session still cannot mint a key — a permissions refusal because the signed-in Apple ID is not Admin or Account Holder on its team — does the app-specific-password path open, and its only shape is self-service: the user generates the password on any device and enters it through the host's in-session masked prompt into the macOS keychain (`fastlane fastlane-credentials add --username <apple-id>`), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no agentic browser of any kind, for any password, key, or token, under any framing.
|
||||
|
|
|
|||
Loading…
Reference in New Issue