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.
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.
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 (
enginesinpackage.json); npm 10. - Android: what
make setupinstalls, 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 setupcannot 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.
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.tsnames its owner (datalayer), slug and project id (extra.eas.projectId), so a cloud build needs your Expo account to be a member of thedatalayeraccount;eas initis not needed.npx eas-cli project:infoshows 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 EASproductionbuild, uploaded by hand, so that keystore is Play App Signing's upload key. EAS must never generate another keystore forai.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).
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 machineOn 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 SimulatorNo 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 Metronpm 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.
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
Authorizationheader 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:8081origin (CORS);prod1.datalayer.rundoes.
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.
- Local native toolchain:
npm run dev:androidordev:iosonce, thennpm startand fast refresh for TypeScript and assets. - Shared EAS development client:
npm run build:dev:*, installed from the build link and connected to a local Metro. - EAS build failure reproduced locally:
eas build --platform android|ios --profile <profile> --local. - Pull request:
npm run check, thennpm run export;e2ebuilds 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.
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.
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 resolvesExpo 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/ 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.
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.
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.
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:
- refuses to start without the repository secrets
EXPO_TOKEN(an Expo robot token of thedatalayeraccount) andDATALAYER_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-iosand the store screenshots do not block it); - checks that the tag names
version, runsnpm run checkandnpm run check:bundle; - has EAS build the production
.aab(eas build --platform android --profile production --non-interactive --freeze-credentials --wait; EAS numbers it,versionCodeabove the last); - 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; - writes the EAS build id, version,
versionCodeand 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.
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:
- The Expo project is
@datalayer/datalayer; its id is inapp.config.ts. Give a primary and recovery owner access to thedatalayerExpo account and keep a least-privilege robotEXPO_TOKENin GitHub Actions. - Keep the final application identity: Datalayer, Expo slug
datalayer(the Expo project's name only), iOS bundle id and Android packageai.datalayer.mobile. Store identifiers cannot be casually replaced after publication. - Complete
docs/store-listing.mdand 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. - 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.
- 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:
- Enrol the organization in the Apple Developer
Program and accept the current
agreements. In Certificates, Identifiers & Profiles, register
ai.datalayer.mobileand its associated-domain and push capabilities. - 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.
- Let
eas credentials --platform iosmanage 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.jsonnames 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_PLACEHOLDERandAPPLE_TEAM_ID_PLACEHOLDERstand there and an iOS submission must not be started. - 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. - 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:
- 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. - 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 secretDATALAYER_MOBILE_PLAY_SERVICE_ACCOUNTfor the tag release (and, formake publish-android, in EAS witheas credentials --platform android). It is never committed. Confirm the upload keystore and Play App Signing recovery path. - 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.
- 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.aabto 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.
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:
versioninapp.config.tsis the user-facing version; AndroidversionCodeand iOSbuildNumberare managed by EAS (appVersionSource: remote,autoIncrementonstagingandproduction) 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 androidand--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-lockfileartifact 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.enabledisfalseinapp.config.tsfor every build, and no profile has an update channel. The app changes only through a new store version, reviewed by Apple and Google.expo-updatesstays installed and disabled; turning updates on later takes a configuration change, channels and a store release that carries them.
Apple (make publish-ios):
eas build --platform ios --profile production --auto-submitproduces the store.ipaand uploads it to App Store Connect.- Internal TestFlight testers first, external testers after Beta App Review when needed.
- Complete App Privacy, export compliance, sign-in and account-deletion answers as Apple requires on the submission date.
- 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):
eas build --platform android --profile production --auto-submitproduces 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).- Internal testing track first, then closed testing as needed. Complete Data safety, content rating, target audience, app access and target-API requirements.
- 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.
Before tagging:
- confirm the repository secrets
EXPO_TOKENandDATALAYER_MOBILE_PLAY_SERVICE_ACCOUNTexist (Releasing, above); - confirm the release commit is reviewed, clean, pushed and green in CI;
- run
npm run checkandnpm run check:bundle, and see the release commit's Check green (release.yamlrefuses the tag otherwise), itsdoctorandmanifestsjobs included (manifestsholds the generated privacy manifest,Info.plistand merged Android permissions todocs/privacy-and-store.md; itsrelease-manifestsartifact 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.
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-iamBoth 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 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.