Deploy — Flutter OTA
FAQ & limits
Answers to the most common Sankofa Deploy questions — what you can patch, why a patch didn't apply, engine-version binding, signing, offline behavior, and the boundaries of over-the-air Dart updates.
Short answers to the questions Deploy users ask most. If your question is about the boundary of what's patchable, read Design for hot-fixes first — most surprises trace back to it.
The essentials
No. A patch updates the Dart layer of your already-reviewed app over-the-air. Only the base release — the native binary — goes through store review. See Platform & policy for why this is compliant.
Logic reached through a virtual call — an interface method or an override. Method bodies behind a seam, business rules, thresholds, and copy resolved through an interface all ship over-the-air. Direct edits to static/top-level functions or build()/layout are not patchable on iOS, and adding new fields, classes, dependencies, assets, or plugins is never patchable. Full breakdown in Design for hot-fixes.
No. You edit your real lib/*.dart source and run sankofa patch. It rebuilds, diffs against the base snapshot captured at release time, and ships only the changed functions. There is no separate patch format to author.
Yes — the same sankofa patch command ships to both iOS and Android. iOS holds the tighter boundary (virtual-call seam); Android has more headroom. Design to the iOS seam and one patch serves both.
"My patch didn't apply"
Most often the shipped store build's version + build number doesn't match the release Sankofa recorded. On iOS this is almost always the Xcode "Manage Version and Build Number" checkbox left checked at upload — it rewrites the build number so patch addressing misses. See Releasing to the App Store.
The change probably isn't behind a virtual call. If you edited a static/top-level function or a build() method directly, the auto-diff will include it but iOS can't reroute it. Move the logic behind an interface and re-ship. See Design for hot-fixes.
Usually a mismatched engine_version or an unknown app version. The patch was built against a different Sankofa Flutter runtime than the installed app, or targets a version the server doesn't have a base release for. Rebuild the patch against the app's engine, or ship a base release for that version first.
This device previously auto-rolled-back a patch with the same label, so it refuses to re-download it. Roll forward with a new patch (new label) instead of re-publishing the old one — see Rollouts & safety.
The Simulator runs Dart in debug/JIT and ignores applied patches. Verify on a physical device in a release build.
Delivery & safety
The SDK checks on boot and on resume. A device downloads the patch in the background and applies it on the next cold launch. Optional patches ride the next natural relaunch; mandatory patches apply before the user continues.
The device auto-rolls-back on its own: two crashes within 30 seconds of launch disable the patch, restore the last known-good version, and ban the bad label locally. No server round-trip, works offline, no host code. See the boot-crash safety net.
Yes. sankofa patch --rollout 25 (then 50, 100). Bucketing is deterministic per device, so ramping never re-shuffles who's exposed. Rollout defaults to 100%.
Rollback (pause the rollout) stops serving it and reverts devices that took it. Roll-forward — ship a corrected new patch — is usually better, because it reaches even the devices that auto-rolled-back. Never re-publish a broken patch under its old label.
Update checks fail transiently (unavailable) and are retried on the next launch/resume. An already-installed patch keeps running; auto-rollback still works with no network.
They can and should be. Run sankofa keys generate and sankofa keys register once; every patch is then signed with your project's Ed25519 key, and both the server and the SDK verify before applying. Back up the private key.
Limits & boundaries
No. The engine (Sankofa Flutter runtime) is downloaded from download.sankofa.dev at build time and ships inside the native binary. Moving to a new engine requires a new store build. Patches carry Dart changes only.
Not new surface — a patch can't add classes, fields, assets, or dependencies that weren't in the base. It can change what an existing seam does. If you anticipate a feature, put the seam (an interface + a default implementation) in the base release so you can flesh it out later. Genuinely new features ship as a store release.
No — they compose. Use Remote Config to change values with no code push at all, and Switch to gate behavior with flags. Use Deploy when you need to change code — a bug fix or logic correction — without a store release.
Because auto-diff ships only the functions that changed, a typical fix is on the order of kilobytes, not megabytes. The exact size depends on how much compiled output your change touched; checkForUpdate() exposes the byte size so you can show a progress bar.
This section documents the Flutter product. The CLI's release / patch commands also cover React Native (JavaScript bundles); see the CLI reference.