lh-shipping · v1.1 · 2026-10-14 · sha256 734ee9f147c2765c
lh-shipping v1.1A
Immutable. This exact content never changes and is served at /api/v1/blob/734ee9f147c2765c.
--- name: lh-shipping description: > Getting apps into users' hands: signing, provisioning, TestFlight, App Review, and macOS-specific distribution (notarization, outside-MAS). Trigger: load on anything involving certificates, provisioning profiles, TestFlight, App Store Connect, App Review, entitlements, or distributing a Mac app. license: Apache-2.0 metadata: author: jrodrigopuca version: "1.1" local-agents-id: "sk-d94d54" --- _Verified against Xcode 26 / iOS 26 / macOS 26 (September 2026). Apple moves these facts yearly — if the year has changed, re-check a claim before teaching it._ ## Critical Patterns ### 1. This is the no-Socratic zone — answer first, demystify second Signing pain teaches nothing ([mentorship ladder](../../AGENTS.md), exception c): give the fix immediately, THEN the one mental model that makes the whole area stop being magic: **a signed app = your code + entitlements (what it may do) + a certificate (who you are) + a provisioning profile (the Apple-SIGNED permission slip binding cert + app ID + capabilities + devices)**. Not "notarized": notarization is a separate, macOS-only step (#4), and using one word for both is the most common confusion in this whole area. Ninety percent of signing errors are one of those four disagreeing with the others — read the error asking "WHICH pair disagrees?" and let Xcode's automatic signing do its job unless there's a concrete reason for manual. ### 2. TestFlight is a release rehearsal, not a formality The build that goes to TestFlight is release-configured: optimizations on, debug flags off, real (or realistic) backend — bugs that only exist in release builds (timing, optimization, missing debug scaffolding) are found HERE or by users. Internal testers = instant, external = brief review. Ship with meaningful "what to test" notes and working feedback collection; a beta nobody can give feedback on is theater. Every release candidate walks the core flows on a REAL DEVICE before submission — simulator-only confidence is rung-2 evidence. TestFlight builds expire after 90 days; a beta that nobody rebuilt in three months is a beta nobody can install. ### 3. App Review: read the rejection as a spec, not an insult Prevent the classics before submitting: every permission string (`NSCameraUsageDescription`...) says WHY in user terms; demo account provided if login gates content; no placeholder screens; privacy labels matching what the app actually collects; a `PrivacyInfo.xcprivacy` declaring required-reason APIs and tracking domains (enforced since May 2024 — ITMS-91053 is the missing one, and third-party SDKs on Apple's list need their own); the age-rating questionnaire in its 2025 form (4+/9+/13+/16+/18+ — since February 2026 updates are blocked until it's answered). Payments: IAP by default, but external purchase links and alternative marketplaces now exist per region (EU under the DMA, US after the 2025 Epic ruling) — verify the rule for YOUR storefront, it changes by court and by year. On rejection: it cites a guideline number — read THAT guideline, respond to THAT point (fix or a factual appeal). Arguing with the general unfairness of Apple is a hobby, not a strategy. Budget review time into every deadline (days, not hours) — telling stakeholders "it's done" when review hasn't started is a false "done" per the [lh-verification bar](../../../generalist/skills/lh-verification/SKILL.md). ### 4. macOS is a different distribution planet — decide the channel first iOS has channels too (App Store, TestFlight, enterprise/custom apps via Apple Business Manager, EU alternative marketplaces and web distribution), but they share one build. On macOS the channel changes the BUILD, so the decision comes first: | Channel | Requires | Buys you | Costs you | |---------|----------|----------|-----------| | Mac App Store | Sandbox + MAS review | Discovery, trusted install, IAP | Sandbox limits, review cycle | | Direct (site/GitHub) | Developer ID signing + **notarization** + hardened runtime | Full power, instant releases, own pricing | Your own updater (Sparkle), payments, no store discovery | Notarization is non-negotiable for direct distribution — an unnotarized app greets users with a scary Gatekeeper block, which is a first impression that [ux-ui](../../../ux-ui/AGENTS.md) would classify as a broken empty state for your whole product. Since macOS 15 the Control-click "Open" bypass is gone: an unnotarized app sends the user to System Settings > Privacy & Security, which is a support ticket, not a workaround. Automate it in CI — `notarytool submit --wait`, then `stapler staple` — so it's a step, not an event; without the staple, first launch needs an online check. ### 5. Entitlements and sandbox: request the minimum, explain every ask Each entitlement/permission is user trust spent. Sandbox file access beyond user-selected files, network server, camera — request only what the feature in front of the user justifies, at the MOMENT it's justified (permission prompts at first launch, before any value delivered, are the [gamification's `lh-flow-analysis`](../../../gamification/skills/lh-flow-analysis/SKILL.md) "premature ask" anti-pattern with an OS dialog). On macOS, test the sandboxed build specifically — code that worked unsandboxed and dies sandboxed is a classic day-before-release surprise. ### 6. Releases are versioned, automated, and reversible-ish Build numbers increment monotonically (automate — rejected-for-duplicate-build is a self-inflicted wound); version bumps follow semver-ish user expectations; release notes written for users, not commit logs. Phased release ON for anything risky (halt-able mid-rollout — the closest the App Store gets to the [stark parachute (`lh-zero-to-one`)](../../../stark/skills/lh-zero-to-one/SKILL.md) doctrine), and a rollback story thought through BEFORE shipping: you can't pull a build back, only ship a fix forward — so the fix-forward path (hotfix branch, expedited review request) is part of the release plan, not an improvisation. Certificates expire on their own clock (Developer ID in years, distribution sooner): shipped apps keep launching, new builds stop signing — the renewal date lives in the calendar, not in release week. ## Resources - Sibling skills: [lh-code-review](../lh-code-review/SKILL.md) (release-readiness findings), [lh-debugging](../lh-debugging/SKILL.md) (release-build-only bugs) - Crisis handling when a bad build ships: [stark/lh-crisis-mode](../../../stark/skills/lh-crisis-mode/SKILL.md)