Skip to content

feat: migrate from v1 to v2 db - #65

Open
OffRange wants to merge 29 commits into
v2from
feat/migration
Open

feat: migrate from v1 to v2 db#65
OffRange wants to merge 29 commits into
v2from
feat/migration

Conversation

@OffRange

@OffRange OffRange commented Aug 1, 2026

Copy link
Copy Markdown
Owner

No description provided.

OffRange and others added 25 commits July 27, 2026 19:49
…e name

Gives callers outside :core:item a way to commit a batch of item writes as
one atomic transaction without reaching for the module-internal
ItemDatabase. Also locks in the v2 database file name so it can never
collide with the inherited v1 secure_element_database file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…at versions 1-3

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rrect the schema guard docs

The identity hash is computed over sorted fields and Room validates an opened
file by column name, so column order is runtime-inert. Say that in the KDocs
instead of the reverse, and state that the hash assertion and the DDL assertion
cover different things so neither gets deleted as redundant.

Record what actually happens when a version 2 file carries a NULL title or NULL
data: the generated recreate aborts and the file cannot be opened at all. Also
assert the cross ref cascade, count() on a populated database, and that favorite
and modified_at survive the migrations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A NULL title or data in a version 1 or 2 file aborts Room's generated 2-to-3
recreate and makes the whole database unopenable, costing the user every other
row. Repair those two columns in place before Room ever sees the file, leaving
the auto-migrations and the identity hash untouched.

The repair is the smallest thing that clears the constraint: an empty title and
an empty blob, never a deletion. An empty blob cannot decrypt, so the import
reports that row as one failed item instead of losing the file.

Guards against creating a missing file, touching an already-migrated version 3
file, or assuming SecureElement exists, and takes the driver as a parameter so
tests run on the bundled driver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tests

Wraps the sanitizer's count read and both UPDATEs in a single transaction so a
crash mid-repair cannot leave the file half-fixed, inlines the hasTable table
name to remove an unescaped-literal pattern, and makes selectInt fail loudly
on a query with no row instead of returning a garbage 0.

Adds a version-1 repair test (the population this module exists for) and
rewrites the version-3 guard test so it actually fails if the guard is
removed, rather than vacuously passing because a real v3 file can never hold
the NULL it was checking for. Turns the stalled agent's debug println into a
real assertion pinning that an aborted 2-to-3 recreate rolls the file back to
version 2, and fixes a KDoc typo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wires the ported v1 database, the Keystore cipher and the detail parser into
a repository that reads every row, decrypts it, parses it, and returns typed
LegacyItems plus the rows it could not import.

LegacyDatabaseProvider gains its implementation. It sanitizes the inherited
file before Room can open it, because Room runs the 2-to-3 recreate on the
first query and one NULL title or blob aborts it and takes every other row
down with it. The repaired-row count is surfaced so the migration can report
it, and a file that cannot even be sanitized becomes an answer rather than an
exception thrown across the unlock flow.

The Keystore alias is probed once per run instead of once per row. A gone
alias means nothing in the file is recoverable, which is a different outcome
from a single row that will not decrypt, and the two call for opposite
handling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The legacy reader only ever reads a database it inherited, but Room creates
any file it is asked to open, so a read on an install that never ran v1 would
leave an empty secure_element_database behind and every later run would find
it and treat it as inherited data.

This was a KDoc contract on LegacyItemRepository that callers had to remember.
Make it true by construction instead: the provider returns null when the file
is not there, next to the sanitizer call, since both guards are about the
file's state before Room is allowed to touch it. state() keeps asking the file
first so a clean install reads as Absent rather than Unreadable, which matters
because those lead to opposite places.

