diff --git a/.sentrux/baseline.json b/.sentrux/baseline.json index 91f7098..7d40493 100644 --- a/.sentrux/baseline.json +++ b/.sentrux/baseline.json @@ -5,18 +5,18 @@ }, "metrics": { "complex_fn_count": 12, - "coupling_score": 44.78, - "cross_module_edges": 918, + "coupling_score": 45.07, + "cross_module_edges": 1023, "cycle_count": 0, - "files": 205, - "functions": 2804, + "files": 227, + "functions": 3060, "god_file_count": 29, "max_complexity": 162, - "quality_signal": 3971, - "total_import_edges": 918 + "quality_signal": 3969, + "total_import_edges": 1023 }, - "savedAt": 1784946137, + "savedAt": 1785048977, "schema": "code-intel-sentrux-baseline.v2", "scope": ".", - "sourceCommit": "e746956754ff0dc89ae97d03716c7f53b967aa33" + "sourceCommit": "d20e4476c68c34b9596d96dfa7895dceabbd21b4" } diff --git a/.sentrux/rules.toml b/.sentrux/rules.toml index 406d5a0..d3ae527 100644 --- a/.sentrux/rules.toml +++ b/.sentrux/rules.toml @@ -7,10 +7,14 @@ # no god files, coupling grade B) is tracked modernization debt # (issue #14, "Remaining tracked debt"). # -# Measured ratchet evidence (sentrux-native 2.0.0, 2026-07-25, PR #15 tree, -# recorded together with .sentrux/baseline.json in the same commit): +# Measured ratchet evidence (sentrux-native 2.0.0, 2026-07-26, PR #39 tree +# merged with v0.6.0 main, recorded together with .sentrux/baseline.json in +# the same commit): # max_complexity 162 (scripts/tests/test-code-intel-pipeline.ps1), -# god_file_count 29, coupling_score 44.78, cycle_count 0. +# god_file_count 29, coupling_score 45.07, cycle_count 0. +# The 44.78 -> 45.07 coupling and 3971 -> 3969 quality movement is the cost +# of the v0.6.0 audit layer plus the edit.ast-grep-plan adapter module; god +# files and cycles did not move. # The previous B / 70 / no-god thresholds were never green under any engine: # the first honest self-scan of v0.5.0 already failed all three, so restoring # them would guarantee a permanently red release gate, not protection. diff --git a/crates/code-intel-cli/src/capability_inventory.rs b/crates/code-intel-cli/src/capability_inventory.rs index 7a49bb7..ddb529c 100644 --- a/crates/code-intel-cli/src/capability_inventory.rs +++ b/crates/code-intel-cli/src/capability_inventory.rs @@ -29,6 +29,8 @@ mod native_code_evidence; mod project_orientation; #[path = "project_orientation_benchmark.rs"] mod project_orientation_benchmark; +#[path = "structured_edit.rs"] +mod structured_edit; #[path = "understanding_quadrant.rs"] mod understanding_quadrant; @@ -71,6 +73,7 @@ pub(crate) fn execute( "evidence.native-code.compat" => { native_code_evidence::execute(request, verified_inputs, out) } + "edit.ast-grep-plan.compat" => structured_edit::execute(request, verified_inputs, out), "project.orientation.compat" => project_orientation::execute(request, verified_inputs, out), "understanding.quadrant.compat" => { understanding_quadrant::execute(request, verified_inputs, out) @@ -315,7 +318,7 @@ fn inventory(request: &Value, out: &Path) -> Result .collect::>(); baseline_globs.extend( lease - .inventory_gitlink_paths() + .inventory_excluded_paths() .iter() .map(|path| gitlink_exclude_glob(path)), ); diff --git a/crates/code-intel-cli/src/snapshot.rs b/crates/code-intel-cli/src/snapshot.rs index d66d337..72ef470 100644 --- a/crates/code-intel-cli/src/snapshot.rs +++ b/crates/code-intel-cli/src/snapshot.rs @@ -350,6 +350,7 @@ pub(crate) struct SnapshotLease { policy: Policy, scopes: Vec, manifest: InputManifest, + inventory_excluded_paths: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -466,11 +467,20 @@ pub(crate) fn begin_consumption(repo: &Path, expected: &Value) -> Result Vec { + pub(crate) fn inventory_excluded_paths(&self) -> Vec { self.manifest .entries .iter() .filter(|entry| entry.kind == "gitlink") .map(|entry| entry.path.clone()) + .chain(self.inventory_excluded_paths.iter().cloned()) .collect() } @@ -1069,7 +1080,7 @@ fn effective_file_mode(index_mode: &str, metadata: &fs::Metadata) -> String { } } -fn untracked_paths(repo: &Path, scopes: &[String]) -> Result, SnapshotError> { +fn untracked_entries(repo: &Path, scopes: &[String]) -> Result, SnapshotError> { let mut args = vec![ "ls-files", "--others", @@ -1085,6 +1096,20 @@ fn untracked_paths(repo: &Path, scopes: &[String]) -> Result, Snapsh )?) } +fn untracked_paths(repo: &Path, scopes: &[String]) -> Result, SnapshotError> { + Ok(untracked_entries(repo, scopes)? + .into_iter() + .filter(|path| !path.ends_with('/')) + .collect()) +} + +fn untracked_directory_paths(repo: &Path, scopes: &[String]) -> Result, SnapshotError> { + Ok(untracked_entries(repo, scopes)? + .into_iter() + .filter_map(|path| path.strip_suffix('/').map(str::to_string)) + .collect()) +} + #[derive(Clone, Debug, Default, PartialEq, Eq)] struct Overlay { tracked_modified: BTreeSet, diff --git a/crates/code-intel-cli/src/structured_edit.rs b/crates/code-intel-cli/src/structured_edit.rs new file mode 100644 index 0000000..1729710 --- /dev/null +++ b/crates/code-intel-cli/src/structured_edit.rs @@ -0,0 +1,359 @@ +use std::collections::BTreeSet; +use std::fs; +use std::path::{Component, Path}; +use std::process::Command; + +use serde_json::{json, Value}; + +use super::{publish_named, snapshot_adapter_error, AdapterArtifact, AdapterError, AdapterOutput}; +use crate::adapter_contract::AdapterDomainVerdict; +use crate::artifact_ref::VerifiedArtifact; +use crate::snapshot; + +const MAX_PATTERN_BYTES: usize = 16 * 1024; +const MAX_OUTPUT_BYTES: usize = 8 * 1024 * 1024; +const MAX_PATHS: usize = 64; +const GENERATED_COMPONENTS: [&str; 9] = [ + ".git", + ".next", + ".venv", + "__pycache__", + "build", + "dist", + "node_modules", + "target", + "vendor", +]; + +pub(crate) fn execute( + request: &Value, + verified_inputs: &[VerifiedArtifact], + out: &Path, +) -> Result { + if !verified_inputs.is_empty() { + return Err(AdapterError::Contract( + "edit.ast-grep-plan does not accept input artifacts".into(), + )); + } + let options = request["options"] + .as_object() + .ok_or_else(|| AdapterError::InvalidOptions("options must be an object".into()))?; + if options.keys().any(|key| { + !matches!( + key.as_str(), + "repoPath" | "language" | "pattern" | "rewrite" | "paths" + ) + }) { + return Err(AdapterError::InvalidOptions( + "edit.ast-grep-plan accepts only repoPath/language/pattern/rewrite/paths".into(), + )); + } + let repo = required_string(options.get("repoPath"), "options.repoPath")?; + let repo = Path::new(repo); + if !repo.is_dir() { + return Err(AdapterError::InvalidOptions(format!( + "repoPath is not a directory: {}", + repo.display() + ))); + } + let language = required_string(options.get("language"), "options.language")?; + if language.len() > 64 { + return Err(AdapterError::InvalidOptions( + "options.language exceeds 64 bytes".into(), + )); + } + let pattern = bounded_string(options.get("pattern"), "options.pattern")?; + let rewrite = options + .get("rewrite") + .map(|value| bounded_string(Some(value), "options.rewrite")) + .transpose()?; + let paths = requested_paths(options.get("paths"))?; + let canonical_repo = fs::canonicalize(repo) + .map_err(|error| AdapterError::Io(format!("resolve repoPath: {error}")))?; + let snapshot_scopes = request["snapshot"]["scope"] + .as_array() + .expect("validated snapshot scope") + .iter() + .map(|value| { + normalize_relative( + value.as_str().expect("validated snapshot scope item"), + false, + ) + }) + .collect::, _>>()?; + let paths = paths + .into_iter() + .map(|path| validate_path(repo, &canonical_repo, &snapshot_scopes, path)) + .collect::, _>>()?; + + let lease = + snapshot::begin_consumption(repo, &request["snapshot"]).map_err(snapshot_adapter_error)?; + let version = ast_grep_version()?; + let mut command = Command::new("ast-grep"); + command + .args(["run", "--pattern"]) + .arg(pattern) + .args(["--lang", language]); + if let Some(rewrite) = rewrite { + command.args(["--rewrite", rewrite]); + } + command.args(["--json=compact", "--threads", "0", "--"]); + command.args(&paths).current_dir(repo); + let output = command + .output() + .map_err(|error| AdapterError::Unavailable(format!("start ast-grep: {error}")))?; + if !output.status.success() { + return Err(AdapterError::Internal(format!( + "ast-grep failed: {}", + bounded_diagnostic(&output.stderr) + ))); + } + if output.stdout.len() > MAX_OUTPUT_BYTES { + return Err(AdapterError::Contract(format!( + "ast-grep output exceeds {MAX_OUTPUT_BYTES} bytes; narrow options.paths or pattern" + ))); + } + let mut matches: Vec = serde_json::from_slice(&output.stdout).map_err(|error| { + AdapterError::Internal(format!("ast-grep emitted invalid JSON: {error}")) + })?; + let mut files = BTreeSet::new(); + for item in &mut matches { + let file = item + .get("file") + .and_then(Value::as_str) + .ok_or_else(|| AdapterError::Internal("ast-grep match has no file path".into()))?; + let file = normalize_match_file(repo, &canonical_repo, file)?; + item["file"] = Value::String(file.clone()); + files.insert(file); + } + lease.verify_after(repo).map_err(snapshot_adapter_error)?; + + let artifact = json!({ + "schema": "code-intel-structured-edit-plan.v1", + "capability": "edit.ast-grep-plan", + "snapshotIdentity": request["snapshot"]["identity"], + "tool": { + "name": "ast-grep", + "version": version, + "threads": "auto" + }, + "query": { + "language": language, + "pattern": pattern, + "rewrite": rewrite, + "paths": paths + }, + "summary": { + "matches": matches.len(), + "files": files.len(), + "hasRewrite": rewrite.is_some() + }, + "matches": matches, + "authority": { + "mode": "preview_only", + "repositoryMutation": false + } + }); + let bytes = serde_json::to_vec(&artifact) + .map_err(|error| AdapterError::Internal(format!("serialize edit plan: {error}")))?; + publish_named(out, "structured-edit-plan.json", &bytes, |_| Ok(()))?; + Ok(AdapterOutput { + artifacts: vec![AdapterArtifact { + artifact_schema: "code-intel-structured-edit-plan.v1".into(), + artifact_type: "edit.structured-plan".into(), + relative_path: "structured-edit-plan.json".into(), + bytes, + }], + observed_effects: vec![ + "repo_read".into(), + "local_write".into(), + "process_spawn".into(), + ], + domain_verdict: AdapterDomainVerdict::Pass, + domain_failure: None, + }) +} + +fn required_string<'a>(value: Option<&'a Value>, name: &str) -> Result<&'a str, AdapterError> { + value + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + .ok_or_else(|| AdapterError::InvalidOptions(format!("{name} must be non-empty"))) +} + +fn bounded_string<'a>(value: Option<&'a Value>, name: &str) -> Result<&'a str, AdapterError> { + let value = required_string(value, name)?; + if value.len() > MAX_PATTERN_BYTES { + Err(AdapterError::InvalidOptions(format!( + "{name} exceeds {MAX_PATTERN_BYTES} bytes" + ))) + } else { + Ok(value) + } +} + +fn requested_paths(value: Option<&Value>) -> Result, AdapterError> { + match value { + None => Ok(vec!["."]), + Some(value) => { + let paths = value.as_array().ok_or_else(|| { + AdapterError::InvalidOptions("options.paths must be an array".into()) + })?; + if paths.is_empty() || paths.len() > MAX_PATHS { + return Err(AdapterError::InvalidOptions(format!( + "options.paths must contain 1..={MAX_PATHS} entries" + ))); + } + paths + .iter() + .map(|value| required_string(Some(value), "options.paths[]")) + .collect() + } + } +} + +fn validate_path( + repo: &Path, + canonical_repo: &Path, + snapshot_scopes: &[String], + path: &str, +) -> Result { + let normalized = normalize_relative(path, true)?; + if !snapshot_scopes + .iter() + .any(|scope| within_scope(&normalized, scope)) + { + return Err(AdapterError::Contract(format!( + "edit path is outside the requested snapshot scope: {path}" + ))); + } + let full = fs::canonicalize(repo.join(Path::new(&normalized))).map_err(|error| { + AdapterError::InvalidOptions(format!("resolve edit path {path}: {error}")) + })?; + if !full.starts_with(canonical_repo) { + return Err(AdapterError::Contract(format!( + "edit path escapes repository: {path}" + ))); + } + Ok(normalized) +} + +fn normalize_relative(path: &str, reject_generated: bool) -> Result { + let path = Path::new(path); + if path.is_absolute() { + return Err(AdapterError::InvalidOptions(format!( + "edit path must be repository-relative: {}", + path.display() + ))); + } + let mut parts = Vec::new(); + for component in path.components() { + match component { + Component::CurDir => {} + Component::Normal(value) => { + let value = value.to_str().ok_or_else(|| { + AdapterError::InvalidOptions("edit path must be UTF-8".into()) + })?; + if reject_generated + && GENERATED_COMPONENTS + .iter() + .any(|generated| value.eq_ignore_ascii_case(generated)) + { + return Err(AdapterError::InvalidOptions(format!( + "edit path targets excluded generated content: {}", + path.display() + ))); + } + parts.push(value); + } + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(AdapterError::InvalidOptions(format!( + "edit path escapes repository: {}", + path.display() + ))) + } + } + } + Ok(if parts.is_empty() { + ".".into() + } else { + parts.join("/") + }) +} + +fn within_scope(path: &str, scope: &str) -> bool { + scope == "." || path == scope || path.starts_with(&format!("{scope}/")) +} + +fn normalize_match_file( + repo: &Path, + canonical_repo: &Path, + file: &str, +) -> Result { + let file = Path::new(file); + let full = if file.is_absolute() { + file.to_path_buf() + } else { + repo.join(file) + }; + let full = fs::canonicalize(&full) + .map_err(|error| AdapterError::Internal(format!("resolve ast-grep match: {error}")))?; + let relative = full.strip_prefix(canonical_repo).map_err(|_| { + AdapterError::Contract(format!( + "ast-grep returned a match outside the repository: {}", + full.display() + )) + })?; + normalize_relative(&relative.to_string_lossy(), true) +} + +fn ast_grep_version() -> Result { + let output = Command::new("ast-grep") + .arg("--version") + .output() + .map_err(|error| AdapterError::Unavailable(format!("start ast-grep: {error}")))?; + if !output.status.success() { + return Err(AdapterError::Unavailable(format!( + "ast-grep --version failed: {}", + bounded_diagnostic(&output.stderr) + ))); + } + String::from_utf8(output.stdout) + .map(|value| value.trim().to_string()) + .map_err(|error| { + AdapterError::Unavailable(format!("ast-grep version is not UTF-8: {error}")) + }) +} + +fn bounded_diagnostic(bytes: &[u8]) -> String { + String::from_utf8_lossy(&bytes[..bytes.len().min(4096)]) + .trim() + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::capability::sha256_hex; + + #[test] + fn registry_digest_and_path_guards_are_bound_to_this_adapter() { + let registry: Value = + serde_json::from_slice(include_bytes!("../../../orchestration/integrations.json")) + .unwrap(); + let integration = registry["integrations"] + .as_array() + .unwrap() + .iter() + .find(|item| item["id"] == "edit.ast-grep-plan") + .unwrap(); + assert_eq!( + integration["capabilityDeclaration"]["implementation"]["toolchainDigests"][0], + sha256_hex(include_bytes!("structured_edit.rs")) + ); + assert!(normalize_relative("src/../secret.py", true).is_err()); + assert!(normalize_relative("node_modules/pkg/index.js", true).is_err()); + assert!(within_scope("backend/api.py", "backend")); + assert!(!within_scope("frontend/api.ts", "backend")); + } +} diff --git a/crates/code-intel-cli/tests/capability_exec.rs b/crates/code-intel-cli/tests/capability_exec.rs index f2d6b05..296342e 100644 --- a/crates/code-intel-cli/tests/capability_exec.rs +++ b/crates/code-intel-cli/tests/capability_exec.rs @@ -8,6 +8,8 @@ use serde_json::{json, Value}; const IMPLEMENTATION_DIGEST: &str = "43ced9ef578e6484423468e059c93ef0bc5eeeb35d23271451b2d8f1a16f9bb6"; +const STRUCTURED_EDIT_DIGEST: &str = + "58d3687e7e5ac8b3df9ece44bc613f4927fc328c02dddcdaeee72ed3103a04c8"; static TEMP_DIR_SEQUENCE: AtomicU64 = AtomicU64::new(0); fn temp_dir(name: &str) -> PathBuf { @@ -330,6 +332,55 @@ fn inventory_rg_ignores_repository_ignored_workspace_churn() { let _ = fs::remove_dir_all(root); } +#[test] +fn inventory_rg_excludes_linked_worktree_git_pointer() { + let root = temp_dir("linked-worktree-git-pointer"); + let repo = root.join("repo"); + let linked = root.join("linked"); + fs::create_dir_all(&repo).unwrap(); + git(&repo, &["init", "--quiet"]); + git(&repo, &["config", "user.name", "Inventory Test"]); + git( + &repo, + &["config", "user.email", "inventory@example.invalid"], + ); + fs::write(repo.join("kept.py"), "print('kept')\n").unwrap(); + git(&repo, &["add", "kept.py"]); + git(&repo, &["commit", "--quiet", "-m", "baseline"]); + + let added = Command::new("git") + .args(["worktree", "add", "--quiet", "--detach"]) + .arg(&linked) + .arg("HEAD") + .current_dir(&repo) + .output() + .expect("create linked worktree"); + assert!( + added.status.success(), + "{}", + String::from_utf8_lossy(&added.stderr) + ); + assert!(linked.join(".git").is_file()); + + let out = root.join("out"); + let output = run_with_request_file( + &request(&linked, "inventory.rg"), + &root.join("request.json"), + &out, + "inventory.rg", + ); + assert_eq!( + output.status.code(), + Some(0), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + let files = fs::read_to_string(out.join("files.txt")).unwrap(); + assert!(files.lines().any(|path| path == "kept.py")); + assert!(!files.lines().any(|path| path == ".git")); + let _ = fs::remove_dir_all(root); +} + #[test] fn inventory_rejects_repository_changes_after_snapshot_and_honors_snapshot_scope() { let root = temp_dir("snapshot-lease"); @@ -1209,6 +1260,59 @@ fn populated_gitlink_is_literal_excluded_and_oid_bound_for_both_policies() { let _ = fs::remove_dir_all(root); } +#[test] +fn nested_linked_worktree_is_excluded_from_inventory() { + let root = temp_dir("nested-linked-worktree"); + let repo = root.join("repo"); + fs::create_dir_all(&repo).unwrap(); + git(&repo, &["init", "--quiet"]); + git(&repo, &["config", "user.name", "Inventory Test"]); + git( + &repo, + &["config", "user.email", "inventory@example.invalid"], + ); + fs::write(repo.join("root.txt"), "root\n").unwrap(); + git(&repo, &["add", "."]); + git(&repo, &["commit", "--quiet", "-m", "root"]); + + let nested = repo.join(".claude/worktrees/agent-test"); + git( + &repo, + &[ + "worktree", + "add", + "--quiet", + "--detach", + nested.to_str().unwrap(), + "HEAD", + ], + ); + + for policy in ["head_only", "explicit_overlay"] { + let request = request_with_policy_scopes(&repo, "inventory.rg", policy, &["."]); + let out = root.join(format!("{policy}-out")); + let output = run_with_request_file( + &request, + &root.join(format!("{policy}-request.json")), + &out, + "inventory.rg", + ); + assert_eq!( + output.status.code(), + Some(0), + "{policy}: {}", + String::from_utf8_lossy(&output.stderr) + ); + let files = fs::read_to_string(out.join("files.txt")).unwrap(); + assert!(files.lines().any(|path| path == "root.txt")); + assert!(!files + .lines() + .any(|path| path.starts_with(".claude/worktrees/agent-test/"))); + } + + let _ = fs::remove_dir_all(root); +} + #[test] fn inventory_exclude_uses_ripgrep_glob_semantics_for_basename_brace_class_and_segment() { let root = temp_dir("ripgrep-glob-semantics"); @@ -1711,6 +1815,79 @@ fn declaration_determinism_is_used_for_post_declaration_failures() { let _ = fs::remove_dir_all(root); } +#[test] +fn structured_edit_plan_is_scope_bound_and_preview_only() { + let root = temp_dir("structured-edit"); + let repo = root.join("repo"); + fs::create_dir_all(repo.join("src")).unwrap(); + fs::write(repo.join("src/example.py"), "print(\"hello\")\n").unwrap(); + + let mut value = request(&repo, "edit.ast-grep-plan"); + value["implementation"] = json!({ + "id":"edit.ast-grep-plan.compat", + "version":"1.0.0", + "toolchainDigests":[STRUCTURED_EDIT_DIGEST] + }); + value["options"] = json!({ + "repoPath":repo, + "language":"python", + "pattern":"print($A)", + "rewrite":"logger.info($A)", + "paths":["../outside.py"] + }); + value["effectPolicy"]["allowedEffects"] = json!(["repo_read", "local_write", "process_spawn"]); + let rejected = run_with_request_file( + &value, + &root.join("rejected-request.json"), + &root.join("rejected-out"), + "edit.ast-grep-plan", + ); + assert_eq!(rejected.status.code(), Some(64)); + assert!(String::from_utf8_lossy(&rejected.stderr).contains("escapes repository")); + + let ast_grep_available = Command::new("ast-grep") + .arg("--version") + .output() + .is_ok_and(|output| output.status.success()); + if !ast_grep_available { + let _ = fs::remove_dir_all(root); + return; + } + + value["options"]["paths"] = json!(["src"]); + let out = root.join("out"); + let output = run_with_request_file( + &value, + &root.join("request.json"), + &out, + "edit.ast-grep-plan", + ); + assert_eq!( + output.status.code(), + Some(0), + "stderr={} stdout={}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ); + let result: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(result["capability"], "edit.ast-grep-plan"); + assert_eq!( + result["observedEffects"], + json!(["repo_read", "local_write", "process_spawn"]) + ); + let plan: Value = + serde_json::from_slice(&fs::read(out.join("structured-edit-plan.json")).unwrap()).unwrap(); + assert_eq!(plan["summary"]["matches"], 1); + assert_eq!(plan["matches"][0]["file"], "src/example.py"); + assert_eq!(plan["matches"][0]["replacement"], "logger.info(\"hello\")"); + assert_eq!(plan["authority"]["repositoryMutation"], false); + assert_eq!( + fs::read_to_string(repo.join("src/example.py")).unwrap(), + "print(\"hello\")\n" + ); + let _ = fs::remove_dir_all(root); +} + fn normalized_lines(text: &str) -> Vec { let mut lines = text .lines() diff --git a/docs/artifact-data-contract.md b/docs/artifact-data-contract.md index 162f921..023daf6 100644 --- a/docs/artifact-data-contract.md +++ b/docs/artifact-data-contract.md @@ -49,6 +49,9 @@ Tool evidence: - `repomix-output.md`, `repomix-output.xml`, `repomix-output.json`, `repomix-output.txt` - `sentrux-dsm.json`, `sentrux-file-details.json`, `sentrux-hotspots.json`, `sentrux-evolution.json`, `sentrux-what-if.json` - `codenexus-context.json` +- Optional `structured-edit-plan.json` using `code-intel-structured-edit-plan.v1`. It is a + snapshot-bound ast-grep structural search/rewrite preview. It has no repository-mutation + authority; applying a rewrite remains a separate explicitly authorized operation. - Optional `session-evidence.json` using `code-intel-session-evidence.v1`. It is a privacy-reduced, snapshot-bound session-review artifact with advisory-only authority; raw traces, prompts, event summaries, user-message marks, absolute paths, and outside-path values are not published. diff --git a/docs/repository-snapshot-identity.md b/docs/repository-snapshot-identity.md index 761bac7..f607ebc 100644 --- a/docs/repository-snapshot-identity.md +++ b/docs/repository-snapshot-identity.md @@ -27,6 +27,7 @@ Scope is reduced to its smallest prefix set: `src` absorbs `src/nested`, and `.` `head_only` resolves HEAD once and reads its immutable tree by object id. Sorted records retain mode, kind, object id, and relative path. Executable mode, symlink blobs, Gitlinks/submodules, and LFS pointer blobs are therefore checkout-independent. `explicit_overlay` uses the Git index plus non-ignored untracked paths. Sorted length-prefixed records contain domain, kind, Git mode, relative path, byte length, and raw bytes. Deleted tracked files emit tombstones. Intent-to-add (`git add -N`, porcelain ` A`) is a modified overlay member. Gitlinks emit the indexed commit and do not recursively consume the submodule worktree. Symlinks emit their target text and are never followed. LFS worktree files are the bytes actually consumed. Ignored files are excluded and output declares `ignoredPolicy=excluded_by_git_ignore`. +Untracked nested repositories and linked worktrees that Git reports as opaque directory entries ending in `/` are excluded from the outer repository snapshot and inventory rather than recursively consumed. The report separates `trackedModified`, `trackedDeleted`, `untracked`, `renamed`, `typeChanged`, and `staged`; a boolean alone is not the dirty-tree contract. Porcelain v1 XY states are table-driven: ignored (`!!`) is excluded, intent-to-add is retained, and every unmerged state (`DD`, `AU`, `UD`, `UA`, `DU`, `AA`, `UU`) fails closed instead of producing a partial identity. diff --git a/orchestration/integrations.json b/orchestration/integrations.json index c0464f7..4334d38 100644 --- a/orchestration/integrations.json +++ b/orchestration/integrations.json @@ -286,7 +286,7 @@ "version": "1.0.0", "toolchainDigests": [ "8e260c477db93e0651f37b979e521f90f6830a41b3ad5d81c70461f5d01bf68a", - "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175" + "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4" ] }, "determinism": "external_nondeterministic", @@ -732,7 +732,7 @@ "version": "1.0.0", "toolchainDigests": [ "713daf121490de0eb9e3a94139318da47b46d4368fce5ef09c5719b8d546f403", - "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175" + "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4" ] }, "determinism": "deterministic", @@ -823,7 +823,7 @@ "version": "1.0.0", "toolchainDigests": [ "3ba256f4ca0bf62aca08f688f7ecf31800dd10e68371054e2f1605eff197ea81", - "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175", + "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4", "687d30408f14b31117771b126b1400483d3b73a25011b83ee6f5fa39c7af3522", "b118ba78ff3ef4a189525c642184cbd320a268d2885681ab153544db554c5965", "e3072b6b01a4f692c2ac75c7c4995747f9a0fd1ce4f32e1ab1599f348661f570", @@ -886,7 +886,7 @@ "toolchainDigests": [ "13968a5e5da8923b00ab8495b5b848e0f41514e7c004c183ae2311d35d5ff2a3", "687d30408f14b31117771b126b1400483d3b73a25011b83ee6f5fa39c7af3522", - "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175" + "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4" ] }, "determinism": "deterministic", @@ -937,7 +937,7 @@ "toolchainDigests": [ "a6da84444e815dd94c15887190c7d6bd48c7bca5975fb5d493eb77ab39f27d80", "687d30408f14b31117771b126b1400483d3b73a25011b83ee6f5fa39c7af3522", - "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175" + "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4" ] }, "determinism": "deterministic", @@ -1229,6 +1229,47 @@ ], "extensionPoint": "Built-in deterministic baseline only. Parser coverage is heuristic and relationship/call-graph precision remains explicit unknown; external cocoindex and specialized semantic graphs stay separate adapters." }, + { + "id": "edit.ast-grep-plan", + "stage": "localization", + "owner": "code-intel-pipeline", + "kind": "internal-adapter", + "required": false, + "entrypoint": "target/debug/code-intel.exe", + "capabilities": [ + "structural_search", + "rewrite_preview", + "bounded_parallel_scan", + "snapshot_bound" + ], + "commands": { + "capabilityExec": "target/debug/code-intel.exe capability exec edit.ast-grep-plan --request --out " + }, + "capabilityDeclaration": { + "schema": "code-intel-capability-declaration.v1", + "id": "edit.ast-grep-plan", + "contractVersion": 1, + "implementation": { + "id": "edit.ast-grep-plan.compat", + "version": "1.0.0", + "toolchainDigests": [ + "58d3687e7e5ac8b3df9ece44bc613f4927fc328c02dddcdaeee72ed3103a04c8" + ] + }, + "determinism": "external_nondeterministic", + "allowedEffects": ["repo_read", "local_write", "process_spawn"], + "dependencies": ["repo.snapshot"] + }, + "runtimeAdapter": "edit.ast-grep-plan.compat", + "toolchainDigestEvidence": { + "algorithm": "sha256", + "inputs": ["crates/code-intel-cli/src/structured_edit.rs"] + }, + "artifactContract": [ + "structured-edit-plan.json" + ], + "extensionPoint": "Preview-only structural search and rewrite planning through ast-grep. Repository mutation is deliberately excluded and requires a separate explicitly authorized capability." + }, { "id": "memory.repowise", "stage": "semantic_memory", diff --git a/orchestration/internalization/git.json b/orchestration/internalization/git.json index 5a5e637..635cb6e 100644 --- a/orchestration/internalization/git.json +++ b/orchestration/internalization/git.json @@ -1,15 +1,15 @@ { "schema": "code-intel-internalization-record.v1", "id": "internalization.git-record", "projectId": "code-intel-pipeline", - "subject": { "name": "Git read-only repository protocol dependency", "kind": "adapted_capability", "source": { "uri": "https://git-scm.com; installed-path=C:/Program Files/Git/cmd/git.exe", "revision": "installed-version:2.54.0.windows.1; local-license-sha256:5b2198d1645f767585e8a88ac0499b04472164c0d2da22e75ecf97ef443ab32e; local-snapshot-source-sha256:754b9fe5ee95e9371808266cce30e73ab379c774d44889e0296d8c2eba7c84a1; local-conformance-sha256:8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415; measurement-sha256:abca93bc4d649078eae804a992021facdb10e4a8da4e8025da8467a68076bec3" }, "license": { "id": "GPL-2.0-only-LOCAL-LICENSE-COPY-BOUND", "obligations": ["retain C:/Program Files/Git/LICENSE.txt and its bound digest with package provenance before redistribution", "restrict current adapter authority to read-only repository inspection; mutation commands require a separately registered and A05-gated capability"] } }, + "subject": { "name": "Git read-only repository protocol dependency", "kind": "adapted_capability", "source": { "uri": "https://git-scm.com; installed-path=C:/Program Files/Git/cmd/git.exe", "revision": "installed-version:2.54.0.windows.1; local-license-sha256:5b2198d1645f767585e8a88ac0499b04472164c0d2da22e75ecf97ef443ab32e; local-snapshot-source-sha256:63df3be5b673a363281302fa3fa2bcef8efbf97f70b750f0c53bb2f3201ca01f; local-conformance-sha256:8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415; measurement-sha256:abca93bc4d649078eae804a992021facdb10e4a8da4e8025da8467a68076bec3" }, "license": { "id": "GPL-2.0-only-LOCAL-LICENSE-COPY-BOUND", "obligations": ["retain C:/Program Files/Git/LICENSE.txt and its bound digest with package provenance before redistribution", "restrict current adapter authority to read-only repository inspection; mutation commands require a separately registered and A05-gated capability"] } }, "adoption": { "rung": "invoke", "ownedBoundary": ["Pipeline owns portable Snapshot Identity, explicit head_only and explicit_overlay semantics, scope normalization, Git command argument construction, output parsing, and failure taxonomy", "Git owns repository storage and protocol behavior; commit, checkout, reset, clean, add, index mutation, fetch, push, config writes, hooks, credentials, and network operations are out of scope"], "necessityEvidence": { "evidenceIds": ["local:a02:repository.snapshot-identity", "local:b07:snapshot-registered", "local:r10:installed-git-2.54.0.windows.1"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "compatibilityEvidence": { "evidenceIds": ["local:r10:head-overlay-fixtures", "local:r10:unborn-shallow-gitlink-lfs-fixtures", "local:r10:alternate-vcs-adapter-actually-executed", "local:r10:alternate-vcs-mismatch-fail-closed", "local:r10:rollback-to-git-or-unversioned"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "conformanceEvidence": { "evidenceIds": ["local:r10:operation-trace", "local:r10:read-only-command-audit", "local:r10:alternate-mismatch-exit-65", "gap:git:representative-platform-matrix"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "operationTrace": [ - { "integrationId": "repository.snapshot-identity", "operation": "snapshotIdentity", "command": "target/debug/code-intel.exe snapshot identity --repo --working-tree-policy --scope ", "implementationIdentity": { "providerId": "git-system-dependency", "implementationId": "git-2.54.0.windows.1+snapshot-v1", "activation": "required read-only repository inspection" }, "source": { "path": "crates/code-intel-cli/src/snapshot.rs", "sha256": "754b9fe5ee95e9371808266cce30e73ab379c774d44889e0296d8c2eba7c84a1" }, "conformance": { "path": "crates/code-intel-cli/tests/snapshot_identity.rs", "sha256": "8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "testName": "alternate_vcs_contract_fixture_is_fail_closed_and_rolls_back_to_unversioned" } }, - { "integrationId": "repository.snapshot-identity", "operation": "capabilityExec", "command": "target/debug/code-intel.exe capability exec repo.snapshot --request --out ", "implementationIdentity": { "providerId": "git-system-dependency", "implementationId": "git-2.54.0.windows.1+repository.snapshot.compat", "activation": "required A01 read-only envelope" }, "source": { "path": "crates/code-intel-cli/src/snapshot.rs", "sha256": "754b9fe5ee95e9371808266cce30e73ab379c774d44889e0296d8c2eba7c84a1" }, "conformance": { "path": "crates/code-intel-cli/tests/snapshot_identity.rs", "sha256": "8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "testName": "head_snapshot_binds_gitlink_lfs_pointer_and_case_sensitive_scope" } } + { "integrationId": "repository.snapshot-identity", "operation": "snapshotIdentity", "command": "target/debug/code-intel.exe snapshot identity --repo --working-tree-policy --scope ", "implementationIdentity": { "providerId": "git-system-dependency", "implementationId": "git-2.54.0.windows.1+snapshot-v1", "activation": "required read-only repository inspection" }, "source": { "path": "crates/code-intel-cli/src/snapshot.rs", "sha256": "63df3be5b673a363281302fa3fa2bcef8efbf97f70b750f0c53bb2f3201ca01f" }, "conformance": { "path": "crates/code-intel-cli/tests/snapshot_identity.rs", "sha256": "8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "testName": "alternate_vcs_contract_fixture_is_fail_closed_and_rolls_back_to_unversioned" } }, + { "integrationId": "repository.snapshot-identity", "operation": "capabilityExec", "command": "target/debug/code-intel.exe capability exec repo.snapshot --request --out ", "implementationIdentity": { "providerId": "git-system-dependency", "implementationId": "git-2.54.0.windows.1+repository.snapshot.compat", "activation": "required A01 read-only envelope" }, "source": { "path": "crates/code-intel-cli/src/snapshot.rs", "sha256": "63df3be5b673a363281302fa3fa2bcef8efbf97f70b750f0c53bb2f3201ca01f" }, "conformance": { "path": "crates/code-intel-cli/tests/snapshot_identity.rs", "sha256": "8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "testName": "head_snapshot_binds_gitlink_lfs_pointer_and_case_sensitive_scope" } } ], "economics": { "benefit": { "metric": "snapshot identity conformance tests", "value": 12, "unit": "tests" }, "cost": { "metric": "twenty-sample git rev-parse p95 latency", "value": 422.294, "unit": "milliseconds" }, "benefitEvidence": { "evidenceIds": ["local:r10:head-overlay-fixtures-12", "local:r10:read-only-command-audit", "local:r10:mutation-commands-0", "local:r10:alternate-vcs-contract-fixture"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "costEvidence": { "evidenceIds": ["local:r10:rev-parse-samples-20", "local:r10:rev-parse-p50-ms-167.65", "local:r10:rev-parse-p95-ms-422.294", "local:r10:snapshot-suite-ms-27509.7763", "gap:git:representative-platform-matrix"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "assurance": { "maintenanceEvidence": { "evidenceIds": ["local:r10:pinned-installed-version", "gap:git:package-update-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "securityEvidence": { "evidenceIds": ["local:r10:read-only-command-audit", "local:r10:no-network-no-credential-no-mutation", "gap:git:package-supply-chain-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "update": { "policy": "Do not upgrade Git implicitly; retain installer/package and license provenance, rerun all snapshot fixtures and command audit, and measure representative latency before approval", "nextCheckAt": 1791676800, "evidence": { "evidenceIds": ["gap:git:update-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, - "ownedModifications": [{ "path": "crates/code-intel-cli/src/snapshot.rs", "description": "Pipeline-owned portable snapshot semantics and read-only Git invocation adapter", "evidenceIds": ["local:r10:snapshot-source-sha256:754b9fe5ee95e9371808266cce30e73ab379c774d44889e0296d8c2eba7c84a1", "local:r10:conformance-sha256:8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "local:r10:local-license-sha256:5b2198d1645f767585e8a88ac0499b04472164c0d2da22e75ecf97ef443ab32e"] }], + "ownedModifications": [{ "path": "crates/code-intel-cli/src/snapshot.rs", "description": "Pipeline-owned portable snapshot semantics and read-only Git invocation adapter", "evidenceIds": ["local:r10:snapshot-source-sha256:63df3be5b673a363281302fa3fa2bcef8efbf97f70b750f0c53bb2f3201ca01f", "local:r10:conformance-sha256:8ea8073d7407d3c8434b4c14adc1f7891b131272971f7541de6151624267c415", "local:r10:local-license-sha256:5b2198d1645f767585e8a88ac0499b04472164c0d2da22e75ecf97ef443ab32e"] }], "rollback": { "strategy": "rollback-to-git-or-unversioned explicit_overlay after any alternate provider mismatch; exit 65 and publish no artifacts; never add mutation authority", "evidence": { "evidenceIds": ["local:r10:unversioned-fail-closed-fixtures", "local:r10:alternate-vcs-mismatch-fail-closed", "local:r10:rollback-to-git-or-unversioned"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "exit": { "strategy": "replace Git only behind the provider-neutral alternate-vcs-contract-fixture while preserving portable identity and explicit overlays", "replacementCriteria": ["alternate VCS passes head, dirty, unborn, shallow, gitlink, LFS, symlink, scope, case, and missing-provider fixtures", "mismatch exits 65 without artifacts and rollback-to-git-or-unversioned remains tested", "read-only and mutation authority remain structurally separate", "latency, maintenance, security, package provenance, rollback, and license evidence are complete"], "evidence": { "evidenceIds": ["local:r10:alternate-vcs-adapter-actually-executed", "local:r10:alternate-vcs-mismatch-fail-closed", "local:r10:rollback-to-git-or-unversioned"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "retirement": { "status": "candidate", "triggers": ["an alternate VCS implementation passes the pinned provider-neutral port and full fixture matrix", "installed Git identity, package provenance, or security posture becomes unacceptable", "repository snapshot no longer uses Git"] , "evidence": { "evidenceIds": ["local:r10:operation-trace", "local:r10:alternate-vcs-contract-fixture"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, diff --git a/orchestration/internalization/rg.json b/orchestration/internalization/rg.json index d3ab528..a10ca9e 100644 --- a/orchestration/internalization/rg.json +++ b/orchestration/internalization/rg.json @@ -2,16 +2,16 @@ "schema": "code-intel-internalization-record.v1", "id": "internalization.rg-record", "projectId": "code-intel-pipeline", - "subject": { "name": "ripgrep inventory executable", "kind": "adapted_capability", "source": { "uri": "https://github.com/BurntSushi/ripgrep; installed-evidence=rg 15.1.0 (rev af60c2de9d)", "revision": "installed-version:15.1.0-af60c2de9d; local-native-source-sha256:f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175; local-conformance-sha256:fa75116be7d2268655df0e51b60832b223b2c6c3170b2f31c3234f03f118fb5c" }, "license": { "id": "MIT-OR-Unlicense-UPSTREAM-CLAIM-LOCAL-COPY-MISSING", "obligations": ["do not redistribute or upgrade from this record until the selected license text and package provenance are retained locally", "preserve exact inventory scope, exclusions, order normalization, snapshot lease, and failure semantics"] } }, + "subject": { "name": "ripgrep inventory executable", "kind": "adapted_capability", "source": { "uri": "https://github.com/BurntSushi/ripgrep; installed-evidence=rg 15.1.0 (rev af60c2de9d)", "revision": "installed-version:15.1.0-af60c2de9d; local-native-source-sha256:43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4; local-conformance-sha256:9d73aec75139920210325b0d90c386ba2396b8b46f9b97cb335c163eb5d27b56" }, "license": { "id": "MIT-OR-Unlicense-UPSTREAM-CLAIM-LOCAL-COPY-MISSING", "obligations": ["do not redistribute or upgrade from this record until the selected license text and package provenance are retained locally", "preserve exact inventory scope, exclusions, order normalization, snapshot lease, and failure semantics"] } }, "adoption": { "rung": "invoke", "ownedBoundary": ["Pipeline owns inventory.rg request normalization, snapshot-controlled mirror, exclusions, artifact contract, and failure mapping", "ripgrep owns executable search and traversal behavior; this record does not authorize upgrade, vendoring, or reimplementation"], "necessityEvidence": { "evidenceIds": ["local:registry:inventory.rg:required", "local:r09:installed-rg-15.1.0-af60c2de9d", "gap:rg:package-provenance"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "compatibilityEvidence": { "evidenceIds": ["local:a00:inventory-parity", "local:r09:cross-platform-contract", "gap:rg:replacement-command-drill"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "conformanceEvidence": { "evidenceIds": ["local:r09:scope-exclusion-conformance", "local:r09:operation-trace", "gap:rg:representative-platform-matrix"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "operationTrace": [ - { "integrationId": "inventory.rg", "operation": "run", "command": "run-code-intel.ps1 -RepoPath -Mode ", "implementationIdentity": { "providerId": "ripgrep", "implementationId": "rg-15.1.0-af60c2de9d-via-compat-facade", "activation": "required production facade" }, "source": { "path": "run-code-intel.ps1", "sha256": "70632890c48230415da5d5721c3dc0a50e636c9bddc6827c5ad35fea89a9b8f0" }, "conformance": { "path": "crates/code-intel-cli/tests/capability_exec.rs", "sha256": "fa75116be7d2268655df0e51b60832b223b2c6c3170b2f31c3234f03f118fb5c", "testName": "normalized_inventory_matches_real_legacy_runner_with_custom_exclude" } }, - { "integrationId": "inventory.rg", "operation": "capabilityExec", "command": "target/debug/code-intel.exe capability exec inventory.rg --request --out ", "implementationIdentity": { "providerId": "ripgrep", "implementationId": "inventory.rg.compat+rg-15.1.0-af60c2de9d", "activation": "required production capability envelope" }, "source": { "path": "crates/code-intel-cli/src/capability_inventory.rs", "sha256": "f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175" }, "conformance": { "path": "crates/code-intel-cli/tests/capability_exec.rs", "sha256": "fa75116be7d2268655df0e51b60832b223b2c6c3170b2f31c3234f03f118fb5c", "testName": "inventory_rg_exec_emits_one_result_and_stable_real_rg_artifact" } } + { "integrationId": "inventory.rg", "operation": "run", "command": "run-code-intel.ps1 -RepoPath -Mode ", "implementationIdentity": { "providerId": "ripgrep", "implementationId": "rg-15.1.0-af60c2de9d-via-compat-facade", "activation": "required production facade" }, "source": { "path": "run-code-intel.ps1", "sha256": "70632890c48230415da5d5721c3dc0a50e636c9bddc6827c5ad35fea89a9b8f0" }, "conformance": { "path": "crates/code-intel-cli/tests/capability_exec.rs", "sha256": "9d73aec75139920210325b0d90c386ba2396b8b46f9b97cb335c163eb5d27b56", "testName": "normalized_inventory_matches_real_legacy_runner_with_custom_exclude" } }, + { "integrationId": "inventory.rg", "operation": "capabilityExec", "command": "target/debug/code-intel.exe capability exec inventory.rg --request --out ", "implementationIdentity": { "providerId": "ripgrep", "implementationId": "inventory.rg.compat+rg-15.1.0-af60c2de9d", "activation": "required production capability envelope" }, "source": { "path": "crates/code-intel-cli/src/capability_inventory.rs", "sha256": "43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4" }, "conformance": { "path": "crates/code-intel-cli/tests/capability_exec.rs", "sha256": "9d73aec75139920210325b0d90c386ba2396b8b46f9b97cb335c163eb5d27b56", "testName": "inventory_rg_exec_emits_one_result_and_stable_real_rg_artifact" } } ], "economics": { "benefit": { "metric": "registered production inventory operations with recomputable invocation trace", "value": 2, "unit": "operations" }, "cost": { "metric": "unclosed executable lifecycle gaps", "value": 4, "unit": "gaps" }, "benefitEvidence": { "evidenceIds": ["local:r09:operation-trace", "local:r09:scope-exclusion-conformance"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "costEvidence": { "evidenceIds": ["gap:rg:package-provenance", "gap:rg:local-license-copy", "gap:rg:replacement-command-drill", "gap:rg:latency-p50-p95-measurement"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "assurance": { "maintenanceEvidence": { "evidenceIds": ["local:r09:pinned-installed-version", "gap:rg:upstream-maintenance-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 }, "securityEvidence": { "evidenceIds": ["local:r09:no-network-read-only-invocation", "gap:rg:package-supply-chain-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "update": { "policy": "Do not upgrade rg implicitly; before 2026-10-11 retain package provenance/license, rerun cross-platform conformance and benchmark, and exercise the replacement adapter", "nextCheckAt": 1791676800, "evidence": { "evidenceIds": ["gap:rg:update-review"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, - "ownedModifications": [{ "path": "crates/code-intel-cli/src/capability_inventory.rs", "description": "Pipeline-owned inventory adapter and snapshot-controlled invocation boundary", "evidenceIds": ["local:r09:native-source-sha256:f8af3e1acf11023f51e4a21c1d7e31575c55f332cc310713a1ec1be7ba6ef175", "local:r09:conformance-sha256:fa75116be7d2268655df0e51b60832b223b2c6c3170b2f31c3234f03f118fb5c"] }], + "ownedModifications": [{ "path": "crates/code-intel-cli/src/capability_inventory.rs", "description": "Pipeline-owned inventory adapter and snapshot-controlled invocation boundary", "evidenceIds": ["local:r09:native-source-sha256:43e8a67a9d24ea1b6b642c376c81d41a3fcdf92df5daa6c266894fd6af6e97e4", "local:r09:conformance-sha256:9d73aec75139920210325b0d90c386ba2396b8b46f9b97cb335c163eb5d27b56"] }], "rollback": { "strategy": "route inventory.rg to the preserved compatibility facade without changing the artifact contract or upgrading rg", "evidence": { "evidenceIds": ["local:a00:inventory-parity", "gap:rg:replacement-command-drill"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "exit": { "strategy": "replace the executable only behind inventory.rg after exact artifact and failure parity", "replacementCriteria": ["alternate command passes scope, exclusion, symlink, ignore, empty-repository, and snapshot fixtures", "representative latency p50/p95 and cost do not regress beyond the approved budget", "new executable has pinned provenance, license, security, update, rollback, and retirement evidence"], "evidence": { "evidenceIds": ["gap:rg:replacement-command-drill", "gap:rg:latency-p50-p95-measurement"], "checkedAt": 1783900800, "expiresAt": 1791676800 } }, "retirement": { "status": "candidate", "triggers": ["replacement passes the complete inventory contract", "installed executable identity or package provenance becomes unverifiable", "security or maintenance policy rejects the pinned package"], "evidence": { "evidenceIds": ["local:r09:operation-trace", "gap:rg:replacement-command-drill"], "checkedAt": 1783900800, "expiresAt": 1791676800 } },