Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 76 additions & 10 deletions crates/tracedecay-migrate/src/hermes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ pub struct LegacyHermesMigrationReport {
pub failed: Vec<LegacyHermesMigrationIssue>,
}

struct HermesMigrationRuntime<'a, R, F, H> {
registry: &'a R,
read_pinned_project_root: &'a F,
state_importer: &'a H,
}

/// Migrates historical stores below the standard user Hermes integration into
/// the normal `TraceDecay` user profile. No environment or working-directory
/// override can redirect discovery.
Expand Down Expand Up @@ -146,6 +152,37 @@ where
.await
}

/// Migrates historical Hermes stores while the caller retains exclusive
/// lifecycle authority for the destination profile.
pub async fn migrate_legacy_hermes_stores_to_with_runtime_under_lease<R, F, H>(
user_home: &Path,
tracedecay_profile_root: &Path,
lifecycle: &crate::lifecycle_lease::LifecycleLease,
registry: &R,
read_pinned_project_root: &F,
state_importer: &H,
) -> LegacyHermesMigrationReport
where
R: RegistryRuntime,
F: Fn(&Path) -> Option<String>,
H: HermesStateImporter,
{
let runtime = HermesMigrationRuntime {
registry,
read_pinned_project_root,
state_importer,
};
migrate_legacy_hermes_stores_with_lease(
user_home,
tracedecay_profile_root,
&[user_home.join(".hermes")],
None,
lifecycle,
&runtime,
)
.await
}