The provider gained a driver seam, left null in production so Room keeps the
framework helper it has always used. Without it Room cannot create a file
under a JVM test at all, and the test asserting no file appears would hold
whether or not the guard existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two stacked KDoc blocks orphaned the class documentation; only the @param
block was attached to the declaration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
state() no longer folds every withDao query failure into NotLegacy. NotLegacy
now rests only on LegacySecureElementProbe positively answering false that the
SecureElement table is absent; a query failure (corrupt page, a disk that
fills mid-recreate, a cancelled run) reports Unreadable instead, so a file
that was never shown to be free of v1 data can no longer be deleted on a
guess. The probe's hasSecureElementTable()/selectInt() extensions are lifted
out of LegacyDatabaseSanitizer into a shared LegacySecureElementProbe so the
sanitizer and the repository can never drift into disagreeing about what a v1
file is.

Also: withDao rethrows CancellationException instead of folding it into
DatabaseUnreadable through runCatching, so a run cancelled by the unlock scope
going away no longer answers for what is on disk. SanitizingLegacyDatabaseProvider.repairedRows
is marked @volatile since it is written under a lock but read through a plain
getter from another thread. LegacyReadFailure is narrowed to internal.

Deduplicates the schema-seeding test helper that was copy-pasted across
LegacyDatabaseOpenTest, LegacyDatabaseSanitizerTest and
SanitizingLegacyDatabaseProviderTest into one LegacySchemaSeed.kt, and adds
LegacySecureElementProbeTest plus new LegacyItemRepositoryImplTest coverage
for the state()/cancellation/deleteDatabase-ordering behaviour above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…solation

Gates the run on LegacyItemRepository.state(): Absent does nothing, NotLegacy
deletes the stale file, Unreadable is left exactly where it was found, and only
Present reads. Rows that fail are reported and left behind; the ones that made
it are pruned, so a retry cannot duplicate anything.

Deleting the inherited file and v1's Keystore alias only happens once the data
is provably in v2: no failed row, a successful prune, and a legacy file that
counts zero rows afterwards. The alias goes last and only if the file went.

Corrects four defects in the plan's version of this step:

- readAll/prune/remainingCount return Result, not raw values. Transcribing the
  planned call would have read a failed read as an empty file and deleted the
  user's data unseen.
- LegacyDatabaseState.Unreadable had no branch at all.
- The Keystore alias was deleted whether or not the file actually went, which
  would leave encrypted rows on disk with nothing left to open them.
- A cryptographic scope that will not open was blamed on the row as
  UndecryptablePassword, once per row. It now stops the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs MigrateLegacyDataUseCase on all four ways into the app: password create
access, biometric create access, password unlock, and biometric unlock. The
biometric unlock path finished in the composable and navigated on its own, so
it now hands back to the ViewModel through onBiometricUnlockSucceeded and
navigates via navigationEvent instead; left as it was it would have been the
one entry point that never retried a partial import, and the one most
returning users take daily.

The import is waited for rather than launched and forgotten, because
MainActivity pops AuthRoute with popUpTo(inclusive), which clears this
ViewModel and cancels the scope underneath it. An import left running would be
killed on every unlock and never finish.

That wait is what importLegacyData exists to make safe. Unlock is how the user
gets back to their data, so nothing about an optional one-off copy may stand
between them and it.

Corrects three defects in the plan's version of this step:

- migrateLegacyData() was called bare inside viewModelScope.launch. Its
  invoke() is not total: state() and the NotLegacy deleteDatabase() sit outside
  its own catch, and it rethrows CancellationException on purpose. A throw
  skipped handleAuthenticationResult, so the navigation event never fired and
  the loading flag never cleared, stranding a user whose session was already
  live on the lock screen behind a permanent spinner. It is now caught, and a
  run that stops making progress is dropped after 30 seconds.
- the new biometric path ended in trySend on a rendezvous channel whose only
  collector runs under repeatOnLifecycle(STARTED). With a migration now sitting
  between the unlock and the send, backgrounding the app during it dropped the
  event and left the user locked out of an already unlocked vault. Uses send.
- feature/auth alone could not resolve the Koin definition, since implementation
  does not put the module on :app's classpath. Adds the dependency to
  app/build.gradle.kts, mirroring migration:create-access. The plan's fallback
  pointed at app/di/Koin.kt, which does not exist in this repo.

