Skip to content
datalayerPublic

About

☰ πŸ“± Datalayer Mobile.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

139 Commits

Folders and files

Repository files navigation

Datalayer

Become a Sponsor

☰ πŸ“± Datalayer

Datalayer is the mobile app for the agents and applications you run on Datalayer, in your pocket: what needs you, the approvals to read in full before deciding, and the conversations to continue. Expo (SDK 57) and React Native, iOS and Android. The person-facing guide is datalayer.ai/docs/mobile-app.

The notification payload allowlist, event matrix and security-review/device gates are in docs/push-data-classification.md. Privacy/store declarations and the push-credential incident procedure are in docs/privacy-and-store.md. The store listing's words, review notes and the Apple and Google review answers, drafted, are in docs/store-listing.md. Account ownership, identifiers and credential custody are in docs/release-prerequisites.md. The deployed server contracts, their versions and the dated result of exercising each are in docs/contracts.md. The public, person-facing guide is datalayer.ai/docs/mobile-app.

Develop

The package is a workspace of the monorepo: install from the monorepo root (npm install in src/), never here. It pins react and the native modules (react-native-reanimated, react-native-worklets, react-native-gesture-handler) to the versions Expo SDK 57 links, which npm keeps under this package's node_modules when the workspace holds newer ones; metro.config.js resolves them from there. expo-doctor reports those as duplicates: that is the monorepo, not a defect, as long as the versions under this package are the ones expo install --check expects.

Expo Go is not supported: sign-in storage, notifications, links and the native controls of @expo/ui need a development build.

Prerequisites

make setup installs what is missing and keeps what is there: a JDK 17 as the system Java (openjdk-17-jdk with apt on Ubuntu, replacing an older one; Homebrew on macOS), the Android command-line tools under ANDROID_HOME (~/Android/Sdk by default), platform-tools (adb), SDK platform 36 with build-tools 36.0.0 and the NDK React Native 0.86 wants, the emulator with one Pixel 8 API 36 virtual device, EAS CLI, and on macOS the Xcode Command Line Tools and CocoaPods. It ends by printing the export lines (ANDROID_HOME, PATH) to add to your shell profile; the Makefile sets them for its own targets, so make dev-android works before that. make tools shows what is found.

  • Node 20.19, 22.13 or 24.3 and later (engines in package.json); npm 10.
  • Android: what make setup installs, or Android Studio with the same SDK components. A device needs USB debugging on.
  • iOS (macOS only): Xcode 16 or later installed from the App Store and opened once (make setup cannot do that), with an iOS Simulator runtime. A physical iPhone needs Developer Mode on and, for EAS development builds, its UDID registered (eas device:create).
  • EAS: see Accounts below.
  • A physical iPhone and an Android phone for anything about push, links, background behaviour or the keychain; simulators do not establish them.

Accounts

What each way of running the app needs:

You want to Expo account Apple Developer Program ($99/year) Google Play Console ($25 once) Mac with Xcode
make dev-web in a browser no no no no
make dev-android / make dev-device-android, built on this machine no no no no
make dev-ios / make dev-device-ios, built on this machine no free Apple ID on your own iPhone, re-signed every 7 days; the program otherwise no yes
make build-dev-android, built by EAS, installed from a link yes no no no
make build-dev-simulator, built by EAS yes no no to run the Simulator
make build-dev-ios, built by EAS for a real iPhone yes yes (the iPhone registered with eas device:create) no no
make build-preview / build-staging yes yes for iOS for the staging track no
make publish-ios / publish-android (CI releases Android only) yes yes yes no
  • Expo account: free at expo.dev. The free plan gives a limited number of cloud builds a month in a slower queue; paid plans give more and faster ones (see expo.dev/pricing). Then eas login.
  • This project on Expo: @datalayer/datalayer. app.config.ts names its owner (datalayer), slug and project id (extra.eas.projectId), so a cloud build needs your Expo account to be a member of the datalayer account; eas init is not needed. npx eas-cli project:info shows the project.
  • Signing: EAS keeps the signing credentials; nothing is committed here. On Android, the keystore EAS holds for ai.datalayer.mobile (its default build credentials) signs the store bundles: the Play Console app (created 20 October 2026) had no bundle before the first EAS production build, uploaded by hand, so that keystore is Play App Signing's upload key. EAS must never generate another keystore for ai.datalayer.mobile: the workflows build with --freeze-credentials. On iOS, eas credentials --platform ios (signed in with the Apple account) creates or reuses the distribution certificate and provisioning profile. Development and preview builds (.dev, .preview) are other apps and may use keystores EAS generates.
  • Without any account: a plugged Android phone (make dev-device-android) or the browser (make dev-web).

