Define native Android architecture and parity contract (#88)
Dependency security audit / rustsec (push) Failing after 26s
Dependency security audit / rustsec (push) Failing after 26s
This commit is contained in:
1 parent
71a4f289cf
commit
30d9119302
4 files changed
+316
No files matched your search
@@ -0,0 +1,211 @@
|
||||
# Android architecture decision
|
||||
|
||||
Accepted implementation contract for [#88](https://git.rfc1437.de/hugo/IronStorage/issues/88)
|
||||
in milestone 23, **06 - Implement an Android version of the software**.
|
||||
Reviewed against the checkout's iPhone app, AutoFill extension, and shared Rust
|
||||
mobile APIs on 2026-10-08. The milestone goal is full native Android feature
|
||||
parity. This decision records ownership and release requirements; it does not
|
||||
claim that the Android implementation or device audit already exists.
|
||||
|
||||
## Language and presentation
|
||||
|
||||
Use Kotlin, Gradle Kotlin DSL, one activity, Jetpack Compose, Material 3 and
|
||||
Material 3 Adaptive. Use Navigation 3: its [stable release is 1.2.0](https://developer.android.com/jetpack/androidx/releases/navigation3).
|
||||
Screen ViewModels expose immutable StateFlow state and accept typed events.
|
||||
Compose observes with collectAsStateWithLifecycle. Hilt supplies dependencies;
|
||||
KSP runs its processors. Blocking UniFFI calls run on a bounded background
|
||||
coroutine dispatcher. Cancellation cancels the Rust operation as well as the
|
||||
coroutine; a late result must not restore a revoked lease or stale screen.
|
||||
|
||||
Rust owns repository layout, config, keys, OpenPGP, Git, conflicts, documents,
|
||||
generation, OTP, QR payloads, identities, external access permissions and
|
||||
secure-secret orchestration. Kotlin collects input, renders Rust state, and
|
||||
adapts OS mechanics. No app component launches a subprocess. No Kotlin feature
|
||||
reads repository files, parses entries/URIs or error labels, generates passwords
|
||||
or codes, or independently decides credential matches or authentication policy.
|
||||
|
||||
Use typed semantic icons/actions, stable IDs, generation/revision tokens,
|
||||
view-ready records and typed errors. #91 removes SF Symbol and Watch assumptions
|
||||
from shared phone contracts. Keep Swift compatibility through mechanical
|
||||
adapters. #92 generates Kotlin and Swift from one shared UniFFI surface rather
|
||||
than copying crates/apple conversions. A thin Android bridge may adapt platform
|
||||
callbacks; it must contain no password-store decisions or handwritten unsafe
|
||||
Rust. Generated/dependency FFI stays isolated from project-authored safe Rust.
|
||||
|
||||
## Modules and allowed dependencies
|
||||
|
||||
| Module | Responsibility | Allowed project dependencies |
|
||||
| --- | --- | --- |
|
||||
| android/app | Activity, composition root, navigation, manifest and service registration | features, core, platform |
|
||||
| android/features | Related Compose workflows, screen ViewModels, accessibility | core only |
|
||||
| android/core | Android-facing facade, immutable presentation contracts, coroutine scheduling, bridge mapping | rust-bridge |
|
||||
| android/platform | Keystore, BiometricPrompt, clipboard, camera, paths and OS request translation | core contracts |
|
||||
| android/rust-bridge | Generated UniFFI Kotlin and ABI native packaging | shared Rust bridge via build tasks |
|
||||
| Shared Rust bridge | Mechanical records, lifetime/callback/error conversion | crates/storage |
|
||||
| crates/storage | All domain behavior and trust-boundary validation | audited Rust dependencies |
|
||||
|
||||
Start with these five Gradle modules; split feature groups only when useful.
|
||||
Platform implementations are injected at app composition. Feature code cannot
|
||||
import generated UniFFI/JNA types or depend directly on platform/bridge modules.
|
||||
OS services delegate to core; exported service input is untrusted. Only Rust
|
||||
holds repository capabilities. Gradle dependency checks, source-boundary checks,
|
||||
and release manifest inspection are gates in #89/#94/#111, not substitutes for
|
||||
review. Generated bindings and native/build outputs are not checked in.
|
||||
|
||||
## Support and build policy
|
||||
|
||||
Production package, namespace and application ID: **de.rfc1437.ironstorage**.
|
||||
Updates retain this ID and signing lineage. Tests use derived IDs; a development
|
||||
variant may not masquerade as the production signed installation.
|
||||
|
||||
Minimum SDK: **26** (Android 8, including Autofill). Compile/target SDK: **37**
|
||||
(Android 17) at this decision, following the [SDK instructions](https://developer.android.com/about/versions/17/setup-sdk).
|
||||
Recheck stable SDK and [Play requirements](https://support.google.com/googleplay/android-developer/answer/11926878)
|
||||
at implementation and release. Never select preview APIs to satisfy a release
|
||||
policy. The installed android-37.0 SDK reports an empty codename and
|
||||
PreviewSdkInt=0 (revision 2); verify the same package metadata in CI before
|
||||
pinning the build, rather than relying on preview wording in older instructions.
|
||||
|
||||
Release ABIs: **arm64-v8a**, **armeabi-v7a**, **x86_64**, corresponding to
|
||||
**aarch64-linux-android**, **armv7-linux-androideabi**,
|
||||
**x86_64-linux-android**. An ABI may be removed only with a dependency audit and
|
||||
an amendment to this decision; emulator success alone proves no arm64 hardware
|
||||
coverage. All shipped native dependencies, including JNA, must pass ELF LOAD
|
||||
alignment, APK ZIP alignment and actual 16 KB runtime checks. Do not depend on
|
||||
compatibility mode. [Android's page-size guidance](https://developer.android.com/guide/practices/page-sizes)
|
||||
requires auditing dependencies as well as the app library.
|
||||
|
||||
At #89 pin a mutually supported stable AGP/Gradle/Kotlin/KSP/Hilt set, JDK,
|
||||
SDK build tools and NDK, plus wrapper checksum. The current [AGP 9.4 contract](https://developer.android.com/build/releases/agp-9-4-0-release-notes)
|
||||
requires Gradle 9.6.0 and JDK 17 and supports API 37. This is a candidate build
|
||||
baseline, subject to actual artifact availability and plugin compatibility.
|
||||
No dynamic versions, snapshots or automatic toolchain substitutions. #92 pins
|
||||
cargo-ndk, Rust targets/toolchain and the workspace UniFFI version and builds all
|
||||
ABIs from the locked Cargo graph. Retain separate native symbols and R8 mappings.
|
||||
|
||||
Support phones, tablets, foldables, resizing/multi-window, desktop windowing,
|
||||
portrait/landscape, system/light/dark themes, hardware keyboard, large fonts,
|
||||
TalkBack and process recreation. Preserve non-secret navigation/filter state;
|
||||
never serialize decrypted fields, passphrases, tokens, QR key payloads or a lease
|
||||
into SavedStateHandle, bundles, preferences, crash reports or test artifacts.
|
||||
Rotation/process death must not replay a mutation. In-flight mutations resolve
|
||||
through Rust revision/transaction state before a retry.
|
||||
|
||||
Apple Watch, watchOS and Watch Connectivity are excluded. Android Auto, TV,
|
||||
Wear OS and XR-specific experiences are outside the product; Wear is not a
|
||||
substitute for Watch. Do not show disabled Watch controls on Android.
|
||||
|
||||
## Approved dependencies and review policy
|
||||
|
||||
These are approved purposes, not permission to add every library immediately.
|
||||
#89 pins only scaffold dependencies; later owners add their required artifacts.
|
||||
Exact versions live in one version catalog, Cargo.lock and reviewed toolchain
|
||||
pins. Keep Gradle dependency verification metadata and locks where supported;
|
||||
review checksums independently before accepting a new artifact. For each addition
|
||||
and upgrade, record exact coordinates/version, upstream release/maintenance
|
||||
check, SPDX license and bundled notices, transitives, security advisories and
|
||||
compatibility results. Approval of a library family does not approve every
|
||||
transitive version. #94 builds the resolved SBOM/license gate; #112 bundles
|
||||
attributions and available source obligations.
|
||||
|
||||
| Library/API | Single purpose | License review and maintenance source |
|
||||
| --- | --- | --- |
|
||||
| Kotlin and Compose compiler plugin | Kotlin compilation | Apache-2.0, [JetBrains license](https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt); check matching stable compiler/plugin releases |
|
||||
| Gradle, AGP and Android Lint | Reproducible packaging and Android static checks | Apache-2.0 tool distribution notices; [AGP compatibility/release notes](https://developer.android.com/build/releases/about-agp) |
|
||||
| Compose BOM, Material 3, Adaptive, Activity Compose | Native controls/layout and activity hosting | Apache-2.0, [AndroidX license](https://github.com/androidx/androidx/blob/androidx-main/LICENSE.txt); check each resolved artifact's stable AndroidX release |
|
||||
| Navigation 3 | Typed adaptive navigation/back stack | AndroidX Apache-2.0; stable release source above |
|
||||
| Lifecycle ViewModel/Runtime Compose | Lifecycle-aware screen state collection | AndroidX Apache-2.0; stable AndroidX release notes |
|
||||
| kotlinx.coroutines and coroutines-test | Bounded async calls, Flow and deterministic scheduling tests | Apache-2.0, [upstream](https://github.com/Kotlin/kotlinx.coroutines); check Kotlin compatibility and tagged release |
|
||||
| Hilt/Dagger | Dependency injection | Apache-2.0, [upstream license](https://github.com/google/dagger/blob/master/LICENSE.txt); verify KSP/AGP support |
|
||||
| KSP | Hilt code generation | Apache-2.0, [upstream license](https://github.com/google/ksp/blob/main/LICENSE); verify compiler/AGP support |
|
||||
| UniFFI | Shared typed Swift/Kotlin bridge generation | MPL-2.0, [Mozilla license](https://github.com/mozilla/uniffi-rs/blob/main/LICENSE); pin workspace tool/library together |
|
||||
| JNA Android AAR | UniFFI native loading/calls | Choose Apache-2.0 under [JNA dual license](https://github.com/java-native-access/jna/blob/master/LICENSE); verify Android ABI/16 KB binaries per version |
|
||||
| cargo-ndk and NDK | ABI cross compilation/packaging | MIT OR Apache-2.0 for [cargo-ndk](https://github.com/bbqsrc/cargo-ndk); retain NDK component notices and review stable toolchain |
|
||||
| AndroidX Biometric | OS authentication prompt | AndroidX Apache-2.0; choose stable API supporting CryptoObject and required auth policy |
|
||||
| CameraX Preview/ImageAnalysis | Camera capture and frame delivery | AndroidX Apache-2.0; check stable artifacts and device support; Rust decodes/parses frames |
|
||||
| AndroidX Credentials provider | Android 14+ password-provider integration | AndroidX Apache-2.0; verify provider support and stable artifact API |
|
||||
| AndroidX Test and Compose UI Test | Instrumented/system and Compose behavior checks | AndroidX Apache-2.0; align with Compose BOM and test runner |
|
||||
| JUnit | JVM unit tests | EPL-1.0 for [JUnit 4](https://github.com/junit-team/junit4); test-only, pin maintained 4.13.x patch |
|
||||
| ktlint | Kotlin formatting/static style | MIT, [upstream](https://github.com/pinterest/ktlint); pin CLI/rules together and review Kotlin compatibility |
|
||||
| Macrobenchmark/Baseline Profiles | Measure startup and optimize measured bottlenecks | AndroidX Apache-2.0; add only with reproducible measurements in #105 |
|
||||
| WorkManager | Explicitly approved durable noninteractive work | AndroidX Apache-2.0; add only when an operation survives process loss safely |
|
||||
| Android framework | Keystore, KeyguardManager, AutofillService, clipboard, FLAG_SECURE, private paths, backup rules, connectivity and permissions | OS APIs; no extra bundled crypto/storage implementation; behavior audited on supported APIs/OEMs |
|
||||
|
||||
DataStore is allowed only for presentation-only preferences if necessary.
|
||||
Shared preferences use Rust configuration. Room, Retrofit/OkHttp, JGit,
|
||||
Bouncy Castle/OpenPGP, Kotlin OTP/password/parser libraries, ML Kit payload
|
||||
interpretation and a competing QR format are prohibited. Reuse existing Rust
|
||||
crypto/Git/QR code. Do not schedule interactive unlock, secret display or repository
|
||||
mutations as background WorkManager tasks.
|
||||
|
||||
## Threat model and state lifetime
|
||||
|
||||
Protect the pass repository/key material, passphrases, HTTPS application tokens,
|
||||
OTP secrets/codes, decrypted fields, credential selections and identity grants.
|
||||
Untrusted inputs include remote endpoints/data, repository entries and policies,
|
||||
QR/images, OS provider callers, restored files, and concurrent/stale UI requests.
|
||||
Rust validates transports before any repository mutation or connection and uses
|
||||
repository remotes as authoritative. Android starts HTTPS-only with SSH disabled;
|
||||
SSH endpoints still parse to typed unsupported errors. Adding SSH requires an
|
||||
explicit audited support decision while preserving storage-owned transport policy.
|
||||
|
||||
The sandbox and Keystore protect against ordinary cross-app access and accidental
|
||||
backup, not a fully compromised/rooted device. Debuggable builds, logs, screenshots,
|
||||
recents thumbnails, clipboard history, accessibility, keyboard suggestions and
|
||||
crash telemetry are disclosure paths. #100 uses FLAG_SECURE, redaction, background
|
||||
relock, bounded clipboard ownership/expiry and memory cleanup; never claim complete
|
||||
Kotlin/JVM memory erasure. Clear platform references and revoke Rust generations.
|
||||
|
||||
Keystore holds non-exportable AES-GCM wrapping keys. Only sealed envelopes reach
|
||||
private storage. Rust decides secret type, identity, enrollment/invalidation,
|
||||
authentication policy, lease and recovery. Kotlin translates OS error facts.
|
||||
BiometricPrompt authorizes the required CryptoObject; device credential fallback
|
||||
must match the Rust policy/API level and must not silently weaken it. Changed
|
||||
biometrics, lock-screen removal, restored-without-key data, cancellation and a
|
||||
stale callback fail closed and require explicit recovery. Provider candidates
|
||||
are non-secret until authorized user/system selection plus a valid lease.
|
||||
|
||||
## Paths, backup, update and recovery
|
||||
|
||||
#90 receives canonical filesDir, noBackupFilesDir and cacheDir from Android once;
|
||||
Rust creates scoped capabilities and owns all contents. Never infer HOME or use
|
||||
public/shared storage. Place repository, keys, config, TOTP discovery catalog,
|
||||
Keystore envelopes and transactional durable scratch under noBackupFilesDir.
|
||||
Disposable camera/image buffers may use cacheDir only when cleanup is safe;
|
||||
never use the cache for the only copy of a repository or credential.
|
||||
|
||||
Set allowBackup=false and explicit legacy backup and Android 12+ data-extraction
|
||||
rules excluding all sensitive roots from cloud backup and device transfer. Test
|
||||
OEM transfer behavior; no claim that one manifest flag defeats every OEM tool.
|
||||
No automatic key/repository transfer. The TOTP catalog contains only canonical
|
||||
path, ciphertext SHA-256 and is_totp; treat even that metadata as private.
|
||||
|
||||
An APK/Play update installs over the same ID/signing lineage and retains valid
|
||||
private state and Keystore aliases. Migrations are Rust-owned, versioned and
|
||||
atomic; migration failure retains prior recoverable data. Process death revokes
|
||||
in-memory authentication, preserves valid completed cache checkpoints and leaves
|
||||
atomic transactions recoverable. Low storage/read-only/corruption produce typed
|
||||
errors without deleting unrelated data. Uninstall/reset deletes app data and can
|
||||
make keys unrecoverable; reinstall/restore is not an update. Recovery needs the
|
||||
user's externally retained encrypted key material/passphrase and, when configured,
|
||||
a remote clone. Do not seed app data or commit credentials to prove recovery.
|
||||
|
||||
## Verification and acceptance
|
||||
|
||||
The [parity matrix](parity.md) assigns every current non-Watch mobile capability
|
||||
and every Android issue to a Rust owner and Android API. Its pending rows are
|
||||
implementation obligations, not accepted gaps. #111 must replace pending status
|
||||
with evidence and have no unexplained gap before #112 distribution.
|
||||
|
||||
Every issue runs the AGENTS.md Rust gates before commit. Android changes also
|
||||
run build/lint/unit/instrumented checks appropriate to the issue and computer-use
|
||||
validation on the existing emulator. Update by installation over the existing
|
||||
app, never uninstall/reset. If unlocking needs an unavailable user, label the
|
||||
issue untested and document exactly what could not be checked. Emulator evidence
|
||||
cannot satisfy physical biometric/camera/Keystore/autofill or Play acceptance.
|
||||
Apple changes require XcodeGen and affected simulator builds and UI checks;
|
||||
never re-enroll an already enrolled iPhone simulator in ordinary validation.
|
||||
|
||||
#94 owns reproducible CI and test boundaries, #105 accessibility/performance,
|
||||
#111 the complete architecture/security/parity audit, and #112 physical production
|
||||
install/update/Play release evidence. No release approval is implied by this ADR.
|
||||
Reference in new issue
Block a user