#[doc(hidden)]
pub async fn migrate_legacy_hermes_stores_inner<R, F, H>(
user_home: &Path,
Expand All @@ -161,6 +198,11 @@ where
F: Fn(&Path) -> Option<String>,
H: HermesStateImporter,
{
let runtime = HermesMigrationRuntime {
registry,
read_pinned_project_root,
state_importer,
};
let lifecycle = match crate::lifecycle_lease::acquire_exclusive_for_profile(
tracedecay_profile_root,
"legacy Hermes store migration",
Expand All @@ -170,8 +212,32 @@ where
return migration_authority_failure(tracedecay_profile_root, error.to_string());
}
};
let _database_scope = match crate::db::enter_maintenance_database_scope(
migrate_legacy_hermes_stores_with_lease(
user_home,
tracedecay_profile_root,
hermes_homes,
fail_after_table,
&lifecycle,
&runtime,
)
.await
}

async fn migrate_legacy_hermes_stores_with_lease<R, F, H>(
user_home: &Path,
tracedecay_profile_root: &Path,
hermes_homes: &[PathBuf],
fail_after_table: Option<&str>,
lifecycle: &crate::lifecycle_lease::LifecycleLease,
runtime: &HermesMigrationRuntime<'_, R, F, H>,
) -> LegacyHermesMigrationReport
where
R: RegistryRuntime,
F: Fn(&Path) -> Option<String>,
H: HermesStateImporter,
{
let _database_scope = match crate::db::enter_maintenance_database_scope(
lifecycle,
tracedecay_profile_root,
"legacy Hermes store migration",
) {
Expand All @@ -190,9 +256,9 @@ where
&candidate,
tracedecay_profile_root,
fail_after_table,
registry,
read_pinned_project_root,
state_importer,
runtime.registry,
runtime.read_pinned_project_root,
runtime.state_importer,
)
.await
{
Expand All @@ -201,7 +267,7 @@ where
tracedecay_profile_root,
candidate.legacy_registry_project_id.as_deref(),
&candidate.profile_dir,
registry,
runtime.registry,
)
.await
{
Expand All @@ -218,7 +284,7 @@ where
tracedecay_profile_root,
candidate.legacy_registry_project_id.as_deref(),
&candidate.profile_dir,
registry,
runtime.registry,
)
.await
{
Expand All @@ -245,7 +311,7 @@ where
for profile_dir in profile_dirs {
let state_db = profile_dir.join("state.db");
if !state_db.is_file()
|| read_pinned_project_root(&profile_dir.join("config.yaml")).is_none()
|| (runtime.read_pinned_project_root)(&profile_dir.join("config.yaml")).is_none()
{
continue;
}
Expand All @@ -254,9 +320,9 @@ where
hermes_homes,
&profile_dir,
tracedecay_profile_root,
registry,
read_pinned_project_root,
state_importer,
runtime.registry,
runtime.read_pinned_project_root,
runtime.state_importer,
)
.await
{
Expand Down
33 changes: 30 additions & 3 deletions src/agent_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,17 @@ pub(crate) async fn migrate_legacy_hermes_data(home: &Path) -> tracedecay::error
/// stores whose project evidence no longer resolves. Genuine read, integrity,
/// or copy failures still block the reinstall; an explicit Hermes install
/// continues to use the strict cutover above.
async fn migrate_legacy_hermes_data_for_reinstall(home: &Path) -> tracedecay::errors::Result<()> {
let report = tracedecay::migrate::hermes::migrate_legacy_hermes_stores(home).await;
async fn migrate_legacy_hermes_data_for_reinstall(
home: &Path,
lifecycle: Option<&tracedecay::lifecycle_lease::LifecycleLease>,
) -> tracedecay::errors::Result<()> {
let report = match lifecycle {
Some(lifecycle) => {
tracedecay::migrate::hermes::migrate_legacy_hermes_stores_under_lease(home, lifecycle)
.await
}
None => tracedecay::migrate::hermes::migrate_legacy_hermes_stores(home).await,
};
finish_legacy_hermes_reinstall_migration(report)
}

Expand Down Expand Up @@ -455,11 +464,29 @@ pub(crate) async fn reinstall_agent_integrations(
agent_ids: &[String],
home: &Path,
tracedecay_bin: &str,
) -> Vec<(String, tracedecay::errors::Result<()>)> {
reinstall_agent_integrations_with_lease(agent_ids, home, tracedecay_bin, None).await
}

pub(crate) async fn reinstall_agent_integrations_under_lease(
agent_ids: &[String],
home: &Path,
tracedecay_bin: &str,
lifecycle: &tracedecay::lifecycle_lease::LifecycleLease,
) -> Vec<(String, tracedecay::errors::Result<()>)> {
reinstall_agent_integrations_with_lease(agent_ids, home, tracedecay_bin, Some(lifecycle)).await
}

async fn reinstall_agent_integrations_with_lease(
agent_ids: &[String],
home: &Path,
tracedecay_bin: &str,
lifecycle: Option<&tracedecay::lifecycle_lease::LifecycleLease>,
) -> Vec<(String, tracedecay::errors::Result<()>)> {
let project_path = std::env::current_dir().ok();
let mut results = Vec::new();
let hermes_migration_error = if agent_ids.iter().any(|id| id == "hermes") {
migrate_legacy_hermes_data_for_reinstall(home)
migrate_legacy_hermes_data_for_reinstall(home, lifecycle)
.await
.err()
.map(|error| error.to_string())
Expand Down
17 changes: 17 additions & 0 deletions src/migrate/hermes/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ async fn migrates_standard_profile_store_once() {
assert_eq!(count(source_after.conn(), "sessions").await, 1);
}

#[tokio::test]
async fn post_update_migration_reuses_its_exclusive_profile_lease() {
let temp = tempfile::tempdir().unwrap();
let user_home = temp.path().join("home");
let profile_root = temp.path().join("tracedecay-profile");
let lifecycle = crate::lifecycle_lease::acquire_exclusive_for_profile(
&profile_root,
"post-update maintenance",
)
.unwrap();

let report =
migrate_legacy_hermes_stores_to_under_lease(&user_home, &profile_root, &lifecycle).await;

assert!(report.failed.is_empty(), "{report:?}");
}

#[tokio::test]
async fn migration_marker_remerges_when_a_target_row_is_missing() {
let temp = tempfile::tempdir().unwrap();
Expand Down
32 changes: 32 additions & 0 deletions src/migrate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,38 @@ pub mod hermes {
.await
}

pub async fn migrate_legacy_hermes_stores_under_lease(
user_home: &Path,
lifecycle: &crate::lifecycle_lease::LifecycleLease,
) -> LegacyHermesMigrationReport {
let Ok(profile_root) = crate::storage::default_profile_root() else {
return LegacyHermesMigrationReport {
failed: vec![LegacyHermesMigrationIssue {
source_db: user_home.join(".hermes/.tracedecay/sessions.db"),
reason: "could not resolve the TraceDecay user-profile store".to_string(),
}],
..LegacyHermesMigrationReport::default()
};
};
migrate_legacy_hermes_stores_to_under_lease(user_home, &profile_root, lifecycle).await
}

pub async fn migrate_legacy_hermes_stores_to_under_lease(
user_home: &Path,
tracedecay_profile_root: &Path,
lifecycle: &crate::lifecycle_lease::LifecycleLease,
) -> LegacyHermesMigrationReport {
tracedecay_migrate::hermes::migrate_legacy_hermes_stores_to_with_runtime_under_lease(
user_home,
tracedecay_profile_root,
lifecycle,
&RootRegistry,
&crate::agents::hermes::read_config_pinned_project_root,
&RootHermesStateImporter,
)
.await
}

fn code_project(project: CodeProjectRecord) -> registry_adapter::CodeProjectRecord {
registry_adapter::CodeProjectRecord {
project_id: project.project_id,
Expand Down
38 changes: 34 additions & 4 deletions src/update_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,20 @@ pub(crate) fn partition_reinstall_results(
/// resolved, no install runs and a descriptive failure is reported so the
/// version markers stay put.
pub(crate) async fn reinstall_tracked_agents(user_config: &UserConfig) -> ReinstallOutcome {
reinstall_tracked_agents_with_lease(user_config, None).await
}

async fn reinstall_tracked_agents_under_lease(
user_config: &UserConfig,
lifecycle_lease: &tracedecay::lifecycle_lease::LifecycleLease,
) -> ReinstallOutcome {
reinstall_tracked_agents_with_lease(user_config, Some(lifecycle_lease)).await
}

async fn reinstall_tracked_agents_with_lease(
user_config: &UserConfig,
lifecycle_lease: Option<&tracedecay::lifecycle_lease::LifecycleLease>,
) -> ReinstallOutcome {
let (Some(home), Some(bin)) = (
tracedecay::agents::home_dir(),
tracedecay::agents::which_tracedecay(),
Expand All @@ -483,9 +497,25 @@ pub(crate) async fn reinstall_tracked_agents(user_config: &UserConfig) -> Reinst
],
};
};
let results =
crate::agent_cmd::reinstall_agent_integrations(&user_config.installed_agents, &home, &bin)
.await;
let results = match lifecycle_lease {
Some(lifecycle_lease) => {
crate::agent_cmd::reinstall_agent_integrations_under_lease(
&user_config.installed_agents,
&home,
&bin,
lifecycle_lease,
)
.await
}
None => {
crate::agent_cmd::reinstall_agent_integrations(
&user_config.installed_agents,
&home,
&bin,
)
.await
}
};
partition_reinstall_results(results)
}

Expand Down Expand Up @@ -569,7 +599,7 @@ async fn run_post_update_mutations(
config.installed_agents.join(", ")
);
}
match reinstall_tracked_agents(&config).await {
match reinstall_tracked_agents_under_lease(&config, lifecycle_lease).await {
ReinstallOutcome::AllOk => {
if config.mark_version_installed(env!("CARGO_PKG_VERSION")) {
if let Err(err) = config.save() {
Expand Down
Loading