ADR-0029: Export Format and Scope
DateFebruary 24, 2026
CategoryData Architecture
Tagspersistence-localprivacy-security
Context
- Phase 6 (Settings) includes "Data management (export, backup, clear data)" as a scope item.
- LocusFlow is local-first and offline-only (ADR-0002, ADR-0005). There is no cloud backup. If the user loses their device or uninstalls the app, all data is gone.
- ADR-0010 (Deletion vs Archival) explicitly defers audit/recovery of raw captures to "optional export features."
- ADR-0002 lists "define a secure export/import mechanism and backup strategy" as follow-up work.
- Users need a way to create portable backups of their data and, optionally, to inspect or migrate it.
- The app stores data across two persistence mechanisms: Room database (domain entities) and DataStore (settings and feature flags, per ADR-0027/ADR-0028).
- Privacy is paramount: export files may contain sensitive reflection content and must be treated as user-controlled artifacts with no cloud upload from the app.
Constraints:
- No network: export is to local device storage or user-initiated sharing (via Android share sheet).
- Must handle all current domain entities: inbox items, processed items, categories, contexts, daily reflections, reflection answers, reflection questions.
- Must handle settings (from DataStore).
- Should be human-readable for transparency and user trust.
- Must be importable back into the app for restore scenarios.
- The app is pre-release; the export format will evolve. Versioning is essential from day one.
Decision
We will implement a JSON-based full export/import mechanism with a versioned envelope schema.
Export format
A single .json file containing all user data:
{
"format_version": 1,
"app_version": "1.0.0-beta1",
"exported_at": "2026-02-24T10:30:00Z",
"device_timezone": "America/Los_Angeles",
"settings": {
"theme_mode": "dark",
"timezone_override": "",
"llm_idle_timeout_minutes": 5,
"feature_flags": {
"feature_llm_reflection_summary": true,
"feature_llm_morning_briefing": false
}
},
"data": {
"inbox_items": [
...
],
"processed_items": [
...
],
"categories": [
...
],
"contexts": [
...
],
"processed_item_categories": [
...
],
"processed_item_contexts": [
...
],
"daily_reflections": [
...
],
"daily_reflection_answers": [
...
],
"reflection_questions": [
...
]
}
}
Envelope schema
| Field | Type | Description |
|---|
format_version | Int | Schema version of the export file. Starts at 1. Incremented when breaking changes are made to the export structure. |
app_version | String | App version that produced the export, for diagnostic purposes. |
exported_at | String (ISO-8601 UTC) | Timestamp of export. |
device_timezone | String (IANA) | Device timezone at export time, for context. |
settings | Object | Flattened settings from DataStore (ADR-0027). |
data | Object | One key per Room table, each containing an array of row objects. |
Data section mapping
Each Room table is exported as a JSON array of objects. Column names map directly to JSON keys. Types are preserved as follows:
Long → JSON number
String → JSON string
Boolean → JSON boolean
Instant (UTC timestamps) → ISO-8601 string
LocalDate (day keys) → "YYYY-MM-DD" string
- Nullable fields →
null or omitted
Export scope options
The Settings screen offers two export scopes:
| Scope | Contents | Use case |
|---|
| Full backup (default) | All domain data + settings + feature flags | Device migration, disaster recovery |
| Reflections only | Daily reflections, answers, and questions | Journaling portability, sharing with a therapist or coach |
Additional scopes (e.g., "processed items only") can be added later without changing the envelope format — just omit keys from the data object.
Import behavior
- Full restore: Clears all existing data, then inserts the imported data. The app confirms with the user before proceeding ("This will replace all current data. Continue?").
- Merge import: Not supported in the initial implementation. Conflict resolution (duplicate IDs, overlapping day keys) is complex and deferred to a future ADR if needed.
- Version compatibility: The app checks
format_version on import. If the file version is higher than the app supports, import is rejected with a message to update the app. If lower, the app applies forward-compatible transformations (e.g., ignoring unknown keys, providing defaults for missing fields).
File handling
- Export file name:
locusflow-backup-YYYYMMDD-HHmmss.json
- Export destination: user-selected via Android's Storage Access Framework (
ACTION_CREATE_DOCUMENT).
- Import source: user-selected via
ACTION_OPEN_DOCUMENT.
- No automatic/scheduled exports in the initial implementation.
Architecture integration
app/
├── domain/
│ ├── model/
│ │ └── ExportData.kt # Data classes for export envelope
│ ├── usecase/
│ │ ├── ExportDataUseCase.kt # Orchestrates data collection and serialization
│ │ └── ImportDataUseCase.kt # Orchestrates deserialization and data insertion
│ └── repository/
│ └── ExportRepository.kt # Interface for export I/O (write/read file)
├── data/
│ └── export/
│ └── FileExportRepository.kt # SAF-based implementation
└── di/
└── ExportModule.kt # Hilt bindings
Serialization
- Use
kotlinx.serialization (already available in the Kotlin ecosystem) for JSON serialization/deserialization.
- Export runs on
Dispatchers.IO in a coroutine.
- Large exports stream to the output file to avoid OOM on devices with many items.
Rationale
- JSON over SQLite raw backup: A raw
.db file copy is simpler to implement but is opaque to users, ties the backup to Room's internal schema, and breaks if the schema changes between versions. JSON is human-readable, inspectable, and schema-independent.
- JSON over CSV: CSV cannot represent nested or relational data cleanly (e.g., reflection → answers). JSON handles hierarchical data naturally. CSV might be offered as a secondary export for individual tables in the future.
- JSON over Protobuf: Protobuf is more compact and type-safe but not human-readable. Transparency and user trust outweigh compactness for a privacy-focused app. Export files will typically be a few hundred KB to a few MB.
- Versioned envelope: Adding
format_version from the start avoids the classic "how do I import an old backup?" problem. Forward-compatible import rules (ignore unknown, default missing) minimize the need for complex migration code.
- Full restore over merge: Merge import requires conflict resolution for IDs, day keys, and relationships. This is significant complexity for a pre-release app. Full restore (clear + insert) is simple and sufficient for the primary use case (device migration or reinstall).
- SAF over fixed file path: Storage Access Framework gives users control over where the file is saved (internal storage, SD card, Google Drive folder, etc.) and respects Android's scoped storage restrictions.
- Two export scopes: Full backup covers disaster recovery. Reflections-only serves the specific use case of sharing reflection data externally (e.g., exporting journal entries). This keeps the UI simple while covering the two most common needs.
Consequences
-
Positive:
- Users have a tangible, portable backup of all their data — critical for a local-only app.
- Human-readable format builds trust and allows users to inspect what the app stores.
- Versioned envelope future-proofs the format for schema changes.
- SAF integration works with any file provider the user has installed (cloud drives, file managers).
- Export/import is testable: use cases can be unit-tested with in-memory data and string output.
-
Downsides:
- JSON is verbose; large datasets produce sizable files. Acceptable for a personal productivity app (unlikely to exceed a few MB).
- No merge import means restoring a backup is destructive. Users must be clearly warned.
- No automatic/scheduled backups in the initial implementation; users must remember to export manually.
- Export format will evolve as new tables are added (weekly syntheses, personal visions, habits). Each addition requires a
format_version bump and import compatibility handling.
-
Follow-up work:
- Implement
ExportDataUseCase, ImportDataUseCase, ExportRepository.
- Add export/import actions to the Settings screen under "Data Management".
- Add a "Clear all data" action to the Settings screen (separate from import; simply deletes everything).
- Consider scheduled/automatic local backups in a future phase.
- Consider optional encryption of export files (see ADR-0030 for privacy considerations).
- Bump
format_version when new tables (weekly syntheses, personal visions) are added in later phases.
Alternatives Considered
- Raw Room database copy (
.db file) — Rejected: opaque, schema-dependent, not human-readable, breaks on schema changes, and doesn't include DataStore settings.
- CSV export — Rejected as primary format: cannot represent relational/nested data. May be offered as a supplementary per-table export in the future.
- Protobuf export — Rejected: not human-readable; transparency matters for a privacy-first app.
- Automatic scheduled backups via WorkManager — Deferred: adds complexity (where to save, storage management, notification of success/failure). Good future enhancement but not required for Phase 6.
- Merge import with conflict resolution — Deferred: significant complexity for ID mapping, duplicate detection, and relational integrity. Not worth the cost for pre-release.
- Encrypted export by default — Deferred: adds key management complexity (password-based encryption, key derivation). The export file is user-controlled and lives on their device or their chosen storage. Optional encryption can be layered on later.
Notes
- The "Clear all data" action in Settings should clear both Room and DataStore, effectively resetting the app to a fresh install state. This is distinct from import (which replaces data) and should require a separate confirmation dialog.
- Export files do NOT include the LLM model file (~700 MB). Model re-download is handled separately via the LLM model management section of Settings (ADR-0021).
- The
format_version field is for the export schema, not the app version. Multiple app versions may share the same format_version if the export schema hasn't changed.
- Consider adding a SHA-256 checksum field to the envelope in a future version to detect file corruption during transfer.
- See ADR-0031 for the full export format versioning strategy: how
format_version is bumped, how
old exports are upgraded via a transformation chain, forward-compatibility rules, and testing
requirements.