From 09d399a0ec91872a96e8c16e1f77105dfa107cea Mon Sep 17 00:00:00 2001 From: Xitee <59659167+Xitee1@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:01:38 +0200 Subject: [PATCH 1/2] chore(fdroid): prepare repo for F-Droid submission F-Droid builds from source and needs three things this repo could not provide: a statically-parseable version, an unsigned release APK, and a changelog for every released versionCode. - app/version.properties: literal versionName/versionCode for F-Droid's checkupdates (UpdateCheckData). The Gradle build still derives the real values from the git tag via axion-release; this file is not read by it. - app/build.gradle.kts: -PdisableSigning now yields an unsigned release APK (app-release-unsigned.apk) for F-Droid to sign; the debug-signing fallback for keyless local/CI builds is preserved. - fastlane changelogs: add 101000 (v1.1.0) and 101010 (v1.1.1) in en-US and de-DE; move the misfiled Shizuku-startup-race note out of 100010 (v1.0.1) into 101000, where PR #39 actually landed it. - release.yml: fail the tag build if version.properties disagrees with the tag or a changelog is missing/over 500 chars. - AGENTS.md: document the per-release checklist (bump version.properties + write both changelogs before tagging). - README: correct compileSdk platform 35 -> 36. - docs/plans: fdroiddata recipe and submission steps for reference. Verified: `assembleRelease -PdisableSigning` -> unsigned APK; `assembleRelease` -> debug-signed APK; `./gradlew lint` clean. Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 13 ++ AGENTS.md | 7 + README.md | 2 +- app/build.gradle.kts | 14 +- app/version.properties | 5 + docs/plans/2026-07-06-fdroid-submission.md | 127 ++++++++++++++++++ .../android/de-DE/changelogs/100010.txt | 1 - .../android/de-DE/changelogs/101000.txt | 5 + .../android/de-DE/changelogs/101010.txt | 1 + .../android/en-US/changelogs/100010.txt | 1 - .../android/en-US/changelogs/101000.txt | 5 + .../android/en-US/changelogs/101010.txt | 1 + 12 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 app/version.properties create mode 100644 docs/plans/2026-07-06-fdroid-submission.md create mode 100644 fastlane/metadata/android/de-DE/changelogs/101000.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/101010.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/101000.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/101010.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e2cc03..8c78ff2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,19 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Verify release metadata matches tag + run: | + VER="${GITHUB_REF_NAME#v}" + IFS=. read -r MA MI PA <<< "${VER%%-*}" # strip any -suffix, matching versionCodeFrom + CODE=$((MA * 100000 + MI * 1000 + PA * 10)) + grep -qx "versionName=$VER" app/version.properties + grep -qx "versionCode=$CODE" app/version.properties + for LOCALE in en-US de-DE; do + FILE="fastlane/metadata/android/$LOCALE/changelogs/$CODE.txt" + test -f "$FILE" || { echo "Missing F-Droid changelog: $FILE"; exit 1; } + test "$(wc -c < "$FILE")" -le 500 || { echo "Changelog over 500 chars: $FILE"; exit 1; } + done + - name: Decode release keystore if: ${{ env.SIGNING_KEYSTORE_BASE64 != '' }} env: diff --git a/AGENTS.md b/AGENTS.md index 74de201..3d30450 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,13 @@ Toolchain: JDK 17, Android SDK platform 36 (`compileSdk`/`targetSdk`), `minSdk = Versioning is dynamic via the [axion-release](https://github.com/allegro/axion-release-plugin) plugin (applied at the root). `versionName` is derived from the latest `v*` git tag (`project.version`); `versionCode` is computed from that same tag version with the schema `major*100_000 + minor*1_000 + patch*10` (tag `v1.0.1` → `100010`), keeping it monotonic with releases published before dynamic versioning. Tags must be plain SemVer after the `v` prefix — axion-release fails the build on anything else (e.g. `v1.0.1.1`), so a hotfix is just the next patch tag. Don't hand-edit either field in `app/build.gradle.kts`. To cut a release, push a new `v` tag — `.github/workflows/release.yml` builds a signed APK and publishes the GitHub Release. CI checkouts must use `fetch-depth: 0` so tags and full history are visible. +**Release checklist (do this in the commit you will tag, before pushing the tag):** + +1. Bump `app/version.properties` to the new `versionName`/`versionCode`. This file is **not** read by Gradle — axion-release still derives the real build values from the tag — it exists only so F-Droid's `checkupdates` can read the version statically (see `UpdateCheckData` in the fdroiddata recipe). It must match the tag or the release build fails. +2. Hand-write the changelog for the new `versionCode` in **both** locales: `fastlane/metadata/android/en-US/changelogs/.txt` and `.../de-DE/changelogs/.txt`, each ≤500 chars, user-facing tone. F-Droid displays only the current version's file and reads it from the tagged tree. + +The `release.yml` workflow enforces both (a `Verify release metadata matches tag` step) and fails the tag build if `version.properties` disagrees with the tag or a changelog is missing/oversized. If it fails, fix on `main`, then delete and re-push the tag. F-Droid builds the app itself from source (unsigned via `-PdisableSigning`); the GitHub release APK is unaffected. + ## Module architecture Four Gradle modules with a strict one-way dependency flow: diff --git a/README.md b/README.md index 72b932e..3ad126e 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Signed APKs are published on the [Releases page](https://github.com/Xitee1/sleep Requirements: - JDK 17 -- Android SDK with platform 35 (`compileSdk`) +- Android SDK with platform 36 (`compileSdk`) - `minSdk` is 26 (Android 8.0) ```sh diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 31abde5..c26a0a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,12 +49,14 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", ) - // Use release signing when SIGNING_KEYSTORE_PATH env var is provided (CI with secrets); - // otherwise fall back to debug signing so local/CI builds without secrets still succeed. - signingConfig = if (!System.getenv("SIGNING_KEYSTORE_PATH").isNullOrBlank()) { - signingConfigs.getByName("release") - } else { - signingConfigs.getByName("debug") + // F-Droid builds pass -PdisableSigning to emit an unsigned release APK it signs itself. + // Otherwise use release signing when SIGNING_KEYSTORE_PATH is provided (CI with secrets), + // and fall back to debug signing so local/CI builds without secrets still succeed. + signingConfig = when { + project.hasProperty("disableSigning") -> null + !System.getenv("SIGNING_KEYSTORE_PATH").isNullOrBlank() -> + signingConfigs.getByName("release") + else -> signingConfigs.getByName("debug") } } } diff --git a/app/version.properties b/app/version.properties new file mode 100644 index 0000000..2a270ea --- /dev/null +++ b/app/version.properties @@ -0,0 +1,5 @@ +# Read by F-Droid checkupdates (UpdateCheckData in fdroiddata's metadata/dev.xitee.sleeptimer.yml). +# NOT used by the Gradle build — axion-release derives the real values from the git tag. +# Bump in the release commit so these values match the tag about to be created. +versionName=1.1.1 +versionCode=101010 diff --git a/docs/plans/2026-07-06-fdroid-submission.md b/docs/plans/2026-07-06-fdroid-submission.md new file mode 100644 index 0000000..90b9f63 --- /dev/null +++ b/docs/plans/2026-07-06-fdroid-submission.md @@ -0,0 +1,127 @@ +# F-Droid submission + +Reference notes for publishing SleepTimer (`dev.xitee.sleeptimer`) on F-Droid. +Verified against live F-Droid docs and precedent recipes on 2026-07-06. + +## What lives where + +- **This repo** carries everything F-Droid reads from source: `LICENSE` (GPL-3.0), + `fastlane/metadata/android/{en-US,de-DE}/` (title, descriptions, icon, screenshots, + per-`versionCode` changelogs), `app/version.properties` (version literals for the + update checker), and the `-PdisableSigning` build path in `app/build.gradle.kts`. +- **The fdroiddata fork** carries the build recipe below. It is **not** committed to + this repo — F-Droid lint rejects a recipe that duplicates the fastlane + `Summary`/`Description`, and the recipe references paths inside this repo by URL. + +## The recipe — `metadata/dev.xitee.sleeptimer.yml` + +Add this file in a fork of https://gitlab.com/fdroid/fdroiddata (not here): + +```yaml +Categories: + - Timer + - Multimedia +License: GPL-3.0-or-later +AuthorName: Xitee +SourceCode: https://github.com/Xitee1/sleep-timer +IssueTracker: https://github.com/Xitee1/sleep-timer/issues +Changelog: https://github.com/Xitee1/sleep-timer/releases +Donate: https://ko-fi.com/xitee165479 + +AutoName: SleepTimer + +RepoType: git +Repo: https://github.com/Xitee1/sleep-timer.git + +Builds: + - versionName: 1.1.1 + versionCode: 101010 + commit: v1.1.1 + subdir: app + gradle: + - yes + gradleprops: + - disableSigning=true + +AutoUpdateMode: Version +UpdateCheckMode: Tags ^v\d+\.\d+\.\d+$ +UpdateCheckData: app/version.properties|versionCode=(\d+)|.|versionName=(.+) +CurrentVersion: 1.1.1 +CurrentVersionCode: 101010 +``` + +Notes: +- No `Summary:`/`Description:` — the fastlane metadata in this repo supplies them. +- No `AntiFeatures:` — Shizuku is an *optional* Apache-2.0 dependency from Maven Central + (no `NonFreeDep`; precedent: the Hail recipe, which also has optional Shizuku), and the + app has no `INTERNET` permission and no trackers. +- `UpdateCheckData` reads `app/version.properties` because the Gradle build derives + `versionCode`/`versionName` dynamically from the git tag (axion-release), which the + static update checker cannot parse. The `.` reuses the same file for the versionName + regex. The `Tags` regex excludes the old `v0.0.1-beta` tag. +- After the first release, the checkupdates bot appends new build entries automatically + (copying `gradleprops`), so future releases need no recipe edits. + +## Reproducible-builds probe (decide before opening the MR) + +Reproducible builds are a one-way door: if the F-Droid build is bit-identical to the +signed GitHub release APK, F-Droid publishes *your* signature (users can cross-update +between GitHub and F-Droid). If not enabled at inclusion time, F-Droid signs with its own +key and you cannot switch later. Test first: + +```sh +# at the v1.1.1 tag, with JDK 17: +./gradlew :app:assembleRelease -PdisableSigning +pip install apksigcopier +apksigcopier compare --unsigned \ + app/build/outputs/apk/release/app-release-unsigned.apk \ + SleepTimer-v1.1.1.apk # the asset from the GitHub v1.1.1 release +``` + +If it reports the APKs match, add to the build entry: + +```yaml + binary: https://github.com/Xitee1/sleep-timer/releases/download/v%v/SleepTimer-v%v.apk +``` + +and top-level (get the hash from `apksigner verify --print-certs SleepTimer-v1.1.1.apk`): + +```yaml +AllowedAPKSigningKeys: +``` + +If it does not match (R8/JDK nondeterminism is the usual cause), skip these fields and +write "No, I don't want this." in the MR reproducibility question. + +## Local validation + +```sh +python3 -m venv ~/venvs/fdroid && ~/venvs/fdroid/bin/pip install fdroidserver +git clone https://gitlab.com//fdroiddata.git && cd fdroiddata +git switch -c dev.xitee.sleeptimer # branch name = applicationId (convention) +# add metadata/dev.xitee.sleeptimer.yml, then: +fdroid readmeta && fdroid rewritemeta dev.xitee.sleeptimer +fdroid lint dev.xitee.sleeptimer # must be clean +fdroid checkupdates dev.xitee.sleeptimer # only after v1.1.1 is tagged +fdroid build -v -l dev.xitee.sleeptimer # optional locally (needs ANDROID_HOME); fork CI runs it too +``` + +## Submission MR + +1. Fork fdroiddata (public fork, unprotected branch `dev.xitee.sleeptimer`), push. +2. Wait for the fork's GitLab pipelines to go green (they run lint + a full `fdroid build`). +3. Open an MR against fdroiddata `master` with the **App inclusion** template, title + `New app: SleepTimer`, squash enabled. +4. In the description: state you are the upstream author (RFP issue not needed), that there + are no anti-features (optional FLOSS Shizuku, no `INTERNET` permission, no trackers), and + your reproducible-builds decision. + +Review typically takes days to a few weeks; after merge the app appears on f-droid.org +within about a week. + +## After publication + +- Replace the `_Submission pending._` line in `README.md` with the F-Droid badge linking + `https://f-droid.org/packages/dev.xitee.sleeptimer`. +- Optional: add `fastlane/metadata/android/en-US/images/featureGraphic.png` (1024×500) for + a nicer "Latest" listing card. diff --git a/fastlane/metadata/android/de-DE/changelogs/100010.txt b/fastlane/metadata/android/de-DE/changelogs/100010.txt index 43c15ef..f20fa6d 100644 --- a/fastlane/metadata/android/de-DE/changelogs/100010.txt +++ b/fastlane/metadata/android/de-DE/changelogs/100010.txt @@ -1,4 +1,3 @@ * Neu: Fehlende Berechtigungen für die Bildschirm-aus-Funktion werden beim App-Start abgefragt, mit Wiederherstellungs-Dialog beim Verwerfen. -* Behoben: Shizuku-Startup-Race, das Shizuku kurz als „nicht laufend“ melden konnte. * Politur: Material-3-Stil für Dialoge, klarere Titel beim Geräteadministrator-Dialog. * Intern: Upgrade auf AGP 9, Kotlin 2.3, Compose BOM 2026.03.01. diff --git a/fastlane/metadata/android/de-DE/changelogs/101000.txt b/fastlane/metadata/android/de-DE/changelogs/101000.txt new file mode 100644 index 0000000..85a5bbe --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/101000.txt @@ -0,0 +1,5 @@ +* Neu: Neues App-Symbol im Nachthimmel-Design mit Monduhr. +* Neu: Einstellung für den Bildschirm-Rotationsmodus (automatisch, Hoch- oder Querformat). +* Behoben: Korrektheit der Timer-Laufzeit und Darstellungsprobleme. +* Behoben: Shizuku-Startup-Race, das Shizuku kurz als „nicht laufend“ melden konnte. +* Intern: Shizuku-Shell-Zugriff auf einen gebundenen UserService umgestellt. diff --git a/fastlane/metadata/android/de-DE/changelogs/101010.txt b/fastlane/metadata/android/de-DE/changelogs/101010.txt new file mode 100644 index 0000000..9ee11e9 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/101010.txt @@ -0,0 +1 @@ +* Intern: Vorbereitung für F-Droid – Versions-Metadatendatei, Unterstützung für reproduzierbare unsignierte Builds und Änderungsprotokolle pro Version. Keine sichtbaren Änderungen. diff --git a/fastlane/metadata/android/en-US/changelogs/100010.txt b/fastlane/metadata/android/en-US/changelogs/100010.txt index 7f541a9..91e8d3b 100644 --- a/fastlane/metadata/android/en-US/changelogs/100010.txt +++ b/fastlane/metadata/android/en-US/changelogs/100010.txt @@ -1,4 +1,3 @@ * New: missing permissions for the screen-off feature are now prompted on app startup, with a recovery dialog if the prompt is dismissed. -* Fixed: Shizuku startup race that could briefly report Shizuku as not running. * Polish: Material 3 dialog styling, clearer device-admin prompt titles. * Internal: upgraded to AGP 9, Kotlin 2.3, Compose BOM 2026.03.01. diff --git a/fastlane/metadata/android/en-US/changelogs/101000.txt b/fastlane/metadata/android/en-US/changelogs/101000.txt new file mode 100644 index 0000000..fe1ed46 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/101000.txt @@ -0,0 +1,5 @@ +* New: night-sky moon-clock launcher icon. +* New: screen rotation mode setting (auto, portrait, or landscape). +* Fixed: timer runtime correctness and theming issues. +* Fixed: Shizuku startup race that could briefly report Shizuku as not running. +* Internal: migrated Shizuku shell access to a bound UserService. diff --git a/fastlane/metadata/android/en-US/changelogs/101010.txt b/fastlane/metadata/android/en-US/changelogs/101010.txt new file mode 100644 index 0000000..ce0a719 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/101010.txt @@ -0,0 +1 @@ +* Internal: prepared the app for F-Droid — added a version metadata file, support for reproducible unsigned builds, and per-release changelogs. No user-facing changes. From d39795e310577bc69ecaf819989e8e5c95e23360 Mon Sep 17 00:00:00 2001 From: Xitee <59659167+Xitee1@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:32:07 +0200 Subject: [PATCH 2/2] build: keep native debug symbols for reproducible F-Droid builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reproducibility probe against the v1.1.0 release (build the tag from a clean clone, compare zip entries by CRC against the signed GitHub APK) showed 121/122 entries already byte-identical. The only mismatch was libdatastore_shared_counter.so (DataStore's native lib, 4 ABIs): CI strips it host-dependently via the runner's NDK, while a plain build keeps the pristine AAR bytes. Add packaging { jniLibs { keepDebugSymbols += "**/*.so" } } so AGP does not strip native libs — every environment then packages the identical pristine dependency bytes. Verified: the four .so CRCs now equal the datastore-core AAR CRCs. With this in the v1.1.1 tag, the GitHub release APK is also unstripped, so F-Droid's from-source build reproduces it and can ship the app under the developer signature (AllowedAPKSigningKeys) instead of F-Droid's key. Also switch the fdroiddata recipe draft to the reproducible-builds form (binary: + AllowedAPKSigningKeys 87e5fe65...) and record the probe. Co-Authored-By: Claude Fable 5 --- app/build.gradle.kts | 10 ++++ docs/plans/2026-07-06-fdroid-submission.md | 67 +++++++++++++++------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c26a0a3..8d85e2e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,6 +66,16 @@ android { targetCompatibility = JavaVersion.VERSION_17 } + packaging { + jniLibs { + // Package the DataStore native libs as shipped in the dependency instead of + // letting AGP strip them — stripping is host-dependent (build machine's NDK), + // which breaks F-Droid's reproducible-build match against the release APK. + // The unstripped bytes come straight from the AAR, so every build agrees. + keepDebugSymbols += "**/*.so" + } + } + buildFeatures { compose = true } diff --git a/docs/plans/2026-07-06-fdroid-submission.md b/docs/plans/2026-07-06-fdroid-submission.md index 90b9f63..48af90b 100644 --- a/docs/plans/2026-07-06-fdroid-submission.md +++ b/docs/plans/2026-07-06-fdroid-submission.md @@ -42,6 +42,9 @@ Builds: - yes gradleprops: - disableSigning=true + binary: https://github.com/Xitee1/sleep-timer/releases/download/v%v/SleepTimer-v%v.apk + +AllowedAPKSigningKeys: 87e5fe65c58d5b8406d239d68e7a9d6d7f40245ee6c3a1f458e67094b0a67fb0 AutoUpdateMode: Version UpdateCheckMode: Tags ^v\d+\.\d+\.\d+$ @@ -50,6 +53,15 @@ CurrentVersion: 1.1.1 CurrentVersionCode: 101010 ``` +This is the **reproducible-builds (your-key)** form: `binary:` points F-Droid at your +signed GitHub release APK as the reference, and `AllowedAPKSigningKeys` is the SHA-256 of +your release signing certificate (`CN=Xitee`, extracted from the v1.1.0 release APK — valid +as long as v1.1.1 is signed with the same keystore). F-Droid rebuilds from source, confirms +its build is byte-identical to your APK, and ships your APK's signature. **Only submit this +form if the probe below passes for v1.1.1.** If it does not, drop `binary:` and +`AllowedAPKSigningKeys:` and F-Droid signs with its own key (a one-way door — you cannot +switch to your signature later). + Notes: - No `Summary:`/`Description:` — the fastlane metadata in this repo supplies them. - No `AntiFeatures:` — Shizuku is an *optional* Apache-2.0 dependency from Maven Central @@ -62,36 +74,47 @@ Notes: - After the first release, the checkupdates bot appends new build entries automatically (copying `gradleprops`), so future releases need no recipe edits. -## Reproducible-builds probe (decide before opening the MR) +## Reproducible-builds probe — preliminary result (already run against v1.1.0) Reproducible builds are a one-way door: if the F-Droid build is bit-identical to the -signed GitHub release APK, F-Droid publishes *your* signature (users can cross-update -between GitHub and F-Droid). If not enabled at inclusion time, F-Droid signs with its own -key and you cannot switch later. Test first: +signed GitHub release APK, F-Droid ships *your* signature (users cross-update between +GitHub and F-Droid); if not enabled at inclusion time, F-Droid signs with its own key and +you cannot switch later. + +**A preliminary probe was run on 2026-07-06 against the existing v1.1.0 release** (build the +`v1.1.0` tag from a clean clone, unsigned, and compare its zip entries by CRC against +`SleepTimer-v1.1.0.apk`). Result: **121 of 122 entries were already byte-identical** — +including all DEX, resources, and `resources.arsc`. The only mismatch was +`libdatastore_shared_counter.so` (the AndroidX DataStore native lib, 4 ABIs): GitHub Actions +**strips** it (host-dependent, via the runner's NDK), while a plain build leaves it as the +pristine bytes shipped in the `datastore-core` AAR. The unstripped bytes are fixed by the +dependency version, so they are identical on every machine. + +**Fix applied in this repo** (`app/build.gradle.kts`): a `packaging { jniLibs { +keepDebugSymbols += "**/*.so" } }` block, which tells AGP not to strip native libs, so every +environment (GitHub Actions, F-Droid, local) packages the pristine AAR bytes. Verified: with +the block, the four `.so` CRCs equal the pristine AAR CRCs. This **must ship in the v1.1.1 +tag** so the reference GitHub APK is also unstripped — with it in place, all 122 entries +match and the build is reproducible. + +**Confirm on v1.1.1 before submitting** (definitive check, once v1.1.1 is tagged & released): ```sh -# at the v1.1.1 tag, with JDK 17: +# at the v1.1.1 tag, JDK 17, ANDROID_HOME set: ./gradlew :app:assembleRelease -PdisableSigning +export PATH="$ANDROID_HOME/build-tools/:$PATH" # for apksigner pip install apksigcopier -apksigcopier compare --unsigned \ - app/build/outputs/apk/release/app-release-unsigned.apk \ - SleepTimer-v1.1.1.apk # the asset from the GitHub v1.1.1 release -``` - -If it reports the APKs match, add to the build entry: - -```yaml - binary: https://github.com/Xitee1/sleep-timer/releases/download/v%v/SleepTimer-v%v.apk -``` - -and top-level (get the hash from `apksigner verify --print-certs SleepTimer-v1.1.1.apk`): - -```yaml -AllowedAPKSigningKeys: +apksigcopier compare SleepTimer-v1.1.1.apk \ + --unsigned app/build/outputs/apk/release/app-release-unsigned.apk +# apksigcopier 1.1.1 may error parsing the signing block; the robust fallback is a +# per-entry CRC diff of `unzip -v` on both APKs (ignoring META-INF signature files). ``` -If it does not match (R8/JDK nondeterminism is the usual cause), skip these fields and -write "No, I don't want this." in the MR reproducibility question. +The definitive check is F-Droid's own CI on the submission MR (or a local +`fdroid build` in a fdroiddata checkout), which builds in F-Droid's controlled environment. +Given the preliminary result (only the now-fixed `.so` differed), it is very likely to pass. +If it unexpectedly fails, drop `binary:` + `AllowedAPKSigningKeys:` from the recipe and write +"No, I don't want this." in the MR reproducibility question. ## Local validation