Repository navigation
ci(shorebird): move code push to the Divine org and wire it into Codemagic - #7202
Conversation
This comment has been minimized.
This comment has been minimized.
rabble
left a comment
There was a problem hiding this comment.
Reviewing this specifically against shipping a TestFlight build this afternoon, because it sits directly on that path. The work looks careful — the flavor-detection fix, the deliberate Zapstore carve-out, and the FLUTTER_VERSION pinning rationale are all well reasoned. Two timing concerns, neither about the code itself.
1. This adds shorebird_credentials to four workflows — the exact shape that took Codemagic down this morning
main was broken for every workflow until an hour ago with:
Codemagic.yaml references to unknown variable group(s): supporters_credentials
That was #6999 adding a variable group to six workflows before the group existed in Codemagic. Group resolution is config-level validation, so it doesn't degrade the affected workflow — it rejects the entire file and every workflow fails before running a step. #7191 fixed it by removing the reference.
This PR adds - shorebird_credentials to four workflows, and step 9 of your own setup comment says the group has to be created by hand in the Codemagic console with a SHOREBIRD_TOKEN. If this merges before that group exists, main goes straight back to the same total outage — on the afternoon we want to cut a build.
Not a criticism of the PR — you documented the prerequisite, which #6999 didn't. It's a sequencing constraint: the group has to exist in Codemagic before this lands, not after. Same rule that would have prevented this morning.
2. It changes the command that produces the TestFlight artifact
flutter build ipa → shorebird release ios, and flutter build appbundle → shorebird release android.
That's correct for what the PR is for — code push only works on binaries the updater is linked into, and you're right that nothing shipped so far is patchable. But it means the first build after merge is simultaneously:
- the first Codemagic run after a total config outage,
- on a newly created Shorebird app in a new org,
- through a build command that has never produced a Divine artifact,
- with a toolchain that provisions its own Flutter.
If that build fails, we won't know which of the four it was, and we'd be debugging it against a release deadline with no recent green run to diff against — the last known-good Codemagic build predates the outage entirely.
Suggestion
Ship this afternoon's TestFlight on the current known-good path, then land this immediately after, so its first run is a normal build rather than a release-day one. The value here is over-the-air patching of future releases; nothing about waiting one build cycle costs that.
If you'd rather it go in today, the order is: create shorebird_credentials in Codemagic → confirm a green run on this branch → merge. Doing the first two out of order repeats the morning.
No blocking findings in the diff itself. @rabble is at lunch; flagging here so the sequencing decision is visible rather than discovered.
This comment has been minimized.
This comment has been minimized.
00847c0 to
21405b8
Compare
|
Addressed the Shorebird review cleanup on the latest head:
Local verification:
Remaining merge gate is the real Codemagic branch run for |
This comment has been minimized.
This comment has been minimized.
|
Follow-up: refreshed GitHub checks on head I do not have |
rabble
left a comment
There was a problem hiding this comment.
Reviewed this read-only and am routing findings rather than pushing: release automation and CI config are a protected path under PR_REVIEW.md, so a reviewer takeover isn't mine to do here. The engineering is careful — the flavor-detection fix, the Zapstore carve-out, the explicit --allow-*-diffs prohibition, and the fail-fast ordering in confirm_patch are all right, and the reasoning in the inline comments is unusually good. Two things I'd hold it on, both verified against the branch.
BLOCKING 1 — the new dart-define step will fail every store build
codemagic.yaml:170-177 aborts when any of seven vars is absent:
missing = names.reject { |name| ENV.key?(name) }
unless missing.empty?
abort("missing Shorebird dart-define environment variables: #{missing.join(', ')}")
endTwo of the seven are SUPPORTERS_API_BASE_URL and FF_DIVINE_SUPPORTERS. #7191 (e84be55c38, merged yesterday) is titled "drop the supporters variable group that does not exist yet", and the groups: lists on this branch are zendesk_credentials, proofmode_credentials, shorebird_credentials — no supporters group. So both vars are unset and the step aborts.
It also contradicts a check running earlier in the same workflow: mobile/scripts/check_supporters_config.sh:47 says "An unset URL is fine on its own: the feature is inert" and exits 0. One step blesses that state, the next kills the build for it. Affects ios-build (660), android-build (771), ios-patch (864), android-patch (921).
Fix: ENV.fetch(name, '') for the two supporters vars — matching the empty-define semantics the existing --dart-define=X=$X lines already ship — and keep the hard abort for the Zendesk/proofmode vars that do come from real groups. Or define both in Codemagic first, which makes this a second merge-order prerequisite.
BLOCKING 2 — patches can ship from any branch
confirm_patch (:224-244) validates CONFIRM_PATCH == YES and that RELEASE_VERSION matches ^[0-9]+\.[0-9]+\.[0-9]+\+[0-9]+$. That is the entire gate. There is no branch check, and Shorebird builds the Dart source from whatever branch the Codemagic run selects.
SHOREBIRD_CODE_PUSH.md step 1 says patches ship from main; nothing enforces it. As written, anyone with Codemagic build-start permission can put arbitrary unreviewed Dart on every production install within about one app launch — no PR, no CI, no store review, and no artifact in GitHub recording what shipped.
Four lines at the top of confirm_patch:
if [ "$CM_BRANCH" != "main" ]; then
echo "Refusing to patch: patches ship from main only (got '$CM_BRANCH')."
exit 1
fiThat restores the "everything users run passed review and CI" invariant every other path in this repo holds.
SHOULD-FIX
Patch signing is available and not enabled (:355-360, :361-368). Verified against the installed CLI (Shorebird 1.6.67): release takes --public-key-path, patch takes --private-key-path/--public-key-path. Without them the updater accepts any patch Shorebird's servers deliver for app_id 73254661-…. This is the one control that bounds who can push executable code independently of account security — which is the PR's own central claim. Ordering matters: the public key is baked in at release time, so a release cut without it can never be retro-signed; deciding later costs a full store cycle.
Unpinned curl | bash (:152-156) installs from install/main onto a runner holding the Android keystore, the ASC key, and SHOREBIRD_TOKEN. AGENTS.md treats a movable ref as an unconditional failure for Dart dependencies; same hazard, now on the toolchain producing both store artifacts. Assert a known version after install, or pin a tag.
Patches always go to stable (:245-255, :361-368). --track defaults to stable; staging and beta exist. A TRACK input defaulting to staging would give "no rollback window" an actual pre-production step.
RELEASE_VERSION is interpolated into shell before it is validated (:239, and unquoted at :254/:366). Only escalates an already-privileged Codemagic user, but that user is one holding the keystore and ASC key.
The credential is still personal. #7200 is open; the app moved to the Divine org (mobile/shorebird.yaml:8), the authority to patch did not. Worth an explicit call on merging ahead of #7200 rather than letting the title imply it's settled.
New third-party call on every launch, undisclosed. auto_update defaults on, so every install will contact Shorebird at launch with app id and patch state. Nothing in the PR, the runbook, or the App Store data-collection answers covers it. Worth a conscious decision for a privacy-positioned app.
Secrets to a plaintext workspace file (:182-185). PROOFMODE_SIGNING_SERVER_TOKEN lands in build/shorebird/dart_defines.json. Blast radius is contained — /build/ is gitignored, no artifacts: glob or cache_paths covers it, publish_github_release globs only ipa/apk/dmg, and the step never prints the file, with the abort listing names not values. Still worth chmod 600 and mktemp -d outside build/.
No --no-confirm on the patch commands (:254, :366). Shorebird likely auto-detects CI; if it doesn't, the workflow hangs to the 60-minute cap. CONFIRM_PATCH is already the human gate, so it's defensively free.
NIT
build_apk(:194-202) still copy-pastes its defines while the AAB uses the JSON, so Play and sideload builds can silently diverge.- "Never swap those steps back" is doc-only.
test_codemagic_android_build_number.pyis the existing precedent for pinning acodemagic.yamlinvariant by string match.
Clean
No secrets committed — the only literal is app_id, which Shorebird documents as non-secret. No .github/workflows/* changes, so no interaction with the CI-workflow ruleset. Triggers correct: all 8 workflows keep triggering: {events: []}, nothing auto-fires, no fork-secret exposure. submit_to_app_store: false, submit_as_draft: true unchanged. No disclosure-rule violations. Shorebird flag syntax verified against the real CLI — --flutter-version, --dart-define-from-file, --release-version, --export-options-plist are all first-class and need no -- separator, contradicting the general docs. A bad SHOREBIRD_TOKEN fails loudly, not silently.
Could not verify
- Whether
shorebird_credentialsexists in Codemagic withSHOREBIRD_TOKENsecret. An undefined group is config-level: Codemagic rejects the whole file and all 8 workflows fail before step 1. Your assertion in the comments is the merge gate here. - Whether the two supporters vars were added as project-level vars since #7191. If they were, BLOCKING 1 evaporates — worth 30 seconds in the console before acting on it.
- Whether
app_id 73254661-…is genuinely Divine-org-owned, and the org's member list. Needs Shorebird console access. - Rollback semantics for a bad patch. The PR says "no rollback"; I did not confirm whether Shorebird supports patch deactivation vs roll-forward-only, and that distinction changes how bad the worst case is.
- No build was run. All static reading plus local
shorebird --help; the branch run you asked for is the first real proof.
|
Ran an automated review pass over this branch. These findings have not been human-verified — please check them before acting. I deliberately skipped everything already covered in the existing reviews (supporters dart-defines, branch gate on patches, patch signing keys, unpinned Three things left: 1.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Rabble, I pushed the Shorebird hardening follow-up at 56cd204. GitHub checks are green now. Can you confirm the Codemagic validation can proceed on this branch? The run needs the new
I could not start those Codemagic runs locally because |
|
Update: Liz confirmed the Shorebird credentials are already present in Codemagic. So the remaining ask is just to confirm/start the Codemagic branch validation for |
56cd204 to
9a34f8e
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
de472d5 to
7608641
Compare
rabble
left a comment
There was a problem hiding this comment.
Reviewed against PR_REVIEW.md. Draft, so this is feedback-only — no changes pushed.
The gating design is genuinely good and I want to say so first. I parsed codemagic.yaml at head with Ruby YAML and confirmed anchors resolve: ios-patch/android-patch both have triggering: {events: []}, CONFIRM_PATCH defaulting to "NO", and Confirm patch to production → Verify patch source as scripts 1 and 2. There is no path to an automatic patch on merge to main — three independent gates plus --track=staging, so even a successful patch isn't live until a separate manual promote. No secret or credential is hardcoded; app_id is the only identifier and its own header says it's not secret. The private key doesn't reach any artifact glob and gets chmod 0600.
Two blocking problems, both verified against live sources rather than inferred.
Blocking
1. codemagic.yaml:801,923,377,493 — Shorebird has no Flutter 3.44.9
git ls-remote --tags https://github.com/shorebirdtech/flutter.git returns 3.44.0 – 3.44.6 only. flutter/flutter goes to 3.44.9. So shorebird release android/ios --flutter-version=3.44.9 fails at both store workflows — this PR, whose purpose is to make store artifacts patchable, cannot produce a store artifact at all.
The version it removes (3.44.0) is supported. Highest usable today is 3.44.6.
Worse: test_shorebird_flutter_version_uses_current_supported_engine asserts assertNotIn("3.44.0", ...), so the test actively blocks reverting to a known-supported pin.
2. codemagic.yaml:297,341 — the duplicate-release preflight is dead code that fails open
shorebird releases has exactly one subcommand, get-apks. I ran the exact command in the YAML:
$ shorebird releases info --release-version=… --json
exit 64: Could not find an option named "--release-version"
So the if always takes the else branch and prints No existing Shorebird release found; continuing. Two things hide it: the usage error goes to stdout, so it lands in release_info.json and release_info.err is empty; and neither file is ever printed.
Net effect: re-running a release against an existing active version proceeds silently — the exact failure the step exists to prevent. test_shorebird_release_commands_are_signed_and_preflighted asserts the string "already exists and is active" is in the YAML, which can never be reached at runtime, so it passes on unreachable code.
Separately from the missing subcommand, the structure is wrong: any nonzero exit (auth failure, network, rate limit, CLI drift) is treated as "no such release".
3. SDK pin drift — codemagic.yaml moves to 3.44.9 in all ten workflows (including macos-build, e2e-smoke-ios, e2e-smoke-android, which have nothing to do with Shorebird) while mobile/mise.toml:2 and mobile_ci.yaml:78,263,287,313 stay on 3.44.0. Every analyze/test/golden gate validates one SDK; every shipped artifact is built on another; no CI job covers the difference. Whatever version #1 settles on, bump all three in the same commit — and consider splitting the SDK bump out of the Shorebird change.
4. SHOREBIRD_CODE_PUSH.md — two runbook commands don't exist. shorebird releases list isn't a subcommand (only get-apks), and shorebird patches set-track isn't (shorebird patches has only promote). An operator mid-OTA follows step 2, gets a usage error, and has no documented way to find the release version. shorebird patches promote --release-version <v> --patch-number <n> is correct — verified against --help, both flags mandatory.
Non-blocking
codemagic.yaml:196-210—write_shorebird_signing_keyshard-requires both PEMs and writes the private key to disk inios-build/android-build, which only pass--public-key-path. Those jobs also holdGITHUB_TOKEN, the Play service account and the ASC key. Split into public-only for releases, private-only for patches.codemagic.yaml:157—curl … install.sh | bashoffshorebirdtech/install@main, unpinned and unchecksummed, in the same job that materializes the patch-signing private key. The CLI version is therefore unpinned too, so patch behaviour can change with no commit here.codemagic.yaml:226-260— nothing cross-checksRELEASE_COMMITagainstRELEASE_VERSION. A commit newer than the one that cut the release makes the blocklist scan a near-empty range and pass vacuously. (Too-old is safe — it over-scans.) Shorebird's own native-diff detection is still the backstop, but the guard reads stronger than it is.codemagic.yaml:411,502—shorebird patchisn't passed--no-confirm, which the help describes as intended for scripts. If it doesn't auto-skip on a non-TTY runner, the job blocks untilmax_build_duration: 60.codemagic.yaml:228— onlygit fetch --quiet origin mainbeforegit cat-file -e. A shallow clone makes a legitimate release commit report "not a commit in this checkout".codemagic.yaml:29-36— the setup docs don't state the expected encoding.gsub('\\n', "\n")un-escapes literal\n; a base64 or genuinely multiline secure variable yields an invalid PEM with no diagnostic.caption_generator/android/build.gradle.kts:52—add("debugCompileOnly", …)drops the Flutter embedding jar from the release compile classpath, so a standalone./gradlew :caption_generator:assembleReleaseno longer compiles. Nothing pins this; the only signal is a Codemagic release build, which PR CI doesn't run.test_codemagic_android_build_number.py:111—assertLess(android_patch, macos_build)pins the definition order of two workflows in the file. No behaviour breaks if that order changes; it can't fail for a reason anyone cares about. Drop it.- Same file — every assertion is a substring grep. Nothing asserts the file is valid YAML or that anchors resolve, which is the cheapest, highest-value check on a +505-line YAML change.
Verified
Ran python3 -m unittest discover -s .github/scripts/tests against the PR's YAML — 11 OK. Wiring those tests into CI as ci-config-tests is a real gain; they never ran before. Confirmed the Shorebird CLI surface against locally installed 1.6.67 (--flutter-version, --track, --allow-native-diffs, --no-confirm, patches promote all valid; releases info and patches set-track not). Confirmed Android build-number logic is unchanged in substance and cannot regress below the Play floor. Confirmed mobile/shorebird.yaml is in assets: and correctly in the patch blocklist. Confirmed no prior main build used shorebird release, so changing app_id orphans no existing patchable install.
Uncertain
Whether the latest (unpinned) CLI adds releases info or 3.44.7–3.44.9 support — my CLI evidence is 1.6.67, but the Flutter-version finding is independent of CLI version, it's a live tag query. Also unverified: whether SUPPORTERS_API_BASE_URL / FF_DIVINE_SUPPORTERS resolve identically in patch vs release workflows — they're in no group named in codemagic.yaml. If they're group-scoped rather than project-scoped, a patch ships different dart-defines than its release, which is the silent-repointing failure the doc warns about for DEFAULT_ENV. Worth confirming where those two are defined.
7608641 to
6756db4
Compare
|
@NotThatKindOfDrLiz — I rebased this branch onto current
Flagging this because three conflicts needed a judgment call about your intent, and if I misread one you're the person who'd spot it. The conflicts — all the same shapeEvery one was " 1. Worth knowing if you hadn't seen it: that paragraph landed after an undocumented group took the pipeline down for ~21 hours (#7203), and the guard it describes is exactly what requires 2. 3. Verified after rebasing
One thing I did NOT change
Not addressedThis was a rebase only — I haven't reviewed the Shorebird design, the patch-safety gates, or the Android Flutter-jar commits. Still a draft, still unreviewed. Sequencing note: the plan is to ship 1.0.20 to the App Store first, then switch to Shorebird. So this shouldn't land until that submission is in — 1.0.20 will be the last unpatchable build, and Shorebird starts covering 1.0.21 onward. |
|
@rabble please rereview when you have a minute. Keeping this PR in draft intentionally so it cannot be merged accidentally. Update on the Shorebird review items:
Local verification run:
GitHub checks are green on current head |
rabble
left a comment
There was a problem hiding this comment.
The Shorebird wiring itself reads well — preflight before release, staging track on patches, signing keys written to disk rather than passed as args, patches refusing to run when the release commit has no Dart delta, and macOS explicitly excluded with a reason. The two new ios-patch / android-patch workflows are cleanly additive. The Ruby preflight and its pytest coverage are a nice touch for a CI-only change.
Two things below are not defects in the change; they're facts about when it can land. Flagging them because neither is visible from the title.
This converts the store release path itself, so it must not merge before 1.0.20 ships
ios-build and android-build are not left alone. They now consume the Shorebird anchors:
ios-build: … - *write_shorebird_signing_keys
… - *preflight_shorebird_ios_release
… - *build_ios # &build_ios → shorebird release ios --flutter-version=…
android-build: … - *build_aab # &build_aab → shorebird release android --flutter-version=…
So the moment this is on main, the next App Store build is cut by shorebird release — built against Shorebird's engine fork rather than stock Flutter — instead of flutter build ipa / appbundle.
That is correct and unavoidable for Shorebird (a patch can only target a release shorebird release produced, as your own comment in the file says). It just directly inverts the plan of shipping 1.0.20 to the App Store before switching to Shorebird. If this merges first, 1.0.20 is the Shorebird cutover rather than the release preceding it.
I'm not asking for a change — the sequencing is Rabble's call. But it should be a decision rather than a side effect of merge order, so please hold this until 1.0.20 is submitted, or say explicitly that we're cutting over with it.
The repo-wide Flutter bump is doing a lot of work quietly
-flutter = "3.44.0"
+flutter = "3.44.9"mise.toml is the pin every local dev, both git hooks, and CI resolve through, so this moves the toolchain for the whole repo inside a PR titled ci(shorebird). I assume it's forced — Shorebird only supports specific Flutter revisions, and --flutter-version=$FLUTTER_VERSION has to match one. If so, worth one line in the description saying that, because "CI change" and "everyone's compiler changes" read very differently to whoever approves it, and a patch-level Flutter bump landing days before a store submission is its own risk.
Smaller notes
shorebird.yaml app_id changes (5a90f08c… → 73254661…). That's the org move, so it's the point of the PR — just noting for the record that patches cut against the new app can't target releases made under the old id. Harmless now because nothing shipped on the old one; would not be harmless later.
shorebird_release_preflight.rb fails open on a shape it doesn't recognise. If the release exists but platform_statuses isn't a Hash, status stays nil and the script prints "continuing". Same if the version simply isn't found. For a duplicate-release guard that's a defensible default, but the failure mode is "preflight passed" rather than "preflight couldn't tell", which is the less obvious of the two. Consider distinguishing "no such release" (fine, continue) from "release found, statuses unreadable" (abort).
flutterEmbeddingJar → flutterDebugEmbeddingJar in caption_generator/android/build.gradle.kts is a rename with no path change — it still points at bin/cache/artifacts/engine/android-arm64/flutter.jar. If the new name is asserting that artifact is the debug engine, is that right for compileOnly on a release build? Genuine question, not a claim.
Not verified
I reviewed this statically. I have not run a Codemagic build from this branch, so I can't confirm the anchors resolve, the preflight parses real shorebird releases list --json output, or that 3.44.9 builds the app clean. The pytest additions cover the preflight logic but not the YAML wiring.
|
@rabble Holding this PR (in draft so it doesn't accidentally get merged) until next release. Branch has been built on Codemagic for iOS and Android and both releases have been tested on real hardware. No issues found. Say the word and I'll flip the PR to review. |
46a8b25 to
a46851e
Compare
Mobile PR PreviewPreview refreshed for Last refresh:
|
rabble
left a comment
There was a problem hiding this comment.
Automated review pass over head 46a8b25. These findings have not been human-verified — check them before acting. I skipped everything already covered in the resolved threads.
What I actually ran: checked out the head in a scratch worktree and ran python3 -m unittest discover -s .github/scripts/tests → 26 passed, matching the description. All GitHub checks green, including the new CI Configuration Tests (11s). All 9 inline review threads resolved. I did not verify anything needing the Shorebird CLI or a Codemagic run — no shorebird releases list JSON shape, no --force installer behaviour, no real build.
Two things blocking merge that aren't code problems
1. @realmeylisdev's CHANGES_REQUESTED (Aug 17) is still on record — that's why mergeStateStatus is BLOCKED. Their blocker was the warm-cache install no-op. It reads as addressed by 46a8b25: shorebird_install now version-checks the cached binary, reinstalls with bash -s -- --force, and re-validates after, with test_shorebird_install_reuses_only_the_expected_cached_version pinning it. Their three smaller items (presence-vs-empty on dart-defines, the pin scan passing on a deleted pin, the runbook lines) also look fixed — the scan now flags an empty version set. @realmeylisdev, this needs a re-look rather than another fix.
2. This PR is what switches on the Shorebird phone-home for every store install. shorebird_code_push and auto_update: true both predate it, but the updater only runs in an engine built by shorebird release — so until now no store build ever contacted Shorebird. The description flags this and the doc's privacy section is genuinely good. It still means merging creates a new externally-visible data flow (install identifier, release/patch versions, platform, arch) for App Store and Play users before the DPA is executed. That's a decision to make explicitly, not a merge side effect.
Findings
RELEASE_COMMIT is unverified against RELEASE_VERSION, and fails open. The whole patch-safety story — print the diff since the release, block native/asset/dependency changes — rests on the operator typing the right commit. A too-old commit is safe (over-blocks). A too-new one (e.g. current main) makes BLOCKED_PATCH_PATHS empty and every gate in prepare_patch_source pass vacuously, while the patch still carries everything since the real release. Shorebird's own native/asset refusal is still the backstop, so this isn't catastrophic — but a gate advertised as a gate silently becomes a no-op. Cheap partial fix: cross-check the build name in git show $RELEASE_COMMIT:pubspec.yaml against the 1.0.20 half of RELEASE_VERSION. Better: have build_aab/build_ios push a tag at the release commit so the patch workflow resolves it instead of trusting input.
Patch dart-defines come from current env-group values, not the release's. The doc warns about DEFAULT_ENV ("the easiest thing to get wrong") but the same silent-drift risk applies to every other key in dart_defines.json — and those have no operator input at all, so nobody gets a chance to notice. FF_DIVINE_SUPPORTERS is read via bool.fromEnvironment in build_configuration.dart:52: flipping it in the Codemagic group means the next "Dart-only bug fix" patch also ships a feature flip to production. Same shape for PROOFMODE_SIGNING_SERVER_ENDPOINT/_TOKEN and SUPPORTERS_API_BASE_URL. Suggest widening that doc warning past DEFAULT_ENV, and ideally recording a SHA-256 of the release's dart_defines.json so the patch run can print a mismatch.
Zapstore APK and Play AAB are now built on different engines, undocumented. build_apk is stock Flutter 3.44.9; build_aab is Shorebird's 3.44.9 fork. This PR's own justification for the pin — the 3.44.0 Shorebird engine crashing in the cupertino_http async FFI path — is proof the delta is not theoretical. A Shorebird-engine-specific crash reproduces on Play and not on Zapstore, and vice versa. The doc table says the APKs aren't patchable but not that they're a different engine; that's the line a future triage will need.
curl … install.sh | bash from a mutable main now sits on the store-signing path. Before this PR, Shorebird wasn't installed in ios-build/android-build at all. The EXPECTED_SHOREBIRD_VERSION check validates identity, not integrity — a tampered installer reports whatever it likes. Pinning the URL to a commit SHA instead of main is cheap. Related: $HOME/.shorebird is now cached into both release workflows, so a poisoned cache would also pass the version check. Dropping the cache entirely costs a multi-GB engine re-download, so that's a real tradeoff rather than a free fix — but caching only ~/.shorebird/bin/cache/flutter while reinstalling the CLI binary each run gets most of the speed with less trust surface.
Nits
test_codemagic_android_build_number.pynow covers Flutter pin parity, Shorebird gating, YAML parsing and runbook prose. The filename is actively misleading; rename it.ci-config-testsgoes into theresultsarray rather than beside the explicit always-requiredcodemagic-groupscheck. It works — a failure is caught in both theapp-ci-requiredtrue and false branches — but it's inconsistent with the comment that exists specifically to explain why ungated always-required guards get their own check.- Pre-existing but sharper now:
build_apkuses$PROJECT_BUILD_NUMBERwhilebuild_aabuses the Play-floor number, so the GitHub-release APK and the Shorebird release version from one run can differ. One line in the doc table would prevent a triage mistake. caption_generator/android/build.gradle.kts:flutterRootthrows a clearGradleExceptionwhen unset, butflutterDebugEmbeddingJarhas no existence check — a missing jar degrades to an unresolved-reference compile error rather than a named failure.
What's good
The gating design holds up: triggering: {events: []} on both patch workflows, CONFIRM_PATCH defaulting to "NO" as script #1, --track=staging with assertNotIn("--track=stable") pinning it, and the public/private key split so release jobs never materialize the signing key (pinned by test_release_jobs_never_materialize_patch_private_key). The preflight fails closed on a bad envelope, a non-array data.releases, and a non-Hash platform_statuses — I read all seven of its tests and they assert the right things. Moving from a dozen --dart-define= args to a mode-0600 JSON file is also a quiet secret-hygiene win. Zero app-code changes in the diff.
rabble
left a comment
There was a problem hiding this comment.
Requesting changes. This supersedes my APPROVED from earlier today — two items below need an owner decision before this ships, and neither is a code defect.
Automated review pass over head 46a8b25. These findings have not been human-verified — check them before acting. I skipped everything already covered in the resolved threads.
What I actually ran: checked out the head in a scratch worktree and ran python3 -m unittest discover -s .github/scripts/tests → 26 passed, matching the description. All GitHub checks green, including the new CI Configuration Tests (11s). All 9 inline review threads resolved. I did not verify anything needing the Shorebird CLI or a Codemagic run — no shorebird releases list JSON shape, no --force installer behaviour, no real build.
Two things blocking merge that aren't code problems
1. @realmeylisdev's CHANGES_REQUESTED (Aug 17) is still on record — that's why mergeStateStatus is BLOCKED. Their blocker was the warm-cache install no-op. It reads as addressed by 46a8b25: shorebird_install now version-checks the cached binary, reinstalls with bash -s -- --force, and re-validates after, with test_shorebird_install_reuses_only_the_expected_cached_version pinning it. Their three smaller items (presence-vs-empty on dart-defines, the pin scan passing on a deleted pin, the runbook lines) also look fixed — the scan now flags an empty version set. @realmeylisdev, this needs a re-look rather than another fix.
2. This PR is what switches on the Shorebird phone-home for every store install. shorebird_code_push and auto_update: true both predate it, but the updater only runs in an engine built by shorebird release — so until now no store build ever contacted Shorebird. The description flags this and the doc's privacy section is genuinely good. It still means merging creates a new externally-visible data flow (install identifier, release/patch versions, platform, arch) for App Store and Play users before the DPA is executed. That's a decision to make explicitly, not a merge side effect.
Findings
RELEASE_COMMIT is unverified against RELEASE_VERSION, and fails open. The whole patch-safety story — print the diff since the release, block native/asset/dependency changes — rests on the operator typing the right commit. A too-old commit is safe (over-blocks). A too-new one (e.g. current main) makes BLOCKED_PATCH_PATHS empty and every gate in prepare_patch_source pass vacuously, while the patch still carries everything since the real release. Shorebird's own native/asset refusal is still the backstop, so this isn't catastrophic — but a gate advertised as a gate silently becomes a no-op. Cheap partial fix: cross-check the build name in git show $RELEASE_COMMIT:pubspec.yaml against the 1.0.20 half of RELEASE_VERSION. Better: have build_aab/build_ios push a tag at the release commit so the patch workflow resolves it instead of trusting input.
Patch dart-defines come from current env-group values, not the release's. The doc warns about DEFAULT_ENV ("the easiest thing to get wrong") but the same silent-drift risk applies to every other key in dart_defines.json — and those have no operator input at all, so nobody gets a chance to notice. FF_DIVINE_SUPPORTERS is read via bool.fromEnvironment in build_configuration.dart:52: flipping it in the Codemagic group means the next "Dart-only bug fix" patch also ships a feature flip to production. Same shape for PROOFMODE_SIGNING_SERVER_ENDPOINT/_TOKEN and SUPPORTERS_API_BASE_URL. Suggest widening that doc warning past DEFAULT_ENV, and ideally recording a SHA-256 of the release's dart_defines.json so the patch run can print a mismatch.
Zapstore APK and Play AAB are now built on different engines, undocumented. build_apk is stock Flutter 3.44.9; build_aab is Shorebird's 3.44.9 fork. This PR's own justification for the pin — the 3.44.0 Shorebird engine crashing in the cupertino_http async FFI path — is proof the delta is not theoretical. A Shorebird-engine-specific crash reproduces on Play and not on Zapstore, and vice versa. The doc table says the APKs aren't patchable but not that they're a different engine; that's the line a future triage will need.
curl … install.sh | bash from a mutable main now sits on the store-signing path. Before this PR, Shorebird wasn't installed in ios-build/android-build at all. The EXPECTED_SHOREBIRD_VERSION check validates identity, not integrity — a tampered installer reports whatever it likes. Pinning the URL to a commit SHA instead of main is cheap. Related: $HOME/.shorebird is now cached into both release workflows, so a poisoned cache would also pass the version check. Dropping the cache entirely costs a multi-GB engine re-download, so that's a real tradeoff rather than a free fix — but caching only ~/.shorebird/bin/cache/flutter while reinstalling the CLI binary each run gets most of the speed with less trust surface.
Nits
test_codemagic_android_build_number.pynow covers Flutter pin parity, Shorebird gating, YAML parsing and runbook prose. The filename is actively misleading; rename it.ci-config-testsgoes into theresultsarray rather than beside the explicit always-requiredcodemagic-groupscheck. It works — a failure is caught in both theapp-ci-requiredtrue and false branches — but it's inconsistent with the comment that exists specifically to explain why ungated always-required guards get their own check.- Pre-existing but sharper now:
build_apkuses$PROJECT_BUILD_NUMBERwhilebuild_aabuses the Play-floor number, so the GitHub-release APK and the Shorebird release version from one run can differ. One line in the doc table would prevent a triage mistake. caption_generator/android/build.gradle.kts:flutterRootthrows a clearGradleExceptionwhen unset, butflutterDebugEmbeddingJarhas no existence check — a missing jar degrades to an unresolved-reference compile error rather than a named failure.
What's good
The gating design holds up: triggering: {events: []} on both patch workflows, CONFIRM_PATCH defaulting to "NO" as script #1, --track=staging with assertNotIn("--track=stable") pinning it, and the public/private key split so release jobs never materialize the signing key (pinned by test_release_jobs_never_materialize_patch_private_key). The preflight fails closed on a bad envelope, a non-array data.releases, and a non-Hash platform_statuses — I read all seven of its tests and they assert the right things. Moving from a dozen --dart-define= args to a mode-0600 JSON file is also a quiet secret-hygiene win. Zero app-code changes in the diff.
The block said the Skia/Impeller profiling was taken on 3.44.0, "the same pin this branch builds on". #7202 moved mise.toml, codemagic and every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so that clause was false and the surrounding "not a re-evaluation after an engine upgrade" argument read stronger than the facts support. States the pin move instead, and that the profiling predates it. Both cited upstream issues are still closed, so the rest of the paragraph stands. Comment-only: the comment-stripped element tree is unchanged.
The block said the Skia/Impeller profiling was taken on 3.44.0, "the same pin this branch builds on". #7202 moved mise.toml, codemagic and every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so that clause was false and the surrounding "not a re-evaluation after an engine upgrade" argument read stronger than the facts support. States the pin move instead, and that the profiling predates it. Both cited upstream issues are still closed, so the rest of the paragraph stands. Comment-only: the comment-stripped element tree is unchanged.
The block said the Skia/Impeller profiling was taken on 3.44.0, "the same pin this branch builds on". #7202 moved mise.toml, codemagic and every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so that clause was false and the surrounding "not a re-evaluation after an engine upgrade" argument read stronger than the facts support. States the pin move instead, and that the profiling predates it. Both cited upstream issues are still closed, so the rest of the paragraph stands. Comment-only: the comment-stripped element tree is unchanged.
…ivinevideo#7794) * docs(android): record the measured field cost of the Impeller switch The manifest justifies re-entering Impeller as a traded risk but never says what the trade actually cost, so the next reader has no way to judge it except by re-running the analysis. Field measurement across the 1.0.17 -> 1.0.19 boundary puts it at +2.9 points of slow frames (95% CI [+0.5, +5.3]), with iOS flat under the same estimator. Also records why reverting is not the answer: the engine already logs the opt-out as deprecated and slated for removal, so the feed has to be fast on Impeller regardless of what the chroma-key preview is worth. * docs(android): correct the stale Flutter pin in the Impeller comment The block said the Skia/Impeller profiling was taken on 3.44.0, "the same pin this branch builds on". divinevideo#7202 moved mise.toml, codemagic and every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so that clause was false and the surrounding "not a re-evaluation after an engine upgrade" argument read stronger than the facts support. States the pin move instead, and that the profiling predates it. Both cited upstream issues are still closed, so the rest of the paragraph stands. Comment-only: the comment-stripped element tree is unchanged. --------- Co-authored-by: Meylis <meylis@divine.video>
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.
3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.
Also folded in, because they were drifting or would have gone stale:
- The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
previous bump, now match the app.
- README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
the real pin had moved to 3.44.9 back in #7202.
- The AndroidManifest comment explaining why Impeller was re-enabled dates
its profiling against the pin it predates.
- freezed and freezed_annotation are gone from pubspec.yaml — the state
models no longer use them.
The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.
Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
… breaks (divinevideo#8915) * refactor(state): replace the freezed state models with plain Equatable classes Dart 3.13 stopped accepting `final` on a constructor parameter, and that is exactly what freezed 3 emits. The four generated files under lib/state/ no longer compile: lib/state/video_feed_state.freezed.dart:234:35: Error: Can't have modifier 'final' here. `flutter analyze` never saw it — `**/*.freezed.dart` is excluded in analysis_options.yaml — so it only surfaced once dart format could not parse the files. freezed 4.0.0 fixes the generator, but it requires analyzer >= 13 and there is no hive_ce_generator build for analyzer 13 (1.11.2 caps at ^12, 1.11.3 jumps to ^14). analyzer 14 is out of reach because flutter_test pins test_api 0.7.12, which forces test 1.31.1, which requires analyzer < 14. Dropping package:test is not an option either: 28 workspace packages use it, several of them pure Dart, where flutter_test would add a Flutter runtime dependency the package boundary guard rejects. Since these are the only four freezed classes in the repo, and .claude/rules/state_management.md prescribes Equatable for state anyway, they are hand-written instead. That takes freezed out of the dependency graph entirely rather than working around the version conflict. One behavioural detail worth the extra file: freezed's copyWith clears a nullable field when the caller passes an explicit null, which three call sites rely on to reset `error` on refresh. A plain `error ?? this.error` would have turned those into silent no-ops, so copy_with_sentinel.dart restores the distinction between "argument omitted" and "argument set to null". * fix(analysis): resolve the findings Dart 3.13 newly reports The 3.13 analyzer adds two lints that the app trips 24 times, and CI runs `flutter analyze` as a blocking job, so the upgrade cannot land without them. `unawaited_return_in_try_block` (12 sites) is the one that mattered: a `return someFuture();` inside a try does not route the future's failure through the catch, so every one of these had a catch block that logs and returns a fallback which could never fire. Adding the missing `await` makes the handler cover what it reads as covering. `unnecessary_unawaited` (11 sites) is the opposite direction — Flutter 3.47 annotated Navigator.push and Scrollable.ensureVisible with `@awaitNotRequired`, so the `unawaited(...)` wrappers are now redundant. Two `dart:async` imports became unused along with them. Plus one unused catch-stack variable and one conditional expression the analyzer wants as an if-element. The typedef move is a consequence of the same upgrade rather than a cleanup: the 3.13 formatter wraps function-type typedefs differently, which grew auth_service.dart and curated_list_service.dart by two lines each. Both are frozen by check_service_god_file_ceiling.sh, which has no sanctioned raise path, so the callback typedefs move to lib/models/ — where auth_service.dart already re-exports auth_result, auth_state and their siblings from — and both services are re-exported from there. No call site changes, and both files now sit below their ceiling (4516/4536 and 1841/1847). * chore(deps): pin the toolchain to Flutter 3.47.2 Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71 workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went stable, so the repo was sitting on a frozen toolchain. 3.47.2 is the second patch on the line, twelve days old, and Shorebird publishes a matching engine for it — the store builds pass FLUTTER_VERSION straight to `shorebird release`, so a version Shorebird does not carry would break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is updated to the 3.47.2 engine. Also folded in, because they were drifting or would have gone stale: - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the previous bump, now match the app. - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while the real pin had moved to 3.44.9 back in divinevideo#7202. - The AndroidManifest comment explaining why Impeller was re-enabled dates its profiling against the pin it predates. - freezed and freezed_annotation are gone from pubspec.yaml — the state models no longer use them. The regenerated l10n files and the analyzer excludes the 3.47 tool writes into each analysis_options.yaml are committed so `flutter pub get` leaves a clean tree, which the Generated Files check requires. Android needs no native changes: compileSdk, targetSdk and the NDK version are identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin 2.2.20 all sit inside the supported window. * style: reformat for the Dart 3.13 formatter Mechanical, no behaviour change. `dart format` in Dart 3.13 changes two things, and CI's Format job fails on every file that still has the old shape: - A function-type typedef keeps its name and return type on one line and wraps the parameter list, instead of breaking after the `=`. - Import groups are separated by a blank line, so a `dart:` block followed directly by a `package:` block gains one. Produced by `dart format lib test integration_test tools packages` on 3.47.2 — nothing here was edited by hand. * test(goldens): regenerate the two button references for the 3.47.2 engine `divine_button_types` and `divine_button_sizes` failed by 103px and 73px — 0.11% and 0.10%. The isolated diffs contain nothing but faint arcs along the rounded ends of the buttons, and the master and test images are indistinguishable side by side: the 3.47.2 engine rasterizes those curves slightly differently. Nothing in this branch touches how the buttons draw. The only divine_ui changes are the analyzer excludes the 3.47 tool writes into analysis_options.yaml and the `flutter:` constraint in its pubspec. Regenerated on the Ubuntu runner via the update_goldens workflow dispatch, not locally — macOS Skia antialiases differently, so a local reference would fail the very check it is meant to satisfy. `divine_snackbars` came back byte-identical and is left alone. * fix(state): preserve Freezed model contracts * fix(release): align Shorebird with Flutter 3.47 * chore(deps): finish Flutter upgrade cleanup * style: format rebased chroma key cubit * style: format rebased workflow contract test * style: format rebased support center test * chore(analyzer): relock async safety baseline * test(nostr_sdk): stabilize reconnect schedule coverage * fix(state): stop unrelated state changes from rebuilding collection watchers The handwritten replacements for the four Freezed state models hand out an `UnmodifiableListView`/`SetView`/`MapView` on every read of a collection field. Those views inherit identity `==` from `dart:collection`, so two reads of a field that never changed do not compare equal, and a watcher that selects one of them — `curationProvider.select((state) => state.editorsPicks)` is the one in the tree today — is told the picks changed every time anything else in that state changes, a loading flag included. Freezed did not behave that way. It shipped `EqualUnmodifiableListView` and its two siblings, whose `==` compares the wrapped source, precisely so a selector could tell "replaced" from "same list, new wrapper". This restores those three wrappers next to the state models that need them, so the comparison a selector makes means what it did before the migration. `copyWith` now also passes the raw field instead of the wrapped getter. It was feeding each view back into the next constructor, so a state accumulated one more layer of delegating view per `copyWith` for as long as it lived — on the feed's video list, for the length of a session. `props` reads the raw fields for the same reason; Equatable compares collections deeply either way, so state equality is unchanged. * test(state): make the collection contract tests able to fail Four of the tests added with the Freezed migration built their fixtures from `const` collection literals. A const collection is already unmodifiable, so `expect(state.editorsPicks.clear, throwsUnsupportedError)` passed whether or not the getter wrapped anything, and the pair of `const SeenVideosState` values that was meant to prove "distinct instances compare deeply" was canonicalised by the compiler into one instance. They could not fail for the reason they claimed to check. The fixtures now come from mutable collections, and four new tests cover the equality half of the contract: two reads of the same field compare equal, a `copyWith` that does not touch a collection keeps it equal, five of them in a row still do, and replacing the collection makes it unequal. * chore(baseline): relock the service size ceilings for the rebased tree Rebasing onto main moved three of the five tracked services, and the branch's Dart 3.13 reformatting removes a few more lines from each. Every entry is lower than the one on main, so this only locks in the reduction. * style: format rebased audio and thumbnail fixes --------- Co-authored-by: Liz Sweigart <127434495+NotThatKindOfDrLiz@users.noreply.github.com>
Description
Moves Shorebird code push to an app owned by the Divine organization and wires it into Codemagic. Store-bound Android and iOS artifacts are now built by
shorebird release, so future Play and App Store releases can receive signed Dart-only patches.The previous Shorebird app belonged to a personal-account experiment that was never used by CI and had no releases. Nothing already shipped was patchable, so there is no release migration.
What changes
shorebird release androidshorebird release iosflutter build apk --split-per-abiflutter build macosios-patchandandroid-patchworkflows.CONFIRM_PATCH=YES,main, an exact release version, and the full release commit SHA.staging; promotion tostableremains a separate manual action.0600JSON file so the AAB, APK, IPA, and patches cannot silently diverge.1.6.117version.Flutter 3.44.9
This PR deliberately moves every repository and CI workflow pin from Flutter 3.44.0 to 3.44.9. The move is forced by the Shorebird engine: the 3.44.0 build crashed at launch in the Dart async FFI path used by
cupertino_http, while Shorebird 1.6.117 ships the validated 3.44.9 engine. A repo-wide parity test now prevents local, package-CI, GitHub, Codemagic, and Shorebird release pins from drifting apart.Release and patch safety
shorebird releasesucceeds and a later upload step fails, delete that failed Shorebird release or bump the store build number before retrying.\nseparators; CI validates the decoded envelope.auto_updateremains enabled, so installed store builds contact Shorebird at launch with app/release/patch state. The DPA is in flight; store data-sharing answers must not be finalized until it is executed and privacy review confirms coverage for all intended users, including minor users.Merge sequencing
Divine 1.0.20 has been submitted on the previous release path. The sequencing gate is satisfied: the first store build after this merges is intentionally the Shorebird cutover.
Codemagic prerequisite
The
shorebird_credentialsgroup must contain:SHOREBIRD_TOKEN(secure)SHOREBIRD_PATCH_PUBLIC_KEYSHOREBIRD_PATCH_PRIVATE_KEY(secure)Setup and PEM encoding are documented as step 10 in
codemagic.yamland inmobile/docs/SHOREBIRD_CODE_PUSH.md.Verification
python3 -m unittest discover -s .github/scripts/tests— 26 passed after the final hardening update.mobile/scripts/check_codemagic_groups.sh— all five referenced groups documented.codemagic.yamlparses with YAML aliases enabled.3.44.0workflow/release pin remains.Relates to #7200.
Out of scope
SHOREBIRD_TOKENfrom an individual account to a Divine service identity remains tracked in ci(shorebird): move CI credentials to a dedicated service identity #7200.gradlewbeing gitignored is tracked in chore(tooling): fresh worktrees cannot run gradle-based tooling #7201.ios/Podfile.lockremains untracked and is called out byshorebird doctor.