Skip to main content

kmp_embedded/
migration.rs

1//! Store migration, composed.
2//!
3//! The machinery lives in the storage adapter; what belongs here is the one
4//! decision it deliberately does not make — how a context event becomes
5//! projection mutations. That derivation is the application's, and injecting
6//! it here is what keeps the adapter free of the layer above it and the MCP
7//! binary free of the layer below.
8
9use std::path::Path;
10
11use kmp_adapter_embedded::{EmbeddedKernelStore, StorageEngine, StoreMigrationReceipt};
12use kmp_domain::PortError;
13
14/// Migrates `source_dir` into `destination_dir`, returning what was moved.
15/// The destination is created with the default engine.
16///
17/// The source is never opened for writing and its bytes are verified
18/// unchanged when the migration finishes; the destination must not already
19/// hold a store.
20pub async fn migrate_data_dir(
21    source_dir: &Path,
22    destination_dir: &Path,
23) -> Result<StoreMigrationReceipt, PortError> {
24    migrate_data_dir_to(source_dir, destination_dir, StorageEngine::Redb).await
25}
26
27/// [`migrate_data_dir`] with the destination engine chosen — how a store
28/// changes engines (ADR-018). A redb store becomes a SQLite one that two
29/// agent hosts can share by replaying its history into a fresh directory;
30/// the source stays as it was and the receipt records both layouts.
31pub async fn migrate_data_dir_to(
32    source_dir: &Path,
33    destination_dir: &Path,
34    destination_engine: StorageEngine,
35) -> Result<StoreMigrationReceipt, PortError> {
36    crate::data_dir::ensure_data_dir_skeleton(destination_dir)?;
37    let (_store, receipt) = EmbeddedKernelStore::migrate_data_dir_to(
38        source_dir,
39        destination_dir,
40        destination_engine,
41        kmp_application::projection_mutations_for_context_event,
42    )
43    .await?;
44    Ok(receipt)
45}
46
47/// Migrate once, reopen afterwards. Safe on every start: a destination that
48/// already holds a store is opened as it is, and the returned receipt says
49/// whether this call was the one that migrated it.
50pub async fn open_or_migrate_data_dir(
51    source_dir: &Path,
52    destination_dir: &Path,
53) -> Result<Option<StoreMigrationReceipt>, PortError> {
54    crate::data_dir::ensure_data_dir_skeleton(destination_dir)?;
55    let (_store, receipt) = EmbeddedKernelStore::open_or_migrate_data_dir(
56        source_dir,
57        destination_dir,
58        kmp_application::projection_mutations_for_context_event,
59    )
60    .await?;
61    Ok(receipt)
62}