Infrastructure & Environments
Dev/prod split architecture, CloudKit containers, App Store Connect setup, Superwall configuration, and release workflow for True Count.
True Count uses a dev/prod split with two separate App Store Connect app records under the same Apple Developer team. This page documents the full infrastructure: what lives where, how data flows, and how to promote a build from development to production.
Dev/Prod Split
Both apps live under the Monarch Games Apple Developer team (8D63CJZFL9). They are distinct app records with isolated catalogs, CloudKit containers, and Superwall configurations.
| Dev | Prod | |
|---|---|---|
| App name | TrueCount Dev | TrueCount Blackjack |
| Bundle ID | io.monarchgames.truecount.dev | io.monarchgames.truecount |
| App ID | 6770598673 | 6770598123 |
| Xcode build config | Debug | Release |
| Purpose | TestFlight, sandbox IAP, internal QA | App Store release, real customers |
Why Two App Records?
Apple scopes IAPs, Game Center leaderboards, TestFlight groups, and crash analytics per app record. Two records provide:
- Isolated crash analytics. Dev TestFlight crashes do not pollute production metrics.
- Isolated sandbox IAP. Dev sandbox purchases stay separate from production sales.
- Clean promotion path. Dev builds prove the code; prod release uses the same source with Release configuration.
- Independent TestFlight streams. Internal testers on dev, public beta on prod when needed.
Catalog duplication
Every IAP product, Superwall paywall, and CloudKit schema change must be applied to both app records. This is the tradeoff for full isolation. See the Ongoing Maintenance section below.
Xcode Build Configuration
The Xcode project uses per-configuration bundle IDs and entitlements. No manual switching is needed.
| Setting | Debug | Release |
|---|---|---|
| Bundle ID | io.monarchgames.truecount.dev | io.monarchgames.truecount |
| Entitlements file | truecount-dev.entitlements | truecount.entitlements |
| CloudKit container | iCloud.io.monarchgames.truecount.dev | iCloud.io.monarchgames.truecount |
DEV_BUILD flag | Yes (ships debug tools) | No (debug tools stripped) |
| Superwall API key | Dev application key | Prod application key |
Building for Debug (Xcode run, TestFlight archive) automatically targets the dev environment. Building for Release (App Store archive) automatically targets production.
CloudKit
Two Containers, Complete Isolation
Each bundle ID has its own CloudKit container. Data never crosses between them.
- Container ID:
iCloud.io.monarchgames.truecount.dev - Used by: Debug builds (Xcode direct install + TestFlight)
- Private DB: SwiftData sync (game state, statistics, settings)
- Public DB: BankrollBackup + CreditGrant records
- Safe to reset: Yes. Dev data is throwaway.
- Container ID:
iCloud.io.monarchgames.truecount - Used by: Release builds (App Store)
- Private DB: SwiftData sync (game state, statistics, settings)
- Public DB: BankrollBackup + CreditGrant records
- Safe to reset: No. Production user data. Schema changes are permanent.
Both containers use the same schema (cloudkit/CloudKitSchema.ckdb), deployed independently.
CloudKit Environments (per container)
Each container has two CloudKit environments internally:
| Environment | Used by | Notes |
|---|---|---|
| Development | Xcode direct install | Can be reset. Separate from Production. |
| Production | TestFlight + App Store | Permanent. Schema is additive-only after deployment. |
TestFlight uses Production CloudKit
TestFlight builds always connect to the Production CloudKit environment within their container. Deploy the schema to the Production environment before uploading any TestFlight build.
iCloud Storage Impact
- Each container counts against the user's iCloud quota independently.
- The app's data footprint is tiny: a few KB per user for SwiftData private records and public DB records.
- A user who tests via TestFlight (dev container) and uses the App Store release (prod container) will have two small data sets. This is negligible.
- The developer's CloudKit Dashboard shows separate usage metrics per container.
Schema Deployment
The schema file at cloudkit/CloudKitSchema.ckdb is the source of truth. Deploy to both containers:
# Dev container
xcrun cktool import-schema \
--team-id 8D63CJZFL9 \
--container-id iCloud.io.monarchgames.truecount.dev \
--environment development \
--file cloudkit/CloudKitSchema.ckdb
# Prod container
xcrun cktool import-schema \
--team-id 8D63CJZFL9 \
--container-id iCloud.io.monarchgames.truecount \
--environment development \
--file cloudkit/CloudKitSchema.ckdbThen deploy to the Production environment via CloudKit Console for each container.
In-App Purchases
All 10 IAP product IDs use the io.monarchgames.truecount.iap.* prefix:
| Product ID | Credits | Removes Ads |
|---|---|---|
io.monarchgames.truecount.iap.pocketaces | 1,500 | No |
io.monarchgames.truecount.iap.luckydraw | 3,500 | No |
io.monarchgames.truecount.iap.doubledown | 6,000 | No |
io.monarchgames.truecount.iap.adfree | 0 | Yes |
io.monarchgames.truecount.iap.highroller | 12,500 | Yes |
io.monarchgames.truecount.iap.viptable | 28,000 | Yes |
io.monarchgames.truecount.iap.thepit | 65,000 | Yes |
io.monarchgames.truecount.iap.themarker | 105,000 | Yes |
io.monarchgames.truecount.iap.penthousesuite | 190,000 | Yes |
io.monarchgames.truecount.iap.casinovault | 400,000 | Yes |
Different IDs per environment
Apple IAP product IDs are globally unique. The same string cannot exist on two apps. Dev uses io.monarchgames.truecount.dev.iap.* prefix, prod uses io.monarchgames.truecount.iap.*. StoreProductCatalog.swift selects the correct prefix based on build configuration (Debug uses dev IDs, Release uses prod IDs).
Game Center
| Value | |
|---|---|
| Leaderboards | All-Time (io.monarchgames.truecount.leaderboard.alltime), Monthly (io.monarchgames.truecount.leaderboard.monthly) |
| Score type | Net winnings (cumulative) |
| Sharing | Game Center Group links dev and prod app records |
A Game Center Group shares leaderboards between both app records. Scores submitted from dev TestFlight appear on the same leaderboards as prod. Game Center sandbox automatically isolates test scores from production scores (sandbox Game Center accounts only see sandbox data).
Superwall
Superwall only allows one iOS application per project, so dev and prod use separate Superwall projects in the same organization.
| Dev | Prod | |
|---|---|---|
| Project ID | 23503 | 19526 |
| Bundle ID | io.monarchgames.truecount.dev | io.monarchgames.truecount |
| API Key | pk_NobOUD6WxX5UvSWmvF1Kx | pk_suWM599AegV-fV1KJvi7Z |
| Product prefix | io.monarchgames.truecount.dev.iap.* | io.monarchgames.truecount.iap.* |
| Placement name | campaign_trigger | campaign_trigger |
SuperwallService.swift selects the correct API key at runtime based on the active build configuration. Paywalls are configured per project in the Superwall dashboard. When creating or updating a paywall, replicate changes across both projects.
Admin Panel
The admin panel (admin/ directory) manages CloudKit public database records (BankrollBackup, CreditGrant). It connects to one container at a time.
| Value | |
|---|---|
| Framework | Next.js 15 + shadcn/ui |
| Deployed at | https://admintc.vorti.cloud |
| Authentication | CloudKit server-to-server key (ECDSA P-256) |
The environment switcher (Dev/Prod toggle) switches CLOUDKIT_CONTAINER between iCloud.io.monarchgames.truecount.dev and iCloud.io.monarchgames.truecount. Each container has its own server-to-server key.
Release Workflow
There is no Apple mechanism to promote a TestFlight build from one app record to another. The workflow uses the same source code with different Xcode build configurations.
Develop and test on Dev app
Build with Debug configuration. The app uses io.monarchgames.truecount.dev bundle, dev CloudKit container, dev Superwall application, and ships with DEV_BUILD debug tools.
Verify on Dev TestFlight
Upload to the dev app record. Verify sandbox IAP, Game Center, CloudKit sync, and Superwall paywall presentation on a real device.
Archive for Prod
Switch to Release configuration (automatic via Xcode archive). This builds with io.monarchgames.truecount bundle, prod CloudKit container, prod Superwall key, and strips debug tools.
Upload to Prod app
Upload to the prod app record for App Store submission or prod TestFlight beta.
Verify on Prod TestFlight
Quick smoke test before public release. Same checklist as Step 2, but against production services.
The same source code, same commit, same branch. Only the Xcode build configuration differs.
Ongoing Maintenance
When changing Apple-side catalogs, update both app records:
| Change | Where to apply |
|---|---|
| New IAP product | Register on both dev and prod app records |
| IAP price change | Both app records |
| New Game Center leaderboard | Add to the Game Center Group (shared automatically) |
| New Superwall paywall | Configure on both Superwall projects (prod 19526, dev 23503) |
| Superwall product catalog change | Update on both Superwall projects |
| CloudKit schema change | Deploy to both containers (dev first, verify, then prod) |
| App Store metadata/screenshots | Prod app record only |
How is this guide?