diff --git a/app/src-tauri/src/core_process.rs b/app/src-tauri/src/core_process.rs index 5b31be033..b3ca79086 100644 --- a/app/src-tauri/src/core_process.rs +++ b/app/src-tauri/src/core_process.rs @@ -276,14 +276,17 @@ impl CoreProcessHandle { } } - // Readiness budget: 100 iterations × 100ms = 10s. The embedded + // Readiness budget: 200 iterations × 100ms = 20s. The embedded // core's JSON-RPC controller registry has grown over time and - // the previous 4s budget started flaking under CI worker load + // earlier 4s/10s budgets started flaking under CI worker load // (issue: core_process tests intermittently failing with - // "core process did not become ready"). 10s is still well - // under any user-visible startup expectation and matches the - // upper end of observed cold-start times. - for _ in 0..100 { + // "core process did not become ready"), especially under + // cargo-llvm-cov instrumentation where the binary runs ~2x + // slower. 20s is still well under any user-visible startup + // expectation: in normal runs the ready signal arrives in well + // under 1s and the loop exits immediately; the headroom only + // matters on heavily loaded instrumented CI workers. + for _ in 0..200 { if !received_ready { match ready_rx.try_recv() { Ok(ready_signal) => { diff --git a/src/openhuman/test_support/rpc.rs b/src/openhuman/test_support/rpc.rs index 959f304d7..12e5db476 100644 --- a/src/openhuman/test_support/rpc.rs +++ b/src/openhuman/test_support/rpc.rs @@ -4,6 +4,7 @@ //! - no authenticated user (active_user.toml removed, api_key cleared) //! - onboarding not yet completed (chat_onboarding_completed=false) //! - no cron jobs (so the post-onboarding seed re-creates `morning_briefing`) +//! - no memory-tree chunks, summaries, content dirs, or sync cursors //! //! It is intentionally in-process: the sidecar keeps running. Specs reload //! the webview after this call so the renderer also starts from a blank slate. @@ -14,6 +15,7 @@ use serde_json::json; use crate::openhuman::config::Config; use crate::openhuman::config::{clear_active_user, default_root_openhuman_dir}; use crate::openhuman::cron; +use crate::openhuman::memory::tree::read_rpc; use crate::rpc::RpcOutcome; const E2E_MODE_ENV_VAR: &str = "OPENHUMAN_E2E_MODE"; @@ -22,11 +24,21 @@ const E2E_MODE_ENV_VAR: &str = "OPENHUMAN_E2E_MODE"; #[derive(Debug, Serialize)] pub struct ResetSummary { pub cron_jobs_removed: usize, + pub memory_tree_rows_deleted: u64, + pub memory_tree_dirs_removed: Vec, + pub memory_tree_sync_state_cleared: u64, pub onboarding_was_completed: bool, pub api_key_was_set: bool, pub active_user_cleared: bool, } +#[derive(Debug, Serialize)] +struct MemoryTreeResetSummary { + rows_deleted: u64, + dirs_removed: Vec, + sync_state_cleared: u64, +} + fn ensure_e2e_mode_enabled() -> Result<(), String> { ensure_e2e_mode_value(std::env::var(E2E_MODE_ENV_VAR).ok().as_deref()) } @@ -69,6 +81,15 @@ pub async fn reset() -> Result, String> { .map_err(|e| format!("test_reset: cron wipe failed: {e:#}"))?; log::debug!("[test_reset] step=wipe_cron ok removed={cron_jobs_removed}"); + log::debug!("[test_reset] step=wipe_memory_tree start"); + let memory_tree = wipe_memory_tree(&config).await?; + log::debug!( + "[test_reset] step=wipe_memory_tree ok rows={} dirs={:?} sync_state={}", + memory_tree.rows_deleted, + memory_tree.dirs_removed, + memory_tree.sync_state_cleared + ); + log::debug!("[test_reset] step=clear_config_fields start"); config.chat_onboarding_completed = false; config.api_key = None; @@ -88,8 +109,16 @@ pub async fn reset() -> Result, String> { root.display() ); + let memory_tree_log = format!( + "memory_tree wiped rows={} dirs={:?} sync_state={}", + memory_tree.rows_deleted, memory_tree.dirs_removed, memory_tree.sync_state_cleared + ); + let summary = ResetSummary { cron_jobs_removed, + memory_tree_rows_deleted: memory_tree.rows_deleted, + memory_tree_dirs_removed: memory_tree.dirs_removed, + memory_tree_sync_state_cleared: memory_tree.sync_state_cleared, onboarding_was_completed, api_key_was_set, active_user_cleared: true, @@ -104,6 +133,7 @@ pub async fn reset() -> Result, String> { summary, vec![ format!("removed {cron_jobs_removed} cron jobs"), + memory_tree_log, format!("chat_onboarding_completed: {onboarding_was_completed} → false"), format!("api_key cleared (was set: {api_key_was_set})"), "active_user.toml removed".to_string(), @@ -111,12 +141,27 @@ pub async fn reset() -> Result, String> { )) } +async fn wipe_memory_tree(config: &Config) -> Result { + let outcome = read_rpc::wipe_all_rpc(config) + .await + .map_err(|e| format!("test_reset: memory_tree wipe failed: {e}"))?; + let value = outcome.value; + Ok(MemoryTreeResetSummary { + rows_deleted: value.rows_deleted, + dirs_removed: value.dirs_removed, + sync_state_cleared: value.sync_state_cleared, + }) +} + /// Convenience helper for handlers that prefer a raw JSON envelope. #[allow(dead_code)] pub async fn reset_json() -> Result { let outcome = reset().await?; Ok(json!({ "removed_cron_jobs": outcome.value.cron_jobs_removed, + "memory_tree_rows_deleted": outcome.value.memory_tree_rows_deleted, + "memory_tree_dirs_removed": outcome.value.memory_tree_dirs_removed, + "memory_tree_sync_state_cleared": outcome.value.memory_tree_sync_state_cleared, "previously_onboarded": outcome.value.onboarding_was_completed, "previously_authenticated": outcome.value.api_key_was_set, })) @@ -124,8 +169,9 @@ pub async fn reset_json() -> Result { #[cfg(test)] mod tests { - use super::{ensure_e2e_mode_value, reset, E2E_MODE_ENV_VAR}; + use super::*; use std::sync::{Mutex, OnceLock}; + use tempfile::TempDir; static E2E_MODE_ENV_LOCK: OnceLock> = OnceLock::new(); @@ -163,4 +209,28 @@ mod tests { ensure_e2e_mode_value(Some("true")).expect("true enables E2E mode"); ensure_e2e_mode_value(Some("yes")).expect("yes enables E2E mode"); } + + #[tokio::test] + async fn wipe_memory_tree_removes_content_dirs_and_reports_summary() { + let tmp = TempDir::new().unwrap(); + let mut config = Config::default(); + config.workspace_dir = tmp.path().join("workspace"); + + let content_root = config.memory_tree_content_root(); + let raw_dir = content_root.join("raw"); + let wiki_dir = content_root.join("wiki"); + std::fs::create_dir_all(&raw_dir).unwrap(); + std::fs::create_dir_all(&wiki_dir).unwrap(); + std::fs::write(raw_dir.join("chunk.md"), "test chunk").unwrap(); + std::fs::write(wiki_dir.join("summary.md"), "test summary").unwrap(); + + let summary = wipe_memory_tree(&config).await.unwrap(); + + assert_eq!(summary.rows_deleted, 0); + assert_eq!(summary.sync_state_cleared, 0); + assert!(summary.dirs_removed.contains(&"raw".to_string())); + assert!(summary.dirs_removed.contains(&"wiki".to_string())); + assert!(!raw_dir.exists()); + assert!(!wiki_dir.exists()); + } } diff --git a/src/openhuman/test_support/schemas.rs b/src/openhuman/test_support/schemas.rs index a210e2216..c429aded7 100644 --- a/src/openhuman/test_support/schemas.rs +++ b/src/openhuman/test_support/schemas.rs @@ -51,7 +51,7 @@ pub fn schemas(function: &str) -> ControllerSchema { namespace: "test", function: "reset", description: - "Wipe persistent sidecar state in-place: clears auth, onboarding, and cron jobs. \ + "Wipe persistent sidecar state in-place: clears auth, onboarding, cron jobs, and memory tree. \ E2E specs call this between tests so each starts from a fresh-install baseline.", inputs: vec![], outputs: vec![FieldSchema { @@ -64,6 +64,24 @@ pub fn schemas(function: &str) -> ControllerSchema { comment: "Number of cron jobs deleted from the workspace database.", required: true, }, + FieldSchema { + name: "memory_tree_rows_deleted", + ty: TypeSchema::U64, + comment: "Number of memory-tree SQLite rows deleted.", + required: true, + }, + FieldSchema { + name: "memory_tree_dirs_removed", + ty: TypeSchema::Array(Box::new(TypeSchema::String)), + comment: "Memory-tree content directories removed.", + required: true, + }, + FieldSchema { + name: "memory_tree_sync_state_cleared", + ty: TypeSchema::U64, + comment: "Number of Composio memory-tree sync-state rows deleted.", + required: true, + }, FieldSchema { name: "onboarding_was_completed", ty: TypeSchema::Bool,