Problem
After the monorepo split, the Dart binding (warden_ffi) is published to pub.dev, but the native libraries it binds to are not distributed anywhere. ffi/build-mobile.sh builds them, and the outputs are git-ignored (correctly — we don't commit binaries). So today every machine that wants to build a consuming Flutter app for a device must check out warden and cross-compile the binary itself, which requires a full Rust + iOS/Android cross toolchain (Xcode targets, NDK, cargo-ndk).
The binary is platform-specific but not machine-specific — an xcframework/.so built once works everywhere. So there's no reason every consumer should rebuild it. We should build once, publish, and let consumers pull it.
Proposal
Three parts, phased:
1. Publish prebuilt mobile binaries as GitHub Release assets ← Phase 1 (this issue's first PR)
A tag-triggered workflow (v*) cross-compiles via build-mobile.sh on the right runners and attaches to the GitHub Release:
WardenFfi.xcframework.zip (iOS, built on macos-latest)
warden-ffi-android-jniLibs.zip (Android 4-ABI, built on ubuntu-latest)
SHA256SUMS
Tag-triggered (not per-merge) so the binary version stays in lockstep with the warden_ffi pub.dev version — both are cut from the same tag. A redeploy of any consuming app is irrelevant here (the gate crypto bakes in no on-chain addresses). Also add a path-filtered build-check CI (PR + push to main, scoped to core/**, ffi/**) that cross-compiles both platforms and runs the FFI host test, so a break in the mobile build surfaces early without cutting a release.
2. Publish a warden_ffi_flutter wrapper plugin to pub.dev ← Phase 2 (follow-up PR)
A thin Flutter plugin that depends on the pure warden_ffi binding and adds the ios/ podspec + android/ gradle that download the Phase-1 release binary at build time. Consuming apps then add one dependency — flutter pub get + flutter build pulls everything, no Rust toolchain on any dev/CI machine. The pure warden_ffi stays pure (desktop/CLI/host tests keep supplying a dylib path).
Open decision for that PR: iOS currently ships a static lib force-loaded via OTHER_LDFLAGS (symbols resolved at runtime through DynamicLibrary.process()). A pod is friendlier with a dynamic framework (cdylib packaged as .framework, loaded via DynamicLibrary.open), which removes the -force_load wiring from the consumer. This needs real-device verification, which is why it's a separate PR.
3. Docs
Update ffi/README.md, ffi/dart/README.md, and the top README.md so "how to consume on mobile" reflects: download the release binary (Phase 1) and eventually just add warden_ffi_flutter (Phase 2). Build-from-source instructions stay for warden developers.
Why GitHub Releases (not GitHub Packages)
GitHub Packages has no pub or CocoaPods/SwiftPM registry (only its Maven registry would fit Android), and its Maven registry requires a token even for public reads. A GitHub Release asset downloads anonymously on a public repo and is consumable from both a podspec and gradle — one store, all platforms, no auth.
Tasks
Non-goals
- Auto-tagging / releasing on every merge (decouples binary from the published binding version).
- Committing binaries into any repo.
- Touching the
wasm/ (JS SDK) track — that's a separate consumer.
Status: Phase 0 PoC — these binaries are publishable, not audited. The "preview" framing is unchanged.
Problem
After the monorepo split, the Dart binding (
warden_ffi) is published to pub.dev, but the native libraries it binds to are not distributed anywhere.ffi/build-mobile.shbuilds them, and the outputs are git-ignored (correctly — we don't commit binaries). So today every machine that wants to build a consuming Flutter app for a device must check out warden and cross-compile the binary itself, which requires a full Rust + iOS/Android cross toolchain (Xcode targets, NDK,cargo-ndk).The binary is platform-specific but not machine-specific — an xcframework/
.sobuilt once works everywhere. So there's no reason every consumer should rebuild it. We should build once, publish, and let consumers pull it.Proposal
Three parts, phased:
1. Publish prebuilt mobile binaries as GitHub Release assets ← Phase 1 (this issue's first PR)
A tag-triggered workflow (
v*) cross-compiles viabuild-mobile.shon the right runners and attaches to the GitHub Release:WardenFfi.xcframework.zip(iOS, built onmacos-latest)warden-ffi-android-jniLibs.zip(Android 4-ABI, built onubuntu-latest)SHA256SUMSTag-triggered (not per-merge) so the binary version stays in lockstep with the
warden_ffipub.dev version — both are cut from the same tag. A redeploy of any consuming app is irrelevant here (the gate crypto bakes in no on-chain addresses). Also add a path-filtered build-check CI (PR + push tomain, scoped tocore/**,ffi/**) that cross-compiles both platforms and runs the FFI host test, so a break in the mobile build surfaces early without cutting a release.2. Publish a
warden_ffi_flutterwrapper plugin to pub.dev ← Phase 2 (follow-up PR)A thin Flutter plugin that depends on the pure
warden_ffibinding and adds theios/podspec +android/gradle that download the Phase-1 release binary at build time. Consuming apps then add one dependency —flutter pub get+flutter buildpulls everything, no Rust toolchain on any dev/CI machine. The purewarden_ffistays pure (desktop/CLI/host tests keep supplying a dylib path).Open decision for that PR: iOS currently ships a static lib force-loaded via
OTHER_LDFLAGS(symbols resolved at runtime throughDynamicLibrary.process()). A pod is friendlier with a dynamic framework (cdylibpackaged as.framework, loaded viaDynamicLibrary.open), which removes the-force_loadwiring from the consumer. This needs real-device verification, which is why it's a separate PR.3. Docs
Update
ffi/README.md,ffi/dart/README.md, and the topREADME.mdso "how to consume on mobile" reflects: download the release binary (Phase 1) and eventually just addwarden_ffi_flutter(Phase 2). Build-from-source instructions stay for warden developers.Why GitHub Releases (not GitHub Packages)
GitHub Packages has no pub or CocoaPods/SwiftPM registry (only its Maven registry would fit Android), and its Maven registry requires a token even for public reads. A GitHub Release asset downloads anonymously on a public repo and is consumable from both a podspec and gradle — one store, all platforms, no auth.
Tasks
mobile-release.yml(tagv*→ build iOS + Android → Release assets + checksums)mobile-ci.yml(PR/main build-check, path-filtered, + FFI host test)warden_ffi_flutterplugin (podspec + gradle download the pinned release) → pub.devwarden_ffi_flutteras the turn-key Flutter path; update consumer (maktub-app)CLAUDE.mdNon-goals
wasm/(JS SDK) track — that's a separate consumer.