docs: add Apple App Store + Google Play Store release guides

- APPLE/README.md: API key details, App Store Connect setup, TestFlight workflow, submission checklist, provisioning, screenshots/metadata requirements
- GOOGLE/README.md: Play Console setup, service account config, signing keys, AAB upload, testing tracks, data safety, content rating, submission checklist
This commit is contained in:
saravanakumardb1 2026-04-04 23:36:50 -07:00
parent 05594a334f
commit a762c5b07f
2 changed files with 614 additions and 0 deletions

View File

@ -0,0 +1,283 @@
# Apple Developer — App Store & TestFlight Guide
## API Key
| Field | Value |
| --------- | -------------------------------------- |
| Key ID | `PPATU9GL73` |
| Issuer ID | `1dbc2980-1621-4fb9-940b-e28257e6322c` |
| Role | App Manager |
| File | `AuthKey_PPATU9GL73.p8` |
> **⚠️ The `.p8` file is NOT checked into git** (blocked by secret scanner). It lives at `~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8` on each machine. Copy it manually between machines or re-download from App Store Connect (one-time download only — keep a backup).
### Setup on any machine
```bash
mkdir -p ~/.appstoreconnect/private_keys
# Copy the .p8 from another machine or your backup
cp /path/to/AuthKey_PPATU9GL73.p8 ~/.appstoreconnect/private_keys/
chmod 600 ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8
```
---
## Apple Developer Account
| Field | Value |
| ----------------- | ------------------------------------------------------------------ |
| Team ID | `748N7QPX7J` |
| Apple ID | `saravanakumardb1@gmail.com` |
| Portal | [developer.apple.com/account](https://developer.apple.com/account) |
| App Store Connect | [appstoreconnect.apple.com](https://appstoreconnect.apple.com) |
---
## Registered Apps
| Product | Bundle ID | App Store Connect | SKU | Status |
| ------------------ | --------------------------------- | ----------------- | ---------- | ------------------------------------------ |
| LysnrAI | `com.bytelyst.LysnrAI` | ✅ Created | lysnrai | In TestFlight (build 59) |
| ChronoMind | `com.saravana.chronomind` | ❌ Needs creation | chronomind | Bundle ID registered, pending app creation |
| ChronoMind Widgets | `com.saravana.chronomind.widgets` | N/A (extension) | — | Bundle ID registered |
---
## App Store Connect — Creating a New App
1. Go to [App Store Connect → My Apps](https://appstoreconnect.apple.com/apps)
2. Click **"+"** → **"New App"**
3. Fill in:
- **Platforms:** iOS
- **Name:** Product display name (e.g., `ChronoMind`)
- **Primary Language:** English (U.S.)
- **Bundle ID:** Select from dropdown (must be registered first — see below)
- **SKU:** Lowercase product ID (e.g., `chronomind`)
- **User Access:** Full Access
4. Click **Create**
---
## Registering a New Bundle ID
### Via API (preferred)
```python
import jwt, time, json, urllib.request
with open('AuthKey_PPATU9GL73.p8', 'r') as f:
key = f.read()
token = jwt.encode(
{'iss': '1dbc2980-1621-4fb9-940b-e28257e6322c', 'iat': int(time.time()),
'exp': int(time.time()) + 1200, 'aud': 'appstoreconnect-v1'},
key, algorithm='ES256', headers={'kid': 'PPATU9GL73', 'typ': 'JWT'}
)
data = json.dumps({'data': {'type': 'bundleIds', 'attributes': {
'identifier': 'com.saravana.PRODUCT',
'name': 'ProductName',
'platform': 'IOS'
}}}).encode()
req = urllib.request.Request(
'https://api.appstoreconnect.apple.com/v1/bundleIds',
data=data,
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
method='POST'
)
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['id'])
```
### Via Portal
1. Go to [Certificates, Identifiers & Profiles → Identifiers](https://developer.apple.com/account/resources/identifiers/list)
2. Click **"+"** → **App IDs** → **App**
3. Enter description + bundle ID
4. Enable capabilities (App Groups, Push Notifications, etc.)
5. Click **Register**
---
## TestFlight — Uploading a Build
### Automated (recommended)
Each product repo has a `release-testflight.sh` script:
```bash
# LysnrAI
cd learning_voice_ai_agent/mobile_app/ios
# (uses manual pbxproj — see .windsurf/workflows/release-testflight.md)
# ChronoMind
cd learning_ai_clock/ios
bash release-testflight.sh
```
### Manual CLI
```bash
# 1. Archive
xcodebuild archive \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Release \
-archivePath /tmp/MyApp.xcarchive \
-destination 'generic/platform=iOS'
# 2. Export + Upload
xcodebuild -exportArchive \
-archivePath /tmp/MyApp.xcarchive \
-exportPath /tmp/MyApp_export \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates \
-authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 \
-authenticationKeyID PPATU9GL73 \
-authenticationKeyIssuerID 1dbc2980-1621-4fb9-940b-e28257e6322c
```
### ExportOptions.plist (template)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>748N7QPX7J</string>
<key>destination</key>
<string>upload</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
```
---
## App Store Submission Checklist
Before submitting for App Store review, ensure:
### Required Assets
| Asset | Spec | Notes |
| ------------------ | ----------------------- | ---------------------------------------- |
| App Icon | 1024×1024 PNG | No alpha channel, no rounded corners |
| Screenshots (6.7") | 1290×2796 | iPhone 15 Pro Max — required |
| Screenshots (6.5") | 1284×2778 | iPhone 14 Plus — required |
| Screenshots (5.5") | 1242×2208 | iPhone 8 Plus — optional but recommended |
| iPad Screenshots | 2048×2732 | Required if app supports iPad |
| App Preview Video | 1920×1080 or device res | Optional, up to 30 seconds |
### Required Metadata
| Field | Notes |
| ------------------ | ------------------------------------------------ |
| App Name | Max 30 characters |
| Subtitle | Max 30 characters |
| Description | Required, no max (but first 3 lines matter most) |
| Keywords | Max 100 characters, comma-separated |
| Support URL | Must be a valid, reachable URL |
| Privacy Policy URL | **Required** — must be a valid, reachable URL |
| Category | Primary + optional secondary |
| Age Rating | Answer the questionnaire honestly |
| Copyright | e.g., `© 2026 ByteLyst` |
### Required for Review
| Requirement | Details |
| ---------------------- | --------------------------------------------------------------- |
| Privacy Policy URL | Must be publicly accessible |
| App Review Information | Contact info (name, phone, email) for reviewer |
| Demo Account | If app requires login, provide test credentials |
| Notes for Review | Explain any non-obvious features or permissions |
| Content Rights | Confirm you have rights to all content |
| IDFA Declaration | If using AdSupport framework |
| Export Compliance | Encryption usage (most apps: standard encryption = YES, exempt) |
### Privacy & Permissions
For each permission your app uses, you must:
1. Add a usage description string in Info.plist (e.g., `NSMicrophoneUsageDescription`)
2. Declare it in App Store Connect → App Privacy
Common permissions for ByteLyst apps:
| Permission | Info.plist Key | Apps |
| ------------------ | ------------------------------------- | ----------------- |
| Microphone | `NSMicrophoneUsageDescription` | LysnrAI |
| Speech Recognition | `NSSpeechRecognitionUsageDescription` | LysnrAI |
| Location | `NSLocationWhenInUseUsageDescription` | PeakPulse |
| Health | `NSHealthShareUsageDescription` | PeakPulse, NomGap |
| Notifications | `NSUserNotificationsUsageDescription` | All |
| Camera | `NSCameraUsageDescription` | MindLyst |
### App Privacy (Data Collection)
In App Store Connect → App Privacy, declare:
- What data types your app collects
- Whether data is linked to user identity
- Whether data is used for tracking
- Purpose of each data type
---
## TestFlight Beta Testing
### Internal Testing (up to 100 testers)
1. Upload build (see above)
2. Wait ~15-30 min for processing
3. Go to App Store Connect → TestFlight → Internal Testing
4. Add testers by Apple ID email
5. Testers install via TestFlight app
### External Testing (up to 10,000 testers)
1. Create a beta group in TestFlight
2. Add a build to the group
3. Submit for **Beta App Review** (required for external testers)
4. Review takes ~24-48 hours
5. Once approved, distribute via link or email
### Beta App Review Requirements
- Same as App Store review but slightly more lenient
- Still needs: privacy policy URL, app description, contact info
- Crashes or obvious bugs will be rejected
---
## Certificates & Signing
| Type | Purpose | Managed By |
| ------------------ | ----------------------------- | ------------------------------ |
| Apple Development | Debug builds, simulator | Xcode (automatic) |
| Apple Distribution | App Store / TestFlight builds | Xcode (automatic with API key) |
With `-allowProvisioningUpdates` and the API key, Xcode automatically manages certificates and provisioning profiles. No manual certificate management needed.
---
## Troubleshooting
| Error | Fix |
| ------------------------------------------- | -------------------------------------------------------- |
| `No signing certificate "iOS Distribution"` | Use `-allowProvisioningUpdates` with API key |
| `Authentication failed` | Check .p8 key path, Key ID, Issuer ID |
| `Error Downloading App Information` | Create app record in App Store Connect first |
| `App Groups capability` | Register App Group in developer portal |
| `FORBIDDEN_ERROR` on app creation | API key needs Admin role (App Manager can't create apps) |
| `Bundle ID not available` | Someone else registered it — use a different one |
| `Missing compliance` | Set Export Compliance in App Store Connect |
| `Build processing stuck` | Wait up to 1 hour; check email for processing errors |

View File

@ -0,0 +1,331 @@
# Google Play Store — Android App Distribution Guide
## Google Play Console
| Field | Value |
| ----------------- | ---------------------------------------------------------- |
| Console URL | [play.google.com/console](https://play.google.com/console) |
| Developer Account | ByteLyst / saravanakumardb1@gmail.com |
---
## Registered Apps
| Product | Package Name | Play Console | Status |
| ---------- | ---------------------- | ----------------- | -------------------------------- |
| LysnrAI | `com.bytelyst.lysnrai` | ❌ Needs creation | Gradle builds, pending setup |
| ChronoMind | `com.chronomind.app` | ❌ Needs creation | Gradle builds, pending setup |
| MindLyst | `com.mindlyst.android` | ❌ Needs creation | KMP shared module, pending setup |
| JarvisJr | `com.jarvisjr.app` | ❌ Needs creation | Jetpack Compose, pending setup |
---
## One-Time Setup
### 1. Google Play Console Account
If you don't have one yet:
1. Go to [play.google.com/console/signup](https://play.google.com/console/signup)
2. Pay the one-time $25 registration fee
3. Complete identity verification (can take 48 hours)
### 2. Create a Service Account (for CLI uploads)
1. Go to [Google Cloud Console → IAM → Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts)
2. Select or create a project (e.g., `bytelyst-releases`)
3. Click **"+ Create Service Account"**
- Name: `play-upload`
- Role: (skip — we'll grant access in Play Console)
4. Click **"..."** → **"Manage keys"** → **"Add Key"** → **"Create new key"** → **JSON**
5. Save the JSON key file to this directory and `~/.google-play/`
```bash
mkdir -p ~/.google-play
cp service-account-key.json ~/.google-play/
chmod 600 ~/.google-play/service-account-key.json
```
### 3. Link Service Account to Play Console
1. Go to [Play Console → Settings → API access](https://play.google.com/console/developers/api-access)
2. Click **"Link"** next to Google Cloud project
3. Under Service Accounts, find `play-upload`**"Grant access"**
4. Set permissions: **Release manager** (or **Admin** for full control)
5. Add the apps it can manage
---
## Creating a New App in Play Console
1. Go to [Play Console → All apps](https://play.google.com/console/developers)
2. Click **"Create app"**
3. Fill in:
- **App name:** Product name (e.g., `ChronoMind`)
- **Default language:** English (United States)
- **App or Game:** App
- **Free or Paid:** Free
4. Accept declarations and click **"Create app"**
### After Creation — Complete the Store Listing
Navigate through the **Dashboard** checklist:
| Section | Required For |
| --------------- | ------------------------------------------------ |
| App access | If app requires login → provide test credentials |
| Ads | Declare if app contains ads |
| Content rating | Complete IARC questionnaire |
| Target audience | Select age groups |
| News apps | Declare if it's a news app |
| COVID-19 apps | Declare if related |
| Data safety | Privacy questionnaire (like Apple's App Privacy) |
| Government apps | Declare if government-related |
---
## Building an Android Release
### Signing Key Setup (one-time per app)
```bash
# Generate an upload keystore
keytool -genkey -v \
-keystore ~/keystores/chronomind-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias upload \
-storepass <PASSWORD> \
-keypass <PASSWORD> \
-dname "CN=ByteLyst, O=ByteLyst, L=Dallas, ST=TX, C=US"
```
> **⚠️ CRITICAL:** Back up the keystore file and passwords. If you lose them, you cannot update the app. Store a copy in this directory.
### Configure signing in the app
Create or edit `android/keystore.properties` (gitignored):
```properties
storePassword=<PASSWORD>
keyPassword=<PASSWORD>
keyAlias=upload
storeFile=/Users/<you>/keystores/chronomind-upload.jks
```
Reference in `android/app/build.gradle.kts`:
```kotlin
val keystoreProperties = Properties()
val keystoreFile = rootProject.file("keystore.properties")
if (keystoreFile.exists()) keystoreProperties.load(keystoreFile.inputStream())
android {
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as? String
keyPassword = keystoreProperties["keyPassword"] as? String
storeFile = file(keystoreProperties["storeFile"] as? String ?: "")
storePassword = keystoreProperties["storePassword"] as? String
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
```
### Build the Release AAB
```bash
cd android
./gradlew :app:bundleRelease
# Output: app/build/outputs/bundle/release/app-release.aab
```
### Build a Release APK (for direct install)
```bash
./gradlew :app:assembleRelease
# Output: app/build/outputs/apk/release/app-release.apk
```
---
## Uploading to Play Console
### Manual Upload (Play Console UI)
1. Go to app → **Production** (or **Internal testing** / **Closed testing**)
2. Click **"Create new release"**
3. Upload the `.aab` file
4. Add release notes
5. Click **"Review release"** → **"Start rollout"**
### CLI Upload via bundletool + Google API
```bash
pip install google-api-python-client google-auth
python3 -c "
from googleapiclient.discovery import build
from google.oauth2 import service_account
creds = service_account.Credentials.from_service_account_file(
'$HOME/.google-play/service-account-key.json',
scopes=['https://www.googleapis.com/auth/androidpublisher']
)
service = build('androidpublisher', 'v3', credentials=creds)
package = 'com.chronomind.app'
edit = service.edits().insert(body={}, packageName=package).execute()
edit_id = edit['id']
service.edits().bundles().upload(
editId=edit_id, packageName=package,
media_body='app/build/outputs/bundle/release/app-release.aab',
media_mime_type='application/octet-stream'
).execute()
service.edits().tracks().update(
editId=edit_id, packageName=package, track='internal',
body={'releases': [{'status': 'completed', 'versionCodes': ['1']}]}
).execute()
service.edits().commit(editId=edit_id, packageName=package).execute()
print('Upload complete!')
"
```
### Using Gradle Play Publisher Plugin (recommended for CI)
Add to `android/build.gradle.kts`:
```kotlin
plugins {
id("com.github.triplet.play") version "3.11.0"
}
play {
serviceAccountCredentials.set(file("$HOME/.google-play/service-account-key.json"))
track.set("internal") // internal, alpha, beta, production
defaultToAppBundles.set(true)
}
```
Then:
```bash
./gradlew publishBundle # Upload AAB to configured track
./gradlew publishInternalBundle # Upload to internal testing
./gradlew publishAlphaBundle # Upload to closed testing
./gradlew publishBundle --track production # Upload to production
```
---
## Testing Tracks
| Track | Testers | Review Required | Purpose |
| ---------------------- | -------------------- | -------------------- | ----------------- |
| Internal testing | Up to 100 (by email) | No | Quick dev testing |
| Closed testing (Alpha) | Invite-only | No (first time: yes) | Wider beta |
| Open testing (Beta) | Anyone with link | Yes (first time) | Public beta |
| Production | Everyone | Yes | Full release |
### Internal Testing Setup
1. Go to app → **Testing** → **Internal testing**
2. Click **"Create new release"**
3. Upload `.aab`
4. Click **"Review release"** → **"Start rollout"**
5. Go to **Testers** tab → Create email list → Add tester emails
6. Share the opt-in link with testers
---
## Play Store Submission Checklist
### Required Assets
| Asset | Spec | Notes |
| ------------------------ | ------------------------ | ----------------------------- |
| App Icon | 512×512 PNG | 32-bit, no alpha |
| Feature Graphic | 1024×500 PNG or JPG | Required for store listing |
| Screenshots (Phone) | 320-3840px, 16:9 or 9:16 | Min 2, max 8 per device type |
| Screenshots (7" Tablet) | Same as phone | Required if targeting tablets |
| Screenshots (10" Tablet) | Same as phone | Required if targeting tablets |
| Promo Video | YouTube URL | Optional |
### Required Metadata
| Field | Notes |
| ------------------- | --------------------------- |
| App name | Max 30 characters |
| Short description | Max 80 characters |
| Full description | Max 4000 characters |
| App category | Select from predefined list |
| Contact email | Required, public |
| Privacy Policy URL | **Required** |
| App type & category | App or Game + category |
### Data Safety Declaration
In Play Console → App content → Data safety:
- Declare all data types collected
- Whether data is encrypted in transit
- Whether users can request deletion
- Whether data is shared with third parties
- Purpose of each data type
### Content Rating (IARC)
1. Go to App content → Content rating
2. Complete the IARC questionnaire
3. Receive ratings for all regions automatically
---
## Corporate Network / Gradle Proxy
When building on the corporate network:
```bash
# Ensure NETWORK=corp is set (switch-network.sh)
echo $GRADLE_OPTS
# Should show -Djavax.net.ssl.trustStore=~/.gradle/ssl/gradle-cacerts.jks
# If not set:
export GRADLE_OPTS="-Djavax.net.ssl.trustStore=$HOME/.gradle/ssl/gradle-cacerts.jks -Djavax.net.ssl.trustStorePassword=changeit"
```
See `learning_ai_common_plat/AGENTS.md` § 9 for full proxy setup docs.
---
## Troubleshooting
| Error | Fix |
| ----------------------------- | ------------------------------------------------------------ |
| `Keystore was tampered with` | Wrong password or corrupted keystore |
| `No key with alias 'upload'` | Check `keyAlias` in keystore.properties |
| `Version code already exists` | Bump `versionCode` in build.gradle.kts |
| `APK/AAB not signed` | Check signingConfig in release buildType |
| `Deobfuscation file missing` | Upload mapping.txt alongside the AAB |
| `Data safety form incomplete` | Complete all sections in App content → Data safety |
| `Target API level` | Google requires targeting latest Android API (currently 34+) |
| Gradle proxy failure | Set `GRADLE_OPTS` with truststore (see above) |
| `SDK not found` | Set `sdk.dir` in `local.properties` |
---
## Key Files
| File | Location | Purpose |
| ------------------- | ------------------------------------------ | -------------------- |
| Service Account Key | `~/.google-play/service-account-key.json` | CLI uploads |
| Upload Keystore | `~/keystores/<app>-upload.jks` | Release signing |
| keystore.properties | `android/keystore.properties` (gitignored) | Local signing config |