Add Rust distribution auditing, pinned post-build AAB signing, complete offline third-party notices, an Android privacy policy and reviewable Play listing assets. Preserve the application ID and audit both ARM ABIs, native symbols, baseline profiles and R8 mapping. Fix dialog focus ownership, navigation-triggered cleanup loops and stale authentication errors after first-run setup, with native and real-activity regressions. Google Play publication is deferred by the user. Issue #112 remains open for production signing, Console declarations and actual Play install/update verification.
16 KiB
Android Play distribution — issue #112
Status: local release preparation. The user explicitly deferred Google Play
publication on 15 October 2026. #111 was audited and delivered in b54636b.
No Play release, accepted AAB, production signing lineage, or production install
has been established. Keep #112 open until its acceptance criteria are evidenced.
Release identity and artifact gate
The application ID remains de.rfc1437.ironstorage. android/release.properties
is the single Android version source. Code 2 updates earlier code-1 builds when
signed with the same certificate; changing signing certificates cannot be fixed
by uninstalling a retained test app. The name remains 1.0.1. Before publishing,
check the highest code already allocated in Play and advance it if necessary.
The SDK catalog retains min 26 and target/compile 37. The only release ABIs are
arm64-v8a and armeabi-v7a.
The Rust audit-distribution command checks the decoded bundle manifest against
that version source and the SDK catalog. It rejects debug/test/profile flags,
audits every selected native ELF for 16 KB alignment, compares retained FULL
symbols with the bundle's native build IDs, requires both baseline-profile
files, and compares the retained R8 mapping with the bundled mapping byte for
byte. It writes deterministic SHA-256 provenance for the audited artifacts.
The existing APK manifest gate separately audits permissions, exported
components and all backup/transfer exclusions. These are artifact checks;
Google Play processing and installed signing identity require separate evidence.
Signing and trust boundary
Upload signing and Play app signing are different keys. The upload certificate only authorizes submission. Google Play signs delivered APKs with the app-signing key selected in Play Console. Preserve that lineage for all production updates. Never replace the retained emulator's debug-signed app with a differently signed upload or Play APK by uninstalling it. Use a separate named AVD for Play installs.
The distribution command requires an external keystore and these process
variables: IRONSTORAGE_UPLOAD_STORE_FILE, IRONSTORAGE_UPLOAD_STORE_PASSWORD,
IRONSTORAGE_UPLOAD_KEY_ALIAS, IRONSTORAGE_UPLOAD_KEY_PASSWORD, and
IRONSTORAGE_UPLOAD_CERT_SHA256 (64 hexadecimal digits, independently pinned).
Do not put passwords in shell arguments, command history, Gradle properties,
repository files, CI output, or artifacts. Disable shell tracing. Retain a secure
offline upload-key backup. The repository ignores .jks and .keystore; that is
not permission to place other private-key formats in the checkout.
Use the pinned JDK, SDK, Cargo and NDK from android/README.md. Run
python3 .gitea/scripts/android_ci.py distribution. It builds all ordinary
Android verification gates without signing variables in the build processes,
verifies the upload certificate, signs and validates
the AAB, and writes only release artifacts to android/build/distribution:
AAB, R8 mapping, selected native symbols, CycloneDX inventory, decoded manifest,
third-party notices, certificate fingerprint and checksummed provenance. Retained
notices must match the bundle asset byte for byte. It refuses to overwrite an
existing distribution directory. Archive each successful directory by commit
and version in access-controlled release storage before cleanup.
This checkout has no protected main-branch rule (read-only Gitea check on
2026-10-15). No production signing key, Play service account or signing secret
has been installed. Do not mistake a Gitea environment field or a branch
condition in an editable workflow for an approval boundary. Enable production
signing only on a trusted release runner after repository/runner access and
approval rules are configured. Keep signing credentials away from pull-request
jobs and untrusted workflow revisions. Keep any Play publisher credential scoped
to this app and the required testing/release track. Signing can be prepared
without granting the build system permission to publish.
Console, policy and listing
Read-only browser checks on 2026-10-15 reached developer-account registration for the two available consumer Google accounts. The project-domain Workspace account cannot use Play Console under its current admin policy. No account was created, terms accepted, identity information submitted, payment made or bundle uploaded. An enrolled developer account with access to this app is required to continue actual Play processing.
Current primary-source review (recheck when the release is submitted):
- Target API policy: new phone apps and updates require API 36+ from 31 August 2026; target 37 meets that numeric requirement, subject to Console accepting the installed SDK.
- Play Console registration: account enrollment includes a distribution agreement, registration payment and identity verification. New personal accounts also have testing and device verification requirements. Those account requirements are distinct from this milestone's emulator-only product testing; do not claim an emulator satisfies Google's account verification requirements.
- App signing: enroll in Play App Signing and record both app-signing and upload certificate fingerprints before testing updates. No production key has been selected yet.
- Developer verification: verify the publisher identity/package registration for applicable distribution regions. Do not invent an organization, legal identity or verified status.
- Data safety: off-device transmission is collection even when sent to a user-selected server; locally processed data and qualifying end-to-end encrypted payloads have exemptions. Do not declare all Git traffic end-to-end encrypted: commit identity, paths and HTTPS authentication are visible to the selected server.
- Broad package visibility:
QUERY_ALL_PACKAGESrequires a permitted-use declaration/review. It verifies OS-mediated Autofill/Credential Manager caller certificates, including clients without discoverable intent filters. It does not enumerate an installed-app inventory or transmit one. Seecredentials.mdfor the security rationale. Approval is pending; no policy exemption is assumed.
android/play/listing.toml contains the reviewable English listing draft.
The existing adaptive and monochrome launcher icons remain. android/play/
also contains a 512px Play icon, a 1024×500 feature graphic and four genuine
1080×1920 phone screenshots, with editable SVGs and unmodified capture sources.
Their PNG formats, dimensions, sizes and listing text limits were checked.
Tablet screenshots, the IARC-generated rating, final publisher contact details
and Console form submissions remain pending. Use actual app
screenshots with public demonstration data; never publish fixture private keys,
passphrases, tokens, repository locations, or a support diagnostic transcript.
The in-app offline privacy policy is the canonical Android policy and includes
support, third-party server traffic, local storage, camera, provider access,
clipboard and uninstall/backup behavior. Its source URL becomes public only
when the corresponding commit is pushed. Verify anonymous access before using
it in Console. The old Apple-only website policy must not be represented as an
Android policy. The dependency inventory is supplemented by packaged third-party
copyright and license notices, displayed offline in Preferences. The build checks
Rust notice coverage against both ARM normal dependency graphs and fails for
unreviewed Android license exceptions; see android/licenses/README.md.
Draft declarations for review against the final Console questionnaire:
- No ads, analytics, developer-side app account, tracking or crash collection.
- Optional HTTPS sends user-provided Git name/email, authentication identity and unencrypted repository metadata to the configured host. Declare the applicable optional personal-information/user-content categories; collection serves app functionality. The encrypted GPG entry payload is unreadable to intermediaries without recipient keys. Do not apply that exemption to plaintext Git metadata.
- Camera frames, biometric results and requesting-app certificates stay local. User-confirmed provider/clipboard/QR actions transfer the selected content as the user requests; review the user-initiated sharing exception accurately.
- HTTPS uses TLS and rejects cleartext/unsupported remotes. No developer data deletion endpoint is needed for a nonexistent developer account/database; local and server-side deletion limits are stated in the privacy policy.
- The app contains OpenPGP encryption and TLS. Complete the actual encryption/ export questionnaire using the publisher's jurisdiction and applicable classification. No exemption or export classification has been filed.
Install, update and failure evidence still required
Start on the internal track. Record Play processing/version, signing fingerprints, ABI/SDK/permission inspection and pre-launch reports. Complete the closed-testing requirements that the enrolled account actually imposes. A production rollout must follow successful testing and the publisher's chosen release regions; do not substitute a locally signed APK for Play acceptance.
On a dedicated ARM64 Play-capable emulator, install from Play into a new app container through the normal setup UI. Create/import keys and a compatible local store through supported flows, then exercise optional HTTPS clone/sync, browsing/editing, TOTP, QR permission/transfer, biometrics and enabled Android credential providers. Keep review secrets out of public listing material.
For update validation, retain that same AVD, package and signing lineage. Record repository commits and entry counts, configuration, a usable protected record and a populated TOTP cache before installing the next higher Play code. Update through Play without clearing data. Verify each retained item and an actual authorized decrypt/provider operation after update. Repeat on available ARM64 API/page-size classes; ARMv7 gets package/native audit coverage, not an invented runtime result. Physical devices and x86 are outside this milestone.
The app's existing Rust gates cover atomic private-storage writes, cancelled
operations, recovery of corrupt metadata without discarding repository data,
and current-enrollment Keystore invalidation. A failed install must leave the
old package/state usable; a failed operation must retain a recoverable prior
state and display its typed error. APK downgrade is not a recovery mechanism:
ship a corrected build with a higher code and a schema-compatible recovery path.
Do not use adb install -d, erase a container, restore opaque app-data packages,
or weaken authentication to force a test through. Uninstall is destructive and
not a migration test. State on the user's configured Git server is not removed
by uninstalling; ordinary entry deletion also leaves Git history.
Validation record
Local preparation was validated on 15 October 2026. The implementation commit is recorded on #112. These checks do not establish a delivered Play release.
- Required workspace formatting, warnings-as-errors check, Clippy and tests passed: 526 Rust tests, 0 failures, 0 ignored across 41 suites. No Apple app or project source changed; no iPhone simulator update was required.
- CI orchestration self-checks passed (14 tests). Android verification completed with 838 actionable tasks, including unit tests, lint, KtLint, architecture, instrumentation compilation, release R8/baseline packaging and native gates.
- The post-build signing command passed using an external disposable validation key. Strict JAR verification against the pinned upload certificate, bundletool 1.18.3 validation, decoded bundle identity/SDK/permission checks, both ARM ABI/16 KiB audits, matching FULL symbols/R8 mapping/baseline profiles and exact retained notice bytes all passed. This is not the production key and was not uploaded. The generated notice asset covers 114 Android release components and 423 Rust normal dependencies. AAR-embedded JAR notices were inspected; JNA's embedded notices are covered by its retained release notice and the complete Apache text, with its bundled libffi notice also retained.
SettingsScreenTestpassed all three methods on each ARM64 image: API 26 / 4 KiB, API 37 / 4 KiB, API 37 / strict 16 KiB. A real-activity regression first reproduced document dialogs being dismissed by the background-lock cleanup. Owned-window registration and protected dialog surfaces fixed the problem. Tests verify normal dismissal preserves search and real pause/resume clears the presentation. The earlier fixture-only settings test did not cover the activity's real presentation object; it is supplemented, not counted as proof of this path.- Release QA also reproduced an endless protected-presentation rebuild when navigating from Passwords to TOTP. Navigation-entry stop events were revoking the root while that root was being rebuilt. Passwords and TOTP now observe the protected activity's lifecycle for background cleanup. The navigation test first failed with a recomposition timeout, then passed with the correction. It requires a normally configured store and asserts actual Passwords content; a separate test checks immediate masking on stop and clean reveal on resume.
- A normal first-run setup on the API 26 optimized app found a stale authentication configuration error after successful store creation. Setup completion now refreshes Rust-provided authentication state after an existing request settles. The isolated native entry integration test first failed without the refresh, then passed with it, including encrypted entry operations and confirmation that setup does not grant an unlock lease. The API 26 demo store was created through the app's normal key-generation/setup UI.
- The final focused UI checks passed on configured ARM64 stores at API 26 / 4 KiB, API 37 / 4 KiB and API 37 / strict 16 KiB: 14 executed tests per image, with two opt-in interactive inspection fixtures excluded per image. On the main image, the two real-store regressions were explicitly rerun with zero skips after correcting their precondition to accept Rust's ready/locked shell state as well as completed onboarding state. The final native setup/entry regression also passed. Actual optimized-app computer use verified Passwords-to-TOTP navigation on the main and older images and the strict 16 KiB image, retaining the existing stores and cached TOTP navigation.
- Negative checks against the real candidate reject mismatched retained notices and R8 mapping, and emit no successful provenance for either mismatch.
- The optimized APK generated from the final AAB passed APK alignment/strip/
matching-symbol audits and installed with
adb install -rusing the retained emulator's existing debug signing certificate. Computer use confirmed the unchanged three-commit, clean local repository, version 1.0.1, loaded privacy policy including deletion/backup text, and responsive offline notices. Both documents remained open and closed normally. Cold launch also succeeded. This is a local release-equivalent update, not a Play install or production signing-lineage test. - No app uninstall, container/key reset, opaque app-data restore or ordinary biometric re-enrollment was used. The three unused AVDs were shut down gracefully after validation; the visible retained API 37 AVD remains running.
The actual Play acceptance, enrolled developer identity, production signing configuration/automation, live listing/assets/forms and clean/update smoke tests from Play remain outstanding. #112 cannot be closed on local packaging or emulator checks alone. Keep the working changes for this issue's eventual single commit; do not present the disposable-key candidate as a production release.