AuthViewModel cannot be built in a JVM unit test (SavedStateHandle.toRoute
lands in android.os.BaseBundle), so the behaviour lives in importLegacyData,
which takes the migration as a lambda and is tested directly with no mocks.
AuthViewModelMigrationTest guards the call sites, including that none of them
calls the use case unguarded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The import was awaited inside the unlock flow under a 30s budget. That put
the whole thing on the main thread (nothing in :migration:legacy-data hops
off Main, and the per-row decrypt loop has no suspension point for the
timeout to land on), inside the compare-and-set lambda of
MutableStateFlow.update, and ahead of the navigation event. The budget
could not help: the import is atomic, so a database too big to finish in
30s rolls back, prunes nothing and dies at the same point on every unlock,
forever.

Start it on an application-scoped coroutine on Dispatchers.IO instead and
return at once. Unlock latency stops depending on the size of the user's v1
database, and with no wait there is nothing to budget.

StartLegacyDataImportUseCase lives in :migration:legacy-data next to the
use case it drives, so migration-only code stays in the migration module
and @componentscan picks it up unchanged. Its LegacyImportRunner holds the
single-flight gate: two concurrent runs would import every row twice under
ids neither run can recognise as the other's, and a second unlock during a
run is reachable on its own. Nothing escapes into the app scope, where an
uncaught throwable would take the process down, and the throwable is now
logged rather than discarded.

Locking the vault mid-run fails that run, which leaves the legacy file as
it found it and retries on the next unlock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LegacyImportRunner discarded the returned LegacyMigrationOutcome, so every
expected failure from MigrateLegacyDataUseCase (Failed, and a Migrated with
row failures) produced no signal at all. The seam now reports Failed's
cause and a summary of row failures by reason, while leaving the clean
endings silent. Also corrects the KDoc's false claim about a lock
interrupting an in-flight run (no such lock exists today) and closes the
gap where a throwing report implementation could still reach the scope.
Wires the real Room database, v1's real AES-GCM cipher, the real detail
parser and the real converter together and drives them through the real
use case. Two properties only exist between links and so could not be
proved by the per-component tests: v1's double encryption, where a
password comes back as plaintext only if the repository, the parser and
the converter each undo their own layer, and the prune-then-delete
order, where the inherited file is destroyed only once the rows it held
are provably in v2.

The probe is the production one over a real file rather than a fake, so
the NotLegacy verdict that deletes the user's file is earned from the
bytes each test put on disk. The stale v2 fixture is therefore a real
database with no SecureElement table; a file of arbitrary bytes is a
corrupt file, which the probe answers null for and which must be left
where it was found.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…2e suite

The end-to-end no-data-loss suite drove the probe's true and false answers but
never null, so a mutation folding Unreadable into NotLegacy (== false -> !=
true) would delete a corrupt or half-restored file and take the v1 Keystore
alias with it while every existing test stayed green. Add a test that corrupts
the database file directly, confirming the run fails and the file and
Keystore alias survive.

Also read back the migrated credit card's fields, which were counted but
never asserted, and note why readBackPassword's missing wrapped item key is
only safe because the fake crypto provider ignores it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ould not clear

SQLite's bound-parameter limit was 999 until 3.32.0, a ceiling Android's system
SQLite still enforces through API 30. prune() bound one parameter per legacy id
in a single IN (...) delete with no chunking, so a v1 vault over that size would
have every prune throw, be reported as a clean success, and reimport the whole
vault on every subsequent unlock. Chunk the delete, and give the report a
fileRetained flag so a prune/count/delete failure is reported even when no row
failed to read or convert - hasFailures alone couldn't see that ending.

Found by the final whole-branch review across all 22 commits on this branch.
Copilot AI review requested due to automatic review settings August 1, 2026 10:09
@OffRange OffRange linked an issue Aug 1, 2026 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the infrastructure to migrate legacy KeyGo v1 data into the v2 vault, adds supporting migration modules, and updates core domain/data layers (timestamps, transactions, session start signaling) to support reliable import execution without conflicting with the legacy on-disk database.