Dev mode

The first time, and after every native change, build and install the development build; then only Metro runs. make help lists every target; the npm run scripts underneath are shown here.

cp .env.example .env            # done by every dev and start target when .env is missing

make dev-simulator              # no phone: Android Emulator on Linux, iOS Simulator on macOS
make dev-android                # boot the first emulator (or AVD=<name>), build the dev app on it, start Metro
make dev-ios                    # boot the Simulator (SIMULATOR="iPhone 16"), build the dev app on it, start Metro (macOS)
make dev-device-android         # same, on a plugged Android phone with USB debugging on
make dev-device-ios             # same, on a plugged iPhone with Developer Mode on (macOS)
make dev-web                    # in a browser, no emulator or phone (see Web preview)

make start                      # Metro only, for a development build already installed
npm run start:clear             # Metro with its cache cleared
make start-tunnel               # Metro over a tunnel, for a phone that cannot reach this machine

Simulator-only development, without a phone

On Linux, the supported native loop is the Android Emulator. On macOS it is the iOS Simulator. One target selects the available platform:

make setup                                      # first time: SDK, emulator and default virtual device
make dev-simulator                              # build, install and start Metro
make dev-simulator AVD=Pixel_8_API_36           # Linux: choose an Android virtual device
make dev-simulator SIMULATOR="iPhone 16 Pro"    # macOS: choose an iOS Simulator

No Expo, Apple or Google account and no physical phone are needed for this local development loop. On Linux, make setup creates an Android virtual device and make dev-simulator checks /dev/kvm, boots it, installs the development build and starts Metro. On macOS the same target boots the named iOS Simulator and builds with local Xcode. The first native build takes the longest; while it remains installed, make start is enough for TypeScript and asset changes. Re-run make dev-simulator after changing a native dependency, config plugin, identifier, permission or scheme.

The simulator is suitable for screens, navigation, API contracts, streaming, approval races and the automated Maestro journeys. It is not release evidence for APNs/FCM delivery, secure hardware/keychain behavior, background networking, VoiceOver/TalkBack on representative phones, thermals or device memory. Those physical-device gates remain open release gates.

Every dev:* and start* script sets APP_VARIANT=development, so the app installs as Datalayer Dev (ai.datalayer.mobile.dev, scheme datalayer-dev) beside a store build. Once Metro is up, press a or i to open the installed development build, r to reload, j to open the debugger, m for the dev menu. Shake the phone (or Cmd+D / Cmd+M in a simulator) for the same menu on the device.

Against a local server: set EXPO_PUBLIC_DATALAYER_RUN_URL=http://localhost:8080 and EXPO_PUBLIC_DATALAYER_RUNTIMES_URL to the same or the local runtimes plane (and EXPO_PUBLIC_DATALAYER_MCP_URL to the local MCP gateway) for the iOS Simulator or http://10.0.2.2:8080 for the Android emulator; a phone needs an HTTPS URL or npm run start:tunnel plus a tunnel to the server. EXPO_PUBLIC_* values are read when Metro starts: restart it after changing .env.

Without a local native toolchain, make the development build in the cloud and install it from the link:

make build-dev-android          # eas build --profile development
make build-dev-ios              # registered iPhones (eas device:create first)
make build-dev-simulator        # iOS Simulator
make start                      # then connect the installed build to Metro

npm run prebuild regenerates the ios/ and android/ folders (ignored by git) when a plugin or identifier changes; run:* does it on its own when they are missing.

The iOS targets need macOS and Xcode; elsewhere they stop and point to make build-dev-ios or the web preview. The Android emulator needs hardware virtualization (/dev/kvm on Linux); when the firmware has it off, make dev-android says so.

Web preview

