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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions crates/tracedecay-cli/src/lsp_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ fn print_lsp_servers_table(adapters: &[lsp_adapters::LspAdapterDefinition]) {
#[cfg(test)]
mod tests {
use serde_json::{Value, json};
use tracedecay_runtime_core::path_safety::canonical_root_identity;

use super::{bridge_config_error, finish_stdio_bridge, initialize_binding};

Expand Down Expand Up @@ -554,10 +555,7 @@ mod tests {
.to_string();

let binding = initialize_binding(&frame).expect("initialize binding");
assert_eq!(
binding.project_root,
root.path().canonicalize().expect("canonical workspace")
);
assert_eq!(binding.project_root, canonical_root_identity(root.path()));
let forwarded: Value =
serde_json::from_str(&binding.frame).expect("forwarded initialize frame");
assert_eq!(forwarded["params"]["rootUri"], binding.canonical_root_uri);
Expand Down Expand Up @@ -630,11 +628,7 @@ mod tests {
assert!(binding.workspace_folders.is_sorted());
assert_eq!(
binding.project_root,
first
.path()
.canonicalize()
.unwrap()
.min(second.path().canonicalize().unwrap())
canonical_root_identity(first.path()).min(canonical_root_identity(second.path()))
);
let forwarded: Value = serde_json::from_str(&binding.frame).unwrap();
assert_eq!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use super::demand_admission::{
CodeIndexDemandAdmissionV1, CodeIndexDemandUnavailableV1, CodeIndexDemandV1,
};
use super::identity::IndexingIdentityV1;
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

const ACTIVATION_IDLE: u8 = 0;
const ACTIVATION_MOUNTING: u8 = 1;
Expand Down Expand Up @@ -169,8 +170,7 @@ impl CodeIndexActivationV1 {
mount: CodeIndexActivationMountV1,
hint_sink: CodeIndexActivationHintSinkV1,
) -> Self {
let project_root = project_root
.canonicalize()
let project_root = canonical_existing_identity(project_root)
.unwrap_or_else(|_| project_root.to_path_buf());
let identity = Arc::new(Mutex::new(IndexingIdentityV1::resolve(&project_root).ok()));
Self {
Expand Down Expand Up @@ -199,9 +199,7 @@ impl CodeIndexActivationV1 {
}

fn accepts_root(&self, project_root: &Path) -> bool {
project_root
.canonicalize()
.is_ok_and(|root| root == self.project_root)
canonical_existing_identity(project_root).is_ok_and(|root| root == self.project_root)
}

pub fn identity(&self) -> Option<IndexingIdentityV1> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use super::{
DaemonCodeIndexPublicationStoreV1, SharedCodeIndexBytePoolV1,
};
use tracedecay_privacy::CODE_SOURCE_SANITIZER_VERSION_V1;
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

fn git(root: &Path, args: &[&str]) {
let output = Command::new("git")
Expand Down Expand Up @@ -123,10 +124,8 @@ fn publication_store(store_root: &Path) -> DaemonCodeIndexPublicationStoreV1 {
async fn cold_mount_defers_sealed_decode_and_truth_verification_to_the_retained_owner() {
let project = fixture();
let store = TempDir::new().expect("store root");
let canonical_project_root = project
.path()
.canonicalize()
.expect("canonical project root");
let canonical_project_root =
canonical_existing_identity(project.path()).expect("canonical project root");
let scoped_store = super::scoped_code_index_store_root(store.path(), &canonical_project_root);
let generation_id = {
let mut scheduler = open(project.path(), &scoped_store);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ mod tests {
use crate::code_index_scheduler::{
CodeIndexWorktreeSchedulerV1, SharedCodeIndexBytePoolV1, scoped_code_index_store_root,
};
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

fn git(root: &Path, args: &[&str]) -> String {
let output = Command::new("git")
Expand Down Expand Up @@ -620,7 +621,8 @@ mod tests {
let base_tree =
GitOidV1::new(git(project.path(), &["rev-parse", "HEAD^{tree}"])).expect("base tree");
let project_id = ProjectId::new("project.branch-generation-diff").expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
let mut scheduler = CodeIndexWorktreeSchedulerV1::open(
project_id.clone(),
Expand Down Expand Up @@ -978,7 +980,8 @@ mod tests {
git(project.path(), &["checkout", "-q", "main"]);

let project_id = ProjectId::new("project.non-checked-out-refs").expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
let scheduler = CodeIndexWorktreeSchedulerV1::open(
project_id.clone(),
Expand Down Expand Up @@ -1094,7 +1097,8 @@ mod tests {
.expect("dirty source");

let project_id = ProjectId::new("project.dirty-generation").expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
let mut scheduler = CodeIndexWorktreeSchedulerV1::open(
project_id.clone(),
Expand Down Expand Up @@ -1303,7 +1307,8 @@ mod tests {
);

let project_id = ProjectId::new("project.superseded-tip-mint").expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
// Only the current tip is indexed, so the pair's head is served from the
// index and its base, a commit the branch has already left behind, is
Expand Down Expand Up @@ -1432,7 +1437,8 @@ mod tests {
);

let project_id = ProjectId::new(project_id).expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
let mut scheduler = CodeIndexWorktreeSchedulerV1::open(
project_id.clone(),
Expand Down Expand Up @@ -1689,7 +1695,8 @@ mod tests {
);

let project_id = ProjectId::new("project.truncated-index-mint").expect("project id");
let canonical_project = project.path().canonicalize().expect("canonical project");
let canonical_project =
canonical_existing_identity(project.path()).expect("canonical project");
let scoped_store = scoped_code_index_store_root(store.path(), &canonical_project);
let mut scheduler = CodeIndexWorktreeSchedulerV1::open(
project_id.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use super::{
CodeIndexDemandAdmissionV1, CodeIndexPublishedGenerationV1, CodeIndexSchedulerRegistryV1,
ServingGenerationInstallationOutcomeV1, ServingGenerationRollbackOutcomeV1,
};
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

const CODE_INDEX_SCHEDULER_UNAVAILABLE: &str = "code_index_scheduler_unavailable";
const CODE_INDEX_ACTIVATION_UNAVAILABLE: &str = "code_index_activation_unavailable";
Expand Down Expand Up @@ -116,16 +117,17 @@ impl BranchPublicationContextV1 {
branch: &str,
cancellation: &CancellationToken,
) -> Result<BranchAddOutcome, TraceDecayError> {
let canonical_project_root = project_root.canonicalize().map_err(|error| {
TraceDecayError::project_route(
CODE_INDEX_IDENTITY_MISMATCH,
false,
format!(
"failed to canonicalize branch project root '{}': {error}",
project_root.display()
),
)
})?;
let canonical_project_root =
canonical_existing_identity(project_root).map_err(|error| {
TraceDecayError::project_route(
CODE_INDEX_IDENTITY_MISMATCH,
false,
format!(
"failed to canonicalize branch project root '{}': {error}",
project_root.display()
),
)
})?;
if !self.owns_project(&canonical_project_root)? {
return Err(TraceDecayError::project_route(
CODE_INDEX_IDENTITY_MISMATCH,
Expand All @@ -139,16 +141,17 @@ impl BranchPublicationContextV1 {
if cancellation.is_cancelled() {
return Err(branch_publication_cancelled_error(branch));
}
let canonical_worktree_root = worktree_root.canonicalize().map_err(|error| {
TraceDecayError::project_route(
CODE_INDEX_IDENTITY_MISMATCH,
false,
format!(
"failed to canonicalize branch worktree '{}': {error}",
worktree_root.display()
),
)
})?;
let canonical_worktree_root =
canonical_existing_identity(worktree_root).map_err(|error| {
TraceDecayError::project_route(
CODE_INDEX_IDENTITY_MISMATCH,
false,
format!(
"failed to canonicalize branch worktree '{}': {error}",
worktree_root.display()
),
)
})?;
let source_branch = tracedecay_runtime_core::branch::current_branch(
&canonical_worktree_root,
)
Expand Down Expand Up @@ -634,15 +637,12 @@ impl BranchPublicationContextV1 {
}

fn owns_project(&self, canonical_root: &Path) -> Result<bool, TraceDecayError> {
let retained_root =
self.project_root
.canonicalize()
.map_err(|error| TraceDecayError::File {
message: format!(
"failed to canonicalize retained branch project root: {error}"
),
path: self.project_root.display().to_string(),
})?;
let retained_root = canonical_existing_identity(&self.project_root).map_err(|error| {
TraceDecayError::File {
message: format!("failed to canonicalize retained branch project root: {error}"),
path: self.project_root.display().to_string(),
}
})?;
Ok(retained_root == canonical_root)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::time::{SystemTime, UNIX_EPOCH};

use tracedecay_contracts::{ApplicationContractError, ResolvedScope};
use tracedecay_domain::{CommitId, ProjectId, RefId, RepositoryId, TreeId, WorktreeId};
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

/// Failure to resolve an exact indexing identity from a checkout.
#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -315,13 +316,12 @@ pub fn repository_id_for_common_dir(common_dir: &Path) -> Result<RepositoryId, I
}

pub fn worktree_id_for(project_root: &Path) -> Result<WorktreeId, IdentityErrorV1> {
let project_root =
project_root
.canonicalize()
.map_err(|source| IdentityErrorV1::CanonicalWorktreePath {
path: project_root.to_path_buf(),
source,
})?;
let project_root = canonical_existing_identity(project_root).map_err(|source| {
IdentityErrorV1::CanonicalWorktreePath {
path: project_root.to_path_buf(),
source,
}
})?;
WorktreeId::new(format!(
"worktree.daemon.{}",
super::sha256_hex(project_root.to_string_lossy().as_bytes())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use super::{
StaticLanguageRegistry, now_micros, projection_key,
};
use crate::code_index::languages::LanguageRegistry;
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

pub const ADMITTED_SOURCE_READ_CHUNK_BYTES: usize = 64 * 1024;

Expand Down Expand Up @@ -392,7 +393,7 @@ impl CodeIndexWorktreeSchedulerV1 {
.iter()
.all(|admission| {
let absolute = self.project_root.join(&admission.logical_path);
let Ok(canonical) = absolute.canonicalize() else {
let Ok(canonical) = canonical_existing_identity(&absolute) else {
return false;
};
if !canonical.starts_with(&self.project_root) {
Expand Down Expand Up @@ -470,8 +471,7 @@ fn resolve_package_entrypoint(
checkpoint_if_present(control)?;
let package_json = package_root.join("package.json");
if package_json.is_file() {
let canonical_package_json = package_json
.canonicalize()
let canonical_package_json = canonical_existing_identity(&package_json)
.map_err(|_| CodeIndexIgnoredDependencyRefusalV1::UnsupportedImport)?;
if !canonical_package_json.starts_with(canonical_package) {
return Err(CodeIndexIgnoredDependencyRefusalV1::SymlinkEscape.into());
Expand Down Expand Up @@ -554,9 +554,7 @@ fn read_contained_project_source(
{
return Err(CodeIndexIgnoredDependencyRefusalV1::PathEscape.into());
}
let canonical = project_root
.join(relative)
.canonicalize()
let canonical = canonical_existing_identity(&project_root.join(relative))
.map_err(|_| CodeIndexIgnoredDependencyRefusalV1::PathEscape)?;
if !canonical.starts_with(project_root) {
return Err(CodeIndexIgnoredDependencyRefusalV1::PathEscape.into());
Expand All @@ -571,8 +569,7 @@ fn canonical_package_root(
project_root: &Path,
package_root: &Path,
) -> Result<PathBuf, CodeIndexSchedulerErrorV1> {
let canonical = package_root
.canonicalize()
let canonical = canonical_existing_identity(package_root)
.map_err(|_| CodeIndexIgnoredDependencyRefusalV1::UnsupportedImport)?;
if !canonical.starts_with(project_root) || canonical != package_root {
return Err(CodeIndexIgnoredDependencyRefusalV1::SymlinkEscape.into());
Expand Down Expand Up @@ -634,8 +631,7 @@ fn validate_admitted_source(
control: Option<&dyn CodeIndexExecutionControlV1>,
) -> Result<Vec<u8>, CodeIndexSchedulerErrorV1> {
checkpoint_if_present(control)?;
let canonical_entrypoint = entrypoint
.canonicalize()
let canonical_entrypoint = canonical_existing_identity(entrypoint)
.map_err(|_| CodeIndexIgnoredDependencyRefusalV1::UnsupportedImport)?;
if !canonical_entrypoint.starts_with(project_root)
|| !canonical_entrypoint.starts_with(canonical_package)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use super::{
};
#[cfg(test)]
use super::{HeldActiveDecodeV1, reconcile_panic_guard};
use tracedecay_runtime_core::path_safety::canonical_existing_identity;

const MAX_PENDING_HINTS: usize = 1_024;
const MAX_SUPERSEDED_RECONCILE_RETRIES: usize = 4;
Expand Down Expand Up @@ -949,7 +950,7 @@ impl CodeIndexWorktreeSchedulerV1 {
byte_pool: Arc<SharedCodeIndexBytePoolV1>,
policy: CodeIndexHintPolicyV1,
) -> Result<Self, CodeIndexSchedulerErrorV1> {
let project_root = project_root.canonicalize()?;
let project_root = canonical_existing_identity(project_root)?;
// Resolve exact identity BEFORE any indexing work. Paths located this
// checkout; identity authorizes what may be reused.
let identity = identity::IndexingIdentityV1::resolve(&project_root)
Expand Down
Loading
Loading