Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.64.17.0
1.64.18.0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 4 additions & 4 deletions scripts/gstack2/generate-skill-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <apple-id>\` through the host's interactive command path (in Claude Code, the user types \`! fastlane spaceauth -u <email>\` 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 <apple-id>\` through the host's interactive command path (in Claude Code, the user types \`! fastlane spaceauth -u <email>\` 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.

Expand Down Expand Up @@ -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.<bundle-id>.<build>\`. 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 <apple-id>\` 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 <apple-id>\`), 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(<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 \`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 <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 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
Expand Down
Loading
Loading