Add legacy DuckDB migration detection FFI exports
Details
Since the Rust core no longer includes the DuckDB library, migration
from old .duckdb files must be orchestrated from the C# managed side.
These FFI functions support that workflow:
- privstack_has_legacy_databases(path) → bool: detects if any .duckdb
files exist at the data path
- privstack_list_legacy_databases(path) → JSON array of found files
with names, paths, and sizes
- privstack_archive_legacy_databases(path): renames .duckdb files to
.duckdb.bak after successful migration, removes stale WAL files
The C# side should check for legacy databases on first launch with the
new version, run its migration logic (reading DuckDB via its own driver
and writing to the new SQLite/SQLCipher database), then call archive
to move the old files out of the way.
Implement two-phase FFI initialization for SQLCipher encryption
Details
The database can no longer be opened immediately at init time because
SQLCipher requires a password-derived key. This commit restructures
the FFI layer into a two-phase initialization:
Phase 1 (privstack_init): If a salt file exists (encrypted DB), opens
an in-memory placeholder so the handle exists for non-DB operations.
If no salt file exists (first run / legacy), opens unencrypted as before.
Phase 2 (auth_initialize / auth_unlock): Opens the real encrypted
database and swaps the Connection inside the shared Arc<Mutex<Connection>>.
All stores (entity, event, blob, vault) automatically use the new
connection on their next operation since they share the same Arc.
Key changes:
- PrivStackHandle gains a main_conn field holding the shared connection Arc
- PrivStackHandle::swap_connection() replaces the inner Connection and
re-initializes all store schemas on the new database
- EntityStore, EventStore, BlobStore gain reinitialize_schema() methods
for re-running CREATE TABLE IF NOT EXISTS after a connection swap
- VaultManager gains reinitialize_vaults() to clear cached Vault instances
so they re-create tables on the new connection
- auth_initialize: generates salt, derives Argon2id key, creates encrypted
DB via open_db(), swaps connection, writes salt file, then inits vault
- auth_unlock: reads salt, derives key, opens encrypted DB, swaps
connection, then unlocks vault
- auth_lock: swaps back to in-memory placeholder for encrypted DBs
- auth_change_password: rekeys DB with PRAGMA rekey + fresh salt
- Recovery functions: rekey DB after vault password reset
- auth_is_initialized: checks salt file existence as primary indicator
- Salt file: 16-byte random salt at <db_path>.privstack.salt
- derive_db_key helper: Argon2id -> raw hex key for SQLCipher
Clean up remaining DuckDB references in comments
Details
Replace stale DuckDB references with SQLite in comments across
cloud sync engine, datasets preprocessor/mutations/helpers, and
FFI dataset queries. No functional changes.
Consolidate 5 database files into 2 in FFI initialization
Details
Replace 4 separate database opens (vault.db, blobs.db, entities.db,
events.db) with a single shared connection to privstack.db. All stores
(VaultManager, BlobStore, EntityStore, EventStore) now use
open_with_conn() to share one Arc<Mutex<Connection>>.
datasets.db remains separate and unchanged.
Updated both init_core() and init_with_plugin_host_builder() cfg-gated
init functions. Updated privstack_db_diagnostics() and
privstack_compact_databases() to reflect the new file layout — scanning
privstack.db + datasets.db instead of the old 5-file set.
Remove duckdb from workspace and update FFI file paths
Details
Phase 7 (Consolidation — partial):
- Remove duckdb workspace dependency from Cargo.toml entirely
- Update all FFI database file extensions: .duckdb → .db
- Update FFI comments: DuckDB → SQLite throughout
- Full workspace compiles cleanly with zero duckdb references
- All tests pass except 2 pre-existing P2P relay timing tests
(dht_sync_code_bidirectional, multi_entity_divergence_real_p2p)
which are network-timing-sensitive and unrelated to the storage
migration
Remaining Phase 7 work (separate commit):
- Consolidate 5 separate .db files into 2 (privstack.db + datasets.db)
with shared connections — requires reworking the FFI startup flow
- Implement one-time DuckDB→SQLite data migration for existing users
- Wire up SQLCipher-authenticated startup flow
Get notified about new releases