Changes:

  • Added a new :migration:legacy-data module that can open and migrate the v1 secure_element_database, read/decrypt/parse rows, convert them into v2 items, and import them atomically with extensive JVM test coverage.
  • Added a new :migration:create-access module to read/validate/clear the v1 “main password” state via Proto DataStore.
  • Introduced item timestamps + an ItemTransactionRunner abstraction, added Session.sessionStarts to trigger import on every unlock, and renamed the v2 database file to avoid colliding with the legacy v1 database.

Reviewed changes

Copilot reviewed 112 out of 133 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
settings.gradle.kts Registers new nested migration modules (:migration:create-access, :migration:legacy-data).
scripts/new-module.sh Improves key-read reliability and PascalCase generation for module scaffolding.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeRegistrableDomainResolver.kt Adds deterministic domain resolver fake for migration tests.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyKeyRepository.kt Adds fake Keystore-backed legacy key repository for tests.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyItemRepository.kt Adds in-memory legacy item repository fake for tests.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyElementDao.kt Adds in-memory DAO fake for pruning/counting tests.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyDatabaseProvider.kt Adds fake legacy DB provider for tests (open/close/delete behavior).
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyDatabase.kt Adds lightweight fake Room database container for tests.
migration/legacy-data/src/testFixtures/kotlin/de/davis/keygo/migration/legacy_data/data/FakeLegacyCipher.kt Adds reversible cipher fake + failure modes for migration tests.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/LegacyMigrationRealDatabaseTest.kt Adds “real captured v1 DB” end-to-end migration test.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/domain/usecase/LegacyImportRunnerTest.kt Adds concurrency/containment/retry semantics tests for the import runner.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/domain/mapper/LegacyItemConverterTest.kt Adds converter tests for passwords/cards, strength mapping, timestamps, domain parsing.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/repository/LegacyItemRepositoryImplTest.kt Adds repository tests for read/prune/count/delete + file-creation guard.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/local/LegacyTestContext.kt Adds minimal Context stub to open legacy DB via production provider in JVM tests.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/local/LegacySchemaSeed.kt Adds helpers to seed DBs from v1 exported schemas for migration tests.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/local/LegacySchemaIdentityTest.kt Pins Room identity hashes to ensure ported legacy schema stays byte-faithful.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/local/LegacyMigrationTest.kt Tests the handwritten v1 2→3 migration behavior and schema correctness.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/local/LegacyDatabaseOpenTest.kt Verifies ported entities + migrations open v1 DB files across versions.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/json/LegacyDetailParserTest.kt Adds parser tests for v1 JSON quirks and malformed input tolerance.
migration/legacy-data/src/test/kotlin/de/davis/keygo/migration/legacy_data/data/crypto/LegacyAesGcmCipherTest.kt Adds crypto framing tests for v1 AES-GCM format.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/usecase/MigrateLegacyDataUseCase.kt Implements the v1→v2 import orchestration + safe deletion gating.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/usecase/LegacyImportRunner.kt Adds single-flight, retry-on-unlock runner with containment boundary.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/usecase/LegacyDataImportStarter.kt Wires import execution to every session start in an application IO scope.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/repository/LegacyKeyRepository.kt Introduces legacy Keystore key access abstraction.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/repository/LegacyItemRepository.kt Defines legacy DB read/prune/count/delete contract and read result model.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyStrength.kt Adds v1 password strength enum in ordinal order (load-bearing).
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyReadFailure.kt Adds whole-run read failure reasons (empty/unreadable/key unavailable).
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyMigrationReport.kt Adds migration report + row failure detail model.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyMigrationOutcome.kt Adds outcome sealed interface + migration exception type.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyItem.kt Adds legacy row domain model + legacy tag prefix constant.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyFailureReason.kt Adds per-row failure reasons for migration reporting.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/model/LegacyDetail.kt Adds decoded v1 detail models + type discriminator constants.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/mapper/LegacyItemConverter.kt Converts legacy rows into v2 items and re-encrypts secrets under v2 scope.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/domain/crypto/LegacyCipher.kt Adds legacy cipher interface to decrypt v1 blobs using resolved key.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/di/MigrationLegacyDataModule.kt Adds Koin module scanning + includes DB module wiring.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/di/LegacyDatabaseModule.kt Provides LegacyDatabaseProvider via Koin.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/repository/LegacyKeyRepositoryImpl.kt Implements legacy Keystore alias lookup + deletion (non-throwing).
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/repository/LegacyItemRepositoryImpl.kt Implements legacy Room-based read/prune/count/delete with safety gating.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/mapper/LegacyElementMapper.kt Maps decrypted rows to LegacyItem while filtering discriminator tags.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/pojo/LegacyElementWithTags.kt Adds Room relation POJO for SecureElement + tags junction.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/migration/LegacyMigration2To3.kt Adds handwritten v1 2→3 migration handling null title/data safely.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/entity/LegacyTagEntity.kt Ports v1 Tag entity.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/entity/LegacySecureElementTagCrossRef.kt Ports v1 junction entity with FK cascades.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/entity/LegacySecureElementEntity.kt Ports v1 SecureElement entity with schema fidelity constraints.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/datasource/LegacyDatabaseProvider.kt Defines provider contract that must not create legacy DB files.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/datasource/LegacyDatabase.kt Defines Room3 legacy database + auto-migration 1→2.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/datasource/AndroidLegacyDatabaseProvider.kt Implements “open legacy file only if exists” guard + registers 2→3 migration.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/local/dao/LegacyElementDao.kt Adds DAO queries for read/count/prune and insert-only-for-tests helpers.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/json/LegacyDetailParser.kt Parses v1 GSON-shaped JSON into strongly typed legacy details.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/json/LegacyDetailJson.kt Serializable JSON mirror of v1 detail payload shapes.
migration/legacy-data/src/main/kotlin/de/davis/keygo/migration/legacy_data/data/crypto/LegacyAesGcmCipher.kt Implements v1 AES-GCM blob decryption (IV prefix framing).
migration/legacy-data/schemas/de.davis.keygo.migration.legacy_data.data.local.datasource.LegacyDatabase/3.json Adds exported Room schema for legacy DB v3.
migration/legacy-data/schemas/de.davis.keygo.migration.legacy_data.data.local.datasource.LegacyDatabase/2.json Adds exported Room schema for legacy DB v2.
migration/legacy-data/schemas/de.davis.keygo.migration.legacy_data.data.local.datasource.LegacyDatabase/1.json Adds exported Room schema for legacy DB v1.
migration/legacy-data/consumer-rules.pro Consumer ProGuard/R8 rules for legacy-data module.
migration/legacy-data/build.gradle.kts Adds module build config, dependencies, Room3 schema export/test seeding wiring.
migration/create-access/src/main/proto/main_password.proto Defines Proto DataStore model for legacy main password data.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/usecase/ValidateMainPasswordUseCase.kt Renames and tidies main password validation use case.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/usecase/HasMainPasswordUseCase.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/usecase/ClearMainPasswordUseCase.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/repository/MainPasswordRepository.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/repository/HashValidator.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/domain/model/MainPassword.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/di/MigrationCreateAccessModule.kt Provides qualified Proto DataStore and enables component scan.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/di/annotation/MainPasswordQualifier.kt Adds Koin qualifier annotation for main password DataStore.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/data/repository/MainPasswordRepositoryImpl.kt Implements DataStore-backed main password repository.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/data/repository/HashValidatorImpl.kt Minor formatting/update.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/data/mapper/MainPasswordMapper.kt Maps proto timestamp + hash into domain model.
migration/create-access/src/main/kotlin/de/davis/keygo/migration/create_access/data/local/datasource/datastore/MainPasswordSerializer.kt Serializer for Proto DataStore read/write.
migration/create-access/src/main/AndroidManifest.xml Adds module manifest stub.
migration/create-access/proguard-rules.pro Adds module proguard rules template.
migration/create-access/consumer-rules.pro Adds consumer rules for protobuf/datastore keep rules.
migration/create-access/build.gradle.kts Adds module build config + DataStore/protobuf/bcrypt deps.
migration/create-access/.gitignore Ignores module build output.
migration-create-access/src/main/AndroidManifest.xml Removes old module manifest (module path renamed/restructured).
gradle/libs.versions.toml Adds Room3 version + coordinates/plugin aliases.
feature/vault/src/test/kotlin/de/davis/keygo/feature/vault/domain/usecase/MoveItemsToVaultUseCaseTest.kt Updates Login construction to include timestamp.
feature/item/view/src/test/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardViewModelTest.kt Updates CreditCard construction to include timestamp.
feature/item/core/src/test/kotlin/de/davis/keygo/feature/item/core/domain/usecase/CreateNewOrUpdateLoginUseCaseTest.kt Adds timestamp behavior tests + updates helpers for timestamp.
feature/item/core/src/test/kotlin/de/davis/keygo/feature/item/core/domain/usecase/CreateNewOrUpdateCreditCardUseCaseTest.kt Adds timestamp behavior tests + updates helpers for timestamp.
feature/item/core/src/main/kotlin/de/davis/keygo/feature/item/core/domain/usecase/CreateOrUpdateItemUseCase.kt Adds touch() seam and sets modifiedAt on update in shared flow.
feature/item/core/src/main/kotlin/de/davis/keygo/feature/item/core/domain/usecase/CreateNewOrUpdateLoginUseCase.kt Implements touch() and sets timestamp on create.
feature/item/core/src/main/kotlin/de/davis/keygo/feature/item/core/domain/usecase/CreateNewOrUpdateCreditCardUseCase.kt Implements touch(), uses shared expiration parser, sets timestamp on create.
feature/autofill/src/test/kotlin/de/davis/keygo/feature/autofill/presentation/dataset/SuggestionFinderTest.kt Updates Login test fixtures to include timestamp.
feature/autofill/src/test/kotlin/de/davis/keygo/feature/autofill/presentation/activity/AutofillViewModelTest.kt Updates Login test fixture to include timestamp.
feature/autofill/src/test/kotlin/de/davis/keygo/feature/autofill/domain/usecase/DoesItemHaveDomainReferencesUseCaseTest.kt Updates Login test fixture to include timestamp.
feature/autofill/src/test/kotlin/de/davis/keygo/feature/autofill/domain/usecase/AddRegistrableDomainsToLoginUseCaseTest.kt Updates Login test fixture to include timestamp.
feature/auth/src/main/kotlin/de/davis/keygo/feature/auth/presentation/AuthViewModel.kt Uses renamed main-password validator and adds biometric-unlock success hook.
feature/auth/src/main/kotlin/de/davis/keygo/feature/auth/presentation/AuthScreen.kt Routes biometric success through ViewModel to avoid skipping legacy import flow.
feature/auth/build.gradle.kts Updates dependency path for renamed migration module.
core/security/src/testFixtures/kotlin/de/davis/keygo/core/security/crypto/FakeSession.kt Adds sessionStarts emission flow to session fake.
core/security/src/testFixtures/kotlin/de/davis/keygo/core/security/crypto/FakeCryptographicScopeProvider.kt Adds injectable failure mode for itemScope to support migration tests.
core/security/src/test/kotlin/de/davis/keygo/core/security/domain/usecase/ItemWithCryptoScopeUseCaseTest.kt Updates CreditCard fixture to include timestamp.
core/security/src/test/kotlin/de/davis/keygo/core/security/data/SessionImplTest.kt Adds tests ensuring sessionStarts is replayed and not dropped.
core/security/src/main/kotlin/de/davis/keygo/core/security/domain/Session.kt Adds sessionStarts: Flow<Unit> to signal unlocks across entry points.
core/security/src/main/kotlin/de/davis/keygo/core/security/data/SessionImpl.kt Implements replayed, non-suspending sessionStarts emission.
core/item/src/testFixtures/kotlin/de/davis/keygo/core/item/FakeItemTransactionRunner.kt Adds fake transaction runner for atomic import/write tests.
core/item/src/testFixtures/kotlin/de/davis/keygo/core/item/FakeCreditCardRepository.kt Exposes store state for migration and tests.
core/item/src/test/kotlin/de/davis/keygo/core/item/domain/usecase/UpsertItemUseCaseTest.kt Updates item fixtures to include timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/domain/usecase/ObserveAllTagsSortedUseCaseTest.kt Updates Login fixture to include timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/domain/model/LoginTest.kt Updates Login constructor usage for timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/domain/model/CreditCardTest.kt Updates CreditCard constructor usage for timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/LoginRepositoryImplTest.kt Updates Login fixture to include timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/ItemTransactionRunnerImplTest.kt Adds tests for transaction runner delegation semantics.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/repository/CreditCardRepositoryImplTest.kt Updates fixtures and projections to include timestamp mapping.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/mapper/LoginMapperTest.kt Updates mapping expectations for timestamp fields.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/mapper/ItemMapperTest.kt Updates ItemEntity mapping to include timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/mapper/DomainMapperTest.kt Updates Login fixture to include timestamp.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/mapper/CreditCardMapperTest.kt Updates mapping expectations for timestamp fields.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/TagDaoTest.kt Seeds ItemEntity with timestamp for DB tests.
core/item/src/test/kotlin/de/davis/keygo/core/item/data/local/dao/ItemDaoSearchTest.kt Seeds ItemEntity with timestamp for DB tests.
core/item/src/main/kotlin/de/davis/keygo/core/item/domain/repository/ItemTransactionRunner.kt Introduces transaction abstraction to commit batch writes atomically.
core/item/src/main/kotlin/de/davis/keygo/core/item/domain/model/Timestamp.kt Adds domain timestamp model (createdAt/modifiedAt).
core/item/src/main/kotlin/de/davis/keygo/core/item/domain/model/Login.kt Adds timestamp to Login item domain model.
core/item/src/main/kotlin/de/davis/keygo/core/item/domain/model/Item.kt Adds timestamp to Item interface.
core/item/src/main/kotlin/de/davis/keygo/core/item/domain/model/CreditCard.kt Adds timestamp to CreditCard item domain model.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/repository/ItemTransactionRunnerImpl.kt Implements ItemTransactionRunner via Room withTransaction.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/mapper/TimestampMapper.kt Maps between domain and entity timestamp representations.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/mapper/LoginMapper.kt Maps timestamp from ItemEntity into domain Login.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/mapper/ItemMapper.kt Persists domain timestamp into ItemEntity.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/mapper/CreditCardMapper.kt Maps timestamp from ItemEntity into domain CreditCard.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/Timestamp.kt Adds embedded entity timestamp for the item table.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/entity/ItemEntity.kt Embeds timestamp into ItemEntity persistence model.
core/item/src/main/kotlin/de/davis/keygo/core/item/data/local/datasource/ItemDatabase.kt Renames v2 DB file to keygo_database to avoid legacy file collision.
core/item/schemas/de.davis.keygo.core.item.data.local.datasource.ItemDatabase/1.json Updates schema snapshot to include timestamp columns and new identity hash.
build.gradle.kts Applies Room3 plugin alias at root for module use.
app/build.gradle.kts Wires new migration modules into the app module.
.gitignore Ignores Room plugin schema-copy lock files (*.lck).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

OffRange and others added 3 commits August 1, 2026 12:48
The refactor commit changed LegacyAesGcmCipherTest and LegacyMigrationEndToEndTest
to import encryptLikeV1 from de.davis.keygo.migration.legacy_data.data as a shared
top-level function, but never actually created it there — it only ever existed as
a private helper inside the test class it replaced. CI has been failing on the
unresolved reference since.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Room migration

2 participants