diff --git a/benchmark_data/session-temporal/workload-v1.json b/benchmark_data/session-temporal/workload-v1.json index 6d1d912890..9b4c53203f 100644 --- a/benchmark_data/session-temporal/workload-v1.json +++ b/benchmark_data/session-temporal/workload-v1.json @@ -9,7 +9,7 @@ "status": "accepted_for_harness" }, "implementation": { - "path": "crates/tracedecay/src/session_temporal_benchmark.rs" + "path": "crates/tracedecay/benches/session_temporal/harness.rs" }, "measurement_contract": { "measured_repetitions": 30, diff --git a/crates/tracedecay-application/src/tracedecay/mod.rs b/crates/tracedecay-application/src/tracedecay/mod.rs index a98573fbd3..eb4a107cfb 100644 --- a/crates/tracedecay-application/src/tracedecay/mod.rs +++ b/crates/tracedecay-application/src/tracedecay/mod.rs @@ -1,8 +1,22 @@ -//! Narrow root-owned store authorities used by transport-neutral use cases. +//! Transport-neutral store authorities shared by the composition root. +//! +//! Branch diagnostics, fact-owner identity, and store-metadata counters live +//! here so `TraceDecay` can retain owner handles and delegate. -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use serde::Serialize; +use tracedecay_domain::errors::{Result, TraceDecayError}; +use tracedecay_domain::{FactOwnerV1, ProjectId}; +use tracedecay_runtime_core::branch; +use tracedecay_runtime_core::branch_meta; +use tracedecay_runtime_core::config::db_filename; + +mod store_meta; + +pub use store_meta::{ + add_local_counter, get_local_counter, get_tokens_saved, reset_local_counter, set_tokens_saved, +}; #[derive(Debug, Clone, Serialize)] pub struct TrackedBranchDiagnostic { @@ -47,3 +61,269 @@ pub struct BranchDiagnostics { pub branches: Vec, pub warnings: Vec, } + +/// Resolves the only project-memory owner accepted by core routes. +/// +/// The ID is supplied by the resolved store layout, never reconstructed +/// from a filesystem path or a caller-provided display label. +pub fn project_memory_owner_from_layout_id(project_id: Option<&str>) -> Result { + let project_id = project_id.ok_or_else(|| TraceDecayError::Config { + message: "active project has no authoritative project_id for memory".to_string(), + })?; + let project_id = + ProjectId::new(project_id.to_owned()).map_err(|error| TraceDecayError::Config { + message: format!("invalid authoritative project_id for memory: {error}"), + })?; + Ok(FactOwnerV1::Project { project_id }) +} + +/// Resolves the serving-branch provenance for a given live branch. +/// +/// Returns `(db_path, serving_branch, fallback_warning)`. Every branch is +/// served by the single project graph store, so `db_path` is always the +/// canonical main database; the branch argument only decides which +/// tracked branch's provenance the open is scoped to and whether the +/// caller must be warned about a fallback. +pub fn resolve_db_for_branch( + project_root: &Path, + tracedecay_dir: &Path, + branch_name: Option<&str>, +) -> (PathBuf, Option, Option) { + let default_db = tracedecay_dir.join(db_filename(tracedecay_dir)); + + let Some(meta) = branch_meta::load_branch_meta(tracedecay_dir) else { + return (default_db, None, None); + }; + + let Some(branch_name) = branch_name else { + return ( + default_db, + Some(meta.default_branch.clone()), + Some("detached HEAD — using default branch index".to_string()), + ); + }; + + if meta.is_tracked(branch_name) { + return (default_db, Some(branch_name.to_string()), None); + } + + if let Some(ancestor) = branch::find_nearest_tracked_ancestor(project_root, branch_name, &meta) + { + return ( + default_db, + Some(ancestor.clone()), + Some(format!( + "branch '{branch_name}' is not tracked — serving from '{ancestor}'. \ + Run `tracedecay branch add {branch_name}` to track it." + )), + ); + } + + let serving = meta.default_branch.clone(); + ( + default_db, + Some(serving), + Some(format!( + "branch '{branch_name}' is not tracked — serving from '{}'. \ + Run `tracedecay branch add {branch_name}` to track it.", + meta.default_branch + )), + ) +} + +pub fn build_branch_diagnostics( + project_root: &Path, + data_root: &Path, + open_active_branch: Option, + serving_branch: Option, + fallback_warning: Option, + serving_db_path: PathBuf, +) -> BranchDiagnostics { + let meta = branch_meta::load_branch_meta(data_root); + let current_branch = branch::current_branch(project_root); + let tracking_enabled = meta.as_ref().is_some_and(|m| !m.branches.is_empty()); + let branch_drifted = + tracking_enabled && current_branch.as_deref() != open_active_branch.as_deref(); + let is_fallback = fallback_warning.is_some(); + let fallback_target = if is_fallback { + serving_branch.clone() + } else { + None + }; + let serving_db_exists = serving_db_path.exists(); + + let ( + live_branch_tracked, + live_branch_db_path, + live_branch_db_exists, + nearest_tracked_ancestor, + nearest_tracked_ancestor_db_path, + nearest_tracked_ancestor_db_exists, + ) = if let (Some(meta), Some(current)) = (meta.as_ref(), current_branch.as_deref()) { + let live_branch_tracked = meta.is_tracked(current); + let live_branch_db_path = if live_branch_tracked { + branch::resolve_branch_db_path(data_root, current, meta) + } else { + None + }; + let live_branch_db_exists = live_branch_db_path.as_ref().map(|path| path.exists()); + let nearest_tracked_ancestor = if live_branch_tracked { + None + } else { + branch::find_nearest_tracked_ancestor(project_root, current, meta) + }; + let nearest_tracked_ancestor_db_path = nearest_tracked_ancestor + .as_deref() + .and_then(|ancestor| branch::resolve_branch_db_path(data_root, ancestor, meta)); + let nearest_tracked_ancestor_db_exists = nearest_tracked_ancestor_db_path + .as_ref() + .map(|path| path.exists()); + ( + live_branch_tracked, + live_branch_db_path, + live_branch_db_exists, + nearest_tracked_ancestor, + nearest_tracked_ancestor_db_path, + nearest_tracked_ancestor_db_exists, + ) + } else { + (false, None, None, None, None, None) + }; + + let mut warnings = Vec::new(); + if branch_drifted { + warnings.push(format!( + "branch drift detected: working tree is on '{}' but this instance opened on '{}' and is still serving '{}'. Reopen the index so reads and writes target the live branch.", + current_branch.as_deref().unwrap_or("detached HEAD"), + open_active_branch.as_deref().unwrap_or("detached HEAD"), + serving_branch.as_deref().unwrap_or("default branch"), + )); + } + if !serving_db_exists { + warnings.push(format!( + "serving branch '{}' points at a missing DB: {}", + serving_branch.as_deref().unwrap_or("default branch"), + serving_db_path.display(), + )); + } + if let (Some(current), Some(false), Some(path)) = ( + current_branch.as_deref(), + live_branch_db_exists, + live_branch_db_path.as_ref(), + ) { + warnings.push(format!( + "tracked branch '{}' is listed in branch metadata but its DB is missing at '{}'; serving '{}' instead.", + current, + path.display(), + serving_branch.as_deref().unwrap_or("default branch"), + )); + } else if is_fallback { + match ( + current_branch.as_deref(), + nearest_tracked_ancestor.as_deref(), + fallback_target.as_deref(), + ) { + (Some(current), Some(ancestor), Some(target)) => warnings.push(format!( + "branch '{current}' is not tracked; nearest indexed ancestor is '{ancestor}' and tracedecay is serving '{target}' instead." + )), + (Some(current), None, Some(target)) => warnings.push(format!( + "branch '{current}' is not tracked and no indexed ancestor DB was available; tracedecay is serving '{target}' instead." + )), + _ => {} + } + } + + let branch_resolution = if !tracking_enabled { + "single_db".to_string() + } else if branch_drifted { + "stale_serving_branch".to_string() + } else if current_branch.is_none() { + "detached_default".to_string() + } else if is_fallback { + match ( + nearest_tracked_ancestor.as_deref(), + fallback_target.as_deref(), + ) { + (Some(ancestor), Some(target)) if ancestor == target => "fallback_ancestor".to_string(), + _ => "fallback_default".to_string(), + } + } else { + "exact".to_string() + }; + + let mut branches = Vec::new(); + if let Some(meta) = meta.as_ref() { + let mut names: Vec<_> = meta.branches.keys().cloned().collect(); + names.sort(); + for name in names { + let entry = &meta.branches[&name]; + let db_path = data_root.join(&entry.db_file); + let db_exists = db_path.exists(); + let size_bytes = db_path.metadata().map_or(0, |metadata| metadata.len()); + let parent_db_path = entry + .parent + .as_deref() + .and_then(|parent| branch::resolve_branch_db_path(data_root, parent, meta)); + let parent_db_exists = parent_db_path.as_ref().map(|path| path.exists()); + let mut branch_warnings = Vec::new(); + if !db_exists { + branch_warnings.push(format!("missing DB at '{}'", db_path.display())); + } + if entry.parent.is_some() && parent_db_exists == Some(false) { + branch_warnings.push("parent DB is missing".to_string()); + } + branches.push(TrackedBranchDiagnostic { + name: name.clone(), + db_file: entry.db_file.clone(), + db_path, + db_exists, + size_bytes, + parent: entry.parent.clone(), + parent_db_path, + parent_db_exists, + created_at: entry.created_at.clone(), + last_synced_at: entry.last_synced_at.clone(), + is_default: name == meta.default_branch, + is_current: current_branch.as_deref() == Some(name.as_str()), + is_open_active: open_active_branch.as_deref() == Some(name.as_str()), + is_serving: serving_branch.as_deref() == Some(name.as_str()), + warnings: branch_warnings, + }); + } + } + + BranchDiagnostics { + tracking_enabled, + default_branch: meta.as_ref().map(|m| m.default_branch.clone()), + current_branch, + open_active_branch, + serving_branch, + serving_db_path, + serving_db_exists, + branch_drifted, + branch_resolution, + is_fallback, + fallback_target, + fallback_warning, + live_branch_tracked, + live_branch_db_path, + live_branch_db_exists, + nearest_tracked_ancestor, + nearest_tracked_ancestor_db_path, + nearest_tracked_ancestor_db_exists, + tracked_branch_count: branches.len(), + branches, + warnings, + } +} + +#[cfg(test)] +mod tests { + use super::project_memory_owner_from_layout_id; + + #[test] + fn project_memory_owner_requires_a_valid_authoritative_layout_id() { + assert!(project_memory_owner_from_layout_id(None).is_err()); + assert!(project_memory_owner_from_layout_id(Some("")).is_err()); + } +} diff --git a/crates/tracedecay-application/src/tracedecay/store_meta.rs b/crates/tracedecay-application/src/tracedecay/store_meta.rs new file mode 100644 index 0000000000..0618aa3ba7 --- /dev/null +++ b/crates/tracedecay-application/src/tracedecay/store_meta.rs @@ -0,0 +1,51 @@ +//! Named project-store metadata counters. + +use tracedecay_domain::errors::{Result, TraceDecayError}; +use tracedecay_runtime_core::db::Database; + +fn parse_counter(key: &'static str, value: Option) -> Result { + let Some(value) = value else { + return Ok(0); + }; + value + .parse::() + .map_err(|error| TraceDecayError::Database { + operation: format!("read {key}"), + message: format!("persisted {key} counter is invalid: {error}"), + }) +} + +#[hotpath::measure(label = "daemon.store_meta.read_tokens_saved", future = true)] +pub async fn get_tokens_saved(db: &Database) -> Result { + parse_counter("tokens_saved", db.get_metadata("tokens_saved").await?) +} + +#[hotpath::measure(label = "daemon.store_meta.write_tokens_saved", future = true)] +pub async fn set_tokens_saved(db: &Database, value: u64) -> Result<()> { + db.set_metadata("tokens_saved", &value.to_string()).await +} + +#[hotpath::measure(label = "daemon.store_meta.read_local_counter", future = true)] +pub async fn get_local_counter(db: &Database) -> Result { + parse_counter("local_counter", db.get_metadata("local_counter").await?) +} + +#[hotpath::measure(label = "daemon.store_meta.reset_local_counter", future = true)] +pub async fn reset_local_counter(db: &Database) -> Result<()> { + db.set_metadata("local_counter", "0").await +} + +#[hotpath::measure(label = "daemon.store_meta.add_local_counter", future = true)] +pub async fn add_local_counter(db: &Database, delta: u64) -> Result<()> { + let transaction = db.begin_write_transaction("add local counter").await?; + let current = get_local_counter(db).await?; + let updated = current + .checked_add(delta) + .ok_or_else(|| TraceDecayError::Database { + operation: "add local counter".to_owned(), + message: "local_counter overflowed u64".to_owned(), + })?; + db.set_metadata_unguarded(&transaction, "local_counter", &updated.to_string()) + .await?; + transaction.commit().await +} diff --git a/crates/tracedecay-cli/Cargo.toml b/crates/tracedecay-cli/Cargo.toml index b8b3c315c0..53e5f7b716 100644 --- a/crates/tracedecay-cli/Cargo.toml +++ b/crates/tracedecay-cli/Cargo.toml @@ -71,7 +71,7 @@ hotpath-cpu = [ "hotpath/hotpath-cpu", ] hotpath-mcp = ["hotpath", "hotpath/hotpath-mcp"] -production = ["tracedecay/production"] +production = ["tracedecay/production", "tracedecay/bench"] # Opt-in tiered graph storage; not part of `production`. graph-disk-tier = ["tracedecay/graph-disk-tier"] graph-tiered-storage = ["tracedecay/graph-tiered-storage"] diff --git a/crates/tracedecay-cli/tests/core_cli_suite/cli_non_interactive_test.rs b/crates/tracedecay-cli/tests/core_cli_suite/cli_non_interactive_test.rs index fbce703451..170e01bb86 100644 --- a/crates/tracedecay-cli/tests/core_cli_suite/cli_non_interactive_test.rs +++ b/crates/tracedecay-cli/tests/core_cli_suite/cli_non_interactive_test.rs @@ -11,7 +11,7 @@ use crate::provision_host_cli_fixture; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_agent_hosts::PRODUCT_VERSION; use tracedecay_automation_runtime::automation::run_ledger::{ AutomationRunArtifactKind, AutomationRunLedgerRecord, append_run_record, write_run_artifact, diff --git a/crates/tracedecay-cli/tests/core_cli_suite/gain_test.rs b/crates/tracedecay-cli/tests/core_cli_suite/gain_test.rs index 1e2d2859cb..dc2044005e 100644 --- a/crates/tracedecay-cli/tests/core_cli_suite/gain_test.rs +++ b/crates/tracedecay-cli/tests/core_cli_suite/gain_test.rs @@ -1,7 +1,7 @@ use std::fs; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; async fn open_isolated_runtime(tmp: &TempDir) -> HostAdmissionTestRuntimeV1 { HostAdmissionTestRuntimeV1::profile(tmp.path().join(".tracedecay")) diff --git a/crates/tracedecay-global-db/src/api_types.rs b/crates/tracedecay-global-db/src/api_types.rs index fe31eb3f74..54d77e50fa 100644 --- a/crates/tracedecay-global-db/src/api_types.rs +++ b/crates/tracedecay-global-db/src/api_types.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use tracedecay_runtime_core::storage::{ProjectStorageLocation, classify_registry_storage_fields}; @@ -273,6 +273,22 @@ pub struct ProjectRegistryContext { pub stores: Vec, } +/// Candidate enrollment roots a registered project claims: its canonical +/// and display roots plus every registered alias. +pub fn registry_context_candidate_roots(context: &ProjectRegistryContext) -> Vec { + let mut candidates = vec![ + PathBuf::from(&context.project.canonical_root), + PathBuf::from(&context.project.display_root), + ]; + candidates.extend( + context + .aliases + .iter() + .map(|alias| PathBuf::from(&alias.alias_path)), + ); + candidates +} + /// One complete, bounded snapshot of the registered checkout roots that may /// still own derived storage for a project. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] diff --git a/crates/tracedecay-global-db/src/lib.rs b/crates/tracedecay-global-db/src/lib.rs index 13b87090ee..40cc05ea84 100644 --- a/crates/tracedecay-global-db/src/lib.rs +++ b/crates/tracedecay-global-db/src/lib.rs @@ -84,6 +84,7 @@ mod remote_deletion; pub mod schema_contract; pub mod schema_stages; mod stack_delivery; +mod store_registration; pub use schema_stages::ensure_registered_schema; pub use stack_delivery::{ GitHubStackDeliveryKeyV1, GitHubStackDeliveryRecordV1, GitHubStackDeliveryStateV1, @@ -129,6 +130,7 @@ pub use project_registry::{ EPHEMERAL_PROJECT_ROOT_REASON_CODE, GIT_COMMON_DIR_ALIAS_PREFIX, PROJECT_REGISTRY_AUTHORITY, ProjectStoreResolutionError, ReapEntryKind, RegistryReapEntry, RegistryReapPlan, RetainedRegistryEntry, alias_key_path, ephemeral_root_rejection, is_ephemeral_path, + registered_enrollment_roots, }; pub use registered::{ DeliveryAttemptClaimV1, DeliverySourceReceiptReadV1, DurableDeliverySettlementReceiptV1, @@ -145,6 +147,7 @@ pub use remote_deletion::{ RemoteDeletionTarget, RemoteDeletionTombstone, RemoteDeletionTombstoneRecordOutcome, RemoteDeletionTombstoneTransitionOutcome, }; +pub use store_registration::register_project_store; pub use tracedecay_runtime_core::shard_runtime::{ VerifiedGraphRuntimePortV1, VerifiedGraphRuntimeWeakProxyV1, }; @@ -158,7 +161,7 @@ pub use api_types::{ RegisteredProjectRootInventoryV1, SavingsDay, SavingsTotal, SessionActivityRow, SessionIngestHealth, SessionProviderCoverage, SessionProviderCoverageState, StoreArtifactRecord, StoreArtifactUpsert, StoreInstanceRecord, StoreInstanceUpsert, - TranscriptBatch, + TranscriptBatch, registry_context_candidate_roots, }; pub use support::{ AccountingMode, env_flag, env_value_truthy, global_accounting_enabled, global_accounting_mode, diff --git a/crates/tracedecay-global-db/src/project_registry.rs b/crates/tracedecay-global-db/src/project_registry.rs index 6d7f277690..a4c37ff3a9 100644 --- a/crates/tracedecay-global-db/src/project_registry.rs +++ b/crates/tracedecay-global-db/src/project_registry.rs @@ -2079,3 +2079,53 @@ impl RegisteredGlobalDb { Ok(removed) } } + +/// Resolves enrolled checkout roots for a registered project and self-heals +/// the sanctioned `.git/` identity marker when the mount root is a git repo. +pub async fn registered_enrollment_roots( + registry: &RegisteredGlobalDb, + project_root: &Path, + store_layout: &tracedecay_runtime_core::storage::StoreLayout, + project_id: &tracedecay_domain::ProjectId, +) -> tracedecay_domain::errors::Result> { + let mut candidates = vec![ + project_root.to_path_buf(), + store_layout.project_root.clone(), + ]; + if let Some(context) = registry + .project_registry_context_by_id(project_id.as_str()) + .await? + { + candidates.extend(super::registry_context_candidate_roots(&context)); + } + + let mut roots = + tracedecay_runtime_core::storage::enrolled_project_roots(candidates, project_id)?; + let enrollment_root = tracedecay_runtime_core::worktree::repository_identity_root(project_root) + .unwrap_or_else(|| project_root.to_path_buf()); + match enrollment_root.canonicalize() { + Ok(canonical) => { + if tracedecay_runtime_core::storage::read_repository_identity_marker(&canonical)? + .is_none() + { + tracedecay_runtime_core::storage::write_repository_identity_marker( + &canonical, + project_id.as_str(), + )?; + } + if roots.is_empty() { + roots.push(canonical); + } + } + Err(error) if roots.is_empty() => { + return Err(tracedecay_domain::errors::TraceDecayError::Config { + message: format!( + "could not canonicalize project enrollment root '{}': {error}", + enrollment_root.display() + ), + }); + } + Err(_) => {} + } + Ok(roots) +} diff --git a/crates/tracedecay-global-db/src/store_registration.rs b/crates/tracedecay-global-db/src/store_registration.rs new file mode 100644 index 0000000000..123086dcd8 --- /dev/null +++ b/crates/tracedecay-global-db/src/store_registration.rs @@ -0,0 +1,518 @@ +//! Publishing a profile-sharded store's project, store, and branch scope rows. + +use std::collections::{BTreeSet, HashMap}; +use std::path::{Path, PathBuf}; +use std::sync::{LazyLock, Mutex as StdMutex}; +use std::time::SystemTime; + +use tracedecay_domain::errors::{Result, TraceDecayError}; +use tracedecay_runtime_core::branch_meta; +use tracedecay_runtime_core::storage::{self, StoreLayout}; +use tracedecay_runtime_core::tracedecay::current_timestamp; + +use crate::{GraphScopeUpsert, RegisteredGlobalDb, StoreArtifactUpsert, StoreInstanceUpsert}; + +/// Cheap fingerprint of everything that would change what +/// [`register_project_store`] writes. +/// +/// Every field here is load-bearing for the duplicate-store bug the +/// registration body guards against (see the comments on `git_common_dir` +/// and `primary_root` below): dropping `git_common_dir` would make a sibling +/// checkout's next first touch mint a fresh store, and dropping +/// `canonical_root` would let a linked worktree's registration pin the +/// project's canonical/display root to a transient path. `tracked_branches` +/// and the artifact mtimes catch every other observable change (branch +/// tracking, store file replacement) that this function is responsible for +/// publishing. `git_remote_url` is included because `git remote set-url` +/// changes origin identity without touching those other fields, and the +/// registry writes the remote (and its search alias) on every registration. +#[derive(Clone, Debug, PartialEq, Eq)] +struct RegistrationDigest { + project_id: String, + canonical_root: PathBuf, + git_common_dir: Option, + git_remote_url: Option, + tracked_branches: BTreeSet, + artifact_mtimes: Vec>, +} + +/// Process-global cache of the last digest successfully registered for each +/// project id, so a redundant [`register_project_store`] call (every writable +/// open re-runs this) can skip straight to `Ok(())` instead of redoing +/// branch-meta/git lookups and every upsert. +static LAST_REGISTERED_DIGEST: LazyLock>> = + LazyLock::new(|| StdMutex::new(HashMap::new())); + +/// Pure equality check split out of the caching logic above so it can be +/// unit tested against a synthetic cache without a real global database. +fn registration_digest_matches( + cache: &HashMap, + project_id: &str, + digest: &RegistrationDigest, +) -> bool { + cache.get(project_id) == Some(digest) +} + +/// Whether the cached registration may be honored: the digest cache proves +/// this process registered exactly this digest once, not that the registry +/// still holds it — a sibling process, the CLI, or any out-of-band upsert can +/// re-pin `canonical_root` afterwards. One indexed point-read keeps the skip +/// honest before it bypasses the stale-canonical-root repair below; the skip +/// still avoids the registry write lock and every upsert. +async fn cached_registration_is_current( + global_db: &RegisteredGlobalDb, + project_id: &str, + digest: &RegistrationDigest, + registration_root: &Path, +) -> Result { + { + let cache = LAST_REGISTERED_DIGEST + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if !registration_digest_matches(&cache, project_id, digest) { + return Ok(false); + } + } + let registered_root = global_db + .get_code_project(project_id) + .await? + .map(|record| record.canonical_root); + Ok(registered_root.as_deref() + == Some(RegisteredGlobalDb::canonical_project_key(registration_root).as_str())) +} + +fn artifact_mtime(path: &Path) -> Option { + std::fs::metadata(path).ok()?.modified().ok() +} + +/// Publishes a profile-sharded store into the global registry. +#[hotpath::measure(label = "lifecycle.register_project_store", future = true)] +pub async fn register_project_store( + global_db: &RegisteredGlobalDb, + project_root: &Path, + store_layout: &StoreLayout, +) -> Result<()> { + static REGISTRY_WRITE_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + + if store_layout.storage_mode != storage::StorageMode::ProfileSharded { + return Ok(()); + } + + let project_id = store_layout.identity.project_id.as_deref().ok_or_else(|| { + registry_registration_error("profile-sharded store has no project identity") + })?; + let profile_root = profile_root_for_layout(store_layout) + .ok_or_else(|| registry_registration_error("store is outside a profile root"))?; + let store_relpath = profile_relative(&profile_root, &store_layout.data_root) + .ok_or_else(|| registry_registration_error("store root is outside its profile"))?; + + let (meta, git_common_dir, primary_root, git_remote_url, digest) = + hotpath::measure_block!("lifecycle.register_project_store.digest", { + let meta = branch_meta::load_branch_meta(&store_layout.data_root); + // Registering without the git common dir leaves the row unreachable + // by repository identity, so the next first touch from a sibling + // checkout mints a fresh store. Detached worktrees are no exception: + // they belong to the same repository as every other checkout. + let git_common_dir = tracedecay_runtime_core::worktree::git_common_dir(project_root); + + // A shared project id can be reached from any linked worktree (see + // the git-common-dir alias registered below), so registering + // straight from `project_root` would let whichever worktree + // happens to touch the project last pin its canonical_root / + // display_root to a transient worktree path. Redirect registration + // to the primary checkout when one is detected and still exists. + let primary_root = tracedecay_runtime_core::worktree::primary_checkout_root( + project_root, + git_common_dir.as_deref(), + ); + + let tracked_branches: BTreeSet = meta + .as_ref() + .map(|meta| meta.branches.keys().cloned().collect()) + .unwrap_or_default(); + let artifact_mtimes = vec![ + artifact_mtime(&store_layout.graph_db_path), + artifact_mtime(&store_layout.sessions_db_path), + artifact_mtime(&store_layout.branch_meta_path), + store_layout + .manifest_path + .as_deref() + .and_then(artifact_mtime), + ]; + let git_remote_url = tracedecay_runtime_core::git::git_remote_url(project_root); + let digest = RegistrationDigest { + project_id: project_id.to_string(), + canonical_root: primary_root + .as_deref() + .unwrap_or(project_root) + .to_path_buf(), + git_common_dir: git_common_dir.clone(), + git_remote_url: git_remote_url.clone(), + tracked_branches, + artifact_mtimes, + }; + (meta, git_common_dir, primary_root, git_remote_url, digest) + }); + let default_branch = meta.as_ref().map(|meta| meta.default_branch.as_str()); + let registration_root = primary_root.as_deref().unwrap_or(project_root); + + if cached_registration_is_current(global_db, project_id, &digest, registration_root).await? { + hotpath::gauge!("lifecycle.register_project_store.cached_total").inc(1u64); + return Ok(()); + } + + let _registry_write = REGISTRY_WRITE_LOCK.lock().await; + // Re-check under the write lock: a concurrent writable open may have + // just registered the same digest while we were computing ours. + if cached_registration_is_current(global_db, project_id, &digest, registration_root).await? { + hotpath::gauge!("lifecycle.register_project_store.cached_total").inc(1u64); + return Ok(()); + } + + let previous_canonical_root = if primary_root.is_some() { + // Propagated: a database fault here must not read as "no prior + // registration" and skip the stale-canonical-root repair warning + // below. Absence (a truthful `Ok(None)`) still collapses to + // `None`, same as before. + global_db + .get_code_project(project_id) + .await? + .map(|record| record.canonical_root) + } else { + None + }; + + let project = global_db + .upsert_code_project( + project_id, + registration_root, + git_common_dir.as_deref(), + git_remote_url.as_deref(), + default_branch, + ) + // Propagated verbatim. The registry now separates three answers + // this call site used to flatten into one message: a refused + // ephemeral root (typed `ProjectRoute`), an unresolvable authority + // conflict (typed `ResetRequired`, which tells the operator to + // reset the profile), and a database fault. Re-wrapping them as + // "upsert code project failed" is exactly the coercion being + // removed. + .await?; + + storage::write_repository_identity_marker(project_root, &project.project_id)?; + + if let Some(primary_root) = primary_root.as_deref() { + // The registry now points canonical_root/display_root at the + // primary checkout; keep this worktree itself resolvable for + // future lookups by registering its own path as an alias. + // Propagated verbatim, same as `upsert_code_project` above: the + // registry now reports its own database-fault state instead of + // this call site's generic "upsert worktree alias failed". + global_db + .upsert_project_alias(project_root, &project.project_id) + .await?; + + let repaired_stale_worktree_root = previous_canonical_root.is_some_and(|previous| { + previous != RegisteredGlobalDb::canonical_project_key(primary_root) + }); + if repaired_stale_worktree_root { + eprintln!( + "warning: repaired tracedecay project '{project_id}' canonical_root — \ + it was pinned to a linked worktree ({}); restored to the primary checkout ({})", + project_root.display(), + primary_root.display() + ); + } + } + + let store_id = profile_store_id(&project.project_id); + let manifest_relpath = store_layout + .manifest_path + .as_ref() + .and_then(|path| profile_relative(&profile_root, path)); + let now = current_timestamp(); + let store = global_db + .upsert_store_instance(StoreInstanceUpsert { + store_id, + project_id: project.project_id, + store_kind: "code_project".to_string(), + storage_mode: "profile_sharded".to_string(), + store_relpath, + manifest_relpath, + last_verified_at: Some(now), + last_write_at: Some(now), + }) + .await?; + + if let Some(meta) = meta { + for (branch_name, entry) in meta.branches { + let db_path = store_layout.data_root.join(&entry.db_file); + let db_relpath = profile_relative(&profile_root, &db_path).ok_or_else(|| { + registry_registration_error("branch database is outside its profile") + })?; + global_db + .upsert_graph_scope(GraphScopeUpsert { + graph_scope_id: profile_graph_scope_id(&store.store_id, &branch_name), + project_id: store.project_id.clone(), + store_id: store.store_id.clone(), + branch_name: branch_name.clone(), + db_relpath, + parent_scope_id: entry + .parent + .as_deref() + .map(|parent| profile_graph_scope_id(&store.store_id, parent)), + last_synced_at: entry.last_synced_at.parse::().ok(), + writable: true, + }) + .await?; + } + } + + let mut artifacts = Vec::new(); + push_existing_store_artifact( + &mut artifacts, + &store.store_id, + "graph_db", + &profile_root, + &store_layout.graph_db_path, + None, + now, + ); + push_existing_store_artifact( + &mut artifacts, + &store.store_id, + "sessions_db", + &profile_root, + &store_layout.sessions_db_path, + None, + now, + ); + push_existing_store_artifact( + &mut artifacts, + &store.store_id, + "branch_meta", + &profile_root, + &store_layout.branch_meta_path, + None, + now, + ); + if let Some(manifest_path) = &store_layout.manifest_path { + push_existing_store_artifact( + &mut artifacts, + &store.store_id, + "store_manifest", + &profile_root, + manifest_path, + Some(storage::STORE_MANIFEST_SCHEMA_VERSION.to_string()), + now, + ); + } + for artifact in artifacts { + global_db.upsert_store_artifact(artifact).await?; + } + + LAST_REGISTERED_DIGEST + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .insert(project_id.to_string(), digest); + hotpath::gauge!("lifecycle.register_project_store.write_total").inc(1u64); + Ok(()) +} + +fn profile_relative(profile_root: &Path, path: &Path) -> Option { + path.strip_prefix(profile_root) + .ok() + .map(|rel| rel.to_string_lossy().replace('\\', "/")) +} + +fn profile_root_for_layout(layout: &StoreLayout) -> Option { + layout.data_root.parent()?.parent().map(Path::to_path_buf) +} + +fn profile_store_id(project_id: &str) -> String { + format!("store:{project_id}:profile_sharded") +} + +fn registry_registration_error(message: impl Into) -> TraceDecayError { + TraceDecayError::Database { + operation: "register project store".to_string(), + message: message.into(), + } +} + +fn profile_graph_scope_id(store_id: &str, branch_name: &str) -> String { + format!("{store_id}:branch:{branch_name}") +} + +fn push_existing_store_artifact( + artifacts: &mut Vec, + store_id: &str, + artifact_kind: &str, + profile_root: &Path, + path: &Path, + schema_version: Option, + updated_at: i64, +) { + let Some(relpath) = profile_relative(profile_root, path) else { + return; + }; + let Ok(metadata) = std::fs::metadata(path) else { + return; + }; + artifacts.push(StoreArtifactUpsert { + store_id: store_id.to_string(), + artifact_kind: artifact_kind.to_string(), + relpath, + size_bytes: i64::try_from(metadata.len()).ok(), + schema_version, + updated_at: Some(updated_at), + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn digest(canonical_root: &str, branches: &[&str]) -> RegistrationDigest { + RegistrationDigest { + project_id: "proj-1".to_string(), + canonical_root: PathBuf::from(canonical_root), + git_common_dir: Some(PathBuf::from("/repo/.git")), + git_remote_url: Some("https://example.com/repo.git".to_string()), + tracked_branches: branches.iter().map(ToString::to_string).collect(), + artifact_mtimes: vec![None, None, None, None], + } + } + + /// Simulates the real call path: check-then-maybe-register-then-record, + /// counting how many times a "register" (the expensive upsert body) + /// would actually run. + fn simulate_call( + cache: &mut HashMap, + project_id: &str, + digest: &RegistrationDigest, + register_calls: &mut u32, + ) { + if registration_digest_matches(cache, project_id, digest) { + return; + } + *register_calls += 1; + cache.insert(project_id.to_string(), digest.clone()); + } + + #[test] + fn identical_inputs_skip_the_second_registration() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let d = digest("/repo", &["main"]); + + simulate_call(&mut cache, "proj-1", &d, &mut register_calls); + simulate_call(&mut cache, "proj-1", &d, &mut register_calls); + + assert_eq!( + register_calls, 1, + "second call with an identical digest must not re-register" + ); + } + + #[test] + fn changed_branch_set_does_not_skip() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let first = digest("/repo", &["main"]); + let second = digest("/repo", &["main", "feature/x"]); + + simulate_call(&mut cache, "proj-1", &first, &mut register_calls); + simulate_call(&mut cache, "proj-1", &second, &mut register_calls); + + assert_eq!( + register_calls, 2, + "a changed tracked-branch set must force re-registration" + ); + } + + #[test] + fn changed_canonical_root_does_not_skip() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let first = digest("/repo", &["main"]); + let second = digest("/other/primary-checkout", &["main"]); + + simulate_call(&mut cache, "proj-1", &first, &mut register_calls); + simulate_call(&mut cache, "proj-1", &second, &mut register_calls); + + assert_eq!( + register_calls, 2, + "a changed canonical_root (primary-checkout redirect) must force re-registration" + ); + } + + #[test] + fn changed_git_common_dir_does_not_skip() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let mut first = digest("/repo", &["main"]); + first.git_common_dir = Some(PathBuf::from("/repo/.git")); + let mut second = first.clone(); + second.git_common_dir = None; + + simulate_call(&mut cache, "proj-1", &first, &mut register_calls); + simulate_call(&mut cache, "proj-1", &second, &mut register_calls); + + assert_eq!( + register_calls, 2, + "a changed git_common_dir must force re-registration" + ); + } + + #[test] + fn changed_git_remote_does_not_skip() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let first = digest("/repo", &["main"]); + let mut second = first.clone(); + second.git_remote_url = Some("https://example.com/fork.git".to_string()); + + simulate_call(&mut cache, "proj-1", &first, &mut register_calls); + simulate_call(&mut cache, "proj-1", &second, &mut register_calls); + + assert_eq!( + register_calls, 2, + "a changed git remote must force re-registration" + ); + } + + #[test] + fn changed_artifact_mtime_does_not_skip() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let mut first = digest("/repo", &["main"]); + first.artifact_mtimes = vec![Some(SystemTime::UNIX_EPOCH), None, None, None]; + let mut second = first.clone(); + second.artifact_mtimes[0] = Some(SystemTime::now()); + + simulate_call(&mut cache, "proj-1", &first, &mut register_calls); + simulate_call(&mut cache, "proj-1", &second, &mut register_calls); + + assert_eq!( + register_calls, 2, + "a changed artifact mtime must force re-registration" + ); + } + + #[test] + fn different_project_ids_are_tracked_independently() { + let mut cache = HashMap::new(); + let mut register_calls = 0; + let a = digest("/repo-a", &["main"]); + let mut b = digest("/repo-b", &["main"]); + b.project_id = "proj-2".to_string(); + + simulate_call(&mut cache, "proj-1", &a, &mut register_calls); + simulate_call(&mut cache, "proj-2", &b, &mut register_calls); + simulate_call(&mut cache, "proj-1", &a, &mut register_calls); + simulate_call(&mut cache, "proj-2", &b, &mut register_calls); + + assert_eq!(register_calls, 2); + } +} diff --git a/crates/tracedecay-graph-query/src/lib.rs b/crates/tracedecay-graph-query/src/lib.rs index e024e38753..6a2524c78c 100644 --- a/crates/tracedecay-graph-query/src/lib.rs +++ b/crates/tracedecay-graph-query/src/lib.rs @@ -46,8 +46,9 @@ pub use source_authority::{ CodeGraphSourceAuthorityPort, CodeGraphSourceBindFuture, CodeGraphSourceBindRequest, }; pub use verified_query::{ - VerifiedGraphQuery, VerifiedGraphQueryFuture, VerifiedGraphQueryPort, - VerifiedGraphQueryRequest, open_verified_graph_query, + AdmittedVerifiedGraphQueryPort, VerifiedGraphQuery, VerifiedGraphQueryFuture, + VerifiedGraphQueryPort, VerifiedGraphQueryRequest, admitted_verified_graph_query_port, + admitted_verified_graph_query_port_with_source, open_verified_graph_query, }; /// Immutable filesystem and cache values supplied for one admitted source diff --git a/crates/tracedecay-graph-query/src/source_authority.rs b/crates/tracedecay-graph-query/src/source_authority.rs index db00a25eaf..769e1dbc4b 100644 --- a/crates/tracedecay-graph-query/src/source_authority.rs +++ b/crates/tracedecay-graph-query/src/source_authority.rs @@ -57,6 +57,16 @@ where } } +impl CodeGraphSourceAuthorityPort for SourceReadContext { + fn bind<'a>( + &'a self, + _request: CodeGraphSourceBindRequest<'a>, + ) -> CodeGraphSourceBindFuture<'a> { + let source = self.clone(); + Box::pin(async move { Ok(source) }) + } +} + /// Exact source authority frozen at admitted open. /// /// Construction is crate-private: nothing outside this crate can build or diff --git a/crates/tracedecay-graph-query/src/verified_query.rs b/crates/tracedecay-graph-query/src/verified_query.rs index e39c88084f..68ae8bc36f 100644 --- a/crates/tracedecay-graph-query/src/verified_query.rs +++ b/crates/tracedecay-graph-query/src/verified_query.rs @@ -30,7 +30,6 @@ use super::{ CodeGraphReadRequest, application_graph_cancellation, map_code_graph_read_runtime_error, map_projection_error, }; -#[cfg(any(test, feature = "test-helpers"))] use crate::SourceReadContext; use crate::context::read_modes; use crate::context::source_read::{self, SourceReadOutput, SourceReadRequest}; @@ -81,6 +80,59 @@ where } } +/// Closes over admission, projection, and an optional admitted project source. +/// `open` never names a composition-root type. +pub struct AdmittedVerifiedGraphQueryPort { + admission: Arc, + projection: Arc, + source_authority: Option>, +} + +impl AdmittedVerifiedGraphQueryPort { + pub fn new( + admission: Arc, + projection: Arc, + source: Option, + ) -> Self { + Self { + admission, + projection, + source_authority: source + .map(|source| Arc::new(source) as Arc), + } + } +} + +impl VerifiedGraphQueryPort for AdmittedVerifiedGraphQueryPort { + fn open<'a>(&'a self, request: VerifiedGraphQueryRequest<'a>) -> VerifiedGraphQueryFuture<'a> { + Box::pin(open_verified_graph_query( + &*self.admission, + &*self.projection, + request, + self.source_authority.as_deref(), + )) + } +} + +#[must_use] +pub fn admitted_verified_graph_query_port( + admission: Arc, + projection: Arc, +) -> Arc { + admitted_verified_graph_query_port_with_source(admission, projection, None) +} + +#[must_use] +pub fn admitted_verified_graph_query_port_with_source( + admission: Arc, + projection: Arc, + source: Option, +) -> Arc { + Arc::new(AdmittedVerifiedGraphQueryPort::new( + admission, projection, source, + )) +} + /// Generation-pinned analytical queries over the verified Grafeo projection. pub struct VerifiedGraphQuery { reader: CodeGraphInteractiveReader, diff --git a/crates/tracedecay-runtime-core/src/git.rs b/crates/tracedecay-runtime-core/src/git.rs index ed47e8b676..ad94396479 100644 --- a/crates/tracedecay-runtime-core/src/git.rs +++ b/crates/tracedecay-runtime-core/src/git.rs @@ -467,6 +467,26 @@ pub fn git_capture(repo_root: &Path, args: &[&str]) -> Option { (!trimmed.is_empty()).then(|| trimmed.to_string()) } +/// Reads `remote.origin.url` from the repository at `project_root`. +/// +/// Prefers an in-process gix config snapshot (repo-local + global) and +/// falls back to a bounded `git config --get` when gix cannot discover +/// the repository but git still may. +pub fn git_remote_url(project_root: &Path) -> Option { + if let Ok(repo) = gix::discover(project_root) { + let url = repo + .config_snapshot() + .string("remote.origin.url")? + .to_string(); + let url = url.trim(); + return (!url.is_empty()).then(|| url.to_string()); + } + if !crate::worktree::git_may_resolve_repo(project_root) { + return None; + } + git_capture(project_root, &["config", "--get", "remote.origin.url"]) +} + /// Outcome of the bounded `git -C` capture used by repository identity lookup. #[derive(Debug)] pub enum GitCaptureAtResult { diff --git a/crates/tracedecay-runtime-core/src/storage.rs b/crates/tracedecay-runtime-core/src/storage.rs index f70842fedc..1397b5108b 100644 --- a/crates/tracedecay-runtime-core/src/storage.rs +++ b/crates/tracedecay-runtime-core/src/storage.rs @@ -441,10 +441,10 @@ pub use identity::{ pub(crate) use layout::has_path_local_profile_store; pub use layout::{ default_profile_project_id, default_profile_root, default_profile_sharded_layout, - path_local_profile_project_id, profile_sharded_data_root, profile_sharded_layout, - resolve_enrolled_layout_for_current_profile, resolve_layout, - resolve_layout_for_current_profile, resolve_lcm_payload_root, resolve_persisted_layout, - resolve_project_session_db_path, resolve_response_handle_root, + enrolled_project_roots, path_local_profile_project_id, profile_sharded_data_root, + profile_sharded_layout, registered_project_id, resolve_enrolled_layout_for_current_profile, + resolve_layout, resolve_layout_for_current_profile, resolve_lcm_payload_root, + resolve_persisted_layout, resolve_project_session_db_path, resolve_response_handle_root, }; pub use legacy_layouts::matching_legacy_profile_layouts; pub use manifest::{read_store_manifest, write_store_manifest, write_store_manifest_to_path}; diff --git a/crates/tracedecay-runtime-core/src/storage/identity_tests.rs b/crates/tracedecay-runtime-core/src/storage/identity_tests.rs index 25afcbe3f0..bc2a0c86ca 100644 --- a/crates/tracedecay-runtime-core/src/storage/identity_tests.rs +++ b/crates/tracedecay-runtime-core/src/storage/identity_tests.rs @@ -122,3 +122,29 @@ mod identity_root_canonicalization_tests { ); } } + +#[cfg(test)] +mod enrolled_project_roots_tests { + use super::*; + use tracedecay_domain::ProjectId; + + #[test] + fn empty_candidates_yield_no_roots() { + let project_id = ProjectId::new("proj_0123456789abcdef").expect("project id"); + let roots = enrolled_project_roots(Vec::::new(), &project_id).expect("filter"); + assert!(roots.is_empty()); + } + + #[test] + fn keeps_only_roots_whose_path_derived_id_matches() { + let temp = tempfile::tempdir().expect("tempdir"); + let enrolled = temp.path().join("enrolled"); + let other = temp.path().join("other"); + fs::create_dir_all(&enrolled).expect("enrolled"); + fs::create_dir_all(&other).expect("other"); + let project_id = ProjectId::new(default_profile_project_id(&enrolled)).expect("project id"); + let roots = + enrolled_project_roots(vec![enrolled.clone(), other], &project_id).expect("filter"); + assert_eq!(roots, vec![enrolled.canonicalize().expect("canonical")]); + } +} diff --git a/crates/tracedecay-runtime-core/src/storage/layout.rs b/crates/tracedecay-runtime-core/src/storage/layout.rs index db638bf31c..5853127a44 100644 --- a/crates/tracedecay-runtime-core/src/storage/layout.rs +++ b/crates/tracedecay-runtime-core/src/storage/layout.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use sha2::{Digest, Sha256}; use crate::config; +use tracedecay_domain::ProjectId; use tracedecay_domain::errors::{Result, TraceDecayError}; use super::{ @@ -10,6 +11,61 @@ use super::{ StoreLayout, read_repository_identity_marker, validate_project_id, }; +/// Typed project identity recorded on a registered store layout. +/// +/// Absence or an invalid id is a configuration fault — registered code +/// runtimes never invent a project id from the filesystem path. +pub fn registered_project_id(store_layout: &StoreLayout) -> Result { + let project_id = + store_layout + .identity + .project_id + .as_ref() + .ok_or_else(|| TraceDecayError::Config { + message: "registered code runtime requires an authoritative project identity" + .to_owned(), + })?; + ProjectId::new(project_id.clone()).map_err(|error| TraceDecayError::Config { + message: format!("invalid registered project identity: {error}"), + }) +} + +/// Filters candidate roots down to the ones whose root-side evidence +/// names exactly `project_id`: a `.git/` repository identity marker with +/// that id, or (for roots without one) a deterministic path-derived +/// identity equal to it. +/// +/// This never creates or repairs a marker, so a caller that must not mount +/// a store the profile has not enrolled — a cross-project memory reader, +/// for one — can tell "not enrolled here" apart from "enrolled". +pub fn enrolled_project_roots( + candidates: impl IntoIterator, + project_id: &ProjectId, +) -> Result> { + let mut candidates = candidates.into_iter().collect::>(); + candidates.sort(); + candidates.dedup(); + + let mut roots = Vec::new(); + for candidate in candidates { + let candidate = crate::worktree::repository_identity_root(&candidate).unwrap_or(candidate); + let Ok(canonical) = candidate.canonicalize() else { + continue; + }; + if roots.contains(&canonical) { + continue; + } + let named_id = match read_repository_identity_marker(&canonical)? { + Some(marker) => marker.project_id, + None => default_profile_project_id(&canonical), + }; + if named_id == project_id.as_str() { + roots.push(canonical); + } + } + Ok(roots) +} + pub fn profile_sharded_data_root(profile_root: &Path, project_id: &str) -> PathBuf { profile_root.join("projects").join(project_id) } diff --git a/crates/tracedecay/Cargo.toml b/crates/tracedecay/Cargo.toml index a234d01460..5583a57a33 100644 --- a/crates/tracedecay/Cargo.toml +++ b/crates/tracedecay/Cargo.toml @@ -198,6 +198,12 @@ lang-fsharp = ["tracedecay-code-index/lang-fsharp"] lang-quint = ["tracedecay-code-index/lang-quint"] lang-toml = ["tracedecay-code-index/lang-toml"] lang-lean = ["tracedecay-code-index/lang-lean"] +# Shipped `tracedecay bench` / MCP `admin_project` action `bench`. Off the +# default library graph; `tracedecay-cli`'s `production` feature selects it +# so the command stays in the binary without compiling this module into +# `cargo check -p tracedecay --lib`. +bench = [] + # Integration fixture surface. `HostAdmissionTestRuntimeV1` and the registered # database scaffolding it owns are assembled by the composition root, so they # live in the product library rather than a test crate. This feature keeps them diff --git a/crates/tracedecay/src/bench.rs b/crates/tracedecay/benches/query_bench/harness.rs similarity index 98% rename from crates/tracedecay/src/bench.rs rename to crates/tracedecay/benches/query_bench/harness.rs index 8e60ab3e11..9ac40bc03f 100644 --- a/crates/tracedecay/src/bench.rs +++ b/crates/tracedecay/benches/query_bench/harness.rs @@ -59,7 +59,8 @@ impl Default for BenchOptions { /// The embedded default query set. Compiled into the binary so `tracedecay bench` /// works without any external file dependency. -pub const DEFAULT_QUERIES_TOML: &str = include_str!("../../../benchmark_data/queries/default.toml"); +pub const DEFAULT_QUERIES_TOML: &str = + include_str!("../../../../benchmark_data/queries/default.toml"); /// Run the bench from a TOML query file on disk. pub async fn run_bench( diff --git a/crates/tracedecay/src/daemon/connection_serving/rmcp_benchmark.rs b/crates/tracedecay/benches/rmcp/benchmark.rs similarity index 99% rename from crates/tracedecay/src/daemon/connection_serving/rmcp_benchmark.rs rename to crates/tracedecay/benches/rmcp/benchmark.rs index e9fcaea162..1f803e1bf3 100644 --- a/crates/tracedecay/src/daemon/connection_serving/rmcp_benchmark.rs +++ b/crates/tracedecay/benches/rmcp/benchmark.rs @@ -28,8 +28,8 @@ use tracedecay_daemon_protocol::{ use tracedecay_domain::ProjectId; use super::{BrokerStreamTransport, DaemonLifecycle, serve_routed_rmcp_connection}; -use crate::host_admission::HostAdmissionTestRuntimeV1; use crate::mcp::McpServer; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use crate::tracedecay::TraceDecayOpenOptions; pub const PERSISTENT_WARMUP_REQUESTS: usize = 8; diff --git a/crates/tracedecay/src/session_temporal_benchmark.rs b/crates/tracedecay/benches/session_temporal/harness.rs similarity index 99% rename from crates/tracedecay/src/session_temporal_benchmark.rs rename to crates/tracedecay/benches/session_temporal/harness.rs index c58e6769fb..c8769201ae 100644 --- a/crates/tracedecay/src/session_temporal_benchmark.rs +++ b/crates/tracedecay/benches/session_temporal/harness.rs @@ -66,7 +66,7 @@ const HISTORICAL_RESULT_PATH: &str = "benchmark_data/session-temporal/result-pro const HISTORICAL_RESULT_FILE_NAME: &str = "result-provisional.json"; const HISTORICAL_HARNESS_PATH: &str = "src/sessions/session_temporal_benchmark.rs"; const RUNNER_PATH: &str = "scripts/run-session-temporal-benchmark.sh"; -const HARNESS_PATH: &str = "crates/tracedecay/src/session_temporal_benchmark.rs"; +const HARNESS_PATH: &str = "crates/tracedecay/benches/session_temporal/harness.rs"; const SOURCE_MODE_CLEAN: &str = "clean_git_worktree_v1"; const SANITIZATION_RECEIPT_PATH: &str = "benchmark_data/session-temporal/fixtures/codex-sanitization-receipt.json"; @@ -84,13 +84,13 @@ const NATIVE_CODEX_FIXTURES: &[(&str, &str)] = &[ ( "tests/fixtures/provider_normalization/codex/session_meta.input.json", include_str!( - "../../../tests/fixtures/provider_normalization/codex/session_meta.input.json" + "../../../../tests/fixtures/provider_normalization/codex/session_meta.input.json" ), ), ( "tests/fixtures/provider_normalization/codex/agent_message.input.json", include_str!( - "../../../tests/fixtures/provider_normalization/codex/agent_message.input.json" + "../../../../tests/fixtures/provider_normalization/codex/agent_message.input.json" ), ), ]; @@ -718,7 +718,7 @@ fn measurement_result(source_identity: Value, measurement: Value) -> Value { /// benchmark-private handle. fn ensure_admission_resource_authorities() -> Arc { - crate::host_admission::ensure_process_background_cpu_authority() + crate::test_support::host_admission::ensure_process_background_cpu_authority() .expect("install process capture authorities for the benchmark") } diff --git a/crates/tracedecay/src/session_temporal_benchmark/root_relation_fixture.rs b/crates/tracedecay/benches/session_temporal/root_relation_fixture.rs similarity index 100% rename from crates/tracedecay/src/session_temporal_benchmark/root_relation_fixture.rs rename to crates/tracedecay/benches/session_temporal/root_relation_fixture.rs diff --git a/crates/tracedecay/benches/transcript_ingest.rs b/crates/tracedecay/benches/transcript_ingest.rs index fcd5417ec4..cf6addcfba 100644 --- a/crates/tracedecay/benches/transcript_ingest.rs +++ b/crates/tracedecay/benches/transcript_ingest.rs @@ -22,7 +22,7 @@ use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; use futures_util::FutureExt as _; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::ProjectId; use tracedecay_runtime_core::storage::write_repository_identity_marker; use tracedecay_sessions::runtime::SessionProvider; diff --git a/crates/tracedecay/src/config/tests.rs b/crates/tracedecay/src/config/tests.rs index d78ac4ddd7..7236b30b57 100644 --- a/crates/tracedecay/src/config/tests.rs +++ b/crates/tracedecay/src/config/tests.rs @@ -572,9 +572,10 @@ async fn discover_project_root_with_identity_does_not_open_registry_only_store() let _profile = super::PinnedUserDataDir::new(); let profile_root = tracedecay_runtime_core::storage::default_profile_root().unwrap(); - let gdb = crate::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) - .await - .unwrap(); + let gdb = + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) + .await + .unwrap(); let project_dir = TempDir::new().unwrap(); let project_root = project_dir.path().canonicalize().unwrap(); @@ -649,9 +650,10 @@ async fn discover_project_root_with_identity_does_not_open_registry_only_store() async fn config_path_with_identity_does_not_open_registry_without_enrollment() { let _profile = super::PinnedUserDataDir::new(); let profile_root = tracedecay_runtime_core::storage::default_profile_root().unwrap(); - let gdb = crate::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) - .await - .unwrap(); + let gdb = + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) + .await + .unwrap(); let project_dir = TempDir::new().unwrap(); let project_root = project_dir.path().canonicalize().unwrap(); @@ -720,9 +722,10 @@ async fn config_path_with_identity_does_not_open_registry_without_enrollment() { async fn discover_project_root_with_identity_does_not_bind_non_git_child_to_parent_store() { let _profile = super::PinnedUserDataDir::new(); let profile_root = tracedecay_runtime_core::storage::default_profile_root().unwrap(); - let gdb = crate::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) - .await - .unwrap(); + let gdb = + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile(&profile_root) + .await + .unwrap(); let parent_dir = TempDir::new().unwrap(); let parent_root = parent_dir.path().canonicalize().unwrap(); @@ -894,7 +897,7 @@ mod runtime_configuration_cutover { cached_telemetry_config, install_pinned_runtime_configuration, runtime_configuration_for_layout, }; - use crate::host_admission::HostAdmissionTestRuntimeV1; + use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_configuration::{ ConfigurationControlStore, ConfigurationMutationAuthority, DirectConfigurationMutation, ProjectConfigurationRuntime, diff --git a/crates/tracedecay/src/daemon.rs b/crates/tracedecay/src/daemon.rs index 9335f785dc..1a286da582 100644 --- a/crates/tracedecay/src/daemon.rs +++ b/crates/tracedecay/src/daemon.rs @@ -248,6 +248,7 @@ mod database_owner_registry; use database_owner_registry::DatabaseOwnerRegistry; pub(crate) mod dashboard_automation; #[cfg(feature = "test-transport")] +#[path = "../tests/common/dashboard_configuration_test_runtime.rs"] mod dashboard_configuration_test_runtime; pub(crate) mod doctor_kernel; pub(crate) mod hook_v2_replay_consumer; diff --git a/crates/tracedecay/src/daemon/branch_admin.rs b/crates/tracedecay/src/daemon/branch_admin.rs index 6e2905b806..db4a1ab956 100644 --- a/crates/tracedecay/src/daemon/branch_admin.rs +++ b/crates/tracedecay/src/daemon/branch_admin.rs @@ -956,14 +956,13 @@ impl StoreAdministration { if let Some(database) = registry.mounted_project_sessions(&project_id).await { return Ok(database); } - let enrollment_roots = - Box::pin(crate::tracedecay::TraceDecay::registered_enrollment_roots( - project_root, - store_layout, - &project_id, - profile_database.as_ref(), - )) - .await?; + let enrollment_roots = Box::pin(tracedecay_global_db::registered_enrollment_roots( + profile_database.as_ref(), + project_root, + store_layout, + &project_id, + )) + .await?; Box::pin(registry.project_sessions(project_id, enrollment_roots)).await } diff --git a/crates/tracedecay/src/daemon/branch_admin/project_retirement.rs b/crates/tracedecay/src/daemon/branch_admin/project_retirement.rs index 0eb6d70dd6..00293b27d5 100644 --- a/crates/tracedecay/src/daemon/branch_admin/project_retirement.rs +++ b/crates/tracedecay/src/daemon/branch_admin/project_retirement.rs @@ -495,13 +495,13 @@ mod tests { project_id: &str, ) -> ( crate::tracedecay::TraceDecay, - crate::host_admission::HostAdmissionTestRuntimeV1, + crate::test_support::host_admission::HostAdmissionTestRuntimeV1, ) { std::fs::create_dir_all(profile_root).expect("isolated profile root"); std::fs::create_dir_all(project_root).expect("isolated project root"); let project_id = tracedecay_domain::ProjectId::new(project_id.to_owned()) .expect("typed project identity"); - let runtime = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile_root, project_root, project_id, @@ -522,13 +522,13 @@ mod tests { } async fn isolated_sibling_graph( - runtime: &crate::host_admission::HostAdmissionTestRuntimeV1, + runtime: &crate::test_support::host_admission::HostAdmissionTestRuntimeV1, profile_root: &std::path::Path, project_root: &std::path::Path, project_id: &str, ) -> ( crate::tracedecay::TraceDecay, - crate::host_admission::HostAdmissionTestRuntimeV1, + crate::test_support::host_admission::HostAdmissionTestRuntimeV1, ) { std::fs::create_dir_all(project_root).expect("isolated sibling project root"); let project_id = tracedecay_domain::ProjectId::new(project_id.to_owned()) diff --git a/crates/tracedecay/src/daemon/code_index_runtime_graph_activation_tests.rs b/crates/tracedecay/src/daemon/code_index_runtime_graph_activation_tests.rs index c21ace9b55..952d59d354 100644 --- a/crates/tracedecay/src/daemon/code_index_runtime_graph_activation_tests.rs +++ b/crates/tracedecay/src/daemon/code_index_runtime_graph_activation_tests.rs @@ -299,7 +299,7 @@ async fn persistent_graph_activation_publishes_a_small_generation() { // Activation issues verified graph reads; the project graph runtime binds // asynchronously after `project_memory` returns, so an unawaited bind // races activation into "not ready for verified reads". - crate::host_admission::await_bound_graph_runtime( + crate::test_support::host_admission::await_bound_graph_runtime( &project_database, "bind small persistent activation graph runtime", ) @@ -446,7 +446,7 @@ async fn restart_status_case(corrupt_graph: bool, dirty_before_restart: bool) { .project_memory(project_id.clone(), [fixture.path().to_path_buf()]) .await .expect("writable project database"); - crate::host_admission::await_bound_graph_runtime( + crate::test_support::host_admission::await_bound_graph_runtime( &project_database, "bind stale graph status projection", ) @@ -523,7 +523,7 @@ async fn restart_status_case(corrupt_graph: bool, dirty_before_restart: bool) { .project_memory(project_id.clone(), [fixture.path().to_path_buf()]) .await .expect("restarted writable project database"); - crate::host_admission::await_bound_graph_runtime( + crate::test_support::host_admission::await_bound_graph_runtime( &project_database, "bind restarted graph status projection", ) diff --git a/crates/tracedecay/src/daemon/connection_serving.rs b/crates/tracedecay/src/daemon/connection_serving.rs index 679b7aa3c3..3ba4aee753 100644 --- a/crates/tracedecay/src/daemon/connection_serving.rs +++ b/crates/tracedecay/src/daemon/connection_serving.rs @@ -17,6 +17,7 @@ use tracedecay_session_memory::context::CancellationToken; /// routing, selected-project response, delivery-settlement, and RMCP adapter /// path as the daemon without adding a shipped benchmark API. #[cfg(feature = "rmcp-benchmark")] +#[path = "../../benches/rmcp/benchmark.rs"] pub mod rmcp_benchmark; impl BrokerSelectedResponseLease for crate::mcp::server::SelectedProjectResponseLease { diff --git a/crates/tracedecay/src/daemon/context_scout_lifecycle/tests.rs b/crates/tracedecay/src/daemon/context_scout_lifecycle/tests.rs index eeac80d232..5af9781779 100644 --- a/crates/tracedecay/src/daemon/context_scout_lifecycle/tests.rs +++ b/crates/tracedecay/src/daemon/context_scout_lifecycle/tests.rs @@ -15,7 +15,7 @@ use tracedecay_store::{ }; use super::*; -use crate::host_admission::HostAdmissionTestRuntimeV1; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; fn id>(value: &str) -> T diff --git a/crates/tracedecay/src/daemon/invocation_tests/types_tests.rs b/crates/tracedecay/src/daemon/invocation_tests/types_tests.rs index 881089125d..e1d8b30c52 100644 --- a/crates/tracedecay/src/daemon/invocation_tests/types_tests.rs +++ b/crates/tracedecay/src/daemon/invocation_tests/types_tests.rs @@ -910,7 +910,7 @@ async fn feedback_admission_conflicts_construct_zero_losing_producers() { let _pin = tracedecay_runtime_core::config::PinnedUserDataDir::new(); let project = tempfile::tempdir().expect("project root"); let project_id = ProjectId::new("project.feedback.atomic-publication").expect("project id"); - let host = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let host = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( tracedecay_runtime_core::storage::default_profile_root().expect("profile root"), project.path(), project_id.clone(), diff --git a/crates/tracedecay/src/daemon/invocation_tests/work_evidence_journey_tests.rs b/crates/tracedecay/src/daemon/invocation_tests/work_evidence_journey_tests.rs index cf7d781d5b..cf867488c2 100644 --- a/crates/tracedecay/src/daemon/invocation_tests/work_evidence_journey_tests.rs +++ b/crates/tracedecay/src/daemon/invocation_tests/work_evidence_journey_tests.rs @@ -256,7 +256,7 @@ async fn registered_work_evidence_hydrates_the_provider_qualified_task_session() let project_id = id::("project.work.evidence-journey"); let repository_id = id::("repository.work.evidence-journey"); let worktree_id = id::("worktree.work.evidence-journey"); - let host = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let host = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile.path(), &project, project_id.clone(), diff --git a/crates/tracedecay/src/daemon/invocation_tests/work_tests.rs b/crates/tracedecay/src/daemon/invocation_tests/work_tests.rs index 320b6f53fb..207efa620e 100644 --- a/crates/tracedecay/src/daemon/invocation_tests/work_tests.rs +++ b/crates/tracedecay/src/daemon/invocation_tests/work_tests.rs @@ -73,7 +73,7 @@ async fn registered_work_services_dispatch_the_core_lifecycle() { let _pin = tracedecay_runtime_core::config::PinnedUserDataDir::new(); let project = tempfile::tempdir().expect("project root"); let project_id = ProjectId::new("project.work.core-invocation").expect("project id"); - let host = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let host = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( tracedecay_runtime_core::storage::default_profile_root().expect("profile root"), project.path(), project_id.clone(), @@ -494,7 +494,7 @@ async fn committed_work_mutations_publish_task_activity_and_reads_do_not() { let _pin = tracedecay_runtime_core::config::PinnedUserDataDir::new(); let project = tempfile::tempdir().expect("project root"); let project_id = ProjectId::new("project.work.task-activity").expect("project id"); - let host = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let host = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( tracedecay_runtime_core::storage::default_profile_root().expect("profile root"), project.path(), project_id.clone(), diff --git a/crates/tracedecay/src/daemon/project_composition.rs b/crates/tracedecay/src/daemon/project_composition.rs index 8ab675787a..81a9515288 100644 --- a/crates/tracedecay/src/daemon/project_composition.rs +++ b/crates/tracedecay/src/daemon/project_composition.rs @@ -516,7 +516,7 @@ impl ComposedCoreServer { )) .with_code_graph_read_admission_port(Arc::clone(&code_index.graph_read_admission_port)) .with_verified_graph_query_port( - crate::tracedecay::queries::graph::admitted_verified_graph_query_port_with_source( + tracedecay_graph_query::admitted_verified_graph_query_port_with_source( Arc::clone(&code_index.graph_read_admission_port), Arc::clone(&code_index.graph_projection_read_port), cg.source_read_context(), diff --git a/crates/tracedecay/src/daemon/project_open_owners.rs b/crates/tracedecay/src/daemon/project_open_owners.rs index 205cb15eb2..16f9c26c54 100644 --- a/crates/tracedecay/src/daemon/project_open_owners.rs +++ b/crates/tracedecay/src/daemon/project_open_owners.rs @@ -494,7 +494,7 @@ pub(super) async fn register_project_open_production_owners( // Project-open has no authenticated GitHub response or persisted source // record. It mounts policy and delivery only; the review refresh owner is // the sole producer of canonical provider observations and anchors. - if crate::tracedecay::git_remote_url(project_root) + if tracedecay_runtime_core::git::git_remote_url(project_root) .as_deref() .and_then(github_repository_from_remote) .is_some() diff --git a/crates/tracedecay/src/daemon/project_open_owners/advisory_runtime.rs b/crates/tracedecay/src/daemon/project_open_owners/advisory_runtime.rs index e24151f36c..4010fe9177 100644 --- a/crates/tracedecay/src/daemon/project_open_owners/advisory_runtime.rs +++ b/crates/tracedecay/src/daemon/project_open_owners/advisory_runtime.rs @@ -1501,7 +1501,7 @@ fn resolve_production_github_provider_access( project_root: &Path, state: &ProjectOpenDependentOwnerState, ) -> std::result::Result { - let Some(remote_url) = crate::tracedecay::git_remote_url(project_root) else { + let Some(remote_url) = tracedecay_runtime_core::git::git_remote_url(project_root) else { return Err(ProjectDeliveryProviderMountGateV1::NoGitRemote); }; let Some((owner, repository)) = super::github_repository_from_remote(&remote_url) else { diff --git a/crates/tracedecay/src/daemon/project_open_owners/git_catalog_tests.rs b/crates/tracedecay/src/daemon/project_open_owners/git_catalog_tests.rs index 16561d3d07..acea8144d7 100644 --- a/crates/tracedecay/src/daemon/project_open_owners/git_catalog_tests.rs +++ b/crates/tracedecay/src/daemon/project_open_owners/git_catalog_tests.rs @@ -41,7 +41,7 @@ async fn git_owner_uses_explicit_canonical_catalog_and_rechecks_authorization() git(&project_root, &["add", "."]); git(&project_root, &["commit", "-m", "fixture"]); let project_id = ProjectId::new("project.git-catalog").unwrap(); - let fixture = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let fixture = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( &profile_root, &project_root, project_id.clone(), diff --git a/crates/tracedecay/src/daemon/projectless.rs b/crates/tracedecay/src/daemon/projectless.rs index 412125c023..e8e4357c97 100644 --- a/crates/tracedecay/src/daemon/projectless.rs +++ b/crates/tracedecay/src/daemon/projectless.rs @@ -700,7 +700,7 @@ mod projectless_admission_tests { .expect("restrict foreign profile root"); } crate::product_runtime::register_fixture_product_runtime(); - crate::host_admission::ensure_process_background_cpu_authority() + crate::test_support::host_admission::ensure_process_background_cpu_authority() .expect("install fixture worker authority"); let identity = tracedecay_daemon_identity::profile_identity::load_or_create(&real_root) .expect("pin profile identity"); @@ -769,7 +769,7 @@ mod projectless_admission_tests { let temp = tempfile::tempdir().expect("tempdir"); let (real_root, linked_root) = linked_profile_root(temp.path()); crate::product_runtime::register_fixture_product_runtime(); - crate::host_admission::ensure_process_background_cpu_authority() + crate::test_support::host_admission::ensure_process_background_cpu_authority() .expect("install fixture worker authority"); let identity = tracedecay_daemon_identity::profile_identity::load_or_create(&real_root) .expect("pin profile identity"); diff --git a/crates/tracedecay/src/daemon/retained_owner/memory_target.rs b/crates/tracedecay/src/daemon/retained_owner/memory_target.rs index 3efa91fc61..d599665782 100644 --- a/crates/tracedecay/src/daemon/retained_owner/memory_target.rs +++ b/crates/tracedecay/src/daemon/retained_owner/memory_target.rs @@ -146,8 +146,8 @@ async fn open_selected_project_read_only<'a>( if context.project.project_id.as_str() != selected_project_id.as_str() { return denied(); } - let roots = TraceDecay::enrolled_project_roots( - TraceDecay::registry_context_candidate_roots(&context), + let roots = tracedecay_runtime_core::storage::enrolled_project_roots( + tracedecay_global_db::registry_context_candidate_roots(&context), selected_project_id, ) .map_err(map_target_infrastructure_error)?; @@ -224,7 +224,7 @@ mod tests { tempfile::TempDir, TraceDecay, TraceDecay, - Arc, + Arc, ) { let tmp = tempfile::tempdir().unwrap(); // Register the same canonical paths that retained-target lookup uses. diff --git a/crates/tracedecay/src/daemon/retained_owner/session/retained_effect_tests.rs b/crates/tracedecay/src/daemon/retained_owner/session/retained_effect_tests.rs index 3afc954261..08cbb03161 100644 --- a/crates/tracedecay/src/daemon/retained_owner/session/retained_effect_tests.rs +++ b/crates/tracedecay/src/daemon/retained_owner/session/retained_effect_tests.rs @@ -32,8 +32,8 @@ use crate::daemon::StoreOwnerKey; use crate::daemon::retained_owner::session_refresh::{ MountedSessionRefreshAuthorityV1, admitted_session_refresh_command, }; -use crate::host_admission::HostAdmissionTestRuntimeV1; use crate::mcp::server::DaemonSessionRefreshService; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_daemon_service::DaemonWorkflowIndexReadService; use tracedecay_global_db::{RegisteredGlobalDb, RegisteredGlobalDbLeaseV1}; use tracedecay_session_memory::session::{SessionRefreshServiceOutcome, SessionRefreshServicePort}; diff --git a/crates/tracedecay/src/daemon/session_runtime_tests.rs b/crates/tracedecay/src/daemon/session_runtime_tests.rs index e6bc4af636..537e1683cb 100644 --- a/crates/tracedecay/src/daemon/session_runtime_tests.rs +++ b/crates/tracedecay/src/daemon/session_runtime_tests.rs @@ -8,7 +8,7 @@ use tempfile::TempDir; use tracedecay_session_runtime::StoreOwnerKey; use tracedecay_sessions::admission::HostAdmissionScope; -use crate::host_admission::HostAdmissionTestRuntimeV1; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; #[tokio::test] async fn evicted_project_owner_releases_temporal_scheduler() { diff --git a/crates/tracedecay/src/daemon/store_maintenance/mod.rs b/crates/tracedecay/src/daemon/store_maintenance/mod.rs index f5a80727a1..50e4f6f065 100644 --- a/crates/tracedecay/src/daemon/store_maintenance/mod.rs +++ b/crates/tracedecay/src/daemon/store_maintenance/mod.rs @@ -1075,8 +1075,11 @@ async fn collect_scope_root_proof_inputs( .collect::>(); let project_id = tracedecay_domain::ProjectId::new(project_id.to_owned()) .map_err(|_| "registered_project_identity_invalid")?; - let enrolled_roots = TraceDecay::enrolled_project_roots(registered_candidates, &project_id) - .map_err(|_| "registered_enrollment_inventory_unavailable")?; + let enrolled_roots = tracedecay_runtime_core::storage::enrolled_project_roots( + registered_candidates, + &project_id, + ) + .map_err(|_| "registered_enrollment_inventory_unavailable")?; if enrolled_roots.is_empty() { return Err("registered_enrollment_inventory_empty"); } diff --git a/crates/tracedecay/src/daemon/tests/bootstrap.rs b/crates/tracedecay/src/daemon/tests/bootstrap.rs index a8c408baf7..331302c9a9 100644 --- a/crates/tracedecay/src/daemon/tests/bootstrap.rs +++ b/crates/tracedecay/src/daemon/tests/bootstrap.rs @@ -406,11 +406,11 @@ async fn orphaned_store_with_repository_identity_is_readopted_without_aliasing() // marker without creating anything in the working tree. let typed_project_id = tracedecay_store::ProjectId::new(project_id.to_owned()).expect("typed project id"); - let roots = crate::tracedecay::TraceDecay::registered_enrollment_roots( + let roots = tracedecay_global_db::registered_enrollment_roots( + registry.as_ref(), &project, &store_layout, &typed_project_id, - registry.as_ref(), ) .await .expect("re-adoption must resolve the enrollment root"); @@ -3859,8 +3859,10 @@ async fn production_composition_harness_reads_retained_profile_analytics_authori .ledger_writes_settled() .await; - let second_owner = - crate::host_admission::HostAdmissionTestRuntimeV1::profile(harness.profile_root()).await; + let second_owner = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile( + harness.profile_root(), + ) + .await; let error = match second_owner { Ok(_) => panic!("parallel profile authority must remain rejected"), Err(error) => error, diff --git a/crates/tracedecay/src/daemon/tests/restart_proxy.rs b/crates/tracedecay/src/daemon/tests/restart_proxy.rs index 820bc9809f..d1d3aa9aee 100644 --- a/crates/tracedecay/src/daemon/tests/restart_proxy.rs +++ b/crates/tracedecay/src/daemon/tests/restart_proxy.rs @@ -495,9 +495,10 @@ async fn initialize_root_routing_replaces_cached_project_and_scope() { let project_b = TempDir::new().expect("project b temp dir"); let project_a = project_a.path().canonicalize().expect("project a path"); let project_b = project_b.path().canonicalize().expect("project b path"); - let registry = crate::host_admission::HostAdmissionTestRuntimeV1::profile(profile.path()) - .await - .expect("open retained profile runtime"); + let registry = + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile(profile.path()) + .await + .expect("open retained profile runtime"); let global_db_path = profile.path().join("global.db"); registry .upsert_code_project("project-a", &project_a, None, None, None) @@ -596,9 +597,10 @@ async fn daemon_resolves_registry_only_initialize_root_alias() { let alias = alias.path().canonicalize().expect("canonical alias"); let nested = alias.join("nested"); std::fs::create_dir_all(&nested).expect("nested alias path"); - let registry = crate::host_admission::HostAdmissionTestRuntimeV1::profile(profile.path()) - .await - .expect("open retained profile runtime"); + let registry = + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::profile(profile.path()) + .await + .expect("open retained profile runtime"); let global_db_path = profile.path().join("global.db"); registry .upsert_code_project("project-registry-only", &canonical, None, None, None) diff --git a/crates/tracedecay/src/daemon/tests/socket.rs b/crates/tracedecay/src/daemon/tests/socket.rs index 451cc58a31..fbcbb972d2 100644 --- a/crates/tracedecay/src/daemon/tests/socket.rs +++ b/crates/tracedecay/src/daemon/tests/socket.rs @@ -1395,11 +1395,13 @@ async fn daemon_linked_worktree_route_repairs_primary_identity_and_keeps_alias() .expect("linked project registry context present"); assert_eq!( context.project.canonical_root, - crate::host_admission::HostAdmissionTestRuntimeV1::canonical_project_key(&primary) + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::canonical_project_key( + &primary + ) ); assert!(context.aliases.iter().any(|alias| { alias.alias_path - == crate::host_admission::HostAdmissionTestRuntimeV1::canonical_project_key(&linked) + == crate::test_support::host_admission::HostAdmissionTestRuntimeV1::canonical_project_key(&linked) })); } diff --git a/crates/tracedecay/src/daemon/work_evidence_retrieval_tests.rs b/crates/tracedecay/src/daemon/work_evidence_retrieval_tests.rs index 4f571f884c..2ed36bbede 100644 --- a/crates/tracedecay/src/daemon/work_evidence_retrieval_tests.rs +++ b/crates/tracedecay/src/daemon/work_evidence_retrieval_tests.rs @@ -31,7 +31,7 @@ async fn registered_project_session_hydrates_provider_qualified_task_evidence() let project_id = id::("project.work-task-session"); let repository_id = id::("repository.work-task-session"); let worktree_id = id::("worktree.work-task-session"); - let runtime = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile.path(), &project, project_id.clone(), diff --git a/crates/tracedecay/src/daemon/work_evidence_retrieval_tests/continuation.rs b/crates/tracedecay/src/daemon/work_evidence_retrieval_tests/continuation.rs index 939abde127..8c40f628cf 100644 --- a/crates/tracedecay/src/daemon/work_evidence_retrieval_tests/continuation.rs +++ b/crates/tracedecay/src/daemon/work_evidence_retrieval_tests/continuation.rs @@ -25,7 +25,7 @@ async fn continuation_resumes_the_same_provider_session_without_repeating_eviden let project_id = id::("project.work-task-session-continuation"); let repository_id = id::("repository.work-task-session-continuation"); let worktree_id = id::("worktree.work-task-session-continuation"); - let runtime = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile.path(), &project, project_id.clone(), diff --git a/crates/tracedecay/src/dashboard.rs b/crates/tracedecay/src/dashboard.rs index 660ec20d71..c4cfbd762c 100644 --- a/crates/tracedecay/src/dashboard.rs +++ b/crates/tracedecay/src/dashboard.rs @@ -38,6 +38,12 @@ pub use tracedecay_dashboard_api::{ #[doc(hidden)] pub mod observation_seed; +/// Test-only graph fixture. Compiled only under `test-transport`. +#[cfg(feature = "test-transport")] +#[doc(hidden)] +#[path = "dashboard_graph_test_runtime.rs"] +pub mod dashboard_graph_test_runtime; + /// Embedded single-page-app routes shared by production and integration /// servers. The caller supplies the registered product runtime's bundle; /// `tracedecay-api` owns route matching, cache policy, and the API fallback @@ -229,207 +235,6 @@ pub async fn dashboard_configuration_authorities_for_test( crate::daemon::dashboard_configuration_authorities_for_test(cg, profile_database).await } -/// Root-owned graph composition used by dashboard integration tests. -/// -/// The dashboard API crate cannot own daemon session registration or graph -/// lifecycle. This opaque adapter keeps those authorities at the root while -/// exposing only graph initialization and reopening to the integration suite. -#[cfg(feature = "test-transport")] -#[doc(hidden)] -pub struct DashboardGraphTestRuntimeV1 { - profile_root: std::path::PathBuf, - profile_database: tracedecay_global_db::RegisteredGlobalDbLeaseV1, - profile_sessions_database: tracedecay_global_db::RegisteredGlobalDbLeaseV1, - registry: std::sync::Arc, - _database_scope: tracedecay_runtime_core::db::DaemonDatabaseScope, -} - -#[cfg(feature = "test-transport")] -impl DashboardGraphTestRuntimeV1 { - #[hotpath::skip] - pub async fn open( - profile_root: impl AsRef, - ) -> tracedecay_domain::errors::Result { - use std::sync::atomic::{AtomicU64, Ordering}; - - // This fixture bypasses CLI and host-admission constructors, so it - // must install the same root ports before graph init publishes Hook - // bindings for the admitted project. - crate::register_runtime_ports()?; - - static NEXT_ELECTION_EPOCH: AtomicU64 = AtomicU64::new(1); - - let profile_root = profile_root.as_ref().to_path_buf(); - let identity = tracedecay_daemon_identity::profile_identity::load_or_create(&profile_root)?; - let epoch = NEXT_ELECTION_EPOCH.fetch_add(1, Ordering::Relaxed); - let database_scope = tracedecay_runtime_core::db::enter_daemon_database_scope( - identity.profile_root(), - epoch, - "dashboard-graph-test-runtime", - )?; - let registry = std::sync::Arc::new( - hotpath::future!( - tracedecay_store_runtime::DaemonSessionRuntimeRegistryV1::open(identity,), - label = "dashboard.graph.registry" - ) - .await?, - ); - let profile_database = hotpath::future!( - registry.profile_database(), - label = "dashboard.graph.profile_database" - ) - .await?; - let profile_sessions_database = hotpath::future!( - registry.profile_sessions(), - label = "dashboard.graph.profile_sessions" - ) - .await?; - Ok(Self { - profile_root, - profile_database, - profile_sessions_database, - registry, - _database_scope: database_scope, - }) - } - - pub fn profile_database(&self) -> tracedecay_global_db::RegisteredGlobalDbLeaseV1 { - self.profile_database.clone() - } - - pub fn profile_sessions_database(&self) -> tracedecay_global_db::RegisteredGlobalDbLeaseV1 { - self.profile_sessions_database.clone() - } - - #[hotpath::skip] - pub async fn project_sessions( - &self, - project_root: &std::path::Path, - project_id: tracedecay_domain::ProjectId, - ) -> tracedecay_domain::errors::Result { - let registered = hotpath::future!( - self.registry - .project_sessions(project_id.clone(), [project_root.to_path_buf()]), - label = "dashboard.graph.project_sessions" - ) - .await?; - // Production project open binds a weak project graph proxy to the - // registered project-sessions authority before any ingest runs; - // git-evidence publication (Loom spans) requires that mount, so the - // dashboard test composition provides the same binding. The registry - // caches the mount per project, so repeated opens reuse the proxy. - if registered.project_graph_runtime().is_none() { - let project_database = hotpath::future!( - self.registry - .project_memory(project_id.clone(), [project_root.to_path_buf()]), - label = "dashboard.graph.project_memory" - ) - .await?; - let graph_proxy = crate::host_admission::await_bound_graph_runtime( - &project_database, - "bind dashboard project graph", - ) - .await?; - // A lost set race means another caller already bound the same - // weak proxy; the required postcondition holds either way. - let _ = registered.bind_project_graph_runtime(graph_proxy); - } - Ok(registered) - } - - #[hotpath::skip] - pub async fn initialize( - &self, - project_root: &std::path::Path, - project_id: tracedecay_domain::ProjectId, - ) -> tracedecay_domain::errors::Result { - // Fixture identity is pinned in the sanctioned `.git/` repository - // identity marker; nothing is written into the working tree. - tracedecay_runtime_core::storage::pin_fixture_repository_identity( - project_root, - project_id.as_str(), - )?; - let options = crate::tracedecay::TraceDecayOpenOptions { - profile_root: Some(self.profile_root.clone()), - global_db_path: Some(self.profile_database.db_path().to_path_buf()), - }; - let layout = hotpath::future!( - crate::tracedecay::TraceDecay::resolve_registered_configuration_layout( - project_root, - &options, - self.profile_database.as_ref(), - ), - label = "dashboard.graph.layout" - ) - .await?; - if layout.identity.project_id.as_deref() != Some(project_id.as_str()) { - return Err(tracedecay_domain::errors::TraceDecayError::Config { - message: "dashboard graph identity differs from its test authority".to_owned(), - }); - } - let project_database = self.project_sessions(project_root, project_id).await?; - hotpath::future!( - crate::tracedecay::TraceDecay::init_with_registered_configuration( - project_root, - options, - layout, - project_database, - self.profile_database.clone(), - std::sync::Arc::clone(&self.registry), - ), - label = "dashboard.graph.init" - ) - .await - } - - #[hotpath::skip] - pub async fn reopen( - &self, - project_root: &std::path::Path, - ) -> tracedecay_domain::errors::Result { - let options = crate::tracedecay::TraceDecayOpenOptions { - profile_root: Some(self.profile_root.clone()), - global_db_path: Some(self.profile_database.db_path().to_path_buf()), - }; - let layout = hotpath::future!( - crate::tracedecay::TraceDecay::resolve_registered_configuration_layout( - project_root, - &options, - self.profile_database.as_ref(), - ), - label = "dashboard.graph.reopen.layout" - ) - .await?; - let project_id = layout - .identity - .project_id - .as_deref() - .ok_or_else(|| tracedecay_domain::errors::TraceDecayError::Config { - message: "dashboard graph fixture has no project identity".to_owned(), - }) - .and_then(|project_id| { - tracedecay_domain::ProjectId::new(project_id.to_owned()).map_err(|error| { - tracedecay_domain::errors::TraceDecayError::Config { - message: format!("invalid dashboard graph fixture identity: {error}"), - } - }) - })?; - let project_database = self.project_sessions(project_root, project_id).await?; - hotpath::future!( - crate::tracedecay::TraceDecay::open_with_registered_configuration( - project_root, - options, - layout, - project_database, - self.profile_database.clone(), - std::sync::Arc::clone(&self.registry), - ), - label = "dashboard.graph.reopen.open" - ) - .await - } -} - /// Composes the daemon-owned LCM read authority over the fixture's /// registered project-sessions store — the same `DashboardLcmReadAdapter` /// over the daemon session retrieval service that the MCP dashboard diff --git a/crates/tracedecay/src/dashboard_graph_test_runtime.rs b/crates/tracedecay/src/dashboard_graph_test_runtime.rs new file mode 100644 index 0000000000..7e0cae1e56 --- /dev/null +++ b/crates/tracedecay/src/dashboard_graph_test_runtime.rs @@ -0,0 +1,201 @@ +//! Test-only dashboard graph fixture. Compiled only under `test-transport` +//! because no production caller needs this runtime. + +use std::sync::atomic::{AtomicU64, Ordering}; + +/// Root-owned graph composition used by dashboard integration tests. +/// +/// The dashboard API crate cannot own daemon session registration or graph +/// lifecycle. This adapter keeps those authorities at the test composition +/// layer while exposing only graph initialization and reopening. +#[doc(hidden)] +pub struct DashboardGraphTestRuntimeV1 { + profile_root: std::path::PathBuf, + profile_database: tracedecay_global_db::RegisteredGlobalDbLeaseV1, + profile_sessions_database: tracedecay_global_db::RegisteredGlobalDbLeaseV1, + registry: std::sync::Arc, + _database_scope: tracedecay_runtime_core::db::DaemonDatabaseScope, +} + +impl DashboardGraphTestRuntimeV1 { + #[hotpath::skip] + pub async fn open( + profile_root: impl AsRef, + ) -> tracedecay_domain::errors::Result { + // This fixture bypasses CLI and host-admission constructors, so it + // must install the same root ports before graph init publishes Hook + // bindings for the admitted project. + crate::register_runtime_ports()?; + + static NEXT_ELECTION_EPOCH: AtomicU64 = AtomicU64::new(1); + + let profile_root = profile_root.as_ref().to_path_buf(); + let identity = tracedecay_daemon_identity::profile_identity::load_or_create(&profile_root)?; + let epoch = NEXT_ELECTION_EPOCH.fetch_add(1, Ordering::Relaxed); + let database_scope = tracedecay_runtime_core::db::enter_daemon_database_scope( + identity.profile_root(), + epoch, + "dashboard-graph-test-runtime", + )?; + let registry = std::sync::Arc::new( + hotpath::future!( + tracedecay_store_runtime::DaemonSessionRuntimeRegistryV1::open(identity,), + label = "dashboard.graph.registry" + ) + .await?, + ); + let profile_database = hotpath::future!( + registry.profile_database(), + label = "dashboard.graph.profile_database" + ) + .await?; + let profile_sessions_database = hotpath::future!( + registry.profile_sessions(), + label = "dashboard.graph.profile_sessions" + ) + .await?; + Ok(Self { + profile_root, + profile_database, + profile_sessions_database, + registry, + _database_scope: database_scope, + }) + } + + pub fn profile_database(&self) -> tracedecay_global_db::RegisteredGlobalDbLeaseV1 { + self.profile_database.clone() + } + + pub fn profile_sessions_database(&self) -> tracedecay_global_db::RegisteredGlobalDbLeaseV1 { + self.profile_sessions_database.clone() + } + + #[hotpath::skip] + pub async fn project_sessions( + &self, + project_root: &std::path::Path, + project_id: tracedecay_domain::ProjectId, + ) -> tracedecay_domain::errors::Result { + let registered = hotpath::future!( + self.registry + .project_sessions(project_id.clone(), [project_root.to_path_buf()]), + label = "dashboard.graph.project_sessions" + ) + .await?; + // Production project open binds a weak project graph proxy to the + // registered project-sessions authority before any ingest runs; + // git-evidence publication (Loom spans) requires that mount, so the + // dashboard test composition provides the same binding. The registry + // caches the mount per project, so repeated opens reuse the proxy. + if registered.project_graph_runtime().is_none() { + let project_database = hotpath::future!( + self.registry + .project_memory(project_id.clone(), [project_root.to_path_buf()]), + label = "dashboard.graph.project_memory" + ) + .await?; + let graph_proxy = crate::test_support::host_admission::await_bound_graph_runtime( + &project_database, + "bind dashboard project graph", + ) + .await?; + // A lost set race means another caller already bound the same + // weak proxy; the required postcondition holds either way. + let _ = registered.bind_project_graph_runtime(graph_proxy); + } + Ok(registered) + } + + #[hotpath::skip] + pub async fn initialize( + &self, + project_root: &std::path::Path, + project_id: tracedecay_domain::ProjectId, + ) -> tracedecay_domain::errors::Result { + // Fixture identity is pinned in the sanctioned `.git/` repository + // identity marker; nothing is written into the working tree. + tracedecay_runtime_core::storage::pin_fixture_repository_identity( + project_root, + project_id.as_str(), + )?; + let options = crate::tracedecay::TraceDecayOpenOptions { + profile_root: Some(self.profile_root.clone()), + global_db_path: Some(self.profile_database.db_path().to_path_buf()), + }; + let layout = hotpath::future!( + crate::tracedecay::TraceDecay::resolve_registered_configuration_layout( + project_root, + &options, + self.profile_database.as_ref(), + ), + label = "dashboard.graph.layout" + ) + .await?; + if layout.identity.project_id.as_deref() != Some(project_id.as_str()) { + return Err(tracedecay_domain::errors::TraceDecayError::Config { + message: "dashboard graph identity differs from its test authority".to_owned(), + }); + } + let project_database = self.project_sessions(project_root, project_id).await?; + hotpath::future!( + crate::tracedecay::TraceDecay::init_with_registered_configuration( + project_root, + options, + layout, + project_database, + self.profile_database.clone(), + std::sync::Arc::clone(&self.registry), + ), + label = "dashboard.graph.init" + ) + .await + } + + #[hotpath::skip] + pub async fn reopen( + &self, + project_root: &std::path::Path, + ) -> tracedecay_domain::errors::Result { + let options = crate::tracedecay::TraceDecayOpenOptions { + profile_root: Some(self.profile_root.clone()), + global_db_path: Some(self.profile_database.db_path().to_path_buf()), + }; + let layout = hotpath::future!( + crate::tracedecay::TraceDecay::resolve_registered_configuration_layout( + project_root, + &options, + self.profile_database.as_ref(), + ), + label = "dashboard.graph.reopen.layout" + ) + .await?; + let project_id = layout + .identity + .project_id + .as_deref() + .ok_or_else(|| tracedecay_domain::errors::TraceDecayError::Config { + message: "dashboard graph fixture has no project identity".to_owned(), + }) + .and_then(|project_id| { + tracedecay_domain::ProjectId::new(project_id.to_owned()).map_err(|error| { + tracedecay_domain::errors::TraceDecayError::Config { + message: format!("invalid dashboard graph fixture identity: {error}"), + } + }) + })?; + let project_database = self.project_sessions(project_root, project_id).await?; + hotpath::future!( + crate::tracedecay::TraceDecay::open_with_registered_configuration( + project_root, + options, + layout, + project_database, + self.profile_database.clone(), + std::sync::Arc::clone(&self.registry), + ), + label = "dashboard.graph.reopen.open" + ) + .await + } +} diff --git a/crates/tracedecay/src/host_admission_test.rs b/crates/tracedecay/src/host_admission_test.rs index bff7fa78a1..e8706b9a32 100644 --- a/crates/tracedecay/src/host_admission_test.rs +++ b/crates/tracedecay/src/host_admission_test.rs @@ -169,8 +169,9 @@ async fn host_ingress_binds_provenance_to_authoritative_project_and_replays_stab // process's worker plan. Observation capture prepares under the background // CPU authority that plan installs and refuses with // `Unavailable/background_cpu_unavailable` when none is injected. - let background_cpu = crate::host_admission::ensure_process_background_cpu_authority() - .expect("install the process background CPU authority"); + let background_cpu = + crate::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install the process background CPU authority"); let root = TempDir::new().unwrap(); let repository_root = root.path().join("repository"); initialize_repository(&repository_root); @@ -372,8 +373,9 @@ async fn registered_profile_runtime_is_required_and_mismatch_never_falls_back() // process's worker plan. Observation capture prepares under the background // CPU authority that plan installs and refuses with // `Unavailable/background_cpu_unavailable` when none is injected. - let background_cpu = crate::host_admission::ensure_process_background_cpu_authority() - .expect("install the process background CPU authority"); + let background_cpu = + crate::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install the process background CPU authority"); let temporary = TempDir::new().unwrap(); let profile_root = temporary.path().join("profile"); let identity = @@ -505,8 +507,9 @@ async fn registered_project_runtime_is_exact_and_revocation_never_falls_back() { // process's worker plan. Observation capture prepares under the background // CPU authority that plan installs and refuses with // `Unavailable/background_cpu_unavailable` when none is injected. - let background_cpu = crate::host_admission::ensure_process_background_cpu_authority() - .expect("install the process background CPU authority"); + let background_cpu = + crate::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install the process background CPU authority"); let temporary = TempDir::new().unwrap(); let profile_root = temporary.path().join("profile"); let project_root = temporary.path().join("project"); diff --git a/crates/tracedecay/src/lib.rs b/crates/tracedecay/src/lib.rs index b04210fb3c..7b144cd8a2 100644 --- a/crates/tracedecay/src/lib.rs +++ b/crates/tracedecay/src/lib.rs @@ -35,11 +35,16 @@ #![allow(clippy::missing_fields_in_debug)] #![allow(clippy::single_match_else)] +// Query-bench implementation for `tracedecay bench` / MCP `admin_project` +// action `bench`. Kept off the default library graph; the CLI production +// feature selects `bench` so the shipped command still compiles. +#[cfg(any(test, feature = "bench", feature = "test-helpers"))] +#[path = "../benches/query_bench/harness.rs"] +pub mod bench; // Fixture surface for integration tests, assembled by the composition root. // Gated so a default or `production` build carries none of it. -pub mod bench; #[cfg(any(test, feature = "test-helpers"))] -pub mod host_admission; +pub mod test_support; pub use tracedecay_code_index as code_index; pub use tracedecay_query as query; pub mod config; @@ -60,10 +65,10 @@ mod project_store_runtime; mod runtime_ports; pub use runtime_ports::{hook_runtime, register_runtime_ports}; pub mod serve; -// Benchmark harness, not product surface: the shipped library must not carry -// its fixture provisioning or process-environment mutation. The `session_temporal` -// bench target and the `test-helpers` integration lanes select it explicitly. +// Session-temporal harness lives under `benches/`; the lib only paths it in +// when a bench target or integration lane asks for `test-helpers`. #[cfg(any(test, feature = "test-helpers"))] +#[path = "../benches/session_temporal/harness.rs"] pub mod session_temporal_benchmark; pub mod tracedecay; #[doc(hidden)] diff --git a/crates/tracedecay/src/mcp/server.rs b/crates/tracedecay/src/mcp/server.rs index cd173fa82d..0bcdb0da68 100644 --- a/crates/tracedecay/src/mcp/server.rs +++ b/crates/tracedecay/src/mcp/server.rs @@ -437,7 +437,8 @@ pub struct McpServer { admitted_project_scope: Option, retained_project_server_resolver: Option, #[cfg(any(test, feature = "test-transport"))] - _host_admission_test_runtime: Option>, + _host_admission_test_runtime: + Option>, hook_project_routes: SharedHookProjectRouteCache, version_cache: std::sync::Mutex, pending_notifications: std::sync::Mutex>, @@ -650,7 +651,7 @@ impl McpServer { #[doc(hidden)] pub fn host_admission_test_runtime_for_test( &self, - ) -> Option<&crate::host_admission::HostAdmissionTestRuntimeV1> { + ) -> Option<&crate::test_support::host_admission::HostAdmissionTestRuntimeV1> { self._host_admission_test_runtime.as_deref() } @@ -660,7 +661,7 @@ impl McpServer { pub async fn new_with_host_admission_test_runtime_for_test( cg: TraceDecay, scope_prefix: Option, - runtime: crate::host_admission::ProjectScopedTestRuntimeV1, + runtime: crate::test_support::host_admission::ProjectScopedTestRuntimeV1, ) -> tracedecay_domain::errors::Result> { Self::new_with_retained_test_servers_for_test(cg, scope_prefix, runtime, Vec::new()).await } @@ -678,7 +679,7 @@ impl McpServer { pub async fn new_with_retained_test_servers_for_test( cg: TraceDecay, scope_prefix: Option, - runtime: crate::host_admission::ProjectScopedTestRuntimeV1, + runtime: crate::test_support::host_admission::ProjectScopedTestRuntimeV1, retained_servers: Vec>, ) -> tracedecay_domain::errors::Result> { let runtime = runtime.into_runtime(); diff --git a/crates/tracedecay/src/mcp/server/construction.rs b/crates/tracedecay/src/mcp/server/construction.rs index 8ae49b9f3f..f81b46176d 100644 --- a/crates/tracedecay/src/mcp/server/construction.rs +++ b/crates/tracedecay/src/mcp/server/construction.rs @@ -187,7 +187,7 @@ pub(crate) struct McpServerConstructionContext { pub(crate) project_server_live: Option>, #[cfg(any(test, feature = "test-transport"))] pub(crate) host_admission_test_runtime: - Option>, + Option>, } pub(crate) struct McpServerWriters { diff --git a/crates/tracedecay/src/mcp/server/freshness_tests.rs b/crates/tracedecay/src/mcp/server/freshness_tests.rs index bfb8f88efe..14c10061f3 100644 --- a/crates/tracedecay/src/mcp/server/freshness_tests.rs +++ b/crates/tracedecay/src/mcp/server/freshness_tests.rs @@ -60,7 +60,7 @@ fn git(root: &std::path::Path, args: &[&str]) { struct FreshnessFixtureAuthority { _pin: PinnedUserDataDir, - _runtime: Arc, + _runtime: Arc, } async fn init_indexed_repo() -> (TraceDecay, TempDir, FreshnessFixtureAuthority) { diff --git a/crates/tracedecay/src/mcp/server/host_admission_tests.rs b/crates/tracedecay/src/mcp/server/host_admission_tests.rs index f376d6259e..3287f68d64 100644 --- a/crates/tracedecay/src/mcp/server/host_admission_tests.rs +++ b/crates/tracedecay/src/mcp/server/host_admission_tests.rs @@ -11,8 +11,8 @@ use super::writer_test_support::{ WriterTestFixtureAuthority, init_indexed_repo, registered_context, registered_runtime, }; use super::{CodeIndexReconcileSink, McpServer, McpServerConstructionContext}; -use crate::host_admission::HostAdmissionTestRuntimeV1; use crate::mcp::project_route::HookProjectRouteCache; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_hooks::core_events::{ DaemonHookEvent, HookAgent, HookRouteMetadata, HookTerminalReceipt, }; diff --git a/crates/tracedecay/src/mcp/server/lcm_claude_recall_tests.rs b/crates/tracedecay/src/mcp/server/lcm_claude_recall_tests.rs index 1d69db5466..3f836afeb7 100644 --- a/crates/tracedecay/src/mcp/server/lcm_claude_recall_tests.rs +++ b/crates/tracedecay/src/mcp/server/lcm_claude_recall_tests.rs @@ -15,7 +15,7 @@ use tempfile::TempDir; use tracedecay_domain::{ObservationScopeV1, ProjectId, SessionId}; use super::McpServer; -use crate::host_admission::HostAdmissionTestRuntimeV1; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use crate::tracedecay::TraceDecayOpenOptions; use tracedecay_application::observation::ObservationCancellation; use tracedecay_mcp::transport::JsonRpcRequest; diff --git a/crates/tracedecay/src/mcp/server/routing.rs b/crates/tracedecay/src/mcp/server/routing.rs index 019443dce4..8d0e368e59 100644 --- a/crates/tracedecay/src/mcp/server/routing.rs +++ b/crates/tracedecay/src/mcp/server/routing.rs @@ -411,7 +411,7 @@ mod tests { use tempfile::TempDir; use super::{resolve_initialize_roots_project_path, select_initialize_project_path}; - use crate::host_admission::HostAdmissionTestRuntimeV1; + use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; fn run_git(root: &Path, args: &[&str]) { diff --git a/crates/tracedecay/src/mcp/server/writer_test_support.rs b/crates/tracedecay/src/mcp/server/writer_test_support.rs index c7abe663a2..7a8d7743af 100644 --- a/crates/tracedecay/src/mcp/server/writer_test_support.rs +++ b/crates/tracedecay/src/mcp/server/writer_test_support.rs @@ -5,8 +5,8 @@ use tempfile::TempDir; use tracedecay_runtime_core::path_safety::{plain_git_args, plain_host_path}; use crate::config::PinnedUserDataDir; -use crate::host_admission::HostAdmissionTestRuntimeV1; use crate::mcp::server::McpServerConstructionContext; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use crate::tracedecay::TraceDecay; pub(super) fn git(root: &Path, args: &[&str]) { diff --git a/crates/tracedecay/src/mcp/tools/handlers/admin_cli.rs b/crates/tracedecay/src/mcp/tools/handlers/admin_cli.rs index 6d9600f852..9afbd76b8f 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/admin_cli.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/admin_cli.rs @@ -862,10 +862,10 @@ mod tests { project_id: &str, ) -> ( TraceDecay, - crate::host_admission::HostAdmissionTestRuntimeV1, + crate::test_support::host_admission::HostAdmissionTestRuntimeV1, ) { std::fs::create_dir_all(root).unwrap(); - let runtime = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile, root, ProjectId::new(project_id).unwrap(), diff --git a/crates/tracedecay/src/mcp/tools/handlers/admin_project.rs b/crates/tracedecay/src/mcp/tools/handlers/admin_project.rs index 3f26939a6e..1daf1d87bf 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/admin_project.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/admin_project.rs @@ -218,23 +218,36 @@ pub(super) async fn handle_admin_project( json, max_nodes, } => { - let report = crate::bench::run_bench_with_toml( - cg, - queries_toml - .as_deref() - .unwrap_or(crate::bench::DEFAULT_QUERIES_TOML), - crate::bench::BenchOptions { - format: crate::bench::OutputFormat::Json, - max_nodes, - }, - ) - .await?; - let output = if json { - crate::bench::format_report_json(&report) - } else { - crate::bench::format_report_console(&report) - }; - json!({ "output": output }) + #[cfg(any(test, feature = "bench", feature = "test-helpers"))] + { + let report = crate::bench::run_bench_with_toml( + cg, + queries_toml + .as_deref() + .unwrap_or(crate::bench::DEFAULT_QUERIES_TOML), + crate::bench::BenchOptions { + format: crate::bench::OutputFormat::Json, + max_nodes, + }, + ) + .await?; + let output = if json { + crate::bench::format_report_json(&report) + } else { + crate::bench::format_report_console(&report) + }; + json!({ "output": output }) + } + #[cfg(not(any(test, feature = "bench", feature = "test-helpers")))] + { + let _ = (queries_toml, json, max_nodes); + return Err(TraceDecayError::ProjectRoute { + reason_code: "verified-code-context-benchmark-unavailable".to_owned(), + retryable: false, + detail: "the benchmark is not yet mounted on an admitted code-graph authority" + .to_owned(), + }); + } } AdminProjectAction::AutomaticFactReceiptList { state, limit } => { let db = cg.open_project_store_db().await?; diff --git a/crates/tracedecay/src/mcp/tools/handlers/dispatch_test_support.rs b/crates/tracedecay/src/mcp/tools/handlers/dispatch_test_support.rs index 281df333f3..3057fe410a 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/dispatch_test_support.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/dispatch_test_support.rs @@ -228,7 +228,7 @@ fn verified_graph_options_with_freshness<'a>( options.admitted_project_scope = Some(scope.clone()); options.code_graph_read_admission_port = Some(Arc::new(FixtureCodeGraphAdmission { scope })); options.verified_graph_query_port = Some( - crate::tracedecay::queries::graph::admitted_verified_graph_query_port_with_source( + tracedecay_graph_query::admitted_verified_graph_query_port_with_source( options .code_graph_read_admission_port .clone() @@ -256,8 +256,8 @@ pub(super) fn verified_graph_error_options<'a>( let mut options = verified_graph_options(cg, options); options.code_graph_projection_read_port = Some(Arc::new(FailingFixtureCodeGraphProjection { error })); - options.verified_graph_query_port = Some( - crate::tracedecay::queries::graph::admitted_verified_graph_query_port( + options.verified_graph_query_port = + Some(tracedecay_graph_query::admitted_verified_graph_query_port( options .code_graph_read_admission_port .clone() @@ -266,8 +266,7 @@ pub(super) fn verified_graph_error_options<'a>( .code_graph_projection_read_port .clone() .expect("graph fixture projection"), - ), - ); + )); options } @@ -277,12 +276,12 @@ pub(super) fn verified_graph_error_options<'a>( /// runtime's daemon session registry instead of constructing another runtime /// on the same profile. pub(super) async fn init_sibling_registered_fixture( - runtime: &crate::host_admission::HostAdmissionTestRuntimeV1, + runtime: &crate::test_support::host_admission::HostAdmissionTestRuntimeV1, project_root: &Path, project_id: &str, ) -> ( TraceDecay, - Arc, + Arc, ) { let profile_root = tracedecay_runtime_core::storage::default_profile_root().expect("sibling profile root"); diff --git a/crates/tracedecay/src/mcp/tools/handlers/dispatch_tests.rs b/crates/tracedecay/src/mcp/tools/handlers/dispatch_tests.rs index 53fa12b53b..d59fcb40ef 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/dispatch_tests.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/dispatch_tests.rs @@ -922,7 +922,7 @@ async fn selected_project_retrieve_finds_selected_project_response_handle() { let target_server = crate::mcp::McpServer::new_with_host_admission_test_runtime_for_test( target, None, - crate::host_admission::ProjectScopedTestRuntimeV1::new(target_runtime) + crate::test_support::host_admission::ProjectScopedTestRuntimeV1::new(target_runtime) .expect("target project-scoped runtime"), ) .await @@ -930,7 +930,7 @@ async fn selected_project_retrieve_finds_selected_project_response_handle() { let server = crate::mcp::McpServer::new_with_retained_test_servers_for_test( active, None, - crate::host_admission::ProjectScopedTestRuntimeV1::new(active_runtime) + crate::test_support::host_admission::ProjectScopedTestRuntimeV1::new(active_runtime) .expect("active project-scoped runtime"), vec![target_server], ) diff --git a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/context_scout/tests.rs b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/context_scout/tests.rs index 3753ba5a16..3b3e1b3c12 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/context_scout/tests.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/context_scout/tests.rs @@ -128,7 +128,7 @@ fn hook_v2_native_session_requires_exact_protected_locator() { async fn kimi_and_opencode_queued_lifecycle_delivery_prepares_scout_lookup() { let temporary = tempfile::tempdir().unwrap(); let project_id = ProjectId::new("project.native-hook-scout").unwrap(); - let runtime = crate::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( temporary.path().join("profile"), temporary.path().join("project"), project_id.clone(), diff --git a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/hermes/tests.rs b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/hermes/tests.rs index 50686d5a6c..a136bcb3e3 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/hermes/tests.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/hermes/tests.rs @@ -1,5 +1,5 @@ use super::super::*; -use crate::host_admission::HostAdmissionTestRuntimeV1; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_mcp::structured_hook_error_data; use tracedecay_sessions::admission::{HostAdmissionScope, HostAdmissionStatus}; diff --git a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/ingest/tests.rs b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/ingest/tests.rs index a97caf5cde..33ca39c72e 100644 --- a/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/ingest/tests.rs +++ b/crates/tracedecay/src/mcp/tools/handlers/hook_runtime/ingest/tests.rs @@ -1,5 +1,5 @@ use super::super::*; -use crate::host_admission::HostAdmissionTestRuntimeV1; +use crate::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_mcp::structured_hook_error_data; use super::*; diff --git a/crates/tracedecay/src/host_admission.rs b/crates/tracedecay/src/test_support/host_admission.rs similarity index 100% rename from crates/tracedecay/src/host_admission.rs rename to crates/tracedecay/src/test_support/host_admission.rs diff --git a/crates/tracedecay/src/host_admission/accounting_test_support.rs b/crates/tracedecay/src/test_support/host_admission/accounting_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/accounting_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/accounting_test_support.rs diff --git a/crates/tracedecay/src/host_admission/integration_test_support.rs b/crates/tracedecay/src/test_support/host_admission/integration_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/integration_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/integration_test_support.rs diff --git a/crates/tracedecay/src/host_admission/lcm_api_test_support.rs b/crates/tracedecay/src/test_support/host_admission/lcm_api_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/lcm_api_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/lcm_api_test_support.rs diff --git a/crates/tracedecay/src/host_admission/lcm_fixture_test_support.rs b/crates/tracedecay/src/test_support/host_admission/lcm_fixture_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/lcm_fixture_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/lcm_fixture_test_support.rs diff --git a/crates/tracedecay/src/host_admission/profile_registry_test_support.rs b/crates/tracedecay/src/test_support/host_admission/profile_registry_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/profile_registry_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/profile_registry_test_support.rs diff --git a/crates/tracedecay/src/host_admission/session_test_support.rs b/crates/tracedecay/src/test_support/host_admission/session_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/session_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/session_test_support.rs diff --git a/crates/tracedecay/src/host_admission/verified_graph_test_support.rs b/crates/tracedecay/src/test_support/host_admission/verified_graph_test_support.rs similarity index 100% rename from crates/tracedecay/src/host_admission/verified_graph_test_support.rs rename to crates/tracedecay/src/test_support/host_admission/verified_graph_test_support.rs diff --git a/crates/tracedecay/src/test_support/mod.rs b/crates/tracedecay/src/test_support/mod.rs new file mode 100644 index 0000000000..81e4de3eef --- /dev/null +++ b/crates/tracedecay/src/test_support/mod.rs @@ -0,0 +1,4 @@ +//! Test-only composition-root fixtures. Compiled under `cfg(test)` or +//! `test-helpers`, never into a default or `production` library build. + +pub mod host_admission; diff --git a/crates/tracedecay/src/tracedecay.rs b/crates/tracedecay/src/tracedecay.rs index 10e2f15832..381b99d0a8 100644 --- a/crates/tracedecay/src/tracedecay.rs +++ b/crates/tracedecay/src/tracedecay.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, OnceLock}; use crate::config::TraceDecayConfig; use tracedecay_contracts::context_scout::ContextScoutAddressV1; use tracedecay_domain::errors::Result; +use tracedecay_graph_query::SourceReadContext; use tracedecay_runtime_core::db::{Database, DatabaseStorageTelemetryHandle}; use tracedecay_runtime_core::storage::{self, StoreLayout}; use tracedecay_store_runtime::DaemonSessionRuntimeRegistryV1; @@ -26,7 +27,6 @@ mod source_edit_runtime; pub use diagnostics::{BranchDiagnostics, TrackedBranchDiagnostic}; pub use lifecycle::MovedStoreAdoption; -pub(crate) use lifecycle::git_remote_url; /// Central orchestrator that coordinates all subsystems of the code graph. /// @@ -59,7 +59,8 @@ pub struct TraceDecay { >, context_scout_claim_authorities: tokio::sync::RwLock>, #[cfg(any(test, feature = "test-transport"))] - test_runtime_guard: Option>, + test_runtime_guard: + Option>, _standalone_maintenance_scope: Option>, } @@ -106,7 +107,7 @@ impl TraceDecay { #[cfg(any(test, feature = "test-transport"))] pub fn test_runtime_for_test( &self, - ) -> Option> { + ) -> Option> { self.test_runtime_guard.clone() } @@ -114,6 +115,15 @@ impl TraceDecay { &self.store_layout } + pub(crate) fn source_read_context(&self) -> Option { + Some(SourceReadContext::new( + self.project_root.clone(), + self.db.clone(), + self.read_only, + self.store_layout.identity.project_id.clone()?, + )) + } + pub(crate) fn context_scout_owner( &self, ) -> Option<&Arc> diff --git a/crates/tracedecay/src/tracedecay/diagnostics.rs b/crates/tracedecay/src/tracedecay/diagnostics.rs index db09dd74c2..f0df49a5e6 100644 --- a/crates/tracedecay/src/tracedecay/diagnostics.rs +++ b/crates/tracedecay/src/tracedecay/diagnostics.rs @@ -1,11 +1,10 @@ //! Branch-state accessors and branch-tracking diagnostics for the open //! store. -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use tracedecay_domain::errors::{Result, TraceDecayError}; use tracedecay_runtime_core::branch; -use tracedecay_runtime_core::branch_meta; use tracedecay_runtime_core::db::Database; use tracedecay_runtime_core::storage::StoreLayout; @@ -152,196 +151,8 @@ impl TraceDecay { Ok(database) } - fn build_branch_diagnostics( - project_root: &Path, - data_root: &Path, - open_active_branch: Option, - serving_branch: Option, - fallback_warning: Option, - serving_db_path: PathBuf, - ) -> BranchDiagnostics { - let meta = branch_meta::load_branch_meta(data_root); - let current_branch = branch::current_branch(project_root); - let tracking_enabled = meta.as_ref().is_some_and(|m| !m.branches.is_empty()); - let branch_drifted = - tracking_enabled && current_branch.as_deref() != open_active_branch.as_deref(); - let is_fallback = fallback_warning.is_some(); - let fallback_target = if is_fallback { - serving_branch.clone() - } else { - None - }; - let serving_db_exists = serving_db_path.exists(); - - let ( - live_branch_tracked, - live_branch_db_path, - live_branch_db_exists, - nearest_tracked_ancestor, - nearest_tracked_ancestor_db_path, - nearest_tracked_ancestor_db_exists, - ) = if let (Some(meta), Some(current)) = (meta.as_ref(), current_branch.as_deref()) { - let live_branch_tracked = meta.is_tracked(current); - let live_branch_db_path = if live_branch_tracked { - branch::resolve_branch_db_path(data_root, current, meta) - } else { - None - }; - let live_branch_db_exists = live_branch_db_path.as_ref().map(|path| path.exists()); - let nearest_tracked_ancestor = if live_branch_tracked { - None - } else { - branch::find_nearest_tracked_ancestor(project_root, current, meta) - }; - let nearest_tracked_ancestor_db_path = nearest_tracked_ancestor - .as_deref() - .and_then(|ancestor| branch::resolve_branch_db_path(data_root, ancestor, meta)); - let nearest_tracked_ancestor_db_exists = nearest_tracked_ancestor_db_path - .as_ref() - .map(|path| path.exists()); - ( - live_branch_tracked, - live_branch_db_path, - live_branch_db_exists, - nearest_tracked_ancestor, - nearest_tracked_ancestor_db_path, - nearest_tracked_ancestor_db_exists, - ) - } else { - (false, None, None, None, None, None) - }; - - let mut warnings = Vec::new(); - if branch_drifted { - warnings.push(format!( - "branch drift detected: working tree is on '{}' but this instance opened on '{}' and is still serving '{}'. Reopen the index so reads and writes target the live branch.", - current_branch.as_deref().unwrap_or("detached HEAD"), - open_active_branch.as_deref().unwrap_or("detached HEAD"), - serving_branch.as_deref().unwrap_or("default branch"), - )); - } - if !serving_db_exists { - warnings.push(format!( - "serving branch '{}' points at a missing DB: {}", - serving_branch.as_deref().unwrap_or("default branch"), - serving_db_path.display(), - )); - } - if let (Some(current), Some(false), Some(path)) = ( - current_branch.as_deref(), - live_branch_db_exists, - live_branch_db_path.as_ref(), - ) { - warnings.push(format!( - "tracked branch '{}' is listed in branch metadata but its DB is missing at '{}'; serving '{}' instead.", - current, - path.display(), - serving_branch.as_deref().unwrap_or("default branch"), - )); - } else if is_fallback { - match ( - current_branch.as_deref(), - nearest_tracked_ancestor.as_deref(), - fallback_target.as_deref(), - ) { - (Some(current), Some(ancestor), Some(target)) => warnings.push(format!( - "branch '{current}' is not tracked; nearest indexed ancestor is '{ancestor}' and tracedecay is serving '{target}' instead." - )), - (Some(current), None, Some(target)) => warnings.push(format!( - "branch '{current}' is not tracked and no indexed ancestor DB was available; tracedecay is serving '{target}' instead." - )), - _ => {} - } - } - - let branch_resolution = if !tracking_enabled { - "single_db".to_string() - } else if branch_drifted { - "stale_serving_branch".to_string() - } else if current_branch.is_none() { - "detached_default".to_string() - } else if is_fallback { - match ( - nearest_tracked_ancestor.as_deref(), - fallback_target.as_deref(), - ) { - (Some(ancestor), Some(target)) if ancestor == target => { - "fallback_ancestor".to_string() - } - _ => "fallback_default".to_string(), - } - } else { - "exact".to_string() - }; - - let mut branches = Vec::new(); - if let Some(meta) = meta.as_ref() { - let mut names: Vec<_> = meta.branches.keys().cloned().collect(); - names.sort(); - for name in names { - let entry = &meta.branches[&name]; - let db_path = data_root.join(&entry.db_file); - let db_exists = db_path.exists(); - let size_bytes = db_path.metadata().map_or(0, |metadata| metadata.len()); - let parent_db_path = entry - .parent - .as_deref() - .and_then(|parent| branch::resolve_branch_db_path(data_root, parent, meta)); - let parent_db_exists = parent_db_path.as_ref().map(|path| path.exists()); - let mut branch_warnings = Vec::new(); - if !db_exists { - branch_warnings.push(format!("missing DB at '{}'", db_path.display())); - } - if entry.parent.is_some() && parent_db_exists == Some(false) { - branch_warnings.push("parent DB is missing".to_string()); - } - branches.push(TrackedBranchDiagnostic { - name: name.clone(), - db_file: entry.db_file.clone(), - db_path, - db_exists, - size_bytes, - parent: entry.parent.clone(), - parent_db_path, - parent_db_exists, - created_at: entry.created_at.clone(), - last_synced_at: entry.last_synced_at.clone(), - is_default: name == meta.default_branch, - is_current: current_branch.as_deref() == Some(name.as_str()), - is_open_active: open_active_branch.as_deref() == Some(name.as_str()), - is_serving: serving_branch.as_deref() == Some(name.as_str()), - warnings: branch_warnings, - }); - } - } - - BranchDiagnostics { - tracking_enabled, - default_branch: meta.as_ref().map(|m| m.default_branch.clone()), - current_branch, - open_active_branch, - serving_branch, - serving_db_path, - serving_db_exists, - branch_drifted, - branch_resolution, - is_fallback, - fallback_target, - fallback_warning, - live_branch_tracked, - live_branch_db_path, - live_branch_db_exists, - nearest_tracked_ancestor, - nearest_tracked_ancestor_db_path, - nearest_tracked_ancestor_db_exists, - tracked_branch_count: branches.len(), - branches, - warnings, - } - } - pub fn branch_diagnostics(&self) -> BranchDiagnostics { - Self::build_branch_diagnostics( + tracedecay_application::tracedecay::build_branch_diagnostics( &self.project_root, &self.store_layout.data_root, self.active_branch.clone(), diff --git a/crates/tracedecay/src/tracedecay/facts.rs b/crates/tracedecay/src/tracedecay/facts.rs index bbbf7f6801..982a6f5f23 100644 --- a/crates/tracedecay/src/tracedecay/facts.rs +++ b/crates/tracedecay/src/tracedecay/facts.rs @@ -1,36 +1,16 @@ //! Session-memory (holographic fact store) surface of [`TraceDecay`]. -use tracedecay_session_memory::memory::MemoryApplication; -// The shared resolvers live in `tracedecay_session_memory::memory` (the crate that -// owns `MemoryApplication`/`MemoryApplicationError`) rather than in -// `tracedecay-runtime-core` — that crate is a *dependency* of -// `tracedecay-application`, so hosting these there would require a circular -// crate dependency. Both this module and -// `tracedecay-dashboard-api::tracedecay::facts` delegate to the same -// functions instead of keeping independent copies. -use tracedecay_domain::errors::{Result, TraceDecayError}; -use tracedecay_domain::{FactOwnerV1, ProjectId}; +use tracedecay_application::tracedecay::project_memory_owner_from_layout_id; +use tracedecay_domain::FactOwnerV1; +use tracedecay_domain::errors::Result; use tracedecay_session_memory::fact_store::{ProjectFactStore, ProjectMemoryDbHandle}; +use tracedecay_session_memory::memory::MemoryApplication; use tracedecay_session_memory::memory::memory_application_error; use super::TraceDecay; -fn project_memory_owner_from_layout_id(project_id: Option<&str>) -> Result { - let project_id = project_id.ok_or_else(|| TraceDecayError::Config { - message: "active project has no authoritative project_id for memory".to_string(), - })?; - let project_id = - ProjectId::new(project_id.to_owned()).map_err(|error| TraceDecayError::Config { - message: format!("invalid authoritative project_id for memory: {error}"), - })?; - Ok(FactOwnerV1::Project { project_id }) -} - impl TraceDecay { /// Returns the only project-memory owner accepted by core routes. - /// - /// The ID is supplied by the resolved store layout, never reconstructed - /// from a filesystem path or a caller-provided display label. pub(crate) fn project_memory_owner(&self) -> Result { project_memory_owner_from_layout_id(self.store_layout.identity.project_id.as_deref()) } @@ -66,15 +46,3 @@ impl TraceDecay { MemoryApplication::new(owner, store).map_err(memory_application_error) } } - -#[cfg(test)] -#[allow(clippy::expect_used, clippy::unwrap_used)] -mod tests { - use super::*; - - #[test] - fn project_memory_owner_requires_a_valid_authoritative_layout_id() { - assert!(project_memory_owner_from_layout_id(None).is_err()); - assert!(project_memory_owner_from_layout_id(Some("")).is_err()); - } -} diff --git a/crates/tracedecay/src/tracedecay/lifecycle/branches.rs b/crates/tracedecay/src/tracedecay/lifecycle/branches.rs index 6e50002acf..9abc7a1b5b 100644 --- a/crates/tracedecay/src/tracedecay/lifecycle/branches.rs +++ b/crates/tracedecay/src/tracedecay/lifecycle/branches.rs @@ -4,16 +4,15 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, OnceLock}; use crate::config::{ - db_filename, install_usecase_runtime_configuration_authority, + install_usecase_runtime_configuration_authority, open_runtime_configuration_for_registered_database_read_only, }; use tracedecay_configuration::ProjectConfigurationRuntime; use tracedecay_domain::errors::{Result, TraceDecayError}; -use tracedecay_global_db::RegisteredGlobalDbLeaseV1; -use tracedecay_runtime_core::branch; +use tracedecay_global_db::{RegisteredGlobalDbLeaseV1, registered_enrollment_roots}; use tracedecay_runtime_core::branch_meta; use tracedecay_runtime_core::db::DatabaseAccessMode; -use tracedecay_runtime_core::storage::StoreLayout; +use tracedecay_runtime_core::storage::{self, StoreLayout}; use tracedecay_store_runtime::DaemonSessionRuntimeRegistryV1; use super::{TraceDecay, TraceDecayOpenOptions}; @@ -32,49 +31,10 @@ impl TraceDecay { tracedecay_dir: &Path, branch: Option<&str>, ) -> (PathBuf, Option, Option) { - let default_db = tracedecay_dir.join(db_filename(tracedecay_dir)); - - let Some(meta) = branch_meta::load_branch_meta(tracedecay_dir) else { - // No branch metadata — single-DB mode (backward compat) - return (default_db, None, None); - }; - - let Some(branch) = branch else { - // Detached HEAD — serve the default branch's provenance - return ( - default_db, - Some(meta.default_branch.clone()), - Some("detached HEAD — using default branch index".to_string()), - ); - }; - - // Exact match: branch is tracked - if meta.is_tracked(branch) { - return (default_db, Some(branch.to_string()), None); - } - - // Fallback: find nearest tracked ancestor - if let Some(ancestor) = branch::find_nearest_tracked_ancestor(project_root, branch, &meta) { - return ( - default_db, - Some(ancestor.clone()), - Some(format!( - "branch '{branch}' is not tracked — serving from '{ancestor}'. \ - Run `tracedecay branch add {branch}` to track it." - )), - ); - } - - // Last resort: default branch provenance - let serving = meta.default_branch.clone(); - ( - default_db, - Some(serving), - Some(format!( - "branch '{branch}' is not tracked — serving from '{}'. \ - Run `tracedecay branch add {branch}` to track it.", - meta.default_branch - )), + tracedecay_application::tracedecay::resolve_db_for_branch( + project_root, + tracedecay_dir, + branch, ) } @@ -147,12 +107,12 @@ impl TraceDecay { profile_database.as_ref(), ) .await?; - let project_id = Self::registered_project_id(&store_layout)?; - let enrollment_roots = Self::registered_enrollment_roots( + let project_id = storage::registered_project_id(&store_layout)?; + let enrollment_roots = registered_enrollment_roots( + profile_database.as_ref(), project_root, &store_layout, &project_id, - profile_database.as_ref(), ) .await?; let configuration_database = runtime_registry diff --git a/crates/tracedecay/src/tracedecay/lifecycle/identity.rs b/crates/tracedecay/src/tracedecay/lifecycle/identity.rs index 8c0db75518..0d47fc97c7 100644 --- a/crates/tracedecay/src/tracedecay/lifecycle/identity.rs +++ b/crates/tracedecay/src/tracedecay/lifecycle/identity.rs @@ -1,33 +1,15 @@ //! Store-layout identity resolution: mapping a project root to its //! authoritative store layout. -use std::path::{Path, PathBuf}; +use std::path::Path; use tracedecay_domain::errors::{Result, TraceDecayError}; use tracedecay_global_db::RegisteredGlobalDb; use tracedecay_runtime_core::storage::{self, StoreLayout}; -use tracedecay_store::ProjectId; use super::{MovedStoreAdoption, TraceDecay, TraceDecayOpenOptions}; impl TraceDecay { - pub(in crate::tracedecay) fn registered_project_id( - store_layout: &StoreLayout, - ) -> Result { - let project_id = - store_layout - .identity - .project_id - .as_ref() - .ok_or_else(|| TraceDecayError::Config { - message: "registered code runtime requires an authoritative project identity" - .to_owned(), - })?; - ProjectId::new(project_id.clone()).map_err(|error| TraceDecayError::Config { - message: format!("invalid registered project identity: {error}"), - }) - } - #[hotpath::measure(label = "lifecycle.resolve_registered_layout", future = true)] pub(crate) async fn resolve_registered_configuration_layout( project_root: &Path, @@ -89,111 +71,6 @@ impl TraceDecay { .await } - /// Candidate enrollment roots a registered project claims: its canonical - /// and display roots plus every registered alias. - pub(crate) fn registry_context_candidate_roots( - context: &tracedecay_global_db::ProjectRegistryContext, - ) -> Vec { - let mut candidates = vec![ - PathBuf::from(&context.project.canonical_root), - PathBuf::from(&context.project.display_root), - ]; - candidates.extend( - context - .aliases - .iter() - .map(|alias| PathBuf::from(&alias.alias_path)), - ); - candidates - } - - /// Filters candidate roots down to the ones whose root-side evidence - /// names exactly `project_id`: a `.git/` repository identity marker with - /// that id, or (for roots without one) a deterministic path-derived - /// identity equal to it. - /// - /// This never creates or repairs a marker, so a caller that must not mount - /// a store the profile has not enrolled — a cross-project memory reader, - /// for one — can tell "not enrolled here" apart from "enrolled". - pub(crate) fn enrolled_project_roots( - candidates: impl IntoIterator, - project_id: &ProjectId, - ) -> Result> { - let mut candidates = candidates.into_iter().collect::>(); - candidates.sort(); - candidates.dedup(); - - let mut roots = Vec::new(); - for candidate in candidates { - let candidate = tracedecay_runtime_core::worktree::repository_identity_root(&candidate) - .unwrap_or(candidate); - let Ok(canonical) = candidate.canonicalize() else { - continue; - }; - if roots.contains(&canonical) { - continue; - } - let named_id = match storage::read_repository_identity_marker(&canonical)? { - Some(marker) => marker.project_id, - None => storage::default_profile_project_id(&canonical), - }; - if named_id == project_id.as_str() { - roots.push(canonical); - } - } - Ok(roots) - } - - #[hotpath::measure(label = "lifecycle.enrollment_roots", future = true)] - pub(crate) async fn registered_enrollment_roots( - project_root: &Path, - store_layout: &StoreLayout, - project_id: &ProjectId, - registry_database: &RegisteredGlobalDb, - ) -> Result> { - let mut candidates = vec![ - project_root.to_path_buf(), - store_layout.project_root.clone(), - ]; - if let Some(context) = registry_database - .project_registry_context_by_id(project_id.as_str()) - .await? - { - candidates.extend(Self::registry_context_candidate_roots(&context)); - } - - let mut roots = Self::enrolled_project_roots(candidates, project_id)?; - // Self-heal the sanctioned `.git/`-side anchor: a session mount for a - // registered project rewrites a missing repository identity marker in - // place (re-adoption after loss, first mount, or a moved checkout). - // A non-git root persists nothing — its identity is deterministic - // from the canonical path with the registry as the durable home. - // Nothing is ever written into the working tree. - let enrollment_root = - tracedecay_runtime_core::worktree::repository_identity_root(project_root) - .unwrap_or_else(|| project_root.to_path_buf()); - match enrollment_root.canonicalize() { - Ok(canonical) => { - if storage::read_repository_identity_marker(&canonical)?.is_none() { - storage::write_repository_identity_marker(&canonical, project_id.as_str())?; - } - if roots.is_empty() { - roots.push(canonical); - } - } - Err(error) if roots.is_empty() => { - return Err(TraceDecayError::Config { - message: format!( - "could not canonicalize project enrollment root '{}': {error}", - enrollment_root.display() - ), - }); - } - Err(_) => {} - } - Ok(roots) - } - #[hotpath::measure(label = "lifecycle.resolve_store_layout", future = true)] async fn resolve_store_layout_for_authority( project_root: &Path, diff --git a/crates/tracedecay/src/tracedecay/lifecycle/mod.rs b/crates/tracedecay/src/tracedecay/lifecycle/mod.rs index a90a9d34d4..3463c06d5b 100644 --- a/crates/tracedecay/src/tracedecay/lifecycle/mod.rs +++ b/crates/tracedecay/src/tracedecay/lifecycle/mod.rs @@ -16,7 +16,7 @@ use crate::project_store_runtime::join_standalone_session_registry; use tokio::sync::Mutex as AsyncMutex; use tracedecay_configuration::ProjectConfigurationRuntime; use tracedecay_domain::errors::{Result, TraceDecayError}; -use tracedecay_global_db::RegisteredGlobalDbLeaseV1; +use tracedecay_global_db::{RegisteredGlobalDbLeaseV1, registered_enrollment_roots}; use tracedecay_runtime_core::branch; use tracedecay_runtime_core::branch_meta::{self, BranchMeta}; use tracedecay_runtime_core::db::{Database, DatabaseAccessMode, DatabaseAuthority}; @@ -33,7 +33,6 @@ mod branches; mod identity; mod registry; -pub(crate) use registry::git_remote_url; pub use tracedecay_daemon_protocol::MovedStoreAdoption; #[cfg(not(any(test, feature = "test-transport")))] @@ -56,7 +55,12 @@ static STANDALONE_MAINTENANCE_SCOPES: LazyLock< /// fresh mounts. #[cfg(any(test, feature = "test-transport"))] static STANDALONE_TEST_RUNTIMES: LazyLock< - AsyncMutex>, + AsyncMutex< + WeakRegistry< + (PathBuf, PathBuf), + crate::test_support::host_admission::HostAdmissionTestRuntimeV1, + >, + >, > = LazyLock::new(|| AsyncMutex::new(WeakRegistry::new())); impl TraceDecay { @@ -108,7 +112,7 @@ impl TraceDecay { async fn standalone_test_runtime( project_root: &Path, open_options: &TraceDecayOpenOptions, - ) -> Result> { + ) -> Result> { let profile_root = open_options.resolved_profile_root()?; if !tracedecay_runtime_core::db::is_isolated_test_path(project_root) || !tracedecay_runtime_core::db::is_isolated_test_path(&profile_root) @@ -132,7 +136,7 @@ impl TraceDecay { return Ok(runtime); } let runtime = Arc::new( - crate::host_admission::HostAdmissionTestRuntimeV1::project( + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile_root, project_root, project_id, @@ -151,7 +155,7 @@ impl TraceDecay { operation: &'static str, access: DatabaseAccessMode, ) -> Result { - let project_id = Self::registered_project_id(store_layout)?; + let project_id = storage::registered_project_id(store_layout)?; let canonical_database_path = &store_layout.graph_db_path; if matches!(access, DatabaseAccessMode::ReadOnly) { return runtime @@ -244,7 +248,7 @@ impl TraceDecay { profile_database.as_ref(), ) .await?; - let project_id = Self::registered_project_id(&store_layout)?; + let project_id = storage::registered_project_id(&store_layout)?; // Persist the minted identity in the sanctioned repo-adjacent anchor: // the `.git/` repository identity marker. A non-git root persists // nothing here — its identity is deterministic from the canonical @@ -273,7 +277,10 @@ impl TraceDecay { pub(crate) async fn init_test_fixture_with_registered_runtime( project_root: &Path, project_id: &str, - ) -> Result<(Self, Arc)> { + ) -> Result<( + Self, + Arc, + )> { let profile_root = tracedecay_runtime_core::storage::default_profile_root()?; let project_id = tracedecay_domain::ProjectId::new(project_id).map_err(|error| { TraceDecayError::Config { @@ -281,7 +288,7 @@ impl TraceDecay { } })?; let runtime = Arc::new( - crate::host_admission::HostAdmissionTestRuntimeV1::project( + crate::test_support::host_admission::HostAdmissionTestRuntimeV1::project( &profile_root, project_root, project_id, @@ -516,12 +523,12 @@ impl TraceDecay { profile_database.as_ref(), ) .await?; - let project_id = Self::registered_project_id(&store_layout)?; - let enrollment_roots = Self::registered_enrollment_roots( + let project_id = storage::registered_project_id(&store_layout)?; + let enrollment_roots = registered_enrollment_roots( + profile_database.as_ref(), project_root, &store_layout, &project_id, - profile_database.as_ref(), ) .await?; let configuration_database = runtime_registry @@ -727,12 +734,12 @@ impl TraceDecay { profile_database.as_ref(), ) .await?; - let project_id = Self::registered_project_id(&store_layout)?; - let enrollment_roots = Self::registered_enrollment_roots( + let project_id = storage::registered_project_id(&store_layout)?; + let enrollment_roots = registered_enrollment_roots( + profile_database.as_ref(), project_root, &store_layout, &project_id, - profile_database.as_ref(), ) .await?; let configuration_database = runtime_registry diff --git a/crates/tracedecay/src/tracedecay/lifecycle/registry.rs b/crates/tracedecay/src/tracedecay/lifecycle/registry.rs index 78a3a3e595..ca28c85022 100644 --- a/crates/tracedecay/src/tracedecay/lifecycle/registry.rs +++ b/crates/tracedecay/src/tracedecay/lifecycle/registry.rs @@ -1,551 +1,18 @@ -//! Global-registry registration: publishing a profile-sharded store's -//! project, store, and branch scope rows so cross-project lookups and the -//! registry-driven identity resolution in [`super::identity`] can find it. +//! Global-registry registration: `TraceDecay` retains owner handles and +//! delegates publishing to [`tracedecay_global_db::register_project_store`]. -use std::collections::{BTreeSet, HashMap}; -use std::path::{Path, PathBuf}; -use std::sync::{LazyLock, Mutex as StdMutex}; -use std::time::SystemTime; - -use crate::tracedecay::current_timestamp; -use tracedecay_domain::errors::{Result, TraceDecayError}; -use tracedecay_global_db::{ - GraphScopeUpsert, RegisteredGlobalDb, StoreArtifactUpsert, StoreInstanceUpsert, -}; -use tracedecay_runtime_core::branch_meta; -use tracedecay_runtime_core::storage::{self, StoreLayout}; +use tracedecay_domain::errors::Result; use super::TraceDecay; -/// Cheap fingerprint of everything that would change what -/// [`TraceDecay::register_project_store_in_global_registry`] writes. -/// -/// Every field here is load-bearing for the duplicate-store bug the -/// registration body guards against (see the comments on `git_common_dir` -/// and `primary_root` below): dropping `git_common_dir` would make a sibling -/// checkout's next first touch mint a fresh store, and dropping -/// `canonical_root` would let a linked worktree's registration pin the -/// project's canonical/display root to a transient path. `tracked_branches` -/// and the artifact mtimes catch every other observable change (branch -/// tracking, store file replacement) that this function is responsible for -/// publishing. `git_remote_url` is included because `git remote set-url` -/// changes origin identity without touching those other fields, and the -/// registry writes the remote (and its search alias) on every registration. -#[derive(Clone, Debug, PartialEq, Eq)] -struct RegistrationDigest { - project_id: String, - canonical_root: PathBuf, - git_common_dir: Option, - git_remote_url: Option, - tracked_branches: BTreeSet, - artifact_mtimes: Vec>, -} - -/// Process-global cache of the last digest successfully registered for each -/// project id, so a redundant `register_project_store_in_global_registry` -/// call (every writable open re-runs this) can skip straight to `Ok(())` -/// instead of redoing branch-meta/git lookups and every upsert. -static LAST_REGISTERED_DIGEST: LazyLock>> = - LazyLock::new(|| StdMutex::new(HashMap::new())); - -/// Pure equality check split out of the caching logic above so it can be -/// unit tested against a synthetic cache without a real global database. -fn registration_digest_matches( - cache: &HashMap, - project_id: &str, - digest: &RegistrationDigest, -) -> bool { - cache.get(project_id) == Some(digest) -} - -/// Whether the cached registration may be honored: the digest cache proves -/// this process registered exactly this digest once, not that the registry -/// still holds it — a sibling process, the CLI, or any out-of-band upsert can -/// re-pin `canonical_root` afterwards. One indexed point-read keeps the skip -/// honest before it bypasses the stale-canonical-root repair below; the skip -/// still avoids the registry write lock and every upsert. -async fn cached_registration_is_current( - global_db: &RegisteredGlobalDb, - project_id: &str, - digest: &RegistrationDigest, - registration_root: &Path, -) -> Result { - { - let cache = LAST_REGISTERED_DIGEST - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - if !registration_digest_matches(&cache, project_id, digest) { - return Ok(false); - } - } - let registered_root = global_db - .get_code_project(project_id) - .await? - .map(|record| record.canonical_root); - Ok(registered_root.as_deref() - == Some(RegisteredGlobalDb::canonical_project_key(registration_root).as_str())) -} - -fn artifact_mtime(path: &Path) -> Option { - std::fs::metadata(path).ok()?.modified().ok() -} - impl TraceDecay { #[hotpath::measure(label = "lifecycle.register_project_store", future = true)] pub(crate) async fn register_project_store_in_global_registry(&self) -> Result<()> { - static REGISTRY_WRITE_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); - - if self.store_layout.storage_mode != storage::StorageMode::ProfileSharded { - return Ok(()); - } - - let project_id = self - .store_layout - .identity - .project_id - .as_deref() - .ok_or_else(|| { - registry_registration_error("profile-sharded store has no project identity") - })?; - let profile_root = profile_root_for_layout(&self.store_layout) - .ok_or_else(|| registry_registration_error("store is outside a profile root"))?; - let store_relpath = profile_relative(&profile_root, &self.store_layout.data_root) - .ok_or_else(|| registry_registration_error("store root is outside its profile"))?; - - let global_db = self.profile_database.as_ref(); - - let (meta, git_common_dir, primary_root, git_remote_url, digest) = - hotpath::measure_block!("lifecycle.register_project_store.digest", { - let meta = branch_meta::load_branch_meta(&self.store_layout.data_root); - // Registering without the git common dir leaves the row unreachable - // by repository identity, so the next first touch from a sibling - // checkout mints a fresh store. Detached worktrees are no exception: - // they belong to the same repository as every other checkout. - let git_common_dir = - tracedecay_runtime_core::worktree::git_common_dir(&self.project_root); - - // A shared project id can be reached from any linked worktree (see - // the git-common-dir alias registered below), so registering - // straight from `self.project_root` would let whichever worktree - // happens to touch the project last pin its canonical_root / - // display_root to a transient worktree path. Redirect registration - // to the primary checkout when one is detected and still exists. - let primary_root = tracedecay_runtime_core::worktree::primary_checkout_root( - &self.project_root, - git_common_dir.as_deref(), - ); - - let tracked_branches: BTreeSet = meta - .as_ref() - .map(|meta| meta.branches.keys().cloned().collect()) - .unwrap_or_default(); - let artifact_mtimes = vec![ - artifact_mtime(&self.store_layout.graph_db_path), - artifact_mtime(&self.store_layout.sessions_db_path), - artifact_mtime(&self.store_layout.branch_meta_path), - self.store_layout - .manifest_path - .as_deref() - .and_then(artifact_mtime), - ]; - let git_remote_url = git_remote_url(&self.project_root); - let digest = RegistrationDigest { - project_id: project_id.to_string(), - canonical_root: primary_root - .as_deref() - .unwrap_or(&self.project_root) - .to_path_buf(), - git_common_dir: git_common_dir.clone(), - git_remote_url: git_remote_url.clone(), - tracked_branches, - artifact_mtimes, - }; - (meta, git_common_dir, primary_root, git_remote_url, digest) - }); - let default_branch = meta.as_ref().map(|meta| meta.default_branch.as_str()); - let registration_root = primary_root.as_deref().unwrap_or(&self.project_root); - - if cached_registration_is_current(global_db, project_id, &digest, registration_root).await? - { - hotpath::gauge!("lifecycle.register_project_store.cached_total").inc(1u64); - return Ok(()); - } - - let _registry_write = REGISTRY_WRITE_LOCK.lock().await; - // Re-check under the write lock: a concurrent writable open may have - // just registered the same digest while we were computing ours. - if cached_registration_is_current(global_db, project_id, &digest, registration_root).await? - { - hotpath::gauge!("lifecycle.register_project_store.cached_total").inc(1u64); - return Ok(()); - } - - let previous_canonical_root = if primary_root.is_some() { - // Propagated: a database fault here must not read as "no prior - // registration" and skip the stale-canonical-root repair warning - // below. Absence (a truthful `Ok(None)`) still collapses to - // `None`, same as before. - global_db - .get_code_project(project_id) - .await? - .map(|record| record.canonical_root) - } else { - None - }; - - let project = global_db - .upsert_code_project( - project_id, - registration_root, - git_common_dir.as_deref(), - git_remote_url.as_deref(), - default_branch, - ) - // Propagated verbatim. The registry now separates three answers - // this call site used to flatten into one message: a refused - // ephemeral root (typed `ProjectRoute`), an unresolvable authority - // conflict (typed `ResetRequired`, which tells the operator to - // reset the profile), and a database fault. Re-wrapping them as - // "upsert code project failed" is exactly the coercion being - // removed. - .await?; - - storage::write_repository_identity_marker(&self.project_root, &project.project_id)?; - - if let Some(primary_root) = primary_root.as_deref() { - // The registry now points canonical_root/display_root at the - // primary checkout; keep this worktree itself resolvable for - // future lookups by registering its own path as an alias. - // Propagated verbatim, same as `upsert_code_project` above: the - // registry now reports its own database-fault state instead of - // this call site's generic "upsert worktree alias failed". - global_db - .upsert_project_alias(&self.project_root, &project.project_id) - .await?; - - let repaired_stale_worktree_root = previous_canonical_root.is_some_and(|previous| { - previous != RegisteredGlobalDb::canonical_project_key(primary_root) - }); - if repaired_stale_worktree_root { - eprintln!( - "warning: repaired tracedecay project '{project_id}' canonical_root — \ - it was pinned to a linked worktree ({}); restored to the primary checkout ({})", - self.project_root.display(), - primary_root.display() - ); - } - } - - let store_id = profile_store_id(&project.project_id); - let manifest_relpath = self - .store_layout - .manifest_path - .as_ref() - .and_then(|path| profile_relative(&profile_root, path)); - let now = current_timestamp(); - let store = global_db - .upsert_store_instance(StoreInstanceUpsert { - store_id, - project_id: project.project_id, - store_kind: "code_project".to_string(), - storage_mode: "profile_sharded".to_string(), - store_relpath, - manifest_relpath, - last_verified_at: Some(now), - last_write_at: Some(now), - }) - .await?; - - if let Some(meta) = meta { - for (branch_name, entry) in meta.branches { - let db_path = self.store_layout.data_root.join(&entry.db_file); - let db_relpath = profile_relative(&profile_root, &db_path).ok_or_else(|| { - registry_registration_error("branch database is outside its profile") - })?; - global_db - .upsert_graph_scope(GraphScopeUpsert { - graph_scope_id: profile_graph_scope_id(&store.store_id, &branch_name), - project_id: store.project_id.clone(), - store_id: store.store_id.clone(), - branch_name: branch_name.clone(), - db_relpath, - parent_scope_id: entry - .parent - .as_deref() - .map(|parent| profile_graph_scope_id(&store.store_id, parent)), - last_synced_at: entry.last_synced_at.parse::().ok(), - writable: true, - }) - .await?; - } - } - - let mut artifacts = Vec::new(); - push_existing_store_artifact( - &mut artifacts, - &store.store_id, - "graph_db", - &profile_root, - &self.store_layout.graph_db_path, - None, - now, - ); - push_existing_store_artifact( - &mut artifacts, - &store.store_id, - "sessions_db", - &profile_root, - &self.store_layout.sessions_db_path, - None, - now, - ); - push_existing_store_artifact( - &mut artifacts, - &store.store_id, - "branch_meta", - &profile_root, - &self.store_layout.branch_meta_path, - None, - now, - ); - if let Some(manifest_path) = &self.store_layout.manifest_path { - push_existing_store_artifact( - &mut artifacts, - &store.store_id, - "store_manifest", - &profile_root, - manifest_path, - Some(storage::STORE_MANIFEST_SCHEMA_VERSION.to_string()), - now, - ); - } - for artifact in artifacts { - global_db.upsert_store_artifact(artifact).await?; - } - - LAST_REGISTERED_DIGEST - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .insert(project_id.to_string(), digest); - hotpath::gauge!("lifecycle.register_project_store.write_total").inc(1u64); - Ok(()) - } -} - -fn profile_relative(profile_root: &Path, path: &Path) -> Option { - path.strip_prefix(profile_root) - .ok() - .map(|rel| rel.to_string_lossy().replace('\\', "/")) -} - -fn profile_root_for_layout(layout: &StoreLayout) -> Option { - layout.data_root.parent()?.parent().map(Path::to_path_buf) -} - -fn profile_store_id(project_id: &str) -> String { - format!("store:{project_id}:profile_sharded") -} - -fn registry_registration_error(message: impl Into) -> TraceDecayError { - TraceDecayError::Database { - operation: "register project store".to_string(), - message: message.into(), - } -} - -pub(crate) fn git_remote_url(project_root: &Path) -> Option { - // gix reads the same config `git config --get` would (repo-local + - // global) without a subprocess spawn. - if let Ok(repo) = gix::discover(project_root) { - let url = repo - .config_snapshot() - .string("remote.origin.url")? - .to_string(); - let url = url.trim(); - return (!url.is_empty()).then(|| url.to_string()); - } - if !tracedecay_runtime_core::worktree::git_may_resolve_repo(project_root) { - return None; - } - tracedecay_runtime_core::git::git_capture( - project_root, - &["config", "--get", "remote.origin.url"], - ) -} - -fn profile_graph_scope_id(store_id: &str, branch_name: &str) -> String { - format!("{store_id}:branch:{branch_name}") -} - -fn push_existing_store_artifact( - artifacts: &mut Vec, - store_id: &str, - artifact_kind: &str, - profile_root: &Path, - path: &Path, - schema_version: Option, - updated_at: i64, -) { - let Some(relpath) = profile_relative(profile_root, path) else { - return; - }; - let Ok(metadata) = std::fs::metadata(path) else { - return; - }; - artifacts.push(StoreArtifactUpsert { - store_id: store_id.to_string(), - artifact_kind: artifact_kind.to_string(), - relpath, - size_bytes: i64::try_from(metadata.len()).ok(), - schema_version, - updated_at: Some(updated_at), - }); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn digest(canonical_root: &str, branches: &[&str]) -> RegistrationDigest { - RegistrationDigest { - project_id: "proj-1".to_string(), - canonical_root: PathBuf::from(canonical_root), - git_common_dir: Some(PathBuf::from("/repo/.git")), - git_remote_url: Some("https://example.com/repo.git".to_string()), - tracked_branches: branches.iter().map(ToString::to_string).collect(), - artifact_mtimes: vec![None, None, None, None], - } - } - - /// Simulates the real call path: check-then-maybe-register-then-record, - /// counting how many times a "register" (the expensive upsert body) - /// would actually run. - fn simulate_call( - cache: &mut HashMap, - project_id: &str, - digest: &RegistrationDigest, - register_calls: &mut u32, - ) { - if registration_digest_matches(cache, project_id, digest) { - return; - } - *register_calls += 1; - cache.insert(project_id.to_string(), digest.clone()); - } - - #[test] - fn identical_inputs_skip_the_second_registration() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let d = digest("/repo", &["main"]); - - simulate_call(&mut cache, "proj-1", &d, &mut register_calls); - simulate_call(&mut cache, "proj-1", &d, &mut register_calls); - - assert_eq!( - register_calls, 1, - "second call with an identical digest must not re-register" - ); - } - - #[test] - fn changed_branch_set_does_not_skip() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let first = digest("/repo", &["main"]); - let second = digest("/repo", &["main", "feature/x"]); - - simulate_call(&mut cache, "proj-1", &first, &mut register_calls); - simulate_call(&mut cache, "proj-1", &second, &mut register_calls); - - assert_eq!( - register_calls, 2, - "a changed tracked-branch set must force re-registration" - ); - } - - #[test] - fn changed_canonical_root_does_not_skip() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let first = digest("/repo", &["main"]); - let second = digest("/other/primary-checkout", &["main"]); - - simulate_call(&mut cache, "proj-1", &first, &mut register_calls); - simulate_call(&mut cache, "proj-1", &second, &mut register_calls); - - assert_eq!( - register_calls, 2, - "a changed canonical_root (primary-checkout redirect) must force re-registration" - ); - } - - #[test] - fn changed_git_common_dir_does_not_skip() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let mut first = digest("/repo", &["main"]); - first.git_common_dir = Some(PathBuf::from("/repo/.git")); - let mut second = first.clone(); - second.git_common_dir = None; - - simulate_call(&mut cache, "proj-1", &first, &mut register_calls); - simulate_call(&mut cache, "proj-1", &second, &mut register_calls); - - assert_eq!( - register_calls, 2, - "a changed git_common_dir must force re-registration" - ); - } - - #[test] - fn changed_git_remote_does_not_skip() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let first = digest("/repo", &["main"]); - let mut second = first.clone(); - second.git_remote_url = Some("https://example.com/fork.git".to_string()); - - simulate_call(&mut cache, "proj-1", &first, &mut register_calls); - simulate_call(&mut cache, "proj-1", &second, &mut register_calls); - - assert_eq!( - register_calls, 2, - "a changed git remote must force re-registration" - ); - } - - #[test] - fn changed_artifact_mtime_does_not_skip() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let mut first = digest("/repo", &["main"]); - first.artifact_mtimes = vec![Some(SystemTime::UNIX_EPOCH), None, None, None]; - let mut second = first.clone(); - second.artifact_mtimes[0] = Some(SystemTime::now()); - - simulate_call(&mut cache, "proj-1", &first, &mut register_calls); - simulate_call(&mut cache, "proj-1", &second, &mut register_calls); - - assert_eq!( - register_calls, 2, - "a changed artifact mtime must force re-registration" - ); - } - - #[test] - fn different_project_ids_are_tracked_independently() { - let mut cache = HashMap::new(); - let mut register_calls = 0; - let a = digest("/repo-a", &["main"]); - let mut b = digest("/repo-b", &["main"]); - b.project_id = "proj-2".to_string(); - - simulate_call(&mut cache, "proj-1", &a, &mut register_calls); - simulate_call(&mut cache, "proj-2", &b, &mut register_calls); - simulate_call(&mut cache, "proj-1", &a, &mut register_calls); - simulate_call(&mut cache, "proj-2", &b, &mut register_calls); - - assert_eq!(register_calls, 2); + tracedecay_global_db::register_project_store( + self.profile_database.as_ref(), + &self.project_root, + &self.store_layout, + ) + .await } } diff --git a/crates/tracedecay/src/tracedecay/queries/graph.rs b/crates/tracedecay/src/tracedecay/queries/graph.rs deleted file mode 100644 index 5a5052404d..0000000000 --- a/crates/tracedecay/src/tracedecay/queries/graph.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::sync::Arc; - -use tracedecay_graph_query::{ - CodeGraphProjectionReadPort, CodeGraphReadAdmissionPort, CodeGraphSourceAuthorityPort, - CodeGraphSourceBindFuture, CodeGraphSourceBindRequest, SourceReadContext, - VerifiedGraphQueryFuture, VerifiedGraphQueryPort, VerifiedGraphQueryRequest, - open_verified_graph_query, -}; - -use crate::tracedecay::TraceDecay; - -impl TraceDecay { - pub(crate) fn source_read_context(&self) -> Option { - Some(SourceReadContext::new( - self.project_root().to_path_buf(), - self.db().clone(), - self.is_read_only(), - self.store_layout().identity.project_id.clone()?, - )) - } -} - -struct BoundCodeGraphSourceAuthority { - source: SourceReadContext, -} - -impl CodeGraphSourceAuthorityPort for BoundCodeGraphSourceAuthority { - fn bind<'a>( - &'a self, - _request: CodeGraphSourceBindRequest<'a>, - ) -> CodeGraphSourceBindFuture<'a> { - let source = self.source.clone(); - Box::pin(async move { Ok(source) }) - } -} - -/// Root adapter: closes over admission, projection, and the admitted project -/// source authority. `open` never names [`crate::tracedecay::TraceDecay`]. -pub(crate) struct AdmittedVerifiedGraphQueryPort { - admission: Arc, - projection: Arc, - source_authority: Option>, -} - -impl AdmittedVerifiedGraphQueryPort { - pub(crate) fn new( - admission: Arc, - projection: Arc, - source: Option, - ) -> Self { - Self { - admission, - projection, - source_authority: source.map(|source| { - Arc::new(BoundCodeGraphSourceAuthority { source }) - as Arc - }), - } - } -} - -impl VerifiedGraphQueryPort for AdmittedVerifiedGraphQueryPort { - fn open<'a>(&'a self, request: VerifiedGraphQueryRequest<'a>) -> VerifiedGraphQueryFuture<'a> { - Box::pin(open_verified_graph_query( - &*self.admission, - &*self.projection, - request, - self.source_authority.as_deref(), - )) - } -} - -#[cfg(test)] -pub(crate) fn admitted_verified_graph_query_port( - admission: Arc, - projection: Arc, -) -> Arc { - admitted_verified_graph_query_port_with_source(admission, projection, None) -} - -pub(crate) fn admitted_verified_graph_query_port_with_source( - admission: Arc, - projection: Arc, - source: Option, -) -> Arc { - Arc::new(AdmittedVerifiedGraphQueryPort::new( - admission, projection, source, - )) -} diff --git a/crates/tracedecay/src/tracedecay/queries/meta.rs b/crates/tracedecay/src/tracedecay/queries/meta.rs index 05e8643bf3..b81e29f8ae 100644 --- a/crates/tracedecay/src/tracedecay/queries/meta.rs +++ b/crates/tracedecay/src/tracedecay/queries/meta.rs @@ -2,68 +2,35 @@ use std::path::Path; use crate::config::TraceDecayConfig; use crate::tracedecay::TraceDecay; -use tracedecay_domain::errors::{Result, TraceDecayError}; - -fn parse_counter(key: &'static str, value: Option) -> Result { - let Some(value) = value else { - return Ok(0); - }; - value - .parse::() - .map_err(|error| TraceDecayError::Database { - operation: format!("read {key}"), - message: format!("persisted {key} counter is invalid: {error}"), - }) -} +use tracedecay_application::tracedecay::{ + add_local_counter, get_local_counter, get_tokens_saved, reset_local_counter, set_tokens_saved, +}; +use tracedecay_domain::errors::Result; impl TraceDecay { /// Returns the persisted tokens-saved counter. - #[hotpath::measure(label = "daemon.store_meta.read_tokens_saved", future = true)] pub async fn get_tokens_saved(&self) -> Result { - parse_counter("tokens_saved", self.db.get_metadata("tokens_saved").await?) + get_tokens_saved(&self.db).await } /// Persists the tokens-saved counter to the database. - #[hotpath::measure(label = "daemon.store_meta.write_tokens_saved", future = true)] pub async fn set_tokens_saved(&self, value: u64) -> Result<()> { - self.db - .set_metadata("tokens_saved", &value.to_string()) - .await + set_tokens_saved(&self.db, value).await } /// Returns the resettable project-local token counter. - /// - /// This is separate from the main `tokens_saved` counter and can be - /// independently reset via [`Self::reset_local_counter`]. - #[hotpath::measure(label = "daemon.store_meta.read_local_counter", future = true)] pub async fn get_local_counter(&self) -> Result { - parse_counter( - "local_counter", - self.db.get_metadata("local_counter").await?, - ) + get_local_counter(&self.db).await } /// Resets the project-local token counter to zero. - #[hotpath::measure(label = "daemon.store_meta.reset_local_counter", future = true)] pub async fn reset_local_counter(&self) -> Result<()> { - self.db.set_metadata("local_counter", "0").await + reset_local_counter(&self.db).await } /// Increments the project-local token counter by the given amount. - #[hotpath::measure(label = "daemon.store_meta.add_local_counter", future = true)] pub async fn add_local_counter(&self, delta: u64) -> Result<()> { - let transaction = self.db.begin_write_transaction("add local counter").await?; - let current = self.get_local_counter().await?; - let updated = current - .checked_add(delta) - .ok_or_else(|| TraceDecayError::Database { - operation: "add local counter".to_owned(), - message: "local_counter overflowed u64".to_owned(), - })?; - self.db - .set_metadata_unguarded(&transaction, "local_counter", &updated.to_string()) - .await?; - transaction.commit().await + add_local_counter(&self.db, delta).await } /// Checkpoints the WAL and closes the database connection. diff --git a/crates/tracedecay/src/tracedecay/queries/mod.rs b/crates/tracedecay/src/tracedecay/queries/mod.rs index 2a741c5c7d..be549de63e 100644 --- a/crates/tracedecay/src/tracedecay/queries/mod.rs +++ b/crates/tracedecay/src/tracedecay/queries/mod.rs @@ -1,5 +1,3 @@ -//! Read-side query surface for admitted graph projections and retained -//! project metadata. +//! Read-side store-metadata accessors for an open [`super::TraceDecay`]. -pub(crate) mod graph; mod meta; diff --git a/crates/tracedecay/tests/automation_runner_test/scheduler.rs b/crates/tracedecay/tests/automation_runner_test/scheduler.rs index d0aa286a91..9d2a3dc490 100644 --- a/crates/tracedecay/tests/automation_runner_test/scheduler.rs +++ b/crates/tracedecay/tests/automation_runner_test/scheduler.rs @@ -1,5 +1,5 @@ use tempfile::tempdir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_automation_runtime::automation::backend::{AgentTaskFailureClass, AgentTaskKind}; use tracedecay_automation_runtime::automation::config::{ AutomationBackend, AutomationConfig, AutomationConfigPatch, AutomationTaskConfig, diff --git a/crates/tracedecay/tests/automation_runner_test/support.rs b/crates/tracedecay/tests/automation_runner_test/support.rs index 1197c79602..b4f0cc3288 100644 --- a/crates/tracedecay/tests/automation_runner_test/support.rs +++ b/crates/tracedecay/tests/automation_runner_test/support.rs @@ -23,7 +23,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; pub(crate) use serde_json::{Value, json}; pub(crate) use tempfile::tempdir; -pub(crate) use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +pub(crate) use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; pub(crate) use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions, current_timestamp}; pub(crate) use tracedecay_automation_runtime::automation::automatic_facts::{ AutomaticFactState, list_automatic_fact_receipts, load_automatic_fact_receipt, diff --git a/crates/tracedecay/tests/automation_runner_test/support/fixtures.rs b/crates/tracedecay/tests/automation_runner_test/support/fixtures.rs index ace53d22ef..48cb901506 100644 --- a/crates/tracedecay/tests/automation_runner_test/support/fixtures.rs +++ b/crates/tracedecay/tests/automation_runner_test/support/fixtures.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use serde_json::{Value, json}; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions, current_timestamp}; use tracedecay_automation_runtime::automation::automatic_facts::record_session_automatic_facts; use tracedecay_automation_runtime::automation::run_ledger::{ diff --git a/crates/tracedecay/tests/claude_observation_benchmark/runner.rs b/crates/tracedecay/tests/claude_observation_benchmark/runner.rs index 1db4eff601..4ed50f9836 100644 --- a/crates/tracedecay/tests/claude_observation_benchmark/runner.rs +++ b/crates/tracedecay/tests/claude_observation_benchmark/runner.rs @@ -9,7 +9,7 @@ use tracedecay_store::{ ObservationReplayRequest, SESSION_MESSAGE_PROJECTOR_VERSION, StoredObservation, }; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::ObservationCancellation; use tracedecay_runtime_core::sqlite_read_snapshot::open_immutable_read_only; use tracedecay_runtime_core::storage::{ @@ -35,7 +35,7 @@ use tracedecay_sessions::runtime::{codex, cursor, hermes, kiro}; /// delegates to the same helper `HostAdmissionTestRuntimeV1` uses instead of /// racing it with a benchmark-private handle. fn ensure_background_cpu_authority() { - tracedecay::host_admission::ensure_process_background_cpu_authority() + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() .expect("install process capture authorities for the benchmark"); } diff --git a/crates/tracedecay/src/daemon/dashboard_configuration_test_runtime.rs b/crates/tracedecay/tests/common/dashboard_configuration_test_runtime.rs similarity index 100% rename from crates/tracedecay/src/daemon/dashboard_configuration_test_runtime.rs rename to crates/tracedecay/tests/common/dashboard_configuration_test_runtime.rs diff --git a/crates/tracedecay/tests/common/fixture.rs b/crates/tracedecay/tests/common/fixture.rs index 8821eca4df..b78165532f 100644 --- a/crates/tracedecay/tests/common/fixture.rs +++ b/crates/tracedecay/tests/common/fixture.rs @@ -43,7 +43,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::{Arc, OnceLock}; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; use tracedecay_domain::ProjectId; use tracedecay_runtime_core::storage::{self, StoreLayout}; @@ -482,9 +482,13 @@ impl UnenrolledProject { impl RegisteredProject { /// This project's runtime, promoted to the project scope that project-graph /// and project-session seams require. - pub fn project_scoped_runtime(&self) -> tracedecay::host_admission::ProjectScopedTestRuntimeV1 { - tracedecay::host_admission::ProjectScopedTestRuntimeV1::new(Arc::clone(&self.registry)) - .unwrap_or_else(|err| panic!("fixture project runtime must be project-scoped: {err}")) + pub fn project_scoped_runtime( + &self, + ) -> tracedecay::test_support::host_admission::ProjectScopedTestRuntimeV1 { + tracedecay::test_support::host_admission::ProjectScopedTestRuntimeV1::new(Arc::clone( + &self.registry, + )) + .unwrap_or_else(|err| panic!("fixture project runtime must be project-scoped: {err}")) } /// An MCP server for this project with its registry database, retained diff --git a/crates/tracedecay/tests/common/mod.rs b/crates/tracedecay/tests/common/mod.rs index 49ffaceb3b..2300458079 100644 --- a/crates/tracedecay/tests/common/mod.rs +++ b/crates/tracedecay/tests/common/mod.rs @@ -25,7 +25,7 @@ use tempfile::NamedTempFile; use tempfile::TempDir; use tokio::sync::OnceCell; use tracedecay::config::USER_DATA_DIR_ENV; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_runtime_core::db::{Database, DatabaseAuthority, TestDatabaseRuntimeMode}; use tracedecay_runtime_core::storage::PrivateStoreIo; use tracedecay_sessions::admission::{HostAdmissionOutcome, HostAdmissionScope}; diff --git a/crates/tracedecay/tests/cursor_observation_identity_fallback.rs b/crates/tracedecay/tests/cursor_observation_identity_fallback.rs index f28584a59e..3440f6d12d 100644 --- a/crates/tracedecay/tests/cursor_observation_identity_fallback.rs +++ b/crates/tracedecay/tests/cursor_observation_identity_fallback.rs @@ -1,6 +1,6 @@ use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_capture::cursor::{ cursor_observation_identity, cursor_projected_message_id, normalize_cursor_observation_with_message_id, diff --git a/crates/tracedecay/tests/daemon_suite/workflow_handoff_test.rs b/crates/tracedecay/tests/daemon_suite/workflow_handoff_test.rs index 551fbba1d9..a399b3f102 100644 --- a/crates/tracedecay/tests/daemon_suite/workflow_handoff_test.rs +++ b/crates/tracedecay/tests/daemon_suite/workflow_handoff_test.rs @@ -8,7 +8,7 @@ //! durability here. use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_contracts::{ TaskHandoffAuthorityPort, TaskHandoffConsumeOutcome, TaskHandoffGrant, TaskHandoffScope, WorkHandoffFrontierV1, WorkHandoffLineageV1, WorkflowDefinitionAuthorityPort, diff --git a/crates/tracedecay/tests/dashboard_api_test/runtime.rs b/crates/tracedecay/tests/dashboard_api_test/runtime.rs index e13396e413..1910e03854 100644 --- a/crates/tracedecay/tests/dashboard_api_test/runtime.rs +++ b/crates/tracedecay/tests/dashboard_api_test/runtime.rs @@ -129,7 +129,7 @@ pub(crate) struct DashboardTestRuntimeV1 { profile_database: RegisteredGlobalDbLeaseV1, profile_sessions_database: RegisteredGlobalDbLeaseV1, project_database: RegisteredGlobalDbLeaseV1, - graph: dashboard::DashboardGraphTestRuntimeV1, + graph: dashboard::dashboard_graph_test_runtime::DashboardGraphTestRuntimeV1, project_id: ProjectId, } @@ -154,7 +154,10 @@ impl DashboardTestRuntimeV1 { let graph_profile_root = profile_root .join("dashboard-test-graphs") .join(project_id.as_str()); - let graph = dashboard::DashboardGraphTestRuntimeV1::open(&graph_profile_root).await?; + let graph = dashboard::dashboard_graph_test_runtime::DashboardGraphTestRuntimeV1::open( + &graph_profile_root, + ) + .await?; let profile_database = graph.profile_database(); let profile_sessions_database = graph.profile_sessions_database(); let project_database = graph diff --git a/crates/tracedecay/tests/hooks_lsp_suite/hint_settlement_test.rs b/crates/tracedecay/tests/hooks_lsp_suite/hint_settlement_test.rs index be47990380..a18528c453 100644 --- a/crates/tracedecay/tests/hooks_lsp_suite/hint_settlement_test.rs +++ b/crates/tracedecay/tests/hooks_lsp_suite/hint_settlement_test.rs @@ -8,7 +8,7 @@ #![allow(clippy::unwrap_used, clippy::expect_used)] use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_agent_hosts::hooks::hint_outcomes::HintOutcomeStats; use tracedecay_agent_hosts::hooks::hint_outcomes::settlement::{ HintOutcomeSettlement, settle_project_hint_outcomes, diff --git a/crates/tracedecay/tests/hooks_lsp_suite/hook_replay_test.rs b/crates/tracedecay/tests/hooks_lsp_suite/hook_replay_test.rs index aaac7cb259..050de023e1 100644 --- a/crates/tracedecay/tests/hooks_lsp_suite/hook_replay_test.rs +++ b/crates/tracedecay/tests/hooks_lsp_suite/hook_replay_test.rs @@ -16,7 +16,7 @@ use std::path::Path; use std::process::{Command, Stdio}; use serde_json::{Value, json}; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::AnalyticsEventQuery; use tracedecay_runtime_core::storage::{StorageMode, default_profile_sharded_layout}; diff --git a/crates/tracedecay/tests/mcp_suite/analytics_test.rs b/crates/tracedecay/tests/mcp_suite/analytics_test.rs index c29455fcae..c4cc686f82 100644 --- a/crates/tracedecay/tests/mcp_suite/analytics_test.rs +++ b/crates/tracedecay/tests/mcp_suite/analytics_test.rs @@ -11,7 +11,7 @@ use crate::support::{ production_composition_fixture, real_mcp_server, setup_empty_project, }; #[cfg(feature = "test-transport")] -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; #[cfg(feature = "test-transport")] use tracedecay::tracedecay::current_timestamp; #[cfg(feature = "test-transport")] diff --git a/crates/tracedecay/tests/mcp_suite/git_correlation_test.rs b/crates/tracedecay/tests/mcp_suite/git_correlation_test.rs index f2cb00f7b1..a088591c5a 100644 --- a/crates/tracedecay/tests/mcp_suite/git_correlation_test.rs +++ b/crates/tracedecay/tests/mcp_suite/git_correlation_test.rs @@ -9,8 +9,10 @@ use std::process::Command; use serde_json::{Value, json}; -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1}; use tracedecay::mcp::McpServer; +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1, +}; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; use tracedecay_runtime_core::storage::PrivateStoreIo; use tracedecay_sessions::admission::HostAdmissionScope; diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/admin_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/admin_test.rs index 3c95cdb7cf..69c47ef635 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/admin_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/admin_test.rs @@ -5,7 +5,7 @@ use std::fs; #[cfg(feature = "test-transport")] use std::path::Path; #[cfg(feature = "test-transport")] -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_mcp::get_tool_definitions; #[cfg(feature = "test-transport")] diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_test.rs index 2ccf6ea761..d71399db91 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_test.rs @@ -5,7 +5,7 @@ use serde_json::{Value, json}; #[cfg(feature = "test-transport")] use std::time::SystemTime; #[cfg(feature = "test-transport")] -use tracedecay::host_admission::LcmLineageFaultForTest; +use tracedecay::test_support::host_admission::LcmLineageFaultForTest; #[cfg(feature = "test-transport")] use tracedecay_domain::CanonicalMessageRoleV1; #[cfg(feature = "test-transport")] diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/skills_automation_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/skills_automation_test.rs index 1f6fe5233d..c53c86de17 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/skills_automation_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/skills_automation_test.rs @@ -5,10 +5,10 @@ use serde_json::json; use std::fs; use tempfile::TempDir; #[cfg(feature = "test-transport")] -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; -#[cfg(feature = "test-transport")] use tracedecay::mcp::McpServer; #[cfg(feature = "test-transport")] +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; +#[cfg(feature = "test-transport")] use tracedecay_automation_runtime::automation::managed_skills::{ ManagedSkillDraft, ManagedSkillProvenance, ManagedSkillSource, ManagedSupportFile, create_managed_skill, diff --git a/crates/tracedecay/tests/mcp_suite/mcp_server_test/hooks_branch_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_server_test/hooks_branch_test.rs index 733ad9bcae..d26a973817 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_server_test/hooks_branch_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_server_test/hooks_branch_test.rs @@ -4,8 +4,10 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; use tempfile::TempDir; -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1}; use tracedecay::mcp::McpServer; +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1, +}; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; /// Two registered projects inside one throwaway profile for fail-closed route diff --git a/crates/tracedecay/tests/mcp_suite/mcp_server_test/support.rs b/crates/tracedecay/tests/mcp_suite/mcp_server_test/support.rs index 3cd0f899f4..8b1aeacaab 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_server_test/support.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_server_test/support.rs @@ -7,8 +7,8 @@ use std::fs; use std::path::PathBuf; use std::sync::Arc; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::mcp::McpServer; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; use tracedecay_mcp::transport::{ChannelTransport, McpTransport}; use tracedecay_runtime_core::storage::resolve_response_handle_root; @@ -264,7 +264,7 @@ pub(crate) async fn mcp_runtime_events( global_db_path: &std::path::Path, session_id: &str, ) -> Vec { - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::profile( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::profile( global_db_path .parent() .expect("global db has a profile root"), diff --git a/crates/tracedecay/tests/mcp_suite/serve_harness.rs b/crates/tracedecay/tests/mcp_suite/serve_harness.rs index b277fac274..d09be1ec4d 100644 --- a/crates/tracedecay/tests/mcp_suite/serve_harness.rs +++ b/crates/tracedecay/tests/mcp_suite/serve_harness.rs @@ -12,7 +12,7 @@ use std::time::Duration; use serde_json::{Value, json}; use tempfile::TempDir; #[cfg(unix)] -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::TraceDecayOpenOptions; use crate::common::{TestChildProcess, canonical_existing_path, tracedecay_command_with_home}; diff --git a/crates/tracedecay/tests/mcp_suite/support.rs b/crates/tracedecay/tests/mcp_suite/support.rs index b1ed862144..f80dfbe755 100644 --- a/crates/tracedecay/tests/mcp_suite/support.rs +++ b/crates/tracedecay/tests/mcp_suite/support.rs @@ -25,9 +25,11 @@ use tokio::sync::{Mutex, MutexGuard}; #[cfg(feature = "test-transport")] use tracedecay::daemon::ProductionProjectCompositionHarnessV1; #[cfg(feature = "test-transport")] -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1}; -#[cfg(feature = "test-transport")] use tracedecay::mcp::McpServer; +#[cfg(feature = "test-transport")] +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1, +}; use tracedecay::tracedecay::TraceDecay; #[cfg(feature = "test-transport")] use tracedecay_domain::errors::TraceDecayError; diff --git a/crates/tracedecay/tests/mcp_suite/workflow_query_test.rs b/crates/tracedecay/tests/mcp_suite/workflow_query_test.rs index 9e2da9cfad..c3e6d25054 100644 --- a/crates/tracedecay/tests/mcp_suite/workflow_query_test.rs +++ b/crates/tracedecay/tests/mcp_suite/workflow_query_test.rs @@ -10,8 +10,10 @@ use std::path::Path; use serde_json::{Value, json}; -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1}; use tracedecay::mcp::McpServer; +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, ProjectScopedTestRuntimeV1, +}; use tracedecay::tracedecay::TraceDecay; use tracedecay_sessions::runtime::git_correlation::{ DEFAULT_SPAN_MERGE_GAP_SECS, SpanObservation, SpanSource, diff --git a/crates/tracedecay/tests/runtime_acceptance_suite/cross_host_handoff_test.rs b/crates/tracedecay/tests/runtime_acceptance_suite/cross_host_handoff_test.rs index 47c1c7d901..2535798b71 100644 --- a/crates/tracedecay/tests/runtime_acceptance_suite/cross_host_handoff_test.rs +++ b/crates/tracedecay/tests/runtime_acceptance_suite/cross_host_handoff_test.rs @@ -1,6 +1,6 @@ use serde_json::json; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::{CaptureObservationRequest, ObservationCancellation}; use tracedecay_domain::{ CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, diff --git a/crates/tracedecay/tests/runtime_acceptance_suite/host_event_fixture_test.rs b/crates/tracedecay/tests/runtime_acceptance_suite/host_event_fixture_test.rs index 8bb86cbca0..b6a5278965 100644 --- a/crates/tracedecay/tests/runtime_acceptance_suite/host_event_fixture_test.rs +++ b/crates/tracedecay/tests/runtime_acceptance_suite/host_event_fixture_test.rs @@ -4,7 +4,7 @@ use std::process::{Command, Output, Stdio}; use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::{CaptureObservationRequest, ObservationCancellation}; use tracedecay_domain::{ CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, diff --git a/crates/tracedecay/tests/runtime_acceptance_suite/windows_durable_behavior.rs b/crates/tracedecay/tests/runtime_acceptance_suite/windows_durable_behavior.rs index eabef8cf77..82bca1e56e 100644 --- a/crates/tracedecay/tests/runtime_acceptance_suite/windows_durable_behavior.rs +++ b/crates/tracedecay/tests/runtime_acceptance_suite/windows_durable_behavior.rs @@ -75,7 +75,7 @@ mod temporal_kernel_behavior { mod lcm_payload_behavior { use tempfile::TempDir; - use tracedecay::host_admission::HostAdmissionTestRuntimeV1; + use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_lcm::{LcmExpandRequest, LcmExpandTarget}; use tracedecay_sessions::admission::HostAdmissionScope; @@ -128,7 +128,7 @@ mod lcm_payload_behavior { mod lcm_query_behavior { use tempfile::TempDir; - use tracedecay::host_admission::HostAdmissionTestRuntimeV1; + use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; use super::common::{lcm_payload_message, lcm_payload_session}; @@ -173,7 +173,7 @@ mod lcm_query_behavior { mod lcm_schema_durability { use tempfile::TempDir; - use tracedecay::host_admission::HostAdmissionTestRuntimeV1; + use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; use super::common::{lcm_payload_message, lcm_payload_session}; diff --git a/crates/tracedecay/tests/session_suite/anchor_resolution.rs b/crates/tracedecay/tests/session_suite/anchor_resolution.rs index 2789e163ce..1113de7904 100644 --- a/crates/tracedecay/tests/session_suite/anchor_resolution.rs +++ b/crates/tracedecay/tests/session_suite/anchor_resolution.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use serde_json::json; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AnchorResolutionStateV2, ClaudeByteRangeV1, ClaudeFileGenerationV1, ClaudeObservationIdentityMaterialV1, ClaudeSourceCursorV1, ClaudeSourceIdentityV1, diff --git a/crates/tracedecay/tests/session_suite/fact_anchor_authority.rs b/crates/tracedecay/tests/session_suite/fact_anchor_authority.rs index c6ea481b24..e0235661d6 100644 --- a/crates/tracedecay/tests/session_suite/fact_anchor_authority.rs +++ b/crates/tracedecay/tests/session_suite/fact_anchor_authority.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use serde_json::json; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AccessPolicyDigest, AnchorDurabilityClass, AnchorSourceGenerationV2, CapabilityId, ComponentVersion, Confidence, CoverageReportV1, EntityId, EntityKind, EntityRef, EvidenceClass, diff --git a/crates/tracedecay/tests/session_suite/git_backfill.rs b/crates/tracedecay/tests/session_suite/git_backfill.rs index 7306452c69..b9617fde90 100644 --- a/crates/tracedecay/tests/session_suite/git_backfill.rs +++ b/crates/tracedecay/tests/session_suite/git_backfill.rs @@ -14,7 +14,7 @@ use std::process::Command; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::ObservationCancellation; use tracedecay_domain::ObservationScopeV1; use tracedecay_domain::ProjectId; diff --git a/crates/tracedecay/tests/session_suite/global_db.rs b/crates/tracedecay/tests/session_suite/global_db.rs index 4f019aebbb..31030a65d3 100644 --- a/crates/tracedecay/tests/session_suite/global_db.rs +++ b/crates/tracedecay/tests/session_suite/global_db.rs @@ -2,7 +2,7 @@ use std::path::Path; use sha2::{Digest, Sha256}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::{AnalyticsEventInsert, AnalyticsEventQuery}; use tracedecay_lcm::LcmStorageKind; use tracedecay_sessions::admission::HostAdmissionScope; diff --git a/crates/tracedecay/tests/session_suite/lcm_compression/mod.rs b/crates/tracedecay/tests/session_suite/lcm_compression/mod.rs index 52ec8e01ac..0cc25df22f 100644 --- a/crates/tracedecay/tests/session_suite/lcm_compression/mod.rs +++ b/crates/tracedecay/tests/session_suite/lcm_compression/mod.rs @@ -2,7 +2,7 @@ use std::time::Duration; use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_lcm::{ LcmCompressionRequest, LcmGrepRequest, LcmGrepSort, LcmLifecycleUpdate, LcmLoadSessionRequest, LcmMaintenanceDebt, LcmPreflightRequest, LcmScope, LcmSessionBoundaryRequest, LcmSourceRef, diff --git a/crates/tracedecay/tests/session_suite/lcm_dag.rs b/crates/tracedecay/tests/session_suite/lcm_dag.rs index 68f8ddf8c5..b1d2dd94a7 100644 --- a/crates/tracedecay/tests/session_suite/lcm_dag.rs +++ b/crates/tracedecay/tests/session_suite/lcm_dag.rs @@ -1,5 +1,5 @@ use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_lcm::types::{LcmImmutableSummaryPublication, LcmSummaryPublicationDisposition}; use tracedecay_lcm::{ LcmDescribeRequest, LcmDescribeTarget, LcmError, LcmGrepRequest, LcmGrepSort, LcmScope, diff --git a/crates/tracedecay/tests/session_suite/lcm_payload.rs b/crates/tracedecay/tests/session_suite/lcm_payload.rs index 03fbf7551b..16889f88e2 100644 --- a/crates/tracedecay/tests/session_suite/lcm_payload.rs +++ b/crates/tracedecay/tests/session_suite/lcm_payload.rs @@ -4,7 +4,7 @@ use serde_json::{Value, json}; #[cfg(unix)] use sha2::{Digest, Sha256}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_lcm::payload::DeleteOpts; use tracedecay_lcm::types::{LcmImmutableSummaryPublication, LcmSummaryPublicationReceipt}; use tracedecay_lcm::{ diff --git a/crates/tracedecay/tests/session_suite/lcm_query/mod.rs b/crates/tracedecay/tests/session_suite/lcm_query/mod.rs index e6e1b41a1a..5bce0a3a68 100644 --- a/crates/tracedecay/tests/session_suite/lcm_query/mod.rs +++ b/crates/tracedecay/tests/session_suite/lcm_query/mod.rs @@ -1,5 +1,5 @@ use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::ParseOffset; use tracedecay_lcm::{ LCM_SCHEMA_VERSION, LcmContentSlice, LcmDescribeRequest, LcmDescribeTarget, LcmError, diff --git a/crates/tracedecay/tests/session_suite/lcm_raw.rs b/crates/tracedecay/tests/session_suite/lcm_raw.rs index e7cf6c8ec4..2ece7bb03b 100644 --- a/crates/tracedecay/tests/session_suite/lcm_raw.rs +++ b/crates/tracedecay/tests/session_suite/lcm_raw.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_lcm::{LcmCompressionRequest, LcmSummarizerMode}; use tracedecay_sessions::admission::HostAdmissionScope; use tracedecay_sessions::runtime::SessionMessageRecord; diff --git a/crates/tracedecay/tests/session_suite/lcm_summary_lineage_review.rs b/crates/tracedecay/tests/session_suite/lcm_summary_lineage_review.rs index 90886f4bb4..ce97fc8858 100644 --- a/crates/tracedecay/tests/session_suite/lcm_summary_lineage_review.rs +++ b/crates/tracedecay/tests/session_suite/lcm_summary_lineage_review.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use tempfile::TempDir; -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, LcmLineageFaultForTest}; +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, LcmLineageFaultForTest, +}; use tracedecay_graph_db::NeverCancelled; use tracedecay_lcm::types::{LcmImmutableSummaryPublication, LcmSummaryPublicationDisposition}; use tracedecay_lcm::{ diff --git a/crates/tracedecay/tests/session_suite/message_search_eval_test.rs b/crates/tracedecay/tests/session_suite/message_search_eval_test.rs index 65871e971d..c205e696ae 100644 --- a/crates/tracedecay/tests/session_suite/message_search_eval_test.rs +++ b/crates/tracedecay/tests/session_suite/message_search_eval_test.rs @@ -20,7 +20,7 @@ use serde_json::Value; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::ProjectId; use tracedecay_sessions::admission::HostAdmissionScope; use tracedecay_sessions::runtime::SessionMessageSearchResult; diff --git a/crates/tracedecay/tests/session_suite/observation_application.rs b/crates/tracedecay/tests/session_suite/observation_application.rs index 5b44f461c4..4cd7dd7dc1 100644 --- a/crates/tracedecay/tests/session_suite/observation_application.rs +++ b/crates/tracedecay/tests/session_suite/observation_application.rs @@ -1,6 +1,6 @@ use serde_json::{Map, Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::{ AdvanceNonDurableSourceCursorRequest, CaptureClaudeObservationOutcome, CaptureClaudeObservationRequest, CaptureObservationOutcome, CaptureObservationRequest, diff --git a/crates/tracedecay/tests/session_suite/observation_projection/mod.rs b/crates/tracedecay/tests/session_suite/observation_projection/mod.rs index 4600972a6b..2f5b8c5a5d 100644 --- a/crates/tracedecay/tests/session_suite/observation_projection/mod.rs +++ b/crates/tracedecay/tests/session_suite/observation_projection/mod.rs @@ -1,6 +1,6 @@ use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_application::observation::ObservationCancellation; use tracedecay_domain::{ CanonicalGitEvidenceKindV1, CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, diff --git a/crates/tracedecay/tests/session_suite/observation_projection/source_transition.rs b/crates/tracedecay/tests/session_suite/observation_projection/source_transition.rs index f5d3cb4cca..03bf2af676 100644 --- a/crates/tracedecay/tests/session_suite/observation_projection/source_transition.rs +++ b/crates/tracedecay/tests/session_suite/observation_projection/source_transition.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, CanonicalObservationFactV1, CanonicalObservationRelationsV1, ClineTranscriptStream, DurableObservationV1, ObservationId, diff --git a/crates/tracedecay/tests/session_suite/observation_store/mod.rs b/crates/tracedecay/tests/session_suite/observation_store/mod.rs index 2275acd57b..f7556f227e 100644 --- a/crates/tracedecay/tests/session_suite/observation_store/mod.rs +++ b/crates/tracedecay/tests/session_suite/observation_store/mod.rs @@ -5,7 +5,7 @@ use std::path::Path; use serde_json::json; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AnchorDurabilityClass, AnchorSourceGenerationV2, CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, CanonicalObservationFactV1, diff --git a/crates/tracedecay/tests/session_suite/observation_store/retrieval_anchors.rs b/crates/tracedecay/tests/session_suite/observation_store/retrieval_anchors.rs index 7e9b3d5b33..d1f8767183 100644 --- a/crates/tracedecay/tests/session_suite/observation_store/retrieval_anchors.rs +++ b/crates/tracedecay/tests/session_suite/observation_store/retrieval_anchors.rs @@ -2,7 +2,7 @@ use std::process::Command; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AnchorLineageRefV2, AnchorProvenanceRelationV2, AnchorSourceGenerationV2, ClaudeSourceCursorV1, FactOwnerV1, ObservationScopeV1, ObservationSourceGenerationV1, ProjectId, RetrievalAnchorId, diff --git a/crates/tracedecay/tests/session_suite/observation_workflow_projection.rs b/crates/tracedecay/tests/session_suite/observation_workflow_projection.rs index 986f2e1ac6..028d7f3b54 100644 --- a/crates/tracedecay/tests/session_suite/observation_workflow_projection.rs +++ b/crates/tracedecay/tests/session_suite/observation_workflow_projection.rs @@ -2,7 +2,7 @@ use std::path::Path; use serde_json::{Value, json}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, CanonicalObservationFactV1, CanonicalObservationRelationsV1, CanonicalWorkflowEvidenceKindV1, diff --git a/crates/tracedecay/tests/session_suite/session_runtime/mod.rs b/crates/tracedecay/tests/session_suite/session_runtime/mod.rs index fce9ac51a6..f6e7931b16 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/mod.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::errors::{Result, TraceDecayError}; use tracedecay_global_db::RegisteredGlobalDbLeaseV1; use tracedecay_session_runtime::StoreOwnerKey; diff --git a/crates/tracedecay/tests/session_suite/session_runtime/project_lifecycle.rs b/crates/tracedecay/tests/session_suite/session_runtime/project_lifecycle.rs index db84c2a353..1afeb5a18c 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/project_lifecycle.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/project_lifecycle.rs @@ -38,13 +38,13 @@ async fn register( root: &tempfile::TempDir, project_id: ProjectId, ) -> ( - tracedecay::host_admission::HostAdmissionTestRuntimeV1, + tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1, tracedecay_global_db::RegisteredGlobalDbLeaseV1, UserProfileId, ) { let project_root = root.path().join(project_id.as_str()); std::fs::create_dir_all(&project_root).unwrap(); - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( root.path(), &project_root, project_id.clone(), @@ -70,8 +70,9 @@ async fn register( project_sessions: project_sessions.clone(), user_sessions: profile_sessions.clone(), registry: profile_sessions, - background_cpu: tracedecay::host_admission::ensure_process_background_cpu_authority() - .expect("install fixture worker plan authority"), + background_cpu: + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install fixture worker plan authority"), startup_import: false, project_refresh: SessionTemporalRefreshWake::unavailable(), user_refresh: SessionTemporalRefreshWake::unavailable(), @@ -315,13 +316,14 @@ async fn exact_project_retirement_drains_a_keeps_b_live_and_rebinds_a() { // Re-enter the canonical host-admission owner map. This mints a fresh // short-lived registered lease without recovering a runtime or authority // from the retired client. - let replacement_runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( - root_a.path(), - root_a.path().join(project_a.as_str()), - project_a.clone(), - ) - .await - .unwrap(); + let replacement_runtime = + tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( + root_a.path(), + root_a.path().join(project_a.as_str()), + project_a.clone(), + ) + .await + .unwrap(); let replacement_a = replacement_runtime .registered_database_arc(tracedecay_sessions::admission::HostAdmissionScope::Project) .unwrap(); @@ -409,7 +411,7 @@ async fn registration_recovery_fences_concurrent_execute() { let project_id = ProjectId::new("project.session-sync.registration-race").unwrap(); let project_root = root.path().join(project_id.as_str()); std::fs::create_dir_all(&project_root).unwrap(); - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( root.path(), &project_root, project_id.clone(), @@ -459,7 +461,7 @@ async fn registration_recovery_fences_concurrent_execute() { user_sessions: profile_sessions.clone(), registry: profile_sessions, background_cpu: - tracedecay::host_admission::ensure_process_background_cpu_authority() + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() .expect("install fixture worker plan authority"), startup_import: false, project_refresh: SessionTemporalRefreshWake::unavailable(), @@ -500,7 +502,7 @@ async fn terminal_recovered_alias_does_not_suppress_startup_import() { let project_id = ProjectId::new("project.session-sync.terminal-alias").unwrap(); let project_root = root.path().join(project_id.as_str()); std::fs::create_dir_all(&project_root).unwrap(); - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( root.path(), &project_root, project_id.clone(), @@ -568,8 +570,9 @@ async fn terminal_recovered_alias_does_not_suppress_startup_import() { project_sessions, user_sessions: profile_sessions.clone(), registry: profile_sessions.clone(), - background_cpu: tracedecay::host_admission::ensure_process_background_cpu_authority() - .expect("install fixture worker plan authority"), + background_cpu: + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install fixture worker plan authority"), startup_import: true, project_refresh: SessionTemporalRefreshWake::unavailable(), user_refresh: SessionTemporalRefreshWake::unavailable(), @@ -608,7 +611,7 @@ async fn recovery_upgrades_a_journal_whose_frontiers_exceed_one_query() { let project_id = ProjectId::new("project.session-sync.large-frontier").unwrap(); let project_root = root.path().join(project_id.as_str()); std::fs::create_dir_all(&project_root).unwrap(); - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( root.path(), &project_root, project_id.clone(), @@ -663,8 +666,9 @@ async fn recovery_upgrades_a_journal_whose_frontiers_exceed_one_query() { project_sessions, user_sessions: profile_sessions.clone(), registry: profile_sessions.clone(), - background_cpu: tracedecay::host_admission::ensure_process_background_cpu_authority() - .expect("install fixture worker plan authority"), + background_cpu: + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install fixture worker plan authority"), startup_import: false, project_refresh: SessionTemporalRefreshWake::unavailable(), user_refresh: SessionTemporalRefreshWake::unavailable(), diff --git a/crates/tracedecay/tests/session_suite/session_runtime/retained_history.rs b/crates/tracedecay/tests/session_suite/session_runtime/retained_history.rs index 402788c7c1..5c86918018 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/retained_history.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/retained_history.rs @@ -31,7 +31,7 @@ use tracedecay_store::{ build_observation_retrieval_anchor_v2, }; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; use tracedecay_sessions::serving::{ SessionProjectionServingState, SessionProjectionServingStatusPort, SessionProjectionStaleReason, diff --git a/crates/tracedecay/tests/session_suite/session_runtime/session_sync.rs b/crates/tracedecay/tests/session_suite/session_runtime/session_sync.rs index ca77a84875..0a889bce99 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/session_sync.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/session_sync.rs @@ -868,7 +868,7 @@ async fn cancel_in_alias_activation_gap_mirrors_primary_terminal_receipt() { let project_root = profile_root.path().join("project"); std::fs::create_dir_all(&project_root).unwrap(); let project_id = ProjectId::new("project.cancel-alias-race").unwrap(); - let runtime = tracedecay::host_admission::HostAdmissionTestRuntimeV1::project( + let runtime = tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1::project( profile_root.path(), &project_root, project_id.clone(), @@ -898,8 +898,9 @@ async fn cancel_in_alias_activation_gap_mirrors_primary_terminal_receipt() { project_sessions, user_sessions: profile_sessions.clone(), registry: profile_sessions.clone(), - background_cpu: tracedecay::host_admission::ensure_process_background_cpu_authority() - .expect("install fixture worker plan authority"), + background_cpu: + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install fixture worker plan authority"), startup_import: false, project_refresh: SessionTemporalRefreshWake::unavailable(), user_refresh: SessionTemporalRefreshWake::unavailable(), diff --git a/crates/tracedecay/tests/session_suite/session_runtime/temporal_refresh.rs b/crates/tracedecay/tests/session_suite/session_runtime/temporal_refresh.rs index f0017b71a0..52bfce8833 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/temporal_refresh.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/temporal_refresh.rs @@ -44,7 +44,7 @@ use tracedecay_store::{ }; use tracedecay_temporal_query::ports::ExecutionControl; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::{RegisteredGlobalDb, RegisteredGlobalDbLeaseV1}; use tracedecay_session_temporal_store::{SessionRefreshRecoveryV1, SessionRefreshRestartStateV1}; use tracedecay_sessions::admission::HostAdmissionScope; diff --git a/crates/tracedecay/tests/session_suite/session_runtime/worker_persistence.rs b/crates/tracedecay/tests/session_suite/session_runtime/worker_persistence.rs index 3c37f1320f..0f993a132a 100644 --- a/crates/tracedecay/tests/session_suite/session_runtime/worker_persistence.rs +++ b/crates/tracedecay/tests/session_suite/session_runtime/worker_persistence.rs @@ -8,7 +8,7 @@ use tracedecay_store::{ SessionRefreshStore, SessionTemporalProjectionBatchV1, }; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_session_runtime::session_sync::test_harness::{ SessionTemporalRefreshPassReport, SessionTemporalRefreshWakeState, apply_refresh_effect, }; diff --git a/crates/tracedecay/tests/session_suite/temporal_derived_evidence.rs b/crates/tracedecay/tests/session_suite/temporal_derived_evidence.rs index 2673c6aaa1..ad5b911106 100644 --- a/crates/tracedecay/tests/session_suite/temporal_derived_evidence.rs +++ b/crates/tracedecay/tests/session_suite/temporal_derived_evidence.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AnchorProvenanceRelationV2, CopyProofV1, LogicalCopyRecordV1, MessageOccurrenceRecordV1, RetrievalGrainV1, SessionId, TemporalModeV1, diff --git a/crates/tracedecay/tests/session_suite/temporal_projection/mod.rs b/crates/tracedecay/tests/session_suite/temporal_projection/mod.rs index e9ec975576..342ee35c86 100644 --- a/crates/tracedecay/tests/session_suite/temporal_projection/mod.rs +++ b/crates/tracedecay/tests/session_suite/temporal_projection/mod.rs @@ -3,7 +3,7 @@ use std::fmt::Write as _; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ AnchorProvenanceRelationV2, CanonicalMessageRoleV1, CanonicalObservationEnvelopeV1, CanonicalObservationEvidenceV1, CanonicalObservationFactV1, CanonicalObservationRelationsV1, diff --git a/crates/tracedecay/tests/session_suite/temporal_refresh.rs b/crates/tracedecay/tests/session_suite/temporal_refresh.rs index 0705acab67..130867e2f8 100644 --- a/crates/tracedecay/tests/session_suite/temporal_refresh.rs +++ b/crates/tracedecay/tests/session_suite/temporal_refresh.rs @@ -1,7 +1,9 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH}; use tempfile::TempDir; -use tracedecay::host_admission::{HostAdmissionTestRuntimeV1, SessionTemporalFixtureCountV1}; +use tracedecay::test_support::host_admission::{ + HostAdmissionTestRuntimeV1, SessionTemporalFixtureCountV1, +}; use tracedecay_domain::{ SessionId, SessionRefreshKeyV1, SessionRefreshSourceTargetV1, SessionSourceFrontierV1, SessionSourceIdV1, SessionTemporalCoverageRequestV1, TemporalCoverageCountsV1, TemporalModeV1, diff --git a/crates/tracedecay/tests/session_suite/transcript_store.rs b/crates/tracedecay/tests/session_suite/transcript_store.rs index 72f1994718..c28ff5ac0c 100644 --- a/crates/tracedecay/tests/session_suite/transcript_store.rs +++ b/crates/tracedecay/tests/session_suite/transcript_store.rs @@ -1,5 +1,5 @@ use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::ParseOffset; use tracedecay_sessions::admission::HostAdmissionScope; use tracedecay_store::{TranscriptStore, TranscriptStoreError, TranscriptWriteBatch}; diff --git a/crates/tracedecay/tests/storage_suite/global_registry_test.rs b/crates/tracedecay/tests/storage_suite/global_registry_test.rs index 1b77f845c2..7047cbb5d3 100644 --- a/crates/tracedecay/tests/storage_suite/global_registry_test.rs +++ b/crates/tracedecay/tests/storage_suite/global_registry_test.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::os::unix::fs::PermissionsExt; use tempfile::TempDir; use tokio::sync::Mutex; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::{ GraphScopeUpsert, ProjectObservationStoreError, StoreArtifactUpsert, StoreInstanceUpsert, }; diff --git a/crates/tracedecay/tests/storage_suite/native_project_alias_test.rs b/crates/tracedecay/tests/storage_suite/native_project_alias_test.rs index 15d4bce2f7..8612189384 100644 --- a/crates/tracedecay/tests/storage_suite/native_project_alias_test.rs +++ b/crates/tracedecay/tests/storage_suite/native_project_alias_test.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; #[cfg(unix)] fn non_unicode_alias_paths(root: &Path) -> (PathBuf, PathBuf) { diff --git a/crates/tracedecay/tests/storage_suite/project_identity_collapse_test.rs b/crates/tracedecay/tests/storage_suite/project_identity_collapse_test.rs index 616308383f..a50490df50 100644 --- a/crates/tracedecay/tests/storage_suite/project_identity_collapse_test.rs +++ b/crates/tracedecay/tests/storage_suite/project_identity_collapse_test.rs @@ -16,7 +16,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::ReapEntryKind; use tracedecay_global_db::StoreInstanceUpsert; use tracedecay_runtime_core::path_safety::plain_host_path; diff --git a/crates/tracedecay/tests/storage_suite/projects_forget_test.rs b/crates/tracedecay/tests/storage_suite/projects_forget_test.rs index 613afb8374..be92561f2a 100644 --- a/crates/tracedecay/tests/storage_suite/projects_forget_test.rs +++ b/crates/tracedecay/tests/storage_suite/projects_forget_test.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use std::os::unix::fs::PermissionsExt; use tempfile::TempDir; use tokio::sync::Mutex; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_global_db::profile_registry_maintenance::ProfileRegistryMaintenanceRuntime; use tracedecay_global_db::{GraphScopeUpsert, StoreArtifactUpsert, StoreInstanceUpsert}; diff --git a/crates/tracedecay/tests/storage_suite/storage_resolver_test.rs b/crates/tracedecay/tests/storage_suite/storage_resolver_test.rs index d9551d12bf..4b23f42fa5 100644 --- a/crates/tracedecay/tests/storage_suite/storage_resolver_test.rs +++ b/crates/tracedecay/tests/storage_suite/storage_resolver_test.rs @@ -10,7 +10,7 @@ use std::os::unix::fs::symlink; use tempfile::TempDir; use tracedecay::config::{TraceDecayConfig, USER_DATA_DIR_ENV}; use tracedecay::config::{discover_project_root, get_config_path, load_config}; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; use tracedecay_global_db::{ProjectObservationStoreError, StoreInstanceUpsert}; use tracedecay_mcp::response_handles::{ diff --git a/crates/tracedecay/tests/storage_suite/worktree_canonical_root_guard_test.rs b/crates/tracedecay/tests/storage_suite/worktree_canonical_root_guard_test.rs index b9dc9ba680..c312a43634 100644 --- a/crates/tracedecay/tests/storage_suite/worktree_canonical_root_guard_test.rs +++ b/crates/tracedecay/tests/storage_suite/worktree_canonical_root_guard_test.rs @@ -17,7 +17,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay::tracedecay::{TraceDecay, TraceDecayOpenOptions}; use crate::common::canonical_existing_path as canonical_temp_path; diff --git a/crates/tracedecay/tests/transcript_ingest_suite/claude.rs b/crates/tracedecay/tests/transcript_ingest_suite/claude.rs index 1ba94db99d..57f6f28b9c 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/claude.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/claude.rs @@ -1,7 +1,7 @@ use std::io::Write; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ ProviderUsageCounterSemanticsV1, ProviderUsageCountersV1, ProviderUsageModelV1, ProviderUsageScopeV1, diff --git a/crates/tracedecay/tests/transcript_ingest_suite/codex.rs b/crates/tracedecay/tests/transcript_ingest_suite/codex.rs index b24e627fd9..8008e2bb3a 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/codex.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/codex.rs @@ -1,7 +1,7 @@ use std::io::Write; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_sessions::admission::HostAdmissionScope; use tracedecay_sessions::runtime::codex::CodexSource; diff --git a/crates/tracedecay/tests/transcript_ingest_suite/codex_compaction.rs b/crates/tracedecay/tests/transcript_ingest_suite/codex_compaction.rs index 2096f57067..c8fbcb620f 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/codex_compaction.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/codex_compaction.rs @@ -7,7 +7,7 @@ use std::io::Write; use std::process::Stdio; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::ProjectId; use tracedecay_global_db::ParseOffset; use tracedecay_lcm::{ diff --git a/crates/tracedecay/tests/transcript_ingest_suite/cursor.rs b/crates/tracedecay/tests/transcript_ingest_suite/cursor.rs index 32f70f3bd5..aee2443813 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/cursor.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/cursor.rs @@ -2,7 +2,7 @@ use std::hash::BuildHasher; use std::io::Write; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; #[cfg(unix)] use tracedecay_agent_hosts::hooks::cursor_pre_compact_via_daemon; use tracedecay_sessions::admission::HostAdmissionScope; diff --git a/crates/tracedecay/tests/transcript_ingest_suite/hermes.rs b/crates/tracedecay/tests/transcript_ingest_suite/hermes.rs index 9633c81bc8..e19904810e 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/hermes.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/hermes.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use serde_json::json; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ MAX_OBSERVATION_RECORD_BYTES, ProjectId, ProviderUsageCounterSemanticsV1, ProviderUsageCountersV1, ProviderUsageModelV1, ProviderUsageScopeV1, diff --git a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs index de3e4d7966..c285eceda3 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ObservationScopeV1, ObservationSourceCursorV1, ProjectId}; use tracedecay_runtime_core::storage::{ read_repository_identity_marker, write_repository_identity_marker, diff --git a/crates/tracedecay/tests/transcript_ingest_suite/session_ingest.rs b/crates/tracedecay/tests/transcript_ingest_suite/session_ingest.rs index 59882f78d6..c49c007ec4 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/session_ingest.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/session_ingest.rs @@ -54,8 +54,9 @@ impl IngestTestRuntime { /// authority that daemon startup installs; the fixture injects the same /// one the production worker plan uses. fn authority(&self) -> GlobalDbSessionIngestAuthority { - let background_cpu = tracedecay::host_admission::ensure_process_background_cpu_authority() - .expect("install fixture worker plan authority"); + let background_cpu = + tracedecay::test_support::host_admission::ensure_process_background_cpu_authority() + .expect("install fixture worker plan authority"); GlobalDbSessionIngestAuthority::new(self.database.clone()) .with_background_cpu(background_cpu) } diff --git a/crates/tracedecay/tests/transcript_ingest_suite/source_identity.rs b/crates/tracedecay/tests/transcript_ingest_suite/source_identity.rs index 87ddfb60b9..c8c748bdf8 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/source_identity.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/source_identity.rs @@ -4,7 +4,7 @@ //! collapsed into a Cursor project-filter miss or a Codex v2 cursor miss. use tempfile::TempDir; -use tracedecay::host_admission::HostAdmissionTestRuntimeV1; +use tracedecay::test_support::host_admission::HostAdmissionTestRuntimeV1; use tracedecay_domain::{ ClineTranscriptStream, ObservationSourceIdentityV1, ProviderId, SessionId, }; diff --git a/scripts/run-session-temporal-benchmark.sh b/scripts/run-session-temporal-benchmark.sh index da71fb267e..d892c47231 100755 --- a/scripts/run-session-temporal-benchmark.sh +++ b/scripts/run-session-temporal-benchmark.sh @@ -91,7 +91,7 @@ require(stats.get("p99_label") == p99_label, "p99 label mismatch") require(workload.get("production_path", {}).get("available_to_benchmark_target") is True, "production path must be available") implementation = workload.get("implementation") or {} -require(implementation.get("path") == "crates/tracedecay/src/session_temporal_benchmark.rs", +require(implementation.get("path") == "crates/tracedecay/benches/session_temporal/harness.rs", "implementation path mismatch") require((root / implementation["path"]).is_file(), "implementation source missing") runner = workload.get("runner") or {} @@ -133,7 +133,7 @@ elif provisional == "result-current.json": "current result workload manifest mismatch") identity = result.get("source_identity", {}) require(identity.get("harness") - == "crates/tracedecay/src/session_temporal_benchmark.rs", + == "crates/tracedecay/benches/session_temporal/harness.rs", "current result harness identity mismatch") require(identity.get("runner") == "scripts/run-session-temporal-benchmark.sh", "current result runner identity mismatch")