15 — Release runbook
This is the operational checklist for shipping a change to the staging testers. It assumes you’ve read note 14 for the repo/branch/environment map. There is no CI release pipeline — every build happens on an engineer’s laptop.
Two halves move independently:
- Backend (Frappe app) — push to
development, staging auto-pulls and migrates. - Mobile (Flutter app) — build the APK locally, upload to Firebase, notify testers.
If a release only touches one half, you only run that half.
A. Backend release (Frappe app)
Repo: frappe_mform_swasti · Staging branch: development → auto-deploys to stg.swasti.mform.in.
Branch policy:
developmentis the only branch that accepts direct pushes and auto-deploys to staging.master→ uat is PR-only. Never push straight to uat/main.
- Commit your changes. Doctype JSON, controllers, patches, fixtures.
- Push to
development. Staging’s autopull picks it up and runsbench migrateautomatically — no need to ping DevOps if the migration is clean. - Wait ~2–5 min, then verify. Probe the API for any new doctype:
POST /api/method/login usr=Administrator&pwd=<staging pwd> GET /api/method/frappe.client.get_count?doctype=<New Doctype>200= the doctype migrated.404= it didn’t (see the checklist below).
Adding a NEW doctype? Three things must travel together
A new doctype is invisible to the mobile app until all three are in place. Miss any one and the form will render but its dropdowns show “No options available” (the app couldn’t fetch the doctype’s metaJson).
| # | What | Where |
|---|---|---|
| 1 | The doctype JSON + controller + __init__.py | mform_swasti/<module>/doctype/<name>/ |
| 2 | A patch granting Mobile User + Swasti Surveyor read/create perms | patches/v3_0/_NNN_*.py + patches.txt |
| 3 | The same patch appends the doctype to the Mobile Configuration manifest (table_lwis) | same patch |
The SDK only pulls metaJson for doctypes listed in the Mobile Configuration manifest. No manifest row → no meta on the device → empty dropdowns. (Perms baked into the doctype JSON are not enough on their own — the manifest entry is what triggers the metaJson sync.)
Migrate-failure checklist
If bench migrate fails on staging (or new doctypes stay 404), it’s almost always one of these — all seen on this project:
ModuleNotFoundErrorfor a patch → the patch.pyis listed inpatches.txtbut the file was never committed. Rungit ls-filesand confirm every patch inpatches.txtis tracked.- A doctype’s files are untracked → the JSON returns
404but exists locally. Same fix: commit the untracked files. Unknown column/OperationalErrormid-migrate → a patch reads or writes a field that isn’t in the committed doctype JSON. One crashing patch aborts the entire migration, so every patch after it silently never runs. Confirm any column a patch touches is in a committed.json.- Patch ran but had no effect → check
Patch Log(list it filtered by your patch name). A patch only runs once; if it ran with a bug, fixing the code won’t re-run it — bump it to a new_NNNnumber or clear its Patch Log row.
Lesson from 27 May: a
Document Master.is_applyable_documentcolumn was added locally but never pushed. Patch_013read it, crashed on the unknown column, and aborted the migrate — so_014/_015never ran and two whole forms had empty dropdowns. The fix was to commit the field, not to touch the app.
B. Mobile release (Flutter app)
Repo: frappe_mform_swasti_app · builds are local, distribution is Firebase (staging) / Play Store via DevOps (uat/prod).
-
Build the staging APK (from the app root):
flutter build apk --flavor staging -t lib/main_staging.dartOutput:
build/app/outputs/flutter-apk/app-staging-debug.apk(~200 MB). -
Write release notes — plain-language, surveyor-facing, “what to test.” Keep a file (e.g.
/tmp/swasti-release-notes-<date>.txt). -
Upload to Firebase App Distribution:
firebase appdistribution:distribute \ build/app/outputs/flutter-apk/app-staging-release.apk \ --app "1:79062936320:android:cf185872e152002cae61e5" \ --groups "<group-alias[,group-alias…]>" \ --release-notes-file /tmp/swasti-release-notes-<date>.txtSuccess prints the release version + console/tester links. Testers in the named group(s) get a notification.
Which group(s) to distribute to
Two groups exist on the Firebase project (
swasti-mform-v3):Group alias Display name Who When to include dhwaniDhwani 7 internal testers (Abhijit, Ankit, Ritesh, Fahim, etc.) Every drop — internal dev/PM + Swasti PMs swasti-clientSwasti Client Testers Catalysts staff (shanmathi@, dharshini.m@, santosh.r@) Stable drops only — once the change has been verified on the Vivo / dhwani round and the KB tracker isn’t in the middle of a triage burst Default for an in-flight triage commit:
--groups "dhwani". Default for a stable / weekly-summary drop:--groups "dhwani,swasti-client". Don’t fire client drops for every internal fix — they’ll get notification fatigue and the App-Tester install backlog grows.Adding new testers
firebase appdistribution:testers:add <email1> [<email2>…] \ --group-alias <dhwani|swasti-client> \ --project swasti-mform-v3Adding a new group
firebase appdistribution:group:create "<Display Name>" <alias> \ --project swasti-mform-v3 -
Notify the testers on Teams — post the highlights to the mForm V3 ↔ Swasti group chat (always send as HTML). Lead with what’s new and what to test, not the changelog.
Direct install for your own device (skip Firebase)
adb -s <device-id> install -r -d build/app/outputs/flutter-apk/app-staging-debug.apk
C. Production release (Play Store)
Different from a staging drop in three ways: it goes through a PR, it is built by DevOps rather than on the engineer’s laptop, and we verify the binary before it is published.
Who: Ankit Jangir (DevOps) runs the production build and the Play Console upload. First done this way for 1.0.0+48 on 31 July 2026.
1. Promote to main by PR
Production builds from main. Raise the PR from the release branch, and say in it what the build fixes and what is deliberately not in it — a reviewer cannot infer the second from a diff.
2. The SDK must resolve from git, not a path
The app used to declare the SDK as a local path dependency:
frappe_mobile_sdk:
path: ../../raw/sdk/frappe-mobile-sdk
That only resolves inside a working tree that has the SDK checked out as a sibling. Anyone cloning the app repo on its own — which is exactly how a production build is cut — could not resolve the dependency at all. Since 1.0.0+48 it is a git dependency on the Swasti consume branch:
frappe_mobile_sdk:
git:
url: https://github.com/dhwani-ris/frappe-mobile-sdk.git
ref: local/swasti-consume
3. Pin the commit, and do not let a cache decide
pubspec.lock records the resolved commit:
resolved-ref: 5aa63fe7b911a7c52d1f9fa83212757fca735144
Never run flutter pub upgrade before a release build. A git dependency is cached by ref; pub upgrade re-resolves the branch name and can hand back a stale cached copy, which is how a build ends up missing the very fix it was cut for. flutter pub get against the committed lock is what binds the binary to the verified commit.
Have the builder confirm it before building, not after:
grep -A5 frappe_mobile_sdk pubspec.lock # must show the expected resolved-ref
4. Verify the APK before the Play Store release exists
Ask for a debug/release APK first and check it on a device, then confirm back in writing before the AAB is uploaded. Two checks are worth doing every time, because both have nearly gone wrong:
versionCode— read it out of the built artefact, not frompubspec.yaml. A stale Gradle output once carriedversionCode='46'while everything around it said 47.- The baked backend URL —
--flavoralone does not carryFRAPPE_URL. Only--dart-define-from-file=env/prod.jsondoes. A prod-flavoured build without it points nowhere useful.
5. Then, and only then
flutter build appbundle --flavor prod --dart-define-from-file=env/prod.json
Upload to the production track with release notes written for surveyors — what changed for them, not the changelog.
Coupling with the backend
An app release and a backend release are separate, and saying “no backend deploy needed” is only safe when it is actually true. Check whether the fixes people are waiting on are server-side before implying a build resolves them: on 31 July the app release fixed the Livelihood sync block, while the follow-up ordering fixes sat in the Frappe repo’s development, 196 commits ahead of main, and did not go to production with it.
D. After the release
- New doctypes need a sync on-device. Once the manifest is updated server-side, the tester must pull-to-refresh / tap the Home sync pill to pull the new
metaJson. Dropdowns populate after that sync, not on first launch. - Log feedback in the tracker. Triage each batch with the PM before fixing — not every report is in scope.
Standing gaps
- No
release.shwrapper yet — the build/upload/notify steps are run by hand. - No CI. Builds depend on one laptop’s Flutter + Android toolchain.
- The mobile SDK does not yet
ALTERexistingdocs__tables when a synced doctype gains a column — a device that already synced an older schema may need a reinstall to pick up new columns (tracked separately).