Skip to main content

patch Command

The tk-ota-push patch command compiles updated Dart code, verifies native binary integrity against the locked Release, and uploads the new binary (application binary on Android, patch artifact on iOS) to the OTA server (POST /api/admin/v1/patches). The server then computes the binary differential (binary diff + optimized compression) and cryptographically signs it via cryptographic signing engine.

General Syntax​

tk-ota-push patch [version] [options] [-- <flutter-arguments>]

Execution Flow and Differential Generation​

Patch Generation Steps
  1. Preparation: Verifies project metadata and shorebird.yaml configuration.
  2. Dart Compilation: Builds application snapshots for all target hardware architectures (aarch64, arm, etc.).
  3. Diff Computation: Computes an optimized compressed diff against the base release.
  4. Secure Upload: Submits the patch artifact to the server with initial status STAGED.

1. Platform-Specific Compilation and Linking​

On Android (patchAndroid)​

  1. The CLI runs flutter build apk --release (or appbundle when --artifact aab is passed) with SHOREBIRD_PUBLIC_KEY.
  2. It extracts the stripped application binary binaries directly from the .apk or .aab archive for each requested architecture (aarch64 and arm by default with --arch all).
  3. It calls GET /api/admin/v1/releases/lookup to retrieve the reference Release's nativeCodeHash and compares it with the localNativeHash computed by NativeIntegrityScanner.
  4. It uploads each application binary along with its .symbols file (app.android-arm64.symbols / app.android-arm.symbols) to POST /api/admin/v1/patches.

On iOS (patchIos)​

  1. The CLI checks for .ota_ios_release_cache/<version>/App and diff_base.
  2. It computes the local SHA-256 of diff_base and compares it with baseSha256Hash returned by GET /api/admin/v1/releases/lookup (platform=ios, arch=aarch64). If they differ (MISMATCH DE HASH iOS), patch generation is immediately aborted.
  3. It runs flutter build ios --release --no-codesign, locates app.dill inside .dart_tool/flutter_build/, and copies it to build/app.dill.
  4. It generates the ELF snapshot build/out.aot using gen_snapshot_arm64:
    <gen_snapshot_arm64> --deterministic --snapshot-kind=app-aot-elf --elf=build/out.aot [splitDebugArgs] build/app.dill
  5. It runs aot-tools.dill link to link the base App binary and the new out.aot snapshot into build/patch artifact:
    <dart> <aot-tools.dill> link \
    --base=.ota_ios_release_cache/<version>/App \
    --patch=build/out.aot \
    --analyze-snapshot=<analyze_snapshot_arm64> \
    --gen-snapshot=<gen_snapshot_arm64> \
    --kernel=build/app.dill \
    --output=build/patch artifact
  6. It restores the signed Runner.app from .ota_ios_release_cache/<version>/Runner.app, verifies iOS native integrity (Info.plist and Frameworks/), and uploads patch artifact to the OTA server.

2. binary diff + optimized compression Differential and signing engine Signature​

Upon receiving POST /api/admin/v1/patches, the OTA server (TkushPatch Server):

  1. Validates the submitted binary size (anti-bomb guard: 60 MB max by default).
  2. Loads the exact reference binary stored during the Release (application binary or diff_base).
  3. Generates the compressed differential patch in binary diff + optimized compression format and verifies that its size does not exceed maxPatchDiffSizeBytes (15 MB by default).
  4. Computes targetUncompressedSha256Hex (the SHA-256 hash of the full new binary).
  5. If auto_approve=true (no --staged flag) and the tenant does not enforce strict 4-eyes review (enforceFourEyes == false), immediately signs the 64 ASCII bytes of the target SHA-256 via cryptographic signing engine (APPROVED_AND_SIGNED). Otherwise, the patch is saved in STAGED status.

3. Native Checks That Block a Patch​

The OTA engine only replaces Dart code (application binary / App.framework). If you modify native code or OS permissions between the Release and the Patch, NativeIntegrityScanner detects a nativeCodeHash mismatch:

  • Android: changes in classes*.dex (Java/Kotlin), AndroidManifest.xml, or any C/C++ library lib/<arch>/*.so (excluding application binary).
  • iOS: changes in Info.plist, the main Runner executable, or any framework inside Frameworks/ (excluding App.framework).

When a mismatch occurs, the CLI blocks publication with a CliException (exit code 1) and instructs you to publish a new Store Release. Passing --force (-f) overrides this check (as well as the pubspecLockHash check) at your own risk.

Changes in pubspec.lock (Pure Dart Dependencies)

Updating a 100% pure Dart package changes pubspec.lock but does not alter nativeCodeHash. Therefore, the CLI allows the patch without requiring --force, while the server logs an informational message.

4. Key Options for the patch Command​

Option / FlagPurposeExample
-c, --channel <channel>Target publication channel (stable, staging, beta)--channel staging
-m, --message <text>Business description / changelog displayed to reviewers in the Console-m "Fix VAT calculation"
--rollout <0-100>Initial progressive rollout percentage (Canary)--rollout 10
--countries <codes>Comma-separated ISO-2 country codes or "all"--countries "CI,SN,CM"
--stagedPlaces the patch in STAGED quarantine (awaiting 4-eyes approval)--staged
-f, --forceIgnores native divergence and aligns pubspecLockHash with the Release--force

Practical Examples​

# 1. Simple patch on the default channel (100% rollout, worldwide)
tk-ota-push patch -m "Fix account balance display"

# 2. Android-only patch for 64-bit architecture on the staging channel
tk-ota-push patch 1.0.0+3 -p android -a aarch64 -c staging -m "64-bit QA test"

# 3. Patch with prod flavor, env file, country targeting, and 25% staged rollout
tk-ota-push patch 2.1.0+105 -p android \
--flavor prod \
-t lib/main_prod.dart \
--dart-define-from-file=env/prod.json \
--artifact aab \
--countries "CI,SN,CM" \
--rollout 25 \
--staged \
-m "Fix wire transfer fee calculation"