With neither an emulator nor a phone, make dev-web runs the app in a browser at http://localhost:8081, for screens and logic. It is a development aid, not a target: the app ships on iOS and Android only.

  • The token is kept in the tab's session storage (src/storage/deviceStore.web.ts) and gone when the tab closes; on a phone it is in the keychain/keystore.
  • No live updates: a browser WebSocket cannot send the Authorization header and the token is never put in a URL, so lists refresh by pull.
  • Controls are @expo/ui's web versions, not SwiftUI or Compose; no push, no deep links, no biometrics.
  • The services must allow the http://localhost:8081 origin (CORS); prod1.datalayer.run does.

Browser globals and React DOM stay out of the native code: the import gate refuses them everywhere except *.web.ts files, which Metro loads only for the web preview.

Loops

  1. Local native toolchain: npm run dev:android or dev:ios once, then npm start and fast refresh for TypeScript and assets.
  2. Shared EAS development client: npm run build:dev:*, installed from the build link and connected to a local Metro.
  3. EAS build failure reproduced locally: eas build --platform android|ios --profile <profile> --local.
  4. Pull request: npm run check, then npm run export; e2e builds for Maestro when that suite exists.

Rebuild the development build (npm run prebuild, then dev:* or an EAS build) whenever a native dependency, a config plugin, an identifier, a permission or a scheme changes. Everything else is a Metro refresh.

Environments

