apple signing ensure with automatic signing only adds capabilities (no profiles or certificates; the dry run says so) and lists unused storebound profiles with the exact delete command, never run; every capability write is read back (lag retries, timeout) and fails with the API answers when one did not stick; apple status, signing status and validate compare the App ID, apple.signing.capabilities and the iOS project's entitlements (CODE_SIGN_ENTITLEMENTS too) and name what is missing where; api writes outside a terminal say TTY by design and name the dedicated command; pitfalls 64-66; tests

This commit is contained in:
CRTkafa 2026-10-14 19:10:01 +08:00
parent 357319a775
commit daae3715bf
12 changed files with 463 additions and 23 deletions

View file

@ -94,6 +94,28 @@ export function decide(g: GateInput): Decision {
? 'owner-only operation: never through MCP or the desktop app; the owner runs it in a terminal'
: 'writes through `storebound api` are not available to MCP or the desktop app; run it in a terminal' };
if (g.dry || !g.yes) return { action: 'dry', why: g.dry ? '--dry' : 'writes are dry by default: add --yes (in a terminal) to send' };
if (!g.tty) return { action: 'dry', why: 'not an interactive terminal: writes through `storebound api` are only sent from one' };
if (!g.tty) return { action: 'dry', why: 'not an interactive terminal: by design, writes through `storebound api` are sent only from an interactive terminal (--yes alone is not enough)' };
return g.kind === 'owner' ? { action: 'confirm', typed: true } : { action: 'send' };
}
/** storebound commands that do the common writes of a resource the safe way (dry run, read-back, idempotent):
* named when a raw `api` write is held, so an agent uses them instead of a terminal. First match wins. */
const DEDICATED: [Store, RegExp, string][] = [
['apple', /^bundleIdCapabilities_/, 'storebound apple signing ensure --dry, then without --dry (adds apple.signing.capabilities to the App ID and reads them back)'],
['apple', /^bundleIds_/, 'storebound apple bundle --dry, then without --dry'],
['apple', /^(certificates|profiles)_/, 'storebound apple signing ensure (manual signing; automatic signing leaves certificates and profiles to Xcode)'],
['apple', /^devices_/, 'storebound apple adhoc register --dry (adhoc-distribution skill)'],
['apple', /^(inAppPurchases|inAppPurchaseLocalizations|inAppPurchasePriceSchedules)/, 'storebound apple iap --dry'],
['apple', /^(subscriptions|subscriptionGroups|subscriptionLocalizations|subscriptionPrices)/, 'storebound apple subscriptions --dry'],
['apple', /^(appStoreVersionLocalizations|appInfoLocalizations)_/, 'storebound apple listing --dry'],
['apple', /^(appScreenshotSets|appScreenshots)_/, 'storebound apple screenshots --dry'],
['apple', /^betaGroups_|^betaTesters_/, 'storebound apple testflight --dry'],
['play', /\.(listings|details)\./, 'storebound play listing --dry'],
['play', /\.images\./, 'storebound play images --dry'],
['play', /\.(inappproducts|onetimeproducts)\./, 'storebound play products --dry'],
['play', /\.subscriptions\./, 'storebound play subscriptions --dry'],
];
export function dedicatedCommand(store: Store, opId: string): string | null {
return DEDICATED.find(([s, re]) => s === store && re.test(opId))?.[2] ?? null;
}