Repository navigation
task: prove the Shorebird release-backport runbook on iOS and Android #7843
Copy link
Copy link
Closed
Description
Description
Exercise the release-specific Shorebird patch runbook end to end on non-production iOS and Android releases after the implementation in the sibling child issue lands. Prove first patch, cumulative second patch, and rollback with captured evidence.
Context
Static workflow tests cannot prove that operators can build, install, validate, promote, observe, and roll back a patch for the exact targeted store artifact. Parent epic #7841 is not complete until both supported platforms pass a controlled drill.
Preconditions
- The release-specific patch workflow from the sibling implementation task is merged.
- Test releases are built with signed
shorebird releaseworkflows and valid immutable provenance. - The drill uses staging/non-production configuration and must not promote a test patch to the production stable track.
Drill procedure
- Cut identifiable non-production Shorebird releases for iOS and Android.
- Advance
mainwith representative Dart, native, asset, and version changes so it no longer qualifies as a patch source. - Create each canonical release patch line from its recorded baseline.
- Backport one minimal Dart behavior change plus its test.
- Confirm provenance, branch identity, blocked-path, configuration, and signing checks pass.
- Publish each patch to
staging. - Preview/install the exact targeted release and verify the first fix applies.
- Add a second patch on the same release line and prove that both the first and second fixes remain present.
- Exercise rollback and verify the device returns to the expected prior behavior.
- Capture duration, permissions, commands, console steps, logs/Crashlytics patch identity, and any runbook corrections.
Acceptance criteria
- The isolated iOS patch succeeds despite unrelated later changes on
main. - The isolated Android patch succeeds despite unrelated later changes on
main. - The exact release and patch numbers are verified on devices or Shorebird preview builds.
- A second patch preserves the first patch fix on both platforms.
- Rollback is executed and verified on both platforms.
- No drill patch is promoted to the production stable track.
- Required permissions and named operator roles are recorded.
- Timing and evidence are added to this issue.
- Runbook corrections discovered during the drill are merged.
- Parent epic epic(shorebird): make emergency patches independent of main #7841 is updated with the final result.
Testing evidence
Attach Codemagic build links, Shorebird release/patch identifiers, device or preview verification, rollback evidence, and the final clean runbook checklist. Do not include credentials, signing material, dart-define values, or other secrets.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
- StatusShow more project fieldsTesting
Activity
NotThatKindOfDrLiz commentedon Aug 19, 2026
Drill progress — 2026-08-19 (iOS prepared, Android blocked)
iOS: patch line is live and validated
Target release is
1.0.21+851, whose private provenance record is intact andpatchable: true, withpatch_baseline_commit d6ae3c0a00becd3e15679a771e5da0d4af4865a7.Drill step 2 ("advance
mainso it no longer qualifies as a patch source") needed no synthetic work —mainis already 18 commits past that baseline, including native, asset, and dependency changes. That is the real condition the epic claims to survive, so the drill exercises it rather than simulating it.shorebird-patch/ios/1.0.21+851created from the recorded baseline and pushed.57a3acc8dd(fix(library): drop the grid-size button from the editor clip picker, fix(library): drop the grid-size button from the editor clip picker #7790) → cherry-picked as80a25a44b8. Two Dart files, no blocked paths, ships its own test, and is visually obvious on device.mainfix rather than a throwaway: because the line is immutable and cumulative, whatever lands on it must be retained by every future patch for this release. A real fix stays legitimate; a synthetic one would be permanent debt.refs/heads/shorebird-patch/**(deletion + non-fast-forward prohibited).Validator evidence (
main's copy, run against the real line)Accepted:
Rejected, all fail-closed with exit 1:
mainas patch sourcepatch source contains commits already on main1.0.21+850)patch branch must be exactly "shorebird-patch/ios/1.0.21+850"android)patch branch must be exactly "shorebird-patch/android/1.0.21+851"ios/Runner/AppDelegate.swift) in payloadrefusing to patch: … cut a normal store release insteadThe first row is the epic's central claim, proven against the actual advanced
mainrather than a fixture.As a side effect, the pre-push hook ran the backported screen's suite against the release baseline tree: 28 passed. That is a useful extra signal the runbook does not currently ask for — the backport is green on the code it will actually patch, not only on
main.Android: blocked on provenance
divinevideo/divine-release-provenancecontains onlyreleases/ios/. The three Android Shorebird releases (1.0.20+714,+716,+719) all predate automatic provenance recording, so they fail closed exactly like iOS1.0.21+849.The Android half of this drill cannot start until an
android-buildruns and mints a release with a valid record. That is a scheduling dependency, not a defect.Runbook corrections
Two gaps found while preparing the line, fixed in #7917:
.dart_tool/, so the pre-push hook's analyzer fails on the missing package config rather than on the backport. Needsmise trust+flutter pub getfirst, then a cleangit statusso nopubspec.lockchurn joins the payload (it would be a blocked path).main. Documented, with the command to dry-run it locally.Remaining for this issue
ios-patchin Codemagic frommain(CONFIRM_PATCH=YES,RELEASE_VERSION=1.0.21+851,PATCH_BRANCH=shorebird-patch/ios/1.0.21+851,DEFAULT_ENV=PRODUCTIONto match the recorded fingerprint).shorebird preview --track staging --release-version 1.0.21+851.No drill patch will be promoted to the production
stabletrack;patch_ioshardcodes--track=staging.docs(shorebird): record the two patch-line gotchas the drill hit
NotThatKindOfDrLiz commentedon Aug 20, 2026
Drill finding — the patch workflow could never sign a patch
The first
ios-patchrun failed at step 19,Patch iOS (Shorebird), exit 64:Not an input mistake and not a regression from #7844.
shorebird patchneeds both halves —--private-key-pathsigns the patch,--public-key-pathvalidates that signature against the key baked into the release byshorebird release --public-key-path. Both patch commands passed only the private key, and neither patch workflow ran*write_shorebird_public_key, so the public key was missing from the command line and from disk.This has been broken since code push was wired up in #7202. Nothing caught it because no patch had ever actually been built. Every safety mechanism the epic added works — provenance verification, branch identity, ancestry, blocked paths, the not-already-on-main check all passed on this run — the workflow simply could never reach a successful signing step behind them.
Both
ios-patchandandroid-patchwere affected identically, so the Android half would have hit the same wall.Fix in #7936 (green).
Why the config tests missed it
Whole-file substring. The release commands carry that flag, so the assertion passed on their strength while neither patch command had it. The replacement asserts per extracted patch command and per workflow block; both new tests are confirmed red against pre-fix
codemagic.yaml.What this says about the drill
This is the drill doing its job. Static configuration tests pinned every property the implementation was written to have, and the one property nobody had ever exercised — that the command actually runs — was the one that was broken. Worth carrying into the epic's conclusion: the value here was not confirming the guards work, it was reaching the step past them.
Blocked until #7936 merges
Re-run
ios-patchwith the same inputs once it lands:CONFIRM_PATCH=YESRELEASE_VERSION=1.0.21+851PATCH_BRANCH=shorebird-patch/ios/1.0.21+851RELEASE_COMMIT= emptyDEFAULT_ENV=PRODUCTIONThe patch line itself is unchanged and still validates; nothing about
shorebird-patch/ios/1.0.21+851needs redoing.NotThatKindOfDrLiz commentedon Aug 20, 2026
Drill finding — iOS staged-patch validation is not executable as written
The signing fix (#7937) worked:
ios-patchcompleted and patch #1 is published on thestagingtrack for1.0.21+851. Step 7 of the drill procedure — "Preview/install the exact targeted release and verify the first fix applies" — is where it stops, and the reason is structural rather than incidental.shorebird previewcannot work on iOS for DivineFirst attempt failed on wireless transport (
NWError 54 — Connection reset by peerover.coredevice.local). Over USB it failed differently and permanently:The artifact's
embedded.mobileprovision:beta-reports-activetrueget-task-allowfalseProvisionedDevicesThat is an App Store distribution profile. No device list exists, so no device can be provisioned for it directly; it installs through the App Store or TestFlight and nowhere else. This follows from
ios_signing: distribution_type: app_storeincodemagic.yaml, which is correct for a shipping build.shorebird previewis simply not available on this platform for this product.The documented fallback is also closed
The runbook offers "or an equivalent installed build" — the TestFlight copy, which genuinely is the same binary. But an installed build only polls
stable:auto_updateis on, theshorebird_code_pushdefault isUpdateTrack.stable, andlib/main.dartcalls onlyisAvailableandreadCurrentPatch(), neverupdate(track:). The package exposesUpdateTrack.staging; the app does not use it.So the only way to make a staged patch visible on iOS is to promote it to
stable— the production deploy that is supposed to follow validation, not enable it.Tracked as #7979.
Unblocking this drill: promote on
851is safe today, and only todayThe live App Store build is
1.0.21+849. Patches are scoped to a release version, so a patch on851can only reach installs running851— TestFlight testers. There is no path from a851patch to a device running849.Promoting patch #1 to
stableon851therefore has zero production blast radius, and reaches exactly the cohort we want to validate with.This bends the letter of the acceptance criterion "No drill patch is promoted to the production stable track" while preserving its intent, which is that no drill patch reaches production users. Recording the deviation explicitly rather than quietly:
The shortcut expires on success. Once #7980 puts a patchable build in the App Store, promoting to
stableis a production deploy. From that release onward this substitution is invalid and #7979 is the only correct validation path — which is why #7979 must ship in that binary rather than after it.Revised iOS procedure for this drill
stableon1.0.21+851.1.0.21+851: the grid-size button is gone from the editor clip picker. Record the patch number from logs/Crashlytics.Runbook corrections this produces
shorebird preview, state why it cannot work againstapp_store-signed artifacts, and point at task: add a staging-track patch validation affordance to developer options #7979.Android is unaffected by the
previewproblem and can use it normally, once a provenance-recorded release exists.NotThatKindOfDrLiz commentedon Aug 20, 2026
iOS drill evidence — first patch and cumulative patch verified on device
Both verified on a TestFlight install of
1.0.21+851, which is the same store-signed binary App Store users would receive if that build were promoted.Patches
60811580a25a44b8— backport of57a3acc8dd(#7790, drop grid-size button from editor clip picker)stable6098997963afddfa— backport of5c9f747b8a(#7846, Divine Family / Divine Kids links in Support Center)stablePatch line
shorebird-patch/ios/1.0.21+851, rooted at the provenance-recorded baselined6ae3c0a00becd3e15679a771e5da0d4af4865a7. Both backports are genuine mergedmainfixes, chosen deliberately: the line is immutable and cumulative, so anything placed on it must be retained by every later patch for this release.Result
First patch applies and a later patch preserves the active one's fixes. Patch #2 carries both backports, and after promotion the Divine Family and Divine Kids rows appeared in Settings → Support Center on device. Since #2 is cumulative, its verification satisfies both criteria in one observation.
Verification-signal correction, worth recording
The grid-size button (#7790) turned out to be a poor go/no-go signal. It is a small
gridNineicon button that renders only in the editor's clip-picker bottom sheet — not the Library tab — and it was reported as "not there" on both builds while patch #1 was already promoted to stable, meaning absence was the success state being read as a failure. Ambiguous either way.The Support Center links are a much better signal and are what the drill should use going forward: two full-width rows with title and subtitle, on a screen reachable without entering an editor flow, either visibly present or visibly absent. Establish the "before" state before promoting.
Deviation from the acceptance criteria
Both patches were promoted to
stablerather than validated onstaging. This was necessary and is safe only under today's conditions:shorebird previewcannot install anapp_store-signed artifact at all (see the previous comment), sostagingis unreachable on iOS.1.0.21+849. Patches are scoped to an exact release version, so a patch on851has no path to a device running849. Production blast radius was zero.This satisfies the intent of "no drill patch is promoted to the production stable track" — no drill patch reached production users — while breaking its letter. The route expires when #7980 lands; once a patchable build is in the App Store,
stablemeans production and #7979 becomes the only correct validation path.Timings
278.7s19.2sLinking AOT files: the dominant cost. Still running past the 14-minute mark of the overall build before completing.That last figure matters operationally:
ios-patchcarriesmax_build_duration: 60, and nobody previously knew where an app this size lands against it. Worth capturing the exact total from the Codemagic build page for the runbook, so the next operator knows whether 20 minutes is normal or alarming.Verifying patch can be applied to releasepassed before linking, which is the gate that would have caught a patch/release mismatch.Still open
stagingand confirm the device reverts to patch 🏗️ [Infrastructure] Configure R2 Storage Buckets for Media Processing #1 behaviour (Support Center rows gone, grid-size button still gone).shorebird previewnormally once one exists.NotThatKindOfDrLiz commentedon Aug 21, 2026
Android is unblocked — first patchable Android release exists
Android Buildsucceeded after #7988. GradlebundleReleaseran to completion in 304.4s, where the pre-fix run died at 12s on the missingflutter.jar.The precache step did exactly what it was added for —
[4/12] Android Internal Build Artifactspulled all ten Android engine entries includingandroid-arm64, plus the sixandroid-*-{profile,release}/darwin-x64toolchains, in about 20 seconds.The record that has never existed before
divinevideo/divine-release-provenancenow hasreleases/android/1.0.22+739.json:platformandroidrelease_version1.0.22+739build_source_commitb34e84b6dc94c26afd1ac983c80ff2a0b4d5cf88patch_baseline_commitb34e84b6dc94c26afd1ac983c80ff2a0b4d5cf88flutter_version3.44.9patchabletrueBefore this, the provenance repository had no
releases/android/directory at all, so the Android patch workflow had never had a valid input in its life. It does now.Uploaded to Play as a draft (
submit_as_draft: true), so nothing reached users.What now blocks the Android drill: nothing to backport
The baseline is
b34e84b6, which ismain's tip — andgit log b34e84b6..origin/mainis empty. Every merged fix is already inside the release, so cherry-picking one produces an empty diff, and the validator refuses:The iOS half never hit this:
1.0.21+851was cut from a commitmainhad already moved 18 commits past, so there were merged fixes available to backport. A release cut from currentmainhas no such gap by construction.So the Android drill needs a Dart-only fix that merges after
b34e84b6, and it has to be device-visible, since the drill requires confirming the fix actually applied. Normal merge velocity should supply one shortly. Deliberately manufacturing a change for the drill would be worse than waiting — the whole reason both iOS backports were genuine merged fixes is that the patch line is immutable and cumulative.Ruled out as a candidate: #7986 (deleting the unused
AppConfig.appVersion). Dart-only and real, but nothing renders it, so there would be no way to tell whether the patch applied.Once a candidate lands
Android verification is far simpler than iOS —
shorebird preview --track staging --release-version 1.0.22+739works, emulator included. No promotion tostable, no TestFlight, and none of theapp_store-signing obstacles that forced the iOS deviation.Unrelated observation from the build log
Shorebird suggests removing
packaging.jniLibs.keepDebugSymbols.add("**/libapp.so")fromandroid/app/build.gradle.kts, but that line is not in the file — the suggestion does not match this configuration. The effect is real though: Android Dart crashes reach Play Console unsymbolicated, and unlike iOS (upload_dsyms_to_crashlytics) the Android workflow has no symbol-upload step. Pre-existing and unrelated to this epic; noting it here so it is not lost.NotThatKindOfDrLiz commentedon Aug 21, 2026
Drill complete — manual verification passed
Liz confirmed on 2026-08-21 that the remaining manual drill was completed on both platforms and all checks passed, including:
The earlier comments retain the release, provenance, signing, build, and iOS device evidence. This confirmation closes the remaining manual Android and rollback work, so the two-platform drill is complete.
Closing as completed. Parent epic: #7841.
docs(shorebird): record the two patch-line gotchas the drill hit (div…
ci(shorebird): pass both signing keys to the patch commands (divinevi…