Release & recovery

Signed patch engine

Deliver signed replacements for logic, surfaces and assets that your installed app already registered. The app verifies the artifact against its original binary contract, stages it, and activates it on a later launch. Keep a compiled fallback and use signed rollback when a patch needs to come off devices.

What can change

ChangePatch support
Registered module logicSupported within the engine's restricted language and the module's existing input/output contract. Flutter also supports explicitly opted-in @Patchable top-level functions in its supported pure int/bool/String subset.
Registered surfacesSupported using components and actions already shipped in the app: Flutter, native SwiftUI, or native Android Compose. A patch cannot introduce arbitrary widgets or native views.
Registered assetsSupported when the application reads those registered asset IDs through the patch runtime. This does not replace arbitrary files throughout the app bundle.
New contracts, plugins, permissions or native codeShip a new app binary. Unregistered Dart, Kotlin, Java, Swift, Objective-C, engine updates and irreversible data migrations are not general hot reload.

An app that shipped without the patch runtime and matching registrations needs an ordinary app release first. A successful compile does not establish distribution eligibility: use the intended distribution profile and qualify the exact app version, channel and patch class. Store policies remain separate from technical patchability.

Install the published packages

Flutter on Android and iOS

dependencies:
  bug_capsule: 0.4.1-beta.10
  bug_capsule_patch: 0.1.0-dev.1

Run flutter pub get. The patch package includes its native runtime: Android arm64-v8a, armeabi-v7a and x86_64; iOS arm64 and Simulator arm64/x86_64. It requires Dart 3.9+, Flutter 3.35+, Android API 23+ or iOS 15+. Start with an isolated test application. See the published Flutter patch package and runnable Flutter patch lab for integration.

Native iOS

.package(
    url: "https://github.com/BugCapsule/bug_capsule_ios_sdk.git",
    exact: "0.4.1-beta.5"
)

// Select these products in your iOS target:
.product(name: "BugCapsule", package: "bug_capsule_ios_sdk"),
.product(name: "BugCapsulePatch", package: "bug_capsule_ios_sdk")

The canonical repository-root SwiftPM package includes both products and the required XCFramework; no nested local package is needed. Patch APIs require iOS 15+. Core macOS support does not imply macOS patch support. Follow the iOS SDK guide and its PatchLab example.

Native Android publication

The native Android core/patch/Compose release bundle is prepared, but the new native patch artifacts have not been published to Maven Central. Do not add unpublished native Maven coordinates to a customer build. This does not block Flutter Android: the published Flutter patch package already carries its JNI libraries.

CLI and compiler

npm install -g @bugcapsule/cli@0.4.0-beta.5
bugcapsule --version
bugcapsule patch doctor --json

The npm wrapper installs the standalone CLI 0.4.1-beta.10. Patch compilation uses its embedded compiler and needs Node.js 24+; you do not need a separate patch-engine checkout for the standard workflow. --engine is an advanced explicit override. The CLI guide covers installation and login; platform builds still need their normal Flutter, Android or Xcode tools.

Prepare project trust and the original app

  1. Set up patch trust

    Sign in to the dashboard, select your project, and open Emergency Recovery → Patch management → Setup. A person with project-update permission creates root trust keys and a backend channel-signing key. Pin at least two root public keys in the original app. Register the public half of your artifact-signing P-256 key as an external artifact public key; its private key stays in your signing environment.

  2. Create a scoped delivery credential

    Choose the exact application ID and environment. Save the one-time private configuration download securely. Delivery credentials only fetch patches and send receipts; they cannot upload artifacts, manage a rollout or approve one. Never embed a personal token or a signing private key in the app.

  3. Register contracts and fallbacks in the app

    Define the modules, surfaces, actions and asset IDs the original binary supports. Integrate the SDK's patch binding with BugCapsuleConfig, using the immutable base build ID, application/environment, installation identity and pinned roots. Use the SDK's PatchUpdater with HttpPatchTransport for delivery. The Flutter README and iOS README are the API references; copy a matching platform example rather than inventing a second runtime integration.

  4. Report actual runtime health

    Staging is not activation. Relaunch the same installed app to activate the staged generation, check that the app is usable, then call the SDK's markHealthy and flush its receipts. Keep the runtime store across launches. Reinstalling or clearing that store is not a rollback test.

Register the immutable base

For CLI reads and uploads, issue a personal token in Settings → Keys → New personal token. Keep project.read and explicitly select patches.read and patches.upload. These patch scopes are unchecked by default and limited by your role. The one-time private CLI configuration download avoids sharing the token in chat. Normal browser-login defaults do not automatically grant patch upload rights.

bugcapsule patch init --platform flutter
bugcapsule patch doctor --online --json
bugcapsule patch base register base-manifest.json --json

For native apps, use --platform ios or --platform android. Initialization creates bugcapsule.patch.json, an empty patch/registered.json and patch/modules; it does not make an app patchable or overwrite an existing configuration.

Populate the inventory from the contracts actually compiled into the app. The base manifest identifies applicationId, platform (flutter_android, flutter_ios, android or ios), baseBuildId, appVersion and modules, with its registered surfaces/assets and other binary capabilities where applicable. Keep the base and its build provenance with the app release. A marketing version is not an immutable base build ID. See the patch CLI reference for the complete manifest/workflow contract.

