# Android private storage (#90) The platform supplies canonical `Context.filesDir`, `noBackupFilesDir` and `cacheDir` facts off the UI thread. `PrivateStorage` validates existing, distinct roots and opens directory capabilities. It derives all subordinate paths; Kotlin never lists, reads or mutates repository contents. `activate()` installs one immutable process path context so existing Rust configuration/onboarding, Git, key-transfer and TOTP APIs use these roots. Android calls made before activation fail instead of consulting HOME, cwd, XDG or public storage. | Object | Placement | Update / cleanup policy | | --- | --- | --- | | pass-format repository | `noBackupFilesDir/ironstorage/vault` | Retained; existing storage/Git atomic mutation rules | | GPG key material | `noBackupFilesDir/ironstorage/keys` | Retained; Rust crypto import/export only | | secret-free configuration | `noBackupFilesDir/ironstorage/config.toml` | Atomic Rust writes; relative vault/key paths survive relocation | | TOTP discovery catalog | `noBackupFilesDir/ironstorage/totp-catalog.toml` | Valid catalogs survive updates and relocation; invalid/oversized/wrong-store catalogs are removed and rebuilt by discovery | | version / device binding | `noBackupFilesDir/ironstorage/layout.toml` | Atomic version 1 marker; unknown versions require a compatible migration | | transactional scratch | `noBackupFilesDir/ironstorage/scratch` | Per-owner file locks; startup removes orphaned transactions and skips live owners | | disposable cache | `cacheDir/ironstorage` | Only this capability is cleared by `clearDisposableCache`; vault/key/catalog data is retained | | protected binding and credentials | Keystore AES-GCM keys / `noBackupFilesDir/ironstorage/secrets` (#93) | No serialized token or passphrase in configuration or layout metadata | | `filesDir` | OS root fact only | No durable password-store object is placed here | Directory opens use safe Rust `rustix` handles with `NOFOLLOW`; names are single components. Existing vault/key trees reject links and special files. Directories are 0700; files are 0600, preserving an existing owner executable bit. These are Unix permission checks inside the Android UID/SELinux sandbox, not promises about physical media erasure or an untrusted OS. No application process or handwritten unsafe FFI is introduced. Configuration must refer to `vault` and key material below `keys`. A corrupt or missing configuration with remaining data produces typed recovery, never a new store or automatic repository deletion. Interrupted `.local-setup-*` transactions are retained because they can contain the previous repository. Owned orphan `.ironstorage-config-*` and `.ironstorage-atomic-*` files are removed only after taking their file lock; active atomic writers are left alone. A directory gate protects the creation-to-owner-lock interval from concurrent startup cleanup. Android atomic files use private named capabilities and rename because SELinux denies anonymous-file linking via `/proc`; safe `rustix` locks supply the semantics absent from the pinned Rust standard library on Android. Missing layout metadata with existing data is a recovery error, so a device binding cannot be silently discarded. Scratch/cache cleanup unlinks links without following their targets and cannot descend into the vault. `verifySecureStorage` uses the OS-mechanics callback from #91 and a storage-owned `ApplicationBinding` locator. Rust creates an opaque 32-byte device record and persists only its SHA-256 in layout metadata. A missing or mismatched protected record for an already bound layout reports `RestoredWithoutKeystore` or `InvalidSecureBinding`; it never creates a replacement that would conceal key loss. Locked/unavailable storage has separate recovery kinds. #93 supplies the [real Android Keystore adapter](keystore.md); startup verifies the binding through it before presenting the shell. The contract is tested with loss/mismatch fixtures. Errors distinguish full storage, read-only storage, access denial, missing roots, concurrent initialization, corrupt configuration/layout and unsupported layout versions. Failed initialization or writes leave existing repository, key and configuration data intact. Free space or repair the reported condition and retry; destructive reset is not an update/recovery operation. Normal APK/Play updates and process recreation retain no-backup app data. Uninstall deletes the sandbox and Keystore entries; recovery then requires the upstream Git repository and GPG key/passphrase or an explicit encrypted export. Backup/transfer is not an alternative recovery path. The manifest disables backup, and both legacy backup rules and cloud/device-transfer rules exclude all nine credential- and device-protected domains. No-backup and cache roots are intrinsically excluded by Android. See the [Android backup contract](https://developer.android.com/identity/data/autobackup). Validation includes real GPG fixture byte preservation through updates and directory relocation, valid/corrupt TOTP catalogs, interrupted writes/setup, concurrent scratch ownership, link-safe cleanup, device-binding loss/mismatch, and typed storage-pressure/read-only I/O faults. A separate integration test activates isolated roots and uses existing local onboarding to create an actual Git repository and protected GPG key without environment/path overrides. Instrumented tests inspect installed backup XML/manifest resources and execute private initialization/recreation/cache clearing on persistent 4 KB and strict 16 KB Android emulators. No physical-device testing is required. Delivery verification on 2026-10-08: all four required workspace gates passed (431 Rust tests), Android ktlint/unit/lint gates passed, and each persistent API 37 emulator (4 KB and strict 16 KB) passed eight native tests and two app tests covering all 25 shell state/tab combinations and installed backup policy. The native fixture creates a real protected GPG key and Git repository through Rust, persists a preference atomically, and survives subsequent APK/test-process updates. Debug and release install-over/startup tests preserve the layout on both emulators. All three ABI payloads pass the APK/AAB native audit. Swift private runtime/redaction tests and 588 shared contract entries pass; iPhone, Watch and AutoFill simulator builds pass. The persistent iPhone app also launches with its existing store intact. Backup verification reads installed policy and package flags; it does not export sandbox data to a transport.