From 2bbd8a06a92dec04d929d8ae74daafdbcbf83f3c Mon Sep 17 00:00:00 2001 From: Sinabina Date: Fri, 24 Jul 2026 11:21:15 -0700 Subject: [PATCH 1/3] =?UTF-8?q?feat(ship):=20upload=20key=20minted=20from?= =?UTF-8?q?=20web=20session=20=E2=80=94=20Transporter=20never=20takes=20th?= =?UTF-8?q?e=20session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple's binary-upload tool (iTMSTransporter behind deliver/pilot) accepts only an ASC API key or app-specific password; the spaceauth web session is not valid there (live error -22938 + fastlane auth docs). Corrected the contract: the session's job on the upload leg is to MINT the key via the iris endpoints the ASC web UI uses (POST /iris/v1/apiKeys, privateKey = base64-of-PEM downloadable once, issuer = olympus publicProviderId). Key stored at ~/.appstoreconnect/private_keys + ~/.gstack/apple/api-key.json; never expires, so repeat releases skip sign-in. Escalation ladder now: mint → re-sign-in + re-mint → ASP self-service only on a team-permissions refusal. Proven live end to end (mint + JWT lists team apps). Co-Authored-By: Claude Fable 5 --- scripts/gstack2/generate-skill-tree.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/gstack2/generate-skill-tree.ts b/scripts/gstack2/generate-skill-tree.ts index ad33f0b8ae..c20f610977 100644 --- a/scripts/gstack2/generate-skill-tree.ts +++ b/scripts/gstack2/generate-skill-tree.ts @@ -645,7 +645,7 @@ One tool runs the entire release: machine-level fastlane — \`produce\` (app re ## The one authorization moment -The whole journey permits exactly two interactions, and no others. FIRST, up front: confirm the user holds a paid Apple Developer Program membership (US$99/year — the App Store and TestFlight both require it) and authorize the release. Apple sign-in happens inside this same moment: run \`fastlane spaceauth -u \` through the host's interactive command path (in Claude Code, the user types \`! fastlane spaceauth -u \` so their password and one two-factor code go directly to Apple in-session; a separate terminal window is the fallback only when the host has no interactive path). Keep the printed session token out of the transcript — the cached cookie in \`~/.fastlane/spaceship/\` is the credential fastlane actually uses; never store, echo, or log the password or token, and re-run the same one command when the session expires. SECOND, only when preflight finds the icon or screenshots missing: the store-assets question below. Everything else — tool installs, upload, storefront, submission — is covered by the authorization and proceeds without asking. Auth menus, tool-choice questions, plan confirmations, and step-by-step narration requests are contract violations. +The whole journey permits exactly two interactions, and no others. FIRST, up front: confirm the user holds a paid Apple Developer Program membership (US$99/year — the App Store and TestFlight both require it) and authorize the release. Apple sign-in happens inside this same moment: run \`fastlane spaceauth -u \` through the host's interactive command path (in Claude Code, the user types \`! fastlane spaceauth -u \` so their password and one two-factor code go directly to Apple in-session; a separate terminal window is the fallback only when the host has no interactive path). Keep the printed session token out of the transcript — the cached cookie in \`~/.fastlane/spaceship/\` is the credential fastlane actually uses; never store, echo, or log the password or token, and re-run the same one command when the session expires. Immediately after the first sign-in, mint the permanent upload key from the session (step 4 of Archive and upload) — when that key already sits at \`~/.gstack/apple/api-key.json\` and no new app record is needed, skip the sign-in entirely: repeat releases authorize and proceed with zero sign-in. SECOND, only when preflight finds the icon or screenshots missing: the store-assets question below. Everything else — tool installs, upload, storefront, submission — is covered by the authorization and proceeds without asking. Auth menus, tool-choice questions, plan confirmations, and step-by-step narration requests are contract violations. No membership: STOP the App Store path. Offer to walk enrollment at developer.apple.com through \`references/THIRD-PARTY-ACTIONS.md\` (a purchase the user completes themselves; activation can take a day or two), and name the free-account ceiling honestly: personal-team installs on the user's own devices only, expiring after 7 days, no TestFlight, no App Store. @@ -674,9 +674,9 @@ Build this question's options from a LIVE check of installed skills at ask time 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) through the durable state wrapper with a key like \`appstore.upload..\`. Never re-upload on ambiguity; inspect App Store Connect for the build first. -3. The cached session is an env-level credential: never argv, never echoed, never committed. -4. NEVER demand an app-specific password. Per fastlane's documented authentication, the cached spaceauth session ALONE suffices for binary upload through \`deliver\`/\`pilot\`; \`FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD\` is an alternative for environments without a session, not a requirement on top of one. Attempt the session upload FIRST; only an actual authentication error from a real upload attempt may open a fallback conversation, and "Apple requires an app-specific password on 2FA accounts" stated without that error in hand 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 \`references/THIRD-PARTY-ACTIONS.md\`: 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 treat it as an expired or invalid session — ask the user to sign in again (the same \`! fastlane spaceauth -u \` moment as the original authorization) and retry the upload with the fresh session. Only if a FRESH session still fails the same way 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 \`), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no Aside, no agentic browser, for any password, key, or token, under any framing. +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()\` 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/?fields[apiKeys]=privateKey\` — the \`privateKey\` attribute is base64 of the COMPLETE PEM file: decode it exactly once and write \`~/.appstoreconnect/private_keys/AuthKey_.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 \`references/THIRD-PARTY-ACTIONS.md\`: 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 \` 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 \`), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no Aside, no agentic browser, for any password, key, or token, under any framing. 6. App Review contact details (name, email, phone) are required metadata for submission: infer name and email from the signed-in Apple ID and git config, collect the phone number once inside the authorization moment, persist it to the decision store, and never re-ask. Contact details are metadata, not a blocking gate to announce mid-run. ## Storefront completion From 19fdf1ed0231f8e19a08ee68103438badab3dace Mon Sep 17 00:00:00 2001 From: Sinabina Date: Fri, 24 Jul 2026 11:21:15 -0700 Subject: [PATCH 2/3] chore(gstack2): regenerate APPLE-RELEASE.md Co-Authored-By: Claude Fable 5 --- skills/ship/references/APPLE-RELEASE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skills/ship/references/APPLE-RELEASE.md b/skills/ship/references/APPLE-RELEASE.md index 3c9cae0539..ba61f9983e 100644 --- a/skills/ship/references/APPLE-RELEASE.md +++ b/skills/ship/references/APPLE-RELEASE.md @@ -7,7 +7,7 @@ One tool runs the entire release: machine-level fastlane — `produce` (app reco ## The one authorization moment -The whole journey permits exactly two interactions, and no others. FIRST, up front: confirm the user holds a paid Apple Developer Program membership (US$99/year — the App Store and TestFlight both require it) and authorize the release. Apple sign-in happens inside this same moment: run `fastlane spaceauth -u ` through the host's interactive command path (in Claude Code, the user types `! fastlane spaceauth -u ` so their password and one two-factor code go directly to Apple in-session; a separate terminal window is the fallback only when the host has no interactive path). Keep the printed session token out of the transcript — the cached cookie in `~/.fastlane/spaceship/` is the credential fastlane actually uses; never store, echo, or log the password or token, and re-run the same one command when the session expires. SECOND, only when preflight finds the icon or screenshots missing: the store-assets question below. Everything else — tool installs, upload, storefront, submission — is covered by the authorization and proceeds without asking. Auth menus, tool-choice questions, plan confirmations, and step-by-step narration requests are contract violations. +The whole journey permits exactly two interactions, and no others. FIRST, up front: confirm the user holds a paid Apple Developer Program membership (US$99/year — the App Store and TestFlight both require it) and authorize the release. Apple sign-in happens inside this same moment: run `fastlane spaceauth -u ` through the host's interactive command path (in Claude Code, the user types `! fastlane spaceauth -u ` so their password and one two-factor code go directly to Apple in-session; a separate terminal window is the fallback only when the host has no interactive path). Keep the printed session token out of the transcript — the cached cookie in `~/.fastlane/spaceship/` is the credential fastlane actually uses; never store, echo, or log the password or token, and re-run the same one command when the session expires. Immediately after the first sign-in, mint the permanent upload key from the session (step 4 of Archive and upload) — when that key already sits at `~/.gstack/apple/api-key.json` and no new app record is needed, skip the sign-in entirely: repeat releases authorize and proceed with zero sign-in. SECOND, only when preflight finds the icon or screenshots missing: the store-assets question below. Everything else — tool installs, upload, storefront, submission — is covered by the authorization and proceeds without asking. Auth menus, tool-choice questions, plan confirmations, and step-by-step narration requests are contract violations. No membership: STOP the App Store path. Offer to walk enrollment at developer.apple.com through `references/THIRD-PARTY-ACTIONS.md` (a purchase the user completes themselves; activation can take a day or two), and name the free-account ceiling honestly: personal-team installs on the user's own devices only, expiring after 7 days, no TestFlight, no App Store. @@ -36,9 +36,9 @@ Build this question's options from a LIVE check of installed skills at ask time 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) through the durable state wrapper with a key like `appstore.upload..`. Never re-upload on ambiguity; inspect App Store Connect for the build first. -3. The cached session is an env-level credential: never argv, never echoed, never committed. -4. NEVER demand an app-specific password. Per fastlane's documented authentication, the cached spaceauth session ALONE suffices for binary upload through `deliver`/`pilot`; `FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD` is an alternative for environments without a session, not a requirement on top of one. Attempt the session upload FIRST; only an actual authentication error from a real upload attempt may open a fallback conversation, and "Apple requires an app-specific password on 2FA accounts" stated without that error in hand 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 `references/THIRD-PARTY-ACTIONS.md`: 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 treat it as an expired or invalid session — ask the user to sign in again (the same `! fastlane spaceauth -u ` moment as the original authorization) and retry the upload with the fresh session. Only if a FRESH session still fails the same way 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 `), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no Aside, no agentic browser, for any password, key, or token, under any framing. +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()` 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/?fields[apiKeys]=privateKey` — the `privateKey` attribute is base64 of the COMPLETE PEM file: decode it exactly once and write `~/.appstoreconnect/private_keys/AuthKey_.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 `references/THIRD-PARTY-ACTIONS.md`: 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 ` 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 `), then the upload is retried. NEVER offer or recommend a browser drive to create credentials — no Aside, no agentic browser, for any password, key, or token, under any framing. 6. App Review contact details (name, email, phone) are required metadata for submission: infer name and email from the signed-in Apple ID and git config, collect the phone number once inside the authorization moment, persist it to the decision store, and never re-ask. Contact details are metadata, not a blocking gate to announce mid-run. ## Storefront completion From a0caef558dd5fbc981e1aedd6c0a7dc42dcc9283 Mon Sep 17 00:00:00 2001 From: Sinabina Date: Fri, 24 Jul 2026 11:28:25 -0700 Subject: [PATCH 3/3] docs: CHANGELOG + VERSION 1.64.18.0 for session-minted upload key Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6c0f7afc..9fcbd9741d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ > completion state and remaining P0 gates. No version bump or release claim is > made here while that status holds. +## [1.64.18.0] - 2026-07-24 + +**Your sign-in now mints the upload credential. Nobody types an app-specific password.** + +Apple runs binary uploads through a separate tool (iTMSTransporter) that never accepts the web session — it wants an App Store Connect API key or an app-specific password, and until now that surfaced as error -22938 and a dead stop. Probed live and proven: the same web session your one sign-in creates can mint that API key itself, through the exact endpoints the App Store Connect website uses. So the release does it silently — sign in once, gstack creates a permanent upload key, stores it locked-down on your machine, and every release after that needs no sign-in at all. The password prompt Apple's error message asks for is now the last-resort path for team members without admin rights, not the default anyone sees. + +### Itemized changes + +### Changed + +- `skills/ship/references/APPLE-RELEASE.md`: upload leg corrected — the web session mints an ASC API key (iris `POST /v1/apiKeys`, one-time `privateKey` download decoded from base64-of-PEM, issuer from `olympus/v1/session`) stored at `~/.appstoreconnect/private_keys/` + `~/.gstack/apple/api-key.json` (0600); `deliver`/`pilot` run with `api_key_path`; repeat releases skip sign-in entirely. Escalation ladder: mint → re-sign-in + re-mint → self-service app-specific password only on a team-permissions refusal. + ## [1.64.17.0] - 2026-07-24 **The broad fix: claimed limitations now require evidence, everywhere.** diff --git a/VERSION b/VERSION index 052f562808..c23289b8b4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.64.17.0 +1.64.18.0 diff --git a/package.json b/package.json index 5e3e5fc490..9f0dee04ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.64.17.0", + "version": "1.64.18.0", "description": "GStack 2 \u2014 six portable Agent Skills with an optional host-neutral runtime.", "license": "MIT", "type": "module",