The registered inventory, runtime inventory and signed artifact must agree. For example, a binary that registers a summary surface cannot accept a module-only base that omits that surface. Correct the app/base contract instead of weakening verification.

Classify, build and sign

Commit the intended changes and use a clean checkout. Replace the uppercase identifiers below with your project's values. Keep the signing key outside the checkout; author-key must match the external public key registered in Setup.

bugcapsule patch classify \
  --registered patch/registered.json --repo . --base-rev BASE_COMMIT --json

bugcapsule patch build \
  --base INSTALLED_BASE_ID --source patch/modules \
  --repo . --base-rev BASE_COMMIT --project PROJECT_UUID \
  --patch-id coupon-fix --sequence 1 --profile internal \
  --key /secure/location/author.pem --key-id author-key \
  --out /tmp/coupon.bcpatch --json

bugcapsule patch inspect /tmp/coupon.bcpatch --json

The classifier examines all changed paths between the base commit and the selected head. STORE_RELEASE_REQUIRED means ship an app release; UNKNOWN means patchability has not been established. Neither result is an approval to force a patch. The build verifies the immutable base's capabilities and host ABI and refuses omitted patchable files. Select surface documents and assets explicitly with repeatable --surface and --asset options.

The default internal profile binds the signed artifact to committed source and emits coupon.bcpatch.source-diff.json. Use the distribution profile appropriate to your actual app. Explicit development artifacts have different source provenance; do not present a development transport test as production qualification.

Upload and validate the exact artifact

bugcapsule patch upload /tmp/coupon.bcpatch \
  --source-diff /tmp/coupon.bcpatch.source-diff.json --json

Retain the returned artifact ID and upload checkpoint. Repeating the same upload resumes with stable request identities; changing the bytes or source review requires a separate checkpoint. An archived successful upload does not prove the artifact is still eligible for deployment.

In Patch management → Artifacts, inspect the identity, classification and immutable source review. Create validation for that exact artifact and the relevant captured failures. Run baseline, fixed-artifact and regression replay on the real target application. Existing replay/verify commands accept --patch-artifact, --patch-keyring and --patch-keyring-signature to pin the fix. Keep the archived delegated-key metadata with the artifact. Follow replay and verification and the exact evidence workflow.

Creating validation returns PENDING, not a test pass. Submitting evidence records real replay results; it does not run tests or fabricate outcomes. A source diff, successful build or successful HTTP request is not replay evidence.

Review, approve and deploy

Use the dashboard to create a deployment plan for the exact artifact and environment, review validation and any required distribution qualification, and request delivery. Complete the project's approval workflow, including a second person where required. Personal/service upload tokens cannot approve or manage deployments.

The command equivalents below require an explicitly authorized person-session workflow; they are not authorized by the upload token above. For routine operation, use the signed-in dashboard. See the repository's authentication and command reference for your approved CLI setup.

bugcapsule patch validate ARTIFACT_UUID --capsule CAPSULE_UUID --json
bugcapsule patch validate VALIDATION_UUID --get --json

bugcapsule patch plan ARTIFACT_UUID \
  --environment staging --stages 100,500,2500,10000 --json
bugcapsule patch deploy DEPLOYMENT_UUID \
  --expected-version CURRENT_VERSION \
  --reason 'Reviewed exact validation and source' --json
bugcapsule patch status DEPLOYMENT_UUID --json

Plan creation never publishes. deploy requests delivery and may return approval_required; it does not approve itself. Stages use basis points: the example is 1%, 5%, 25%, then 100%. Read the current deployment version before a mutation. A version conflict requires reviewing fresh state, not blindly substituting a newer number.

Watch actual activation, healthy receipts and monitoring evidence before expanding a cohort. Offline, stale and never-reporting installations limit what the dashboard can conclude. Zero reports is not a healthy fleet.

Publish and confirm rollback

Use the deployment's dashboard rollback action to return to the bundled base or a previously approved digest. A permitted person-session CLI workflow can request the same operation:

bugcapsule patch status DEPLOYMENT_UUID --json
bugcapsule patch rollback DEPLOYMENT_UUID \
  --expected-version CURRENT_VERSION --to-base \
  --reason 'Observed regression' --json
bugcapsule patch status DEPLOYMENT_UUID --json

Use exactly one of --to-base or --to-digest sha256:…. --revoke additionally requests revocation of the faulty artifact. A signed rollback publication is not confirmation that every device has applied it: let the same installation fetch it, relaunch without clearing its store, and confirm the resulting identity and receipts. Check pending rollback and stale-report counts. Zero pending among reporting installations does not cover devices that have never reported.

Read command outcomes

Patch commands emit JSON results. Exit 0 means the command was accepted or the read completed, not that devices activated a patch. Exit 2 means validation or approval is still pending; 3 includes store-release requirements, failed/inconclusive validation and conflicts; 4 is unknown patchability; 77 is an authentication or permission problem. Read the returned state and reason. Keep artifact IDs, immutable base identity, validation evidence and rollout receipts together so a later review can reproduce the decision.