Wires the migration into startup and points the four bulk stores at drift.
SharedPreferences keeps the settings, which is what it is for.
Startup runs the migration after prefs are up and BEFORE any store reads, and
awaits it. Letting stores race a half-finished migration is precisely the
shape of #333, where a storage path silently chose the wrong key and 566 real
messages read as empty.
Converted: message_store, channel_message_store, contact_store,
contact_discovery_store. All four now have ZERO prefs get/set for bulk data.
Three safety properties, deliberately built in:
1. readWithPrefsFallback - if a key is somehow not in drift, the prefs copy is
still served rather than reading as empty. It logs a WARNING when it fires,
because a fallback during normal operation means the migration is
incomplete and someone needs to know. Silence here is what made #333 look
like data loss.
2. deleteEverywhere / keysWithPrefix span BOTH backends. A clear that only
removed the drift row would leave a pre-migration prefs copy to reappear
through the fallback, resurrecting deleted history.
3. The legacy-key migrations inside the stores now check both backends and
only touch prefs when a legacy key actually exists. This also carries the
#306 fix into this branch: the unconditional prefs.remove was still present
here, since this branched from dev rather than from the #306 work.
Verified: analyze clean, format clean, 504 tests pass, Windows and web both
build. Migration rehearsed earlier against a copy of a real 7 MB store: 69
keys, 5.21 MB, zero failures.
NOT yet run against live data - that happens on first launch of this build,
and the owner should have a prefs backup before that.