Skip to content

Execute the V1 to V3 migration end-to-end in tests - #568

Merged
bradleymackey merged 1 commit into
mainfrom
test/migration-execution
Sep 15, 2026
Merged

bradleymackey merged 1 commit into
mainfrom
test/migration-execution

Conversation

@bradleymackey

Copy link
Copy Markdown
Member

Problem

The V1 → V3 schema migration — the path that moves plaintext killphrases and search passphrases out of the store and into salted-HMAC digests — was never actually executed by any test. PersistedSchemaMigrationPlanTests asserts only the declaration shape (two custom stages, willMigrate != nil), and every test ModelContainer in the repo omits migrationPlan:. The willMigrate closures, the pending-rehash sidecar files, and the Phase B rehash-on-first-unlock were all unexercised, including the documented plaintext-on-disk window between the phases.

Changes (test-only)

New PersistedSchemaMigrationExecutionTests — builds a real on-disk V1 store with plaintext phrases, reopens it through the real PersistedSchemaMigrationPlan exactly as the production opener does, then runs the real rehash services:

  • v1ToV3_migration_writesPendingSidecarsForNonBlankPhrases — willMigrate snapshots exactly the non-blank (itemID, phrase) pairs into both sidecar files.
  • v1ToV3_migration_skipsBlankAndNilPhrases — no sidecar entries for nil/empty phrases.
  • rehashServices_consumeSidecarsAndDigestsVerifyOriginalPhrases — after KillphraseRehashService/SearchPassphraseRehashService run: sidecars are consumed (securely cleared), killphrase deletion fires with the original phrase (the only public observation point for killphrase digests — deliberate, MANIFESTO C5), and the passphrase-hidden item is unreachable without the matcher but returned with it.
  • rehashServices_idempotentWhenSidecarMissing — writer never invoked when there is nothing pending.

Each test gets its own temp directory (created in init, removed in deinit) to avoid cross-test flake. One real-world catch surfaced while writing these: the persisted visibility/searchableLevel strings are the VaultEncodingConstants values ("ALWAYS", "ONLY_PASSPHRASE"), not the Swift enum case names — the tests now seed with the real constants.

Local verification: suite passes, full VaultFeedTests scheme green on iPhone 18 Pro Max / iOS 27.0.

⚠️ Automatic CI is still disabled (#548), so this is local verification only.

🤖 Generated with Claude Code

The willMigrate closures that snapshot plaintext killphrases and
search passphrases into pending sidecar files were never invoked by
any test, and the Phase B rehash was only covered in isolation. Build
a real on-disk V1 store, reopen it through the real migration plan,
run the rehash services, and prove behaviorally that the digests
verify the original phrases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bradleymackey
bradleymackey merged commit 515cc32 into main Sep 15, 2026
@bradleymackey
bradleymackey deleted the test/migration-execution branch September 15, 2026 06:12
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.

1 participant