# Releasing spt-mobile

<!-- [doc->REQ-RELEASE-PACKAGING] -->
Dev-facing runbook for cutting a release. A release is one GitHub release (tag `vX.Y.Z`)
carrying TWO assets:

| Asset | What | Consumer |
|---|---|---|
| `spt-mobile-vX.Y.Z.apk` | signed, R8-minified Android app | Obtainium / manual sideload |
| `adapter.spt` | host-adapter archive (manifest.toml + `spt-mobile-host` at the ROOT) | `spt adapter add --release SaberMage/spt-mobile` + `spt adapter update mobile` |

The asset name `adapter.spt` is a CONTRACT: it must match `[update].asset` in
`host/manifest.toml` — rename one, rename both.

## One-time setup (per release machine)

1. **APK signing keystore.** Generate once, back it up somewhere safe, and NEVER commit
   it. Every future release MUST use the same key — Android refuses to update an app
   whose signature changed, and Obtainium users would be stranded.

   ```sh
   keytool -genkeypair -v -keystore ~/keystores/spt-mobile-release.jks \
     -alias spt-mobile -keyalg RSA -keysize 4096 -validity 10000
   ```

2. **`app/keystore.properties`** (gitignored) pointing at it:

   ```properties
   storeFile=/absolute/path/to/spt-mobile-release.jks
   storePassword=…
   keyAlias=spt-mobile
   keyPassword=…
   ```

   Without this file the release build silently falls back to the DEBUG key — fine for
   an emulator smoke, never for a published release.

3. Android NDK + `cargo-ndk` for the device-link `.so` (see `ci/build-android.sh`).

## Cutting vX.Y.Z

1. **Bump versions — all four, keep them in agreement with the tag:**
   - `app/build.gradle.kts`: `versionCode` (+1 every release) and `versionName`
   - `host/Cargo.toml`: `version`
   - `host/manifest.toml`: `[adapter].version` — this is what
     `spt adapter update` compares against the latest release tag
2. **Build the app:**
   ```sh
   ci/build-android.sh                # device-link .so, arm64 + x86_64
   cd app && ./gradlew assembleRelease
   ```
3. **Verify the signature is the RELEASE cert** (not the debug fallback):
   ```sh
   apksigner verify --print-certs app/build/outputs/apk/release/spt-mobile-app-release.apk
   # expect: CN=spt-mobile, O=SaberMage
   ```
4. **Package the adapter:**
   ```sh
   ci/package-adapter.sh              # -> dist/adapter.spt (current platform)
   ```
   The binary sits at the archive ROOT beside manifest.toml — spt resolves a
   [session] command's first token as `<install_dir>/<program>` (+`.exe`), no
   subdirectories (a `bin/<triple>/` layout fails to spawn; learned in v1.0.0).
   One archive = one platform; additional platforms ship as separate per-OS
   assets (`adapter-<os>-<arch>.spt` + `spt adapter add --asset`) when needed.
5. **Gate:** full test suite + `traceable-reqs check` green, emulator smoke if app code
   changed.
6. **Release:**
   ```sh
   cp app/build/outputs/apk/release/spt-mobile-app-release.apk dist/spt-mobile-vX.Y.Z.apk
   gh release create vX.Y.Z dist/spt-mobile-vX.Y.Z.apk dist/adapter.spt \
     --title "spt-mobile vX.Y.Z" --notes "…user-facing notes…"
   ```
7. **Post-release verify (both distribution channels):**
   ```sh
   spt adapter update mobile     # expect: pulls X.Y.Z (or UPTODATE if already on it)
   ```
   and confirm Obtainium (or the releases page) shows the new APK.

## Notes

- **Install is the first update:** `spt adapter add --release SaberMage/spt-mobile`
  registers the adapter and the payload rides the update engine (an immediate
  `spt adapter update mobile` completes the pull). Verified against spt-core 0.27.0.
- Host state (device-link identity, pairings, spools, history) lives in `state/` beside
  the installed binary (`{adapter_dir}/state`) and SURVIVES `spt adapter update` —
  verified byte-for-byte on the 1.0.1→1.0.2 update (spt-core 0.28.0): phones stay
  paired across updates. Stop the endpoint before updating — Windows locks the
  running exe.
- The APK ships `arm64-v8a` (devices) + `x86_64` (emulator); both `.so` flavors come
  from `ci/build-android.sh`.
