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.
244 lines
16 KiB
Markdown
244 lines
16 KiB
Markdown
# 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](https://support.google.com/googleplay/android-developer/answer/11926878):
|
||
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](https://support.google.com/googleplay/android-developer/answer/6112435/):
|
||
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](https://developer.android.com/studio/publish/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](https://developer.android.com/developer-verification):
|
||
verify the publisher identity/package registration for applicable distribution
|
||
regions. Do not invent an organization, legal identity or verified status.
|
||
- [Data safety](https://support.google.com/googleplay/android-developer/answer/10787469):
|
||
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](https://support.google.com/googleplay/android-developer/answer/10158779):
|
||
`QUERY_ALL_PACKAGES` requires 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. See `credentials.md` for 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.
|
||
- `SettingsScreenTest` passed 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 -r` using 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.
|