EXPO_PUBLIC_DATALAYER_RUN_URL names the control plane (IAM, the spaces, telemetry; production https://prod1.datalayer.run) and EXPO_PUBLIC_DATALAYER_RUNTIMES_URL the runtimes plane (ai-agents, runtimes, the scheduler, contents, inference and the ai-agents WebSocket; production https://r1.datalayer.run), as Core's config/planes places each service β€” neither host serves the other's routes (docs/contracts.md); both HTTPS except for localhost, 127.0.0.1 and 10.0.2.2 (the Android emulator's host). EXPO_PUBLIC_DATALAYER_MCP_URL names the MCP gateway, which is published on its own host (production https://mcp.datalayer.run; a trailing /mcp is accepted): the protocol audit, a sign-in under way and Direct Conversations are read there. All three are required, with the same HTTPS rule; a build without any of them is refused at start. A phone reaches a local server only through an HTTPS development URL or a tunnel. EXPO_PUBLIC_BUILD_PROFILE is one of development, development-simulator, e2e, preview, staging, production; it decides whether the interim sign-in is offered.

Development, e2e and preview builds sign in with a handle and password or a Datalayer API token; store builds will sign in with Datalayer through the system browser.

Check

npm run check            # typecheck, lint, forbidden imports, tests
npm run test:watch       # tests while editing
npm run check:imports    # no Jupyter, JupyterLab, Lumino, React DOM, Primer web or browser global
npm run check:bundle     # the same, on what Metro resolved into the iOS and Android bundles (source maps)
npm run doctor           # Expo Doctor: a job of Check in CI, outside the monorepo (below)
npm run export           # both bundles into dist/, to prove the Metro graph resolves

Expo Doctor runs in CI (the doctor job of Check: a failure turns Check red), where the repository installs on its own: there package.json's overrides hold every dependency's react and react-dom to the app's own (Core, Agent Runtimes, Jupyter UI and Loro depend on 19.2.8 exactly, the Jupyter packages on 18.3.1), so the app has one React, and all 21 checks pass (online ones included). Inside the monorepo, npm ignores a member's overrides and the root keeps the web's React beside Expo's: metro.config.js and Jest's moduleNameMapper hold the app's own, and Doctor there reports that duplicate; read CI's run instead.

Maestro smoke flows

.maestro/ holds the smoke flows β€” sign-in, an approval answered elsewhere while it is open, the route a tapped notification opens, a prompt streamed and stopped, and pause/reset with a second account refused. CI runs them (e2e-android on every run; e2e-ios, on main on macos-15, is off for now until Apple validates the developer account and the Simulator sign-in is fixed) on an e2e build it makes itself: expo prebuild, then Gradle or xcodebuild; nothing is built on a laptop. Maestro may run the five flows in any order on the one shared emulator or simulator, so every flow prepares authentication itself; that setup accepts an existing authenticated E2E session because the iOS keychain can outlive cleared app state. They act as a test account on the r1 runtimes plane and need the repository secrets DATALAYER_MOBILE_E2E_TOKEN (the test account's API token) and DATALAYER_MOBILE_E2E_SECOND_TOKEN (a second account's), and the secrets DATALAYER_MOBILE_E2E_CHAT_APP (an application of the test account the prompt flow talks to, with two model requests) and DATALAYER_MOBILE_E2E_LIFECYCLE_APP (one the flow pauses and resumes). Without them the jobs stop after the build and say which is missing. The prompt flow asks ai-agents where its conversation is had first: on the application's kept computer when that answers, otherwise on a computer of the test account's own, which the flow starts with Start and continue (10 credits reserved; the flow fails at once, naming the free credits, when the account has fewer). The e2e-cleanup job stops those computers once every flow is done.

Build

eas.json has six profiles: development, development-simulator, e2e, preview, staging and production. APP_VARIANT gives development and preview builds their own name and identifier (ai.datalayer.mobile.dev, .preview) so they install beside the store app.

Publish

One promotion pipeline, from a committed and checked state to the stores. The Makefile runs it: make build-preview, make build-staging and make submit-staging for the rings before release; make publish-ios, make publish-android or make publish for the stores. publish-* refuse a dirty or unpushed tree, run make check, build with EAS and submit the artifact (--auto-submit); the console steps that follow are the release owner's.

Releasing

In CI, a release is a tag, and today it releases Android only, to Google Play's internal testing track. Set version in app.config.ts, commit and push, wait for that commit's Check to be green, then tag and push the tag:

git tag v0.1.0 && git push origin v0.1.0

.github/workflows/release.yaml then:

  1. refuses to start without the repository secrets EXPO_TOKEN (an Expo robot token of the datalayer account) and DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT (the Google Play service account's JSON key, below), and refuses a tag whose commit's Check run did not pass the jobs an Android release rests on (check, doctor, manifests, hermes, e2e-account, e2e-android; while iOS is off, e2e-ios and the store screenshots do not block it);
  2. checks that the tag names version, runs npm run check and npm run check:bundle;
  3. has EAS build the production .aab (eas build --platform android --profile production --non-interactive --freeze-credentials --wait; EAS numbers it, versionCode above the last);
  4. submits that build with EAS Submit (eas submit --platform android --profile production --id <build> --non-interactive) to the internal testing track as a completed release (eas.json, submit.production.android: track: internal, releaseStatus: completed): the internal testers get it at once;
  5. writes the EAS build id, version, versionCode and EAS submission id in the run's summary, and creates the GitHub release with them.

Promotion to production is started by hand: Actions β†’ Promote to production β†’ Run workflow, with the versionCode the internal testers ran and a rollout fraction (below 1 is a staged rollout; run it again to raise it). It promotes that same bundle and refuses a versionCode the internal track does not hold; Google reviews a production release before it reaches people.

iOS is off. Apple has not yet validated Datalayer's developer account, so the release workflow builds and submits nothing for iOS. To turn it back on: fill submit.production.ios in eas.json (the App Store Connect app id and Apple team id), put the distribution certificate and App Store Connect API key in EAS (eas credentials --platform ios), and add the iOS build and submit steps beside the Android ones in release.yaml (its header says how).

The service account key. EAS Submit sends the bundle to Google Play with a Google Cloud service account that Play Console has invited with release permissions on ai.datalayer.mobile (Play Console β†’ Users and permissions). A Play account owner creates its JSON key and stores the whole JSON as the repository secret DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT (Settings β†’ Secrets and variables β†’ Actions); it is never committed. The workflow writes it to the runner's temporary directory, outside the checkout (EAS builds only a clean tree), points DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT_KEY_PATH at it (eas.json reads serviceAccountKeyPath from that variable), and removes it when the job ends. Where the variable is unset, as for make publish-android on a laptop, EAS Submit uses the service account key stored in EAS (eas credentials --platform android), if any.

The first bundle is on the track. Google takes an app's first bundle only outside EAS Submit, and refuses a completed release on a draft app (Only releases with status draft may be created on draft app). On 20 October 2026 the first EAS production .aab (versionCode 5, EAS build 0097304f) was uploaded with the Google Play Developer API and released on internal testing as completed, so the app is no longer a draft app: each tag now submits the next versionCode as a completed internal release.

.github/workflows/eas-build.yaml is started by hand (Actions β†’ EAS build β†’ Run workflow): a preview, staging or production build of a commit whose Check is green, and for staging optionally its submission to TestFlight and the Play internal track (as a draft); a production build from it is never submitted (it is the bundle an account holder uploads by hand); it never runs on a push. EAS installs the dependencies with npm install, like the workflows: with no lockfile, EAS would otherwise run yarn install --frozen-lockfile, so eas.json's base profile sets EAS_FALLBACK_PACKAGE_MANAGER=npm and EAS_NO_FROZEN_LOCKFILE. .github/workflows/check.yaml runs the checks on every pull request and push to main. A failed job turns Check red; it does not block merging, as main has no branch protection yet (a repository administrator may require the Check jobs). A release does require it: release.yaml refuses a tag whose commit's Check run is not green, every job included. The repository commits no lockfile; release.yaml keeps the package-lock.json its install resolved as the release-lockfile artifact and writes its checksum into the GitHub release. It pins the EAS CLI (eas-version); raise it deliberately.

What is needed to publish

Development happens on Linux. That is enough to create and submit both store artifacts with EAS: the iOS build runs on EAS's macOS workers, and EAS Submit works from Linux. Local iOS builds, Simulator runs, Xcode archives and the Transporter fallback still require macOS. Store publication does not replace testing on physical iOS and Android phones; those release gates are currently postponed until the devices and testers are available.

Common preparation:

  1. The Expo project is @datalayer/datalayer; its id is in app.config.ts. Give a primary and recovery owner access to the datalayer Expo account and keep a least-privilege robot EXPO_TOKEN in GitHub Actions.
  2. Keep the final application identity: Datalayer, Expo slug datalayer (the Expo project's name only), iOS bundle id and Android package ai.datalayer.mobile. Store identifiers cannot be casually replaced after publication.
  3. Complete docs/store-listing.md and verify every answer against the release binary and the live store forms. Supply current icons, phone/tablet screenshots, description, category, support and privacy contacts, privacy policy, account-deletion URL and a review account that contains no customer data.
  4. Name the release, incident and recovery owners in the private release register. Keep signing keys, API keys, service-account JSON and tester credentials out of Git. Run the credential-recovery drill before tagging.
  5. Obtain the postponed physical-device evidence (streaming, push delivery, accessibility services, performance) before any external rollout. Simulator CI is useful evidence, but is not evidence for APNs/FCM delivery, keychain/keystore behavior, background networking, accessibility services, thermal behavior or device memory.

Apple App Store:

  1. Enrol the organization in the Apple Developer Program and accept the current agreements. In Certificates, Identifiers & Profiles, register ai.datalayer.mobile and its associated-domain and push capabilities.
  2. In App Store Connect, create the app record before the first upload, assign least-privilege Account Holder/Admin, App Manager and Developer roles, and complete any applicable agreements, tax and banking setup.
  3. Let eas credentials --platform ios manage the distribution certificate, provisioning profile and APNs key. Add an App Store Connect API key to EAS for non-interactive submission; keep a recovery owner for every credential. eas.json names the App Store Connect app id and the Apple Team id for submission (submit.staging.ios, submit.production.ios); until they are filled in, ASC_APP_ID_PLACEHOLDER and APPLE_TEAM_ID_PLACEHOLDER stand there and an iOS submission must not be started.
  4. Complete the product page, age rating, App Privacy, accessibility, encryption/export, content-rights, sign-in and account-deletion answers. Put working demo credentials and the exact review journey in App Review Information. The draft answers live in docs/store-listing.md.
  5. Build and submit the production .ipa, wait for processing, exercise that exact build with internal TestFlight and then any required external Beta App Review. Select the tested build in the version record, add it for review, submit it, and use manual or phased release only after the release gate.

Google Play:

  1. Register and verify the organization in Play Console, accept the Developer Distribution Agreement and pay the registration fee. Create the app with package ai.datalayer.mobile, accept Play App Signing and keep the package name and signing ownership in the private register.
  2. Create a least-privilege Google service account for this app, invite it in Play Console with release permissions on ai.datalayer.mobile, and store its JSON key as the repository secret DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT for the tag release (and, for make publish-android, in EAS with eas credentials --platform android). It is never committed. Confirm the upload keystore and Play App Signing recovery path.
  3. Complete the main store listing and App content: contact details, privacy policy, Data safety, ads, content rating, target audience, app access and review credentials, account deletion and every declaration currently shown by Play Console. Confirm the release meets the live target-API requirement.
  4. The first production .aab (not the preview APK; versionCode 5) is on internal testing, completed, since 20 October 2026; from then on, a tag builds and submits each .aab to internal testing as a completed release. Test the exact bundle, then meet any closed-testing or production-access requirement Play Console shows for this organization and account type. Promote the same artifact through a staged production rollout.

The store consoles and policies change. At every submission, reconcile this runbook with the live App Store Connect workflow, Apple submission steps, Google app setup, Google review declarations, and EAS Submit.

Environments and credentials

The names below are the interface between Git, EAS and the stores. Values and credential files never belong in the repository.

Profile EAS environment Non-secret configuration Credential source
development, development-simulator, e2e development EXPO_PUBLIC_DATALAYER_RUN_URL, EXPO_PUBLIC_DATALAYER_RUNTIMES_URL, EXPO_PUBLIC_DATALAYER_MCP_URL from .env; profile and variant come from eas.json EAS development signing credentials
preview preview EXPO_PUBLIC_DATALAYER_RUN_URL, EXPO_PUBLIC_DATALAYER_RUNTIMES_URL, EXPO_PUBLIC_DATALAYER_MCP_URL (production services, in eas.json) EAS internal-distribution credentials
staging preview (Expo's built-in environment, shared with the preview profile) EXPO_PUBLIC_DATALAYER_RUN_URL, EXPO_PUBLIC_DATALAYER_RUNTIMES_URL and EXPO_PUBLIC_DATALAYER_MCP_URL (production services, in eas.json) The production app's EAS/store signing credentials
production production production EXPO_PUBLIC_DATALAYER_RUN_URL, EXPO_PUBLIC_DATALAYER_RUNTIMES_URL and EXPO_PUBLIC_DATALAYER_MCP_URL (in eas.json) The production app's EAS/store signing credentials
GitHub release.yaml, eas-build.yaml repository Actions secrets none EXPO_TOKEN, scoped to the Datalayer Expo project; DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT (release.yaml)

EAS owns the iOS distribution/provisioning material and Android keystore. EAS Submit owns the App Store Connect API key; the Google Play service-account JSON is the repository secret DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT for the tag release (and may also be kept in EAS for make publish-android). APNs and FCM credentials are configured in the same Datalayer-owned project. Named primary and recovery owners are recorded outside Git according to docs/release-prerequisites.md.

commit β†’ npm run check β†’ preview build β†’ staging (TestFlight, Play internal)
       β†’ release candidate approved β†’ production build β†’ EAS Submit β†’ store review β†’ staged release

Before any store build:

  • version in app.config.ts is the user-facing version; Android versionCode and iOS buildNumber are managed by EAS (appVersionSource: remote, autoIncrement on staging and production) and only ever increase. The first store versions were built locally, outside EAS: before the first EAS store build, set EAS's numbers to the highest ones already uploaded to each store (eas build:version:set --platform android and --platform ios, read from Play Console β†’ App bundle explorer and App Store Connect β†’ TestFlight), so the next build is above them.
  • The build is made from a clean, pushed commit by EAS, never from a working tree. Record the commit, the EAS build id, the runtime version and the lockfile the release resolved (the release-lockfile artifact and its checksum; no lockfile is committed).
  • Signing and submit credentials live in EAS and the platform consoles, with named owners. Nothing is committed here.
  • Store listing, screenshots, privacy and data-safety answers, support and privacy-policy URLs, the account-deletion route and a review account without real customer data are current.
  • No over-the-air updates in v1: updates.enabled is false in app.config.ts for every build, and no profile has an update channel. The app changes only through a new store version, reviewed by Apple and Google. expo-updates stays installed and disabled; turning updates on later takes a configuration change, channels and a store release that carries them.

Apple (make publish-ios):

  1. eas build --platform ios --profile production --auto-submit produces the store .ipa and uploads it to App Store Connect.
  2. Internal TestFlight testers first, external testers after Beta App Review when needed.
  3. Complete App Privacy, export compliance, sign-in and account-deletion answers as Apple requires on the submission date.
  4. Select the tested build for App Review. Release manually or phased; keep the minimum-version kill switch (below) ready to stop a defective client.

Google (make publish-android):

  1. eas build --platform android --profile production --auto-submit produces the .aab (an APK is never the store artifact) and releases it on the internal track (completed) with the least-privilege service account; a tag does the same in CI (Releasing, above).
  2. Internal testing track first, then closed testing as needed. Complete Data safety, content rating, target audience, app access and target-API requirements.
  3. Promote the tested artifact to production with a staged rollout; monitor each ring and halt on auth, approval, push or crash regression.

staging is a store track of the production application, with the same signing and entitlements, pointed at staging services; it is not a differently signed approximation. The release owner does the final console review, chooses the rollout and records the store version URLs and review result. Manual upload with Transporter or the Play Console is the documented recovery path, with the same artifact.

Release-owner checklist

Before tagging:

  • confirm the repository secrets EXPO_TOKEN and DATALAYER_MOBILE_PLAY_SERVICE_ACCOUNT exist (Releasing, above);
  • confirm the release commit is reviewed, clean, pushed and green in CI;
  • run npm run check and npm run check:bundle, and see the release commit's Check green (release.yaml refuses the tag otherwise), its doctor and manifests jobs included (manifests holds the generated privacy manifest, Info.plist and merged Android permissions to docs/privacy-and-store.md; its release-manifests artifact goes to the release record);
  • bump version, verify the remote iOS build number and Android version code will increase;
  • verify staging against the same backend contract and run the sign-in, approval race/stale state, notification tap, stream/stop and pause/reset smoke journeys;
  • verify store copy, screenshots, privacy/data-safety answers, support/privacy/deletion links and the non-customer review account;
  • nominate the release owner, incident owner and explicit go/no-go approver.

After the workflow submits:

  • compare EAS build ids and checksums with the GitHub release and select only those artifacts in the consoles;
  • pass TestFlight/internal and required external/closed testing before review;
  • start the phased/staged rollout, watch auth, push, approval, crash and stream health, and record the checkpoints;
  • close the release record only after the store URLs, review results and rollback evidence are attached.

The minimum-version kill switch

IAM serves the oldest version of the app still supported on each platform at GET /api/iam/v1/oauth/clients/datalayer-mobile/minimum-version.json ({"success": true, "minimum": {"ios": "1.2.0", "android": "1.2.0"}}, public, cached for a minute). The app reads it at launch and every time it comes back to the foreground (src/release/). Below its platform's minimum it stops at Update Datalayer, naming the version to install; an answer it cannot read β€” a 503, a missing or malformed version, no network β€” stops it at Datalayer could not be reached with Try again: an unreadable switch is never taken as off.

To stop an unsafe installed binary, the release owner raises the minimum in the control plane's rc and redeploys IAM (the values go through up.sh, never by hand into the cluster):

export DATALAYER_MOBILE_MINIMUM_VERSION_IOS=1.2.0
export DATALAYER_MOBILE_MINIMUM_VERSION_ANDROID=1.2.0
p reup datalayer-iam

Both are major.minor.patch, as version in app.config.ts; unset, up.sh serves 0.0.1. Raise them only once the fixed build is live in both stores, or people are left with nothing to update to.

Halt, rollback and recovery

Halt a rollout for wrong-account data, incomplete or stale approval context, an authorization bypass, credential leakage, unusable sign-in, elevated crash rate or material push/stream failure. Stop the phased/staged rollout in both store consoles, disable the affected server capability and raise the minimum-supported-version policy (the kill switch above) when an installed binary is unsafe.

There are no over-the-air updates to roll back to: every fix is a new, incremented store binary. If EAS Submit fails after a successful build, download that exact artifact from EAS and upload the .ipa with Transporter or the .aab in Play Console; do not rebuild it. If signing or submission credentials fail, stop, rotate or recover them with their recovery owner, then retry the same recorded commit.

Every release record contains: version and tag; commit and lockfile checksum; release/go-no-go/incident owners; EAS project, build and submit ids; iOS build number and Android version code; artifact checksums; backend environment and contract versions; test evidence; store review ids/results/URLs; rollout percentages and timestamps; metrics and incidents; halt/rollback decisions; and final completion time. A second person must be able to perform a staging release from this section alone before this runbook is considered verified.

About

☰ πŸ“± Datalayer Mobile.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages