tensor-grep 1.76.0__tar.gz → 1.76.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/PKG-INFO +1 -1
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/pyproject.toml +3 -1
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/Cargo.lock +1 -1
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/Cargo.toml +3 -1
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/main.rs +124 -3
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/checkpoint_store.py +29 -24
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/repo_map.py +25 -6
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/runtime_paths.py +7 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/LICENSE +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/NOTICE +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/README.md +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/deny.toml +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/rust-toolchain.toml +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/backend_ast.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/backend_ast_workflow.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/backend_cpu.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/bin/tg_search_fast.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/cli.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/crossover.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/editor_plane.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/gpu_native.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/index.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/index_lock.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/lib.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/mmap_arrow.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/native_search.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/python_sidecar.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/rg_passthrough.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/routing.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/src/runtime_paths.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_ast_backend.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_ast_cache.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_ast_grep_embed.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_ast_rewrite.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_audit_manifest_symlink_unix.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native_error_handling.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native_graphs.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native_integration.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native_long_lines.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_gpu_native_streams.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_index.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_index_lock_concurrency.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_mmap_arrow.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_native_search.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_output.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_public_native_cli_parity.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_python_bootstrap.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_release_profile.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_replace.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_resident_worker.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_routing.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_runtime_path_resolution.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_schema_compat.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_search.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_search_golden.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_sidecar_ipc.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_smart_routing.rs +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/__main__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/ast_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/ast_wrapper_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/base.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/cpu_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/cudf_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/cybert_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/ripgrep_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/rust_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/stringzilla_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/backends/torch_backend.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/_index_lock.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/agent_capsule.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/apply_policy.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/ast_workflows.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/audit_manifest.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/bootstrap.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/codemap.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/commands.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/docs_coverage.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/dogfood.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/evidence_receipt.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/evidence_signing.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/base.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/csv_fmt.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/json_fmt.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/ripgrep_fmt.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/formatters/table_fmt.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/inventory.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/lang_go.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/lang_registry.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/lsp_external_provider.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/lsp_provider_setup.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/lsp_server.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/main.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/mcp_server.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/orient_capsule.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/progress.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/rg_contract.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/rule_packs.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/scan_guardrails.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/session_daemon.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/session_store.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/cli/subprocess_policy.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/config.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/hardware/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/hardware/device_detect.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/hardware/device_inventory.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/hardware/memory_manager.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/observability.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/pipeline.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/query_analyzer.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/registration_check.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/reranker.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/result.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_bm25.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_chunker.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_dense.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_fusion.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_late.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_lexical.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/retrieval_scoring.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/core/semantic_index.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/__init__.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/base.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/directory_scanner.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/reader_cudf.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/reader_dstorage.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/reader_fallback.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/io/reader_kvikio.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/perf_guard.py +0 -0
- {tensor_grep-1.76.0 → tensor_grep-1.76.2}/src/tensor_grep/sidecar.py +0 -0
|
@@ -528,12 +528,14 @@ version_variables = [
|
|
|
528
528
|
|
|
529
529
|
|
|
530
530
|
|
|
531
|
+
|
|
532
|
+
|
|
531
533
|
|
|
532
534
|
|
|
533
535
|
|
|
534
536
|
[project]
|
|
535
537
|
name = "tensor-grep"
|
|
536
|
-
version = "1.76.
|
|
538
|
+
version = "1.76.2"
|
|
537
539
|
description = "High-performance grep-compatible CLI with Rust, GPU, and AST/NLP routing"
|
|
538
540
|
readme = { file = "rust_core/README.md", content-type = "text/markdown" }
|
|
539
541
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tensor_grep_rs"
|
|
3
|
-
version = "1.76.
|
|
3
|
+
version = "1.76.2"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Rust core engine for tensor-grep"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -436,6 +436,8 @@ path = "src/bin/tg_search_fast.rs"
|
|
|
436
436
|
|
|
437
437
|
|
|
438
438
|
|
|
439
|
+
|
|
440
|
+
|
|
439
441
|
|
|
440
442
|
|
|
441
443
|
|
|
@@ -5049,6 +5049,122 @@ mod tests {
|
|
|
5049
5049
|
.is_empty()
|
|
5050
5050
|
);
|
|
5051
5051
|
}
|
|
5052
|
+
|
|
5053
|
+
/// Creates a symlink at `link` pointing to `target`, dispatching to the platform-specific
|
|
5054
|
+
/// primitive (mirrors write_bytes_refuse_symlink's own unix/windows/other split). Tests
|
|
5055
|
+
/// call this and skip gracefully (matching the existing audit-manifest symlink tests) when
|
|
5056
|
+
/// the sandbox lacks symlink privilege instead of failing the whole suite.
|
|
5057
|
+
fn try_symlink_file(target: &Path, link: &Path) -> std::io::Result<()> {
|
|
5058
|
+
#[cfg(unix)]
|
|
5059
|
+
{
|
|
5060
|
+
std::os::unix::fs::symlink(target, link)
|
|
5061
|
+
}
|
|
5062
|
+
#[cfg(windows)]
|
|
5063
|
+
{
|
|
5064
|
+
std::os::windows::fs::symlink_file(target, link)
|
|
5065
|
+
}
|
|
5066
|
+
#[cfg(not(any(unix, windows)))]
|
|
5067
|
+
{
|
|
5068
|
+
let _ = (target, link);
|
|
5069
|
+
Err(std::io::Error::new(
|
|
5070
|
+
std::io::ErrorKind::Unsupported,
|
|
5071
|
+
"symlinks not supported on this platform",
|
|
5072
|
+
))
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
#[test]
|
|
5077
|
+
fn test_create_checkpoint_refuses_symlink_at_index_path() {
|
|
5078
|
+
// #115 Gap 2: checkpoint_index_path (checkpoint_storage_dir(root).join("index.json"))
|
|
5079
|
+
// is a SHARED, PREDICTABLE path across every checkpoint under a given root -- unlike
|
|
5080
|
+
// metadata.json (namespaced under a random checkpoint_id, unpredictable ahead of the
|
|
5081
|
+
// call), an attacker who can write into the checkpoint root can plant a symlink at
|
|
5082
|
+
// index.json in advance of any create_checkpoint call. Confirm create_checkpoint
|
|
5083
|
+
// refuses to follow it instead of silently overwriting whatever the symlink targets.
|
|
5084
|
+
let dir = tempfile::tempdir().unwrap();
|
|
5085
|
+
let file_path = dir.path().join("fixture.py");
|
|
5086
|
+
std::fs::write(&file_path, "def add(x, y): return x + y\n").unwrap();
|
|
5087
|
+
|
|
5088
|
+
let outside_dir = tempfile::tempdir().unwrap();
|
|
5089
|
+
let outside_target = outside_dir.path().join("outside-target.json");
|
|
5090
|
+
// Valid (empty-array) JSON so the pre-write "does index.json already exist -> read
|
|
5091
|
+
// + parse it" branch succeeds and execution actually reaches the write call this
|
|
5092
|
+
// test targets, instead of failing earlier on a JSON-parse error.
|
|
5093
|
+
std::fs::write(&outside_target, b"[]").unwrap();
|
|
5094
|
+
|
|
5095
|
+
// Reuse the production scope-detection so the storage dir we plant the symlink under
|
|
5096
|
+
// is byte-identical to the one create_checkpoint computes internally (both must agree
|
|
5097
|
+
// post-canonicalization, or the symlink would land at the wrong path).
|
|
5098
|
+
let path_str = file_path.to_str().unwrap();
|
|
5099
|
+
let scope = detect_checkpoint_scope(path_str);
|
|
5100
|
+
let storage_dir = checkpoint_storage_dir(&scope.root);
|
|
5101
|
+
std::fs::create_dir_all(&storage_dir).unwrap();
|
|
5102
|
+
let index_path = storage_dir.join("index.json");
|
|
5103
|
+
if let Err(err) = try_symlink_file(&outside_target, &index_path) {
|
|
5104
|
+
eprintln!(
|
|
5105
|
+
"skipping test_create_checkpoint_refuses_symlink_at_index_path: cannot create a symlink in this environment: {err}"
|
|
5106
|
+
);
|
|
5107
|
+
return;
|
|
5108
|
+
}
|
|
5109
|
+
|
|
5110
|
+
let result = create_checkpoint(path_str);
|
|
5111
|
+
assert!(
|
|
5112
|
+
result.is_err(),
|
|
5113
|
+
"create_checkpoint must refuse to write the shared index through a symlink"
|
|
5114
|
+
);
|
|
5115
|
+
assert_eq!(
|
|
5116
|
+
std::fs::read(&outside_target).unwrap(),
|
|
5117
|
+
b"[]",
|
|
5118
|
+
"the symlink's target outside the checkpoint root must be left untouched"
|
|
5119
|
+
);
|
|
5120
|
+
}
|
|
5121
|
+
|
|
5122
|
+
#[test]
|
|
5123
|
+
fn test_restore_validation_rollback_snapshots_refuses_symlink_at_file_path() {
|
|
5124
|
+
// #115 Gap 3: between "apply the edit" and a failed-validation rollback, an edited
|
|
5125
|
+
// file could be swapped for a symlink pointing outside the project (e.g. via a
|
|
5126
|
+
// hostile --lint-cmd/--test-cmd running between apply and rollback).
|
|
5127
|
+
// restore_validation_rollback_snapshots must refuse to follow it instead of writing
|
|
5128
|
+
// the pre-edit snapshot bytes through the symlink.
|
|
5129
|
+
let dir = tempfile::tempdir().unwrap();
|
|
5130
|
+
let file_path = dir.path().join("fixture.py");
|
|
5131
|
+
let original_bytes = b"def add(x, y): return x + y\n".to_vec();
|
|
5132
|
+
std::fs::write(&file_path, &original_bytes).unwrap();
|
|
5133
|
+
|
|
5134
|
+
let outside_dir = tempfile::tempdir().unwrap();
|
|
5135
|
+
let outside_target = outside_dir.path().join("outside-target.py");
|
|
5136
|
+
std::fs::write(&outside_target, b"UNTOUCHED").unwrap();
|
|
5137
|
+
|
|
5138
|
+
let mut snapshots = BTreeMap::new();
|
|
5139
|
+
snapshots.insert(file_path.to_string_lossy().to_string(), original_bytes);
|
|
5140
|
+
|
|
5141
|
+
// Simulate the swap: the tracked file is replaced by a symlink after the pre-apply
|
|
5142
|
+
// snapshot was captured.
|
|
5143
|
+
std::fs::remove_file(&file_path).unwrap();
|
|
5144
|
+
if let Err(err) = try_symlink_file(&outside_target, &file_path) {
|
|
5145
|
+
eprintln!(
|
|
5146
|
+
"skipping test_restore_validation_rollback_snapshots_refuses_symlink_at_file_path: cannot create a symlink in this environment: {err}"
|
|
5147
|
+
);
|
|
5148
|
+
return;
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
let summary = restore_validation_rollback_snapshots(&snapshots);
|
|
5152
|
+
|
|
5153
|
+
assert!(
|
|
5154
|
+
!summary.success,
|
|
5155
|
+
"restore must report failure when a snapshot target is a symlink"
|
|
5156
|
+
);
|
|
5157
|
+
assert!(
|
|
5158
|
+
summary.files_restored.is_empty(),
|
|
5159
|
+
"a refused symlink write must not be counted as restored"
|
|
5160
|
+
);
|
|
5161
|
+
assert_eq!(summary.errors.len(), 1);
|
|
5162
|
+
assert_eq!(
|
|
5163
|
+
std::fs::read(&outside_target).unwrap(),
|
|
5164
|
+
b"UNTOUCHED",
|
|
5165
|
+
"the symlink's target outside the rollback root must be left untouched"
|
|
5166
|
+
);
|
|
5167
|
+
}
|
|
5052
5168
|
}
|
|
5053
5169
|
|
|
5054
5170
|
fn run_command_cli(cli: CommandCli) -> anyhow::Result<()> {
|
|
@@ -8324,7 +8440,7 @@ fn create_checkpoint(path: &str) -> anyhow::Result<CheckpointCreateSummary> {
|
|
|
8324
8440
|
std::fs::create_dir_all(parent)
|
|
8325
8441
|
.with_context(|| format!("failed to create {}", parent.display()))?;
|
|
8326
8442
|
}
|
|
8327
|
-
|
|
8443
|
+
write_bytes_refuse_symlink(&metadata_path, &serde_json::to_vec_pretty(&metadata)?)
|
|
8328
8444
|
.with_context(|| format!("failed to write {}", metadata_path.display()))?;
|
|
8329
8445
|
|
|
8330
8446
|
let index_path = checkpoint_index_path(&root);
|
|
@@ -8352,7 +8468,7 @@ fn create_checkpoint(path: &str) -> anyhow::Result<CheckpointCreateSummary> {
|
|
|
8352
8468
|
std::fs::create_dir_all(parent)
|
|
8353
8469
|
.with_context(|| format!("failed to create {}", parent.display()))?;
|
|
8354
8470
|
}
|
|
8355
|
-
|
|
8471
|
+
write_bytes_refuse_symlink(&index_path, &serde_json::to_vec_pretty(&records)?)
|
|
8356
8472
|
.with_context(|| format!("failed to write {}", index_path.display()))?;
|
|
8357
8473
|
|
|
8358
8474
|
Ok(summary)
|
|
@@ -8972,7 +9088,7 @@ fn restore_validation_rollback_snapshots(
|
|
|
8972
9088
|
let mut errors = Vec::new();
|
|
8973
9089
|
|
|
8974
9090
|
for (file, bytes) in snapshots {
|
|
8975
|
-
match
|
|
9091
|
+
match write_bytes_refuse_symlink(Path::new(file), bytes) {
|
|
8976
9092
|
Ok(()) => files_restored.push(file.clone()),
|
|
8977
9093
|
Err(error) => errors.push(format!("failed to restore {file}: {error}")),
|
|
8978
9094
|
}
|
|
@@ -9007,6 +9123,11 @@ fn emit_rollback_status(summary: &ValidationRollbackSummary) {
|
|
|
9007
9123
|
/// previously followed by a plain `std::fs::write` -- a confined write could escape its
|
|
9008
9124
|
/// anchor. Mirrors the confine-then-open discipline `_write_json_refuse_symlink` already
|
|
9009
9125
|
/// uses on the Python side (`src/tensor_grep/cli/main.py`).
|
|
9126
|
+
///
|
|
9127
|
+
/// audit #115: also guards `create_checkpoint`'s metadata.json + the shared/predictable
|
|
9128
|
+
/// index.json write, and `restore_validation_rollback_snapshots`'s per-file restore write --
|
|
9129
|
+
/// the same TOCTOU class, just with the checkpoint/rollback root standing in for
|
|
9130
|
+
/// `--audit-manifest`'s confined target.
|
|
9010
9131
|
fn write_bytes_refuse_symlink(path: &Path, bytes: &[u8]) -> anyhow::Result<()> {
|
|
9011
9132
|
use std::fs::OpenOptions;
|
|
9012
9133
|
|
|
@@ -853,33 +853,38 @@ def create_checkpoint(path: str = ".") -> CheckpointCreateResult:
|
|
|
853
853
|
# follow_symlinks=False: store a symlink AS a link, never copy its (possibly
|
|
854
854
|
# out-of-root) target content into the snapshot (audit HIGH — symlink disclosure).
|
|
855
855
|
shutil.copy2(source, destination, follow_symlinks=False)
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
856
|
+
|
|
857
|
+
result = CheckpointCreateResult(
|
|
858
|
+
checkpoint_id=checkpoint_id,
|
|
859
|
+
mode=mode,
|
|
860
|
+
root=str(root),
|
|
861
|
+
created_at=created_at,
|
|
862
|
+
file_count=len(entries),
|
|
863
|
+
undo_argv=_undo_argv(scope, checkpoint_id),
|
|
864
|
+
undo_command=_display_command(_undo_argv(scope, checkpoint_id)),
|
|
865
|
+
skipped_nested_repos=skipped_nested_repos,
|
|
866
|
+
)
|
|
867
|
+
_write_checkpoint_metadata(
|
|
868
|
+
root,
|
|
869
|
+
result,
|
|
870
|
+
entries,
|
|
871
|
+
scope_kind=scope.scope_kind,
|
|
872
|
+
original_path=scope.original_path,
|
|
873
|
+
)
|
|
874
|
+
except BaseException:
|
|
875
|
+
# audit #125a: catch BaseException, not Exception -- KeyboardInterrupt/SystemExit
|
|
876
|
+
# subclass BaseException directly (not Exception), so a Ctrl+C here previously escaped
|
|
877
|
+
# this handler entirely and left an uncleaned checkpoint dir behind. The guarded region
|
|
878
|
+
# also now extends through the metadata write below (not just the copy loop above): a
|
|
879
|
+
# failure writing metadata.json after a fully successful copy must not orphan that
|
|
880
|
+
# directory either -- audit H4's original cleanup only covered the copy loop. Remove the
|
|
881
|
+
# WHOLE per-checkpoint dir (metadata.json may or may not have been written yet, but
|
|
882
|
+
# snapshot_dir's own parent must go too, not just the snapshot/ subdir) -- matches how
|
|
883
|
+
# _prune_checkpoint_records removes a checkpoint. Always re-raise: the interrupt or
|
|
884
|
+
# exception must still propagate to the caller, never be swallowed here.
|
|
862
885
|
shutil.rmtree(snapshot_dir.parent, ignore_errors=True)
|
|
863
886
|
raise
|
|
864
887
|
|
|
865
|
-
result = CheckpointCreateResult(
|
|
866
|
-
checkpoint_id=checkpoint_id,
|
|
867
|
-
mode=mode,
|
|
868
|
-
root=str(root),
|
|
869
|
-
created_at=created_at,
|
|
870
|
-
file_count=len(entries),
|
|
871
|
-
undo_argv=_undo_argv(scope, checkpoint_id),
|
|
872
|
-
undo_command=_display_command(_undo_argv(scope, checkpoint_id)),
|
|
873
|
-
skipped_nested_repos=skipped_nested_repos,
|
|
874
|
-
)
|
|
875
|
-
_write_checkpoint_metadata(
|
|
876
|
-
root,
|
|
877
|
-
result,
|
|
878
|
-
entries,
|
|
879
|
-
scope_kind=scope.scope_kind,
|
|
880
|
-
original_path=scope.original_path,
|
|
881
|
-
)
|
|
882
|
-
|
|
883
888
|
# q10 RMW race: load->mutate->write must be atomic w.r.t. every other writer of this
|
|
884
889
|
# index.json, cross-process and cross-thread, or a concurrent insert can be lost (see
|
|
885
890
|
# _index_lock.index_lock docstring / AGENTS.md Backend Fail-Closed Contract). Retention
|
|
@@ -6412,12 +6412,20 @@ def _python_module_matches_definition(
|
|
|
6412
6412
|
repo_root: Path | str | None = None,
|
|
6413
6413
|
*,
|
|
6414
6414
|
level: int = 0,
|
|
6415
|
-
) -> bool:
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6415
|
+
) -> tuple[bool, list[str]]:
|
|
6416
|
+
"""Return `(matched, provenance)`.
|
|
6417
|
+
|
|
6418
|
+
Unlike the bool-only `_js_ts_module_matches_definition` / `_rust_module_matches_definition`
|
|
6419
|
+
siblings, this also threads through `_python_module_match_details`'s `provenance` (notably
|
|
6420
|
+
the "sys-path-insert" tag) -- #155 fix: that tag was computed but provably unreachable
|
|
6421
|
+
(this was the only caller, and it discarded everything but the bool) before this change.
|
|
6422
|
+
The sole caller, `_confirm_import_edges`, uses it to report the tag honestly on `tg
|
|
6423
|
+
importers` reverse edges instead of silently collapsing it into a generic label.
|
|
6424
|
+
"""
|
|
6425
|
+
details = _python_module_match_details(
|
|
6426
|
+
importer_path, module_name, definition_path, repo_root, level=level
|
|
6420
6427
|
)
|
|
6428
|
+
return bool(details["matched"]), list(details["provenance"])
|
|
6421
6429
|
|
|
6422
6430
|
|
|
6423
6431
|
def _group_symbols_by_file(symbols: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
|
|
@@ -15463,6 +15471,7 @@ def _confirm_import_edges(
|
|
|
15463
15471
|
# a confirmed edge for an import whose target we can't actually read (precision
|
|
15464
15472
|
# matters more than recall past this point).
|
|
15465
15473
|
continue
|
|
15474
|
+
python_provenance: list[str] = []
|
|
15466
15475
|
if language_id in ("javascript", "typescript"):
|
|
15467
15476
|
matched = _js_ts_module_matches_definition(
|
|
15468
15477
|
candidate_importer, module, target_file, repo_root
|
|
@@ -15479,7 +15488,7 @@ def _confirm_import_edges(
|
|
|
15479
15488
|
# `_python_module_candidates` resolve a relative import correctly instead of
|
|
15480
15489
|
# falling back to a bare path-suffix match that ignores directory context.
|
|
15481
15490
|
level = int(raw_entry.get("level", 0) or 0)
|
|
15482
|
-
matched = _python_module_matches_definition(
|
|
15491
|
+
matched, python_provenance = _python_module_matches_definition(
|
|
15483
15492
|
candidate_importer, module, target_file, repo_root, level=level
|
|
15484
15493
|
)
|
|
15485
15494
|
if not matched or line in seen_lines:
|
|
@@ -15496,6 +15505,16 @@ def _confirm_import_edges(
|
|
|
15496
15505
|
"provenance": provenance,
|
|
15497
15506
|
"resolution_confidence": _import_graph_resolution_confidence(provenance),
|
|
15498
15507
|
}
|
|
15508
|
+
if python_provenance == ["sys-path-insert"]:
|
|
15509
|
+
# #155 fix: report the sys.path-hack tag honestly on the reverse edge too, mirroring
|
|
15510
|
+
# the forward `tg imports` path (`_resolve_raw_import_entry`). A SEPARATE field, not
|
|
15511
|
+
# an overwrite of `provenance` above -- that string drives
|
|
15512
|
+
# `_import_graph_resolution_confidence`'s enum ("parser-backed"/"heuristic"/
|
|
15513
|
+
# "regex-heuristic", pinned by test_import_span_targeting.py) and this edge is still
|
|
15514
|
+
# exactly as parser-confirmed (exact AST-parsed, exact resolved-path match) as any
|
|
15515
|
+
# other Python edge, just resolved via a sys.path-hacked root instead of a standard
|
|
15516
|
+
# one.
|
|
15517
|
+
edge["path_provenance"] = "sys-path-insert"
|
|
15499
15518
|
if dynamic:
|
|
15500
15519
|
# Payload-bloat fix (#93 SUB-1 follow-up, same rationale as
|
|
15501
15520
|
# _resolve_raw_import_entry): preserve the dynamic markers on an edge that IS
|
|
@@ -84,6 +84,13 @@ def _read_project_version_fallback() -> str:
|
|
|
84
84
|
return "0.0.0"
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
# Safe to cache (unlike `_native_tg_version` below, whose docstring at `inspect_native_tg_binary`
|
|
88
|
+
# warns against a path-keyed cache going stale across an `os.replace` swap): this function takes
|
|
89
|
+
# no arguments and only reads pyproject.toml + installed package metadata, neither of which change
|
|
90
|
+
# within a single process lifetime. Called on every daemon probe (`_probe_daemon` in
|
|
91
|
+
# session_daemon.py) since the daemon became default-on for symbol commands, so an uncached
|
|
92
|
+
# pyproject.toml read + importlib.metadata scan on every probe is wasted I/O.
|
|
93
|
+
@lru_cache(maxsize=1)
|
|
87
94
|
def _expected_tg_version() -> str:
|
|
88
95
|
source_version = _read_project_version_fallback()
|
|
89
96
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensor_grep-1.76.0 → tensor_grep-1.76.2}/rust_core/tests/test_audit_manifest_symlink_unix.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|