From 0b7a5092870777c0b4129387c775d478177bf020 Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:08:50 -0400 Subject: [PATCH 1/7] refactor: derive dependency checks from resolved Cargo graph --- crates/xtask/src/dependency_policy.rs | 310 ++++++++++++++++++++++---- 1 file changed, 264 insertions(+), 46 deletions(-) diff --git a/crates/xtask/src/dependency_policy.rs b/crates/xtask/src/dependency_policy.rs index 71bd8b3..9f6b0e3 100644 --- a/crates/xtask/src/dependency_policy.rs +++ b/crates/xtask/src/dependency_policy.rs @@ -10,7 +10,82 @@ pub const TARGETS: [&str; 5] = [ "x86_64-pc-windows-msvc", ]; -const GRAMMAR_REV: &str = "f777cdbe11281ccc68ffa30bd7ea34cdf4ddbec6"; +const GRAMMAR_REPOSITORY: &str = "https://github.com/bytecodealliance/tree-sitter-wit"; + +fn grammar_revision(syntax_manifest: &str, extension_manifest: &str) -> Result { + let syntax: toml::Value = toml::from_str(syntax_manifest) + .map_err(|error| format!("parse wit-syntax Cargo.toml: {error}"))?; + let extension: toml::Value = toml::from_str(extension_manifest) + .map_err(|error| format!("parse extension.toml: {error}"))?; + + let grammar = extension + .get("grammars") + .and_then(|grammars| grammars.get("wit")) + .ok_or_else(|| "extension.toml has no grammars.wit table".to_owned())?; + let extension_repo = grammar + .get("repository") + .and_then(toml::Value::as_str) + .ok_or_else(|| "extension.toml has no grammars.wit.repository".to_owned())?; + let extension_rev = grammar + .get("rev") + .and_then(toml::Value::as_str) + .ok_or_else(|| "extension.toml has no grammars.wit.rev".to_owned())?; + + let syntax_grammar = syntax + .get("dependencies") + .and_then(|dependencies| dependencies.get("tree-sitter-wit")) + .ok_or_else(|| "wit-syntax Cargo.toml has no tree-sitter-wit dependency".to_owned())?; + let syntax_repo = syntax_grammar + .get("git") + .and_then(toml::Value::as_str) + .ok_or_else(|| "wit-syntax Cargo.toml has no tree-sitter-wit.git".to_owned())?; + let syntax_rev = syntax_grammar + .get("rev") + .and_then(toml::Value::as_str) + .ok_or_else(|| "wit-syntax Cargo.toml has no tree-sitter-wit.rev".to_owned())?; + + if extension_repo != GRAMMAR_REPOSITORY || syntax_repo != GRAMMAR_REPOSITORY { + return Err(format!( + "unreviewed tree-sitter-wit repository: extension={extension_repo:?}, syntax={syntax_repo:?}" + )); + } + if extension_rev != syntax_rev { + return Err(format!( + "grammar pin mismatch: extension={extension_rev}, syntax={syntax_rev}" + )); + } + if syntax_rev.len() != 40 + || !syntax_rev + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + { + return Err(format!("invalid tree-sitter-wit commit revision: {syntax_rev:?}")); + } + + Ok(syntax_rev.to_owned()) +} + +fn pinned_grammar_revision() -> Result { + let root = util::repo_root(); + grammar_revision( + &util::read_nonempty(&root.join("crates/wit-syntax/Cargo.toml"))?, + &util::read_nonempty(&root.join("extension.toml"))?, + ) +} + +fn single_version(packages: &[Value], name: &str, scope: &str) -> Result { + let versions = packages + .iter() + .filter(|package| package.get("name").and_then(Value::as_str) == Some(name)) + .map(|package| package_string(package, "version")) + .collect::, _>>()?; + if versions.len() != 1 { + return Err(format!( + "{scope}: expected exactly one {name} version in resolved dependency graph, found {versions:?}" + )); + } + Ok(versions.into_iter().next().expect("one version").to_owned()) +} pub fn ensure_target(target: &str) -> Result<(), String> { if TARGETS.contains(&target) { @@ -91,7 +166,7 @@ pub fn license_choice(package: &Value) -> Result<&'static str, String> { } } -pub fn native_packages(data: &Value) -> Result, String> { +fn closure_packages(data: &Value, root_name: &str) -> Result, String> { let package_list = packages(data)?; let packages_by_id: HashMap = package_list .iter() @@ -126,14 +201,14 @@ pub fn native_packages(data: &Value) -> Result, String> { .iter() .filter_map(|package| { let id = package.get("id")?.as_str()?; - (package.get("name")?.as_str()? == "wit-language-server" + (package.get("name")?.as_str()? == root_name && workspace_members.contains(id)) .then(|| id.to_owned()) }) .collect(); if roots.len() != 1 { return Err(format!( - "expected one workspace wit-language-server package, found {}", + "expected one workspace {root_name} package, found {}", roots.len() )); } @@ -197,56 +272,47 @@ pub fn native_packages(data: &Value) -> Result, String> { Ok(native) } +pub fn native_packages(data: &Value) -> Result, String> { + closure_packages(data, "wit-language-server") +} + pub fn check(data: &Value) -> Result, String> { let package_list = packages(data)?; + let grammar_revision = pinned_grammar_revision()?; + let expected_grammar_source = + format!("git+{GRAMMAR_REPOSITORY}?rev={grammar_revision}#{grammar_revision}"); + let grammar_count = package_list + .iter() + .filter(|package| package.get("name").and_then(Value::as_str) == Some("tree-sitter-wit")) + .count(); + if grammar_count != 1 { + return Err(format!( + "expected one tree-sitter-wit package in Cargo metadata, found {grammar_count}" + )); + } for package in package_list { license_choice(package)?; - if let Some(source) = package.get("source").and_then(Value::as_str) + let name = package_string(package, "name")?; + let source = package.get("source").and_then(Value::as_str); + if name == "tree-sitter-wit" { + if source != Some(expected_grammar_source.as_str()) { + return Err(format!( + "unreviewed tree-sitter-wit source: {source:?}; expected {expected_grammar_source:?}" + )); + } + } else if let Some(source) = source && !source.starts_with("registry+https://github.com/rust-lang/crates.io-index") { - let name = package_string(package, "name")?; - let expected_git = "git+https://github.com/bytecodealliance/tree-sitter-wit?"; - let expected_revision = format!("#{GRAMMAR_REV}"); - if name != "tree-sitter-wit" - || !source.starts_with(expected_git) - || !source.ends_with(&expected_revision) - { - return Err(format!("unreviewed dependency source: {source}")); - } + return Err(format!("unreviewed dependency source: {source}")); } } let native = native_packages(data)?; - let native_parsers: BTreeSet<&str> = native - .iter() - .filter(|package| package.get("name").and_then(Value::as_str) == Some("wit-parser")) - .filter_map(|package| package.get("version").and_then(Value::as_str)) - .collect(); - if native_parsers != BTreeSet::from(["0.260.0"]) { - return Err(format!("native semantic parser drift: {native_parsers:?}")); - } - - let tree_sitters: BTreeSet<&str> = package_list - .iter() - .filter(|package| package.get("name").and_then(Value::as_str) == Some("tree-sitter")) - .filter_map(|package| package.get("version").and_then(Value::as_str)) - .collect(); - if tree_sitters != BTreeSet::from(["0.26.11"]) { - return Err(format!("Tree-sitter runtime drift: {tree_sitters:?}")); - } - - let parser_versions: BTreeSet<&str> = package_list - .iter() - .filter(|package| package.get("name").and_then(Value::as_str) == Some("wit-parser")) - .filter_map(|package| package.get("version").and_then(Value::as_str)) - .collect(); - if parser_versions != BTreeSet::from(["0.227.1", "0.260.0"]) { - return Err(format!( - "workspace parser versions changed; review adapter bindings: {parser_versions:?}" - )); - } - + single_version(&native, "wit-parser", "native language server")?; + let adapter = closure_packages(data, "zed-wit")?; + single_version(&adapter, "wit-parser", "Zed extension adapter")?; + single_version(package_list, "tree-sitter", "workspace")?; Ok(native) } @@ -254,6 +320,7 @@ pub fn run(target: Option<&str>) -> Result<(), String> { let data = metadata(target)?; let native = check(&data)?; let package_list = packages(&data)?; + let adapter = closure_packages(&data, "zed-wit")?; let licenses: BTreeSet<&str> = package_list .iter() .map(license_choice) @@ -265,9 +332,9 @@ pub fn run(target: Option<&str>) -> Result<(), String> { "target": target.unwrap_or("all-platforms"), "workspace_packages": package_list.len(), "native_and_build_packages": native.len(), - "native_wit_parser": "0.260.0", - "adapter_binding_wit_parser": "0.227.1", - "tree_sitter": "0.26.11", + "native_wit_parser": single_version(&native, "wit-parser", "native language server")?, + "adapter_binding_wit_parser": single_version(&adapter, "wit-parser", "Zed extension adapter")?, + "tree_sitter": single_version(package_list, "tree-sitter", "workspace")?, "licenses": licenses, "result": "passed", })) @@ -311,4 +378,155 @@ mod tests { assert_eq!(license_choice(&package).unwrap(), "MIT"); } } + fn fake_package(id: &str, name: &str, version: &str) -> Value { + json!({ + "id": id, + "name": name, + "version": version, + "license": "MIT", + "source": null, + }) + } + + fn graph_fixture(native_parser: &str, runtime: &str) -> Value { + let revision = pinned_grammar_revision().unwrap(); + let mut grammar = fake_package("grammar", "tree-sitter-wit", "1.4.0"); + grammar["source"] = json!(format!( + "git+{GRAMMAR_REPOSITORY}?rev={revision}#{revision}" + )); + json!({ + "packages": [ + fake_package("native", "wit-language-server", "0.1.2"), + fake_package("adapter", "zed-wit", "0.5.4"), + fake_package("syntax", "wit-syntax", "0.1.0"), + fake_package("native-parser", "wit-parser", native_parser), + fake_package("adapter-parser", "wit-parser", "0.227.1"), + fake_package("runtime", "tree-sitter", runtime), + grammar, + fake_package("dev", "unused-dev-dependency", "1.0.0"), + ], + "workspace_members": ["native", "adapter", "syntax"], + "resolve": { + "nodes": [ + {"id": "native", "deps": [ + {"pkg": "native-parser", "dep_kinds": [{"kind": null}]}, + {"pkg": "runtime", "dep_kinds": [{"kind": "build"}]}, + {"pkg": "syntax", "dep_kinds": [{"kind": null}]}, + {"pkg": "dev", "dep_kinds": [{"kind": "dev"}]}, + ]}, + {"id": "adapter", "deps": [ + {"pkg": "adapter-parser", "dep_kinds": [{"kind": null}]}, + ]}, + {"id": "syntax", "deps": [ + {"pkg": "grammar", "dep_kinds": [{"kind": null}]}, + ]}, + {"id": "native-parser", "deps": []}, + {"id": "adapter-parser", "deps": []}, + {"id": "runtime", "deps": []}, + {"id": "grammar", "deps": []}, + {"id": "dev", "deps": []}, + ], + }, + }) + } + + #[test] + fn normal_parser_and_runtime_upgrades_do_not_require_xtask_changes() { + let data = graph_fixture("0.261.0", "0.26.13"); + let native = check(&data).unwrap(); + assert_eq!(single_version(&native, "wit-parser", "native").unwrap(), "0.261.0"); + let adapter = closure_packages(&data, "zed-wit").unwrap(); + assert_eq!( + single_version(&adapter, "wit-parser", "adapter").unwrap(), + "0.227.1" + ); + } + + #[test] + fn native_graph_excludes_dev_only_edges_but_includes_build_dependencies() { + let data = graph_fixture("0.261.0", "0.26.13"); + let native = native_packages(&data).unwrap(); + assert!(native.iter().any(|p| p["name"] == "tree-sitter")); + assert!(!native.iter().any(|p| p["name"] == "unused-dev-dependency")); + } + + #[test] + fn duplicate_native_semantic_parsers_are_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"] + .as_array_mut() + .unwrap() + .push(fake_package("other-parser", "wit-parser", "0.262.0")); + data["resolve"]["nodes"] + .as_array_mut() + .unwrap() + .push(json!({"id": "other-parser", "deps": []})); + data["resolve"]["nodes"][0]["deps"] + .as_array_mut() + .unwrap() + .push(json!({"pkg": "other-parser", "dep_kinds": [{"kind": null}]})); + assert!(check(&data).unwrap_err().contains("native language server")); + } + + #[test] + fn duplicate_tree_sitter_runtime_versions_are_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"] + .as_array_mut() + .unwrap() + .push(fake_package("other-runtime", "tree-sitter", "0.27.0")); + assert!(check(&data).unwrap_err().contains("workspace")); + } + + #[test] + fn unreviewed_registry_or_git_sources_are_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][3]["source"] = json!("git+https://example.com/unreviewed#123"); + assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + } + + #[test] + fn resolved_grammar_git_commit_must_match_manifest_pin() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][6]["source"] = json!( + "git+https://github.com/bytecodealliance/tree-sitter-wit?rev=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ); + assert!(check(&data).unwrap_err().contains("unreviewed tree-sitter-wit source")); + } + + #[test] + fn mismatched_or_invalid_grammar_manifest_pins_are_rejected() { + let root = util::repo_root(); + let syntax = util::read_nonempty(&root.join("crates/wit-syntax/Cargo.toml")).unwrap(); + let extension = util::read_nonempty(&root.join("extension.toml")).unwrap(); + let revision = grammar_revision(&syntax, &extension).unwrap(); + assert!(grammar_revision(&syntax, &extension.replace( + &revision, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + )) + .unwrap_err() + .contains("grammar pin mismatch")); + assert!(grammar_revision( + &syntax.replace(&revision, "not-a-git-revision"), + &extension.replace(&revision, "not-a-git-revision") + ) + .unwrap_err() + .contains("invalid tree-sitter-wit commit revision")); + } + + #[test] + fn unreviewed_licenses_are_rejected_even_for_valid_dependency_versions() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][3]["license"] = json!("GPL-3.0-only"); + assert!(check(&data).unwrap_err().contains("unreviewed license")); + } + + #[test] + fn release_target_allowlist_is_preserved() { + for target in TARGETS { + assert!(ensure_target(target).is_ok()); + } + assert!(ensure_target("aarch64-pc-windows-msvc").is_err()); + } + } From 9bcf857a6731afc3402e22d6e9429d63fbf1eb7c Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:09:48 -0400 Subject: [PATCH 2/7] docs: explain metadata-derived dependency invariants --- docs/xtask.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/xtask.md b/docs/xtask.md index 661306e..2bd47c5 100644 --- a/docs/xtask.md +++ b/docs/xtask.md @@ -28,7 +28,7 @@ Native binary lookup and local release packaging read the effective output direc | --- | --- | | `check` | Run workspace rustfmt, all-target/all-feature Clippy, and locked `cargo check`. | | `test` | Native tests; supports `--package `, `--filter `, `--runner `. Auto prefers installed nextest unless a substring filter is given. | -| `verify` | Repository policy + dependency checks, `check` and `test-all`. | +| `verify` | Repository policy + metadata-derived dependency checks, `check` and `test-all`. | | `test-all` | Native workspace tests, doctests and a Wasm extension build; optional `--with-zed true` for real Zed smoke. GUI/hosted tests remain separate. | | `test-publish` | Offline LSP/extension release lifecycle tests plus the protected-CD asset SHA-256 verifier exercised against mocked GitHub API responses (23 LSP assets and extension zero-assets). Never publishes anything. | | `test-lsp` | Test syntax, analysis and language-server packages. | @@ -39,6 +39,14 @@ Native binary lookup and local release packaging read the effective output direc `test --filter` uses Cargo substring syntax. Do not pair it with `--runner nextest`, which requires different filter expressions. A default test pass does **not** qualify hosted distribution or cross-platform Zed runtime behavior. +## Dependency policy + +`cargo xtask check-dependencies` reads the locked, resolved Cargo graph. The manifests and `Cargo.lock` are the source of truth for versions: xtask does not maintain a second list of expected `wit-parser` or `tree-sitter` releases. + +The checker requires one resolved semantic-parser version in each of the native language-server and Zed adapter normal/build dependency closures, and one Tree-sitter runtime version across the workspace. It does **not** require the adapter's transitive WIT parser to have the same version as the native semantic parser. The JSON report displays actual resolved versions, not pinned strings from xtask source. A version change still requires API/fixture/compatibility review and passing tests. + +The checker also rejects unreviewed dependency sources and license expressions. The permitted `tree-sitter-wit` Git commit is derived from the two matching, 40-character revision pins in `crates/wit-syntax/Cargo.toml` and `extension.toml`; both the repository origin and the resolved Git source must match. The approved release-target matrix, target-filtered native normal/build closure, and `collect-licenses` redistribution notices remain independently enforced. Valid dependency updates must not bypass those checks. + ## Local release preparation, not publication | Command | Behavior | From 9dffe9b3f2855a926771567842919681cb7fb20c Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:09:51 -0400 Subject: [PATCH 3/7] docs: distinguish historical compatibility baseline from enforced versions --- docs/upstream-compatibility.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/upstream-compatibility.md b/docs/upstream-compatibility.md index 93bd6f3..0395c2a 100644 --- a/docs/upstream-compatibility.md +++ b/docs/upstream-compatibility.md @@ -18,6 +18,8 @@ the other table rows. | Registry extension | Registry `wit` 0.4.0 points to the existing repository | Registry tree points at [`7a0c864b748c0a4931c68beaad10236eda74983f`](https://github.com/valentinegb/zed-wit/tree/7a0c864b748c0a4931c68beaad10236eda74983f) | Replacement is a separate policy gate. | | Existing extension repository | No dependency pin; last observed commit [`b4f20086b45da2edaed9e56965d9583410b468c4`](https://github.com/valentinegb/zed-wit/tree/b4f20086b45da2edaed9e56965d9583410b468c4) | Current main remains at the same October 2024 commit | Age alone grants no transfer rights. | +The version numbers in the table above record the compatibility baseline audited at that time; they are **not** additional version gates maintained by xtask. A dependency upgrade changes its Cargo manifest and lockfile, followed by focused compatibility/fixture review. `cargo xtask check-dependencies` checks resolved graph invariants and source/license policy without duplicating parser or runtime version pins. The grammar revision remains explicitly pinned in both the Zed and Cargo manifests and must agree with the resolved Git source. + ## Syntax and capability boundaries Tree-sitter supplies highlighting, brackets, outline and editing structure. From d270ae08df8b3c25b0022e62c616766c57f6c671 Mon Sep 17 00:00:00 2001 From: Max T <25856189+MaxTCodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:11:36 -0400 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9C=A8=20Format=20dependency=20policy=20?= =?UTF-8?q?code=20and=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/xtask/src/dependency_policy.rs | 73 +++++++++++++++++---------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/crates/xtask/src/dependency_policy.rs b/crates/xtask/src/dependency_policy.rs index 9f6b0e3..64cea9c 100644 --- a/crates/xtask/src/dependency_policy.rs +++ b/crates/xtask/src/dependency_policy.rs @@ -59,7 +59,9 @@ fn grammar_revision(syntax_manifest: &str, extension_manifest: &str) -> Result Result, String> .iter() .filter_map(|package| { let id = package.get("id")?.as_str()?; - (package.get("name")?.as_str()? == root_name - && workspace_members.contains(id)) - .then(|| id.to_owned()) + (package.get("name")?.as_str()? == root_name && workspace_members.contains(id)) + .then(|| id.to_owned()) }) .collect(); if roots.len() != 1 { @@ -434,7 +435,10 @@ mod tests { fn normal_parser_and_runtime_upgrades_do_not_require_xtask_changes() { let data = graph_fixture("0.261.0", "0.26.13"); let native = check(&data).unwrap(); - assert_eq!(single_version(&native, "wit-parser", "native").unwrap(), "0.261.0"); + assert_eq!( + single_version(&native, "wit-parser", "native").unwrap(), + "0.261.0" + ); let adapter = closure_packages(&data, "zed-wit").unwrap(); assert_eq!( single_version(&adapter, "wit-parser", "adapter").unwrap(), @@ -453,10 +457,11 @@ mod tests { #[test] fn duplicate_native_semantic_parsers_are_rejected() { let mut data = graph_fixture("0.261.0", "0.26.13"); - data["packages"] - .as_array_mut() - .unwrap() - .push(fake_package("other-parser", "wit-parser", "0.262.0")); + data["packages"].as_array_mut().unwrap().push(fake_package( + "other-parser", + "wit-parser", + "0.262.0", + )); data["resolve"]["nodes"] .as_array_mut() .unwrap() @@ -471,10 +476,11 @@ mod tests { #[test] fn duplicate_tree_sitter_runtime_versions_are_rejected() { let mut data = graph_fixture("0.261.0", "0.26.13"); - data["packages"] - .as_array_mut() - .unwrap() - .push(fake_package("other-runtime", "tree-sitter", "0.27.0")); + data["packages"].as_array_mut().unwrap().push(fake_package( + "other-runtime", + "tree-sitter", + "0.27.0", + )); assert!(check(&data).unwrap_err().contains("workspace")); } @@ -482,7 +488,11 @@ mod tests { fn unreviewed_registry_or_git_sources_are_rejected() { let mut data = graph_fixture("0.261.0", "0.26.13"); data["packages"][3]["source"] = json!("git+https://example.com/unreviewed#123"); - assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + assert!( + check(&data) + .unwrap_err() + .contains("unreviewed dependency source") + ); } #[test] @@ -491,7 +501,11 @@ mod tests { data["packages"][6]["source"] = json!( "git+https://github.com/bytecodealliance/tree-sitter-wit?rev=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ); - assert!(check(&data).unwrap_err().contains("unreviewed tree-sitter-wit source")); + assert!( + check(&data) + .unwrap_err() + .contains("unreviewed tree-sitter-wit source") + ); } #[test] @@ -500,18 +514,22 @@ mod tests { let syntax = util::read_nonempty(&root.join("crates/wit-syntax/Cargo.toml")).unwrap(); let extension = util::read_nonempty(&root.join("extension.toml")).unwrap(); let revision = grammar_revision(&syntax, &extension).unwrap(); - assert!(grammar_revision(&syntax, &extension.replace( - &revision, - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - )) - .unwrap_err() - .contains("grammar pin mismatch")); - assert!(grammar_revision( - &syntax.replace(&revision, "not-a-git-revision"), - &extension.replace(&revision, "not-a-git-revision") - ) - .unwrap_err() - .contains("invalid tree-sitter-wit commit revision")); + assert!( + grammar_revision( + &syntax, + &extension.replace(&revision, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + ) + .unwrap_err() + .contains("grammar pin mismatch") + ); + assert!( + grammar_revision( + &syntax.replace(&revision, "not-a-git-revision"), + &extension.replace(&revision, "not-a-git-revision") + ) + .unwrap_err() + .contains("invalid tree-sitter-wit commit revision") + ); } #[test] @@ -528,5 +546,4 @@ mod tests { } assert!(ensure_target("aarch64-pc-windows-msvc").is_err()); } - } From b958f7a18265fa69a431677af62a159785ddf25e Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:39:20 -0400 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=92=20Enforce=20trusted=20local=20?= =?UTF-8?q?source=20boundary=20and=20resolved=20package=20identities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/xtask/src/dependency_policy.rs | 204 +++++++++++++++++++++++--- 1 file changed, 186 insertions(+), 18 deletions(-) diff --git a/crates/xtask/src/dependency_policy.rs b/crates/xtask/src/dependency_policy.rs index 64cea9c..11b4219 100644 --- a/crates/xtask/src/dependency_policy.rs +++ b/crates/xtask/src/dependency_policy.rs @@ -1,6 +1,10 @@ use crate::util; use serde_json::{Value, json}; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::{ + collections::{BTreeSet, HashMap, HashSet}, + fs, + path::Path, +}; pub const TARGETS: [&str; 5] = [ "aarch64-apple-darwin", @@ -11,6 +15,7 @@ pub const TARGETS: [&str; 5] = [ ]; const GRAMMAR_REPOSITORY: &str = "https://github.com/bytecodealliance/tree-sitter-wit"; +const CRATES_IO_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index"; fn grammar_revision(syntax_manifest: &str, extension_manifest: &str) -> Result { let syntax: toml::Value = toml::from_str(syntax_manifest) @@ -76,17 +81,54 @@ fn pinned_grammar_revision() -> Result { } fn single_version(packages: &[Value], name: &str, scope: &str) -> Result { - let versions = packages + let matches = packages .iter() .filter(|package| package.get("name").and_then(Value::as_str) == Some(name)) - .map(|package| package_string(package, "version")) - .collect::, _>>()?; - if versions.len() != 1 { + .map(|package| { + Ok(( + package_string(package, "id")?, + package_string(package, "version")?, + )) + }) + .collect::, String>>()?; + if matches.len() != 1 { return Err(format!( - "{scope}: expected exactly one {name} version in resolved dependency graph, found {versions:?}" + "{scope}: expected exactly one resolved {name} package identity, found {matches:?}" )); } - Ok(versions.into_iter().next().expect("one version").to_owned()) + Ok(matches[0].1.to_owned()) +} + +fn approve_local_package( + package: &Value, + workspace_members: &HashSet<&str>, + trusted_root: &Path, +) -> Result<(), String> { + let id = package_string(package, "id")?; + if !workspace_members.contains(id) { + return Err(format!( + "unreviewed local dependency: {id} is not a workspace member" + )); + } + + let manifest_path = Path::new(package_string(package, "manifest_path")?); + let canonical_manifest = fs::canonicalize(manifest_path).map_err(|error| { + format!( + "canonicalize local dependency manifest {}: {error}", + manifest_path.display() + ) + })?; + if !canonical_manifest.starts_with(trusted_root) + || canonical_manifest.file_name().and_then(|name| name.to_str()) != Some("Cargo.toml") + || !canonical_manifest.is_file() + { + return Err(format!( + "unreviewed local dependency manifest for {id}: {} resolves to {} outside the trusted workspace", + manifest_path.display(), + canonical_manifest.display() + )); + } + Ok(()) } pub fn ensure_target(target: &str) -> Result<(), String> { @@ -292,20 +334,42 @@ pub fn check(data: &Value) -> Result, String> { )); } + let trusted_root = fs::canonicalize(util::repo_root()) + .map_err(|error| format!("canonicalize repository root: {error}"))?; + let workspace_members = data + .get("workspace_members") + .and_then(Value::as_array) + .ok_or_else(|| "cargo metadata omitted workspace_members".to_owned())? + .iter() + .map(|member| member.as_str().ok_or_else(|| "invalid workspace member ID".to_owned())) + .collect::, _>>()?; + for package in package_list { license_choice(package)?; let name = package_string(package, "name")?; - let source = package.get("source").and_then(Value::as_str); - if name == "tree-sitter-wit" { - if source != Some(expected_grammar_source.as_str()) { + match package.get("source") { + Some(Value::String(source)) if name == "tree-sitter-wit" => { + if source != &expected_grammar_source { + return Err(format!( + "unreviewed tree-sitter-wit source: {source:?}; expected {expected_grammar_source:?}" + )); + } + } + Some(Value::String(source)) if source == CRATES_IO_SOURCE => {} + Some(Value::Null) if name != "tree-sitter-wit" => { + approve_local_package(package, &workspace_members, &trusted_root)?; + } + Some(Value::String(source)) if name == "tree-sitter-wit" => { return Err(format!( "unreviewed tree-sitter-wit source: {source:?}; expected {expected_grammar_source:?}" )); } - } else if let Some(source) = source - && !source.starts_with("registry+https://github.com/rust-lang/crates.io-index") - { - return Err(format!("unreviewed dependency source: {source}")); + Some(Value::String(source)) => { + return Err(format!("unreviewed dependency source: {source}")); + } + _ => { + return Err(format!("missing or malformed dependency source for {name}")); + } } } @@ -385,7 +449,7 @@ mod tests { "name": name, "version": version, "license": "MIT", - "source": null, + "source": CRATES_IO_SOURCE, }) } @@ -395,11 +459,21 @@ mod tests { grammar["source"] = json!(format!( "git+{GRAMMAR_REPOSITORY}?rev={revision}#{revision}" )); + let root = util::repo_root(); + let mut native = fake_package("native", "wit-language-server", "0.1.2"); + native["source"] = Value::Null; + native["manifest_path"] = json!(root.join("crates/wit-language-server/Cargo.toml")); + let mut adapter = fake_package("adapter", "zed-wit", "0.5.4"); + adapter["source"] = Value::Null; + adapter["manifest_path"] = json!(root.join("Cargo.toml")); + let mut syntax = fake_package("syntax", "wit-syntax", "0.1.0"); + syntax["source"] = Value::Null; + syntax["manifest_path"] = json!(root.join("crates/wit-syntax/Cargo.toml")); json!({ "packages": [ - fake_package("native", "wit-language-server", "0.1.2"), - fake_package("adapter", "zed-wit", "0.5.4"), - fake_package("syntax", "wit-syntax", "0.1.0"), + native, + adapter, + syntax, fake_package("native-parser", "wit-parser", native_parser), fake_package("adapter-parser", "wit-parser", "0.227.1"), fake_package("runtime", "tree-sitter", runtime), @@ -495,6 +569,100 @@ mod tests { ); } + #[test] + fn approved_local_workspace_members_pass() { + check(&graph_fixture("0.261.0", "0.26.13")).unwrap(); + } + + #[test] + fn external_path_dependency_is_rejected_even_if_declared_a_workspace_member() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][0]["manifest_path"] = json!(std::env::temp_dir()); + assert!( + check(&data) + .unwrap_err() + .contains("outside the trusted workspace") + ); + } + + #[test] + fn in_repo_path_package_not_in_workspace_is_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][7]["source"] = Value::Null; + data["packages"][7]["manifest_path"] = json!(util::repo_root().join("Cargo.toml")); + assert!(check(&data).unwrap_err().contains("not a workspace member")); + } + + #[cfg(unix)] + #[test] + fn symlinked_manifest_outside_repo_is_rejected() { + use std::os::unix::fs::symlink; + use std::sync::atomic::{AtomicU64, Ordering}; + static NEXT: AtomicU64 = AtomicU64::new(0); + let nonce = NEXT.fetch_add(1, Ordering::Relaxed); + let name = format!( + "dependency-policy-{}-{nonce}-{:?}", + std::process::id(), + std::thread::current().id() + ); + let outside = std::env::temp_dir().join(format!("{name}-outside")); + let inside = util::repo_root().join("target").join(format!("{name}-link")); + fs::create_dir_all(&outside).unwrap(); + fs::create_dir_all(inside.parent().unwrap()).unwrap(); + fs::write(outside.join("Cargo.toml"), "[package]\nname = \"external\"\nversion = \"0.1.0\"\n").unwrap(); + symlink(&outside, &inside).unwrap(); + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][0]["manifest_path"] = json!(inside.join("Cargo.toml")); + let result = check(&data); + fs::remove_file(&inside).unwrap(); + fs::remove_dir_all(&outside).unwrap(); + assert!( + result + .unwrap_err() + .contains("outside the trusted workspace") + ); + } + + #[test] + fn identical_versions_with_distinct_native_parser_package_ids_are_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"].as_array_mut().unwrap().push(fake_package( + "other-parser", + "wit-parser", + "0.261.0", + )); + data["resolve"]["nodes"] + .as_array_mut() + .unwrap() + .push(json!({"id": "other-parser", "deps": []})); + data["resolve"]["nodes"][0]["deps"] + .as_array_mut() + .unwrap() + .push(json!({"pkg": "other-parser", "dep_kinds": [{"kind": null}]})); + assert!(check(&data).unwrap_err().contains("package identity")); + } + + #[test] + fn identical_versions_with_distinct_tree_sitter_package_ids_are_rejected() { + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"].as_array_mut().unwrap().push(fake_package( + "other-runtime", + "tree-sitter", + "0.26.13", + )); + assert!(check(&data).unwrap_err().contains("package identity")); + } + + #[test] + fn exact_registry_source_passes_and_prefix_lookalikes_fail() { + check(&graph_fixture("0.261.0", "0.26.13")).unwrap(); + let mut data = graph_fixture("0.261.0", "0.26.13"); + data["packages"][3]["source"] = json!(format!("{CRATES_IO_SOURCE}-evil")); + assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + data["packages"][3]["source"] = json!("registry+https://example.org/other-index"); + assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + } + #[test] fn resolved_grammar_git_commit_must_match_manifest_pin() { let mut data = graph_fixture("0.261.0", "0.26.13"); From fa4b3852eee95f483716aef5b23152ce55d32e62 Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:40:05 -0400 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=9D=20Document=20trusted=20Cargo?= =?UTF-8?q?=20sources=20and=20package=20identity=20invariants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/xtask.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/xtask.md b/docs/xtask.md index 2bd47c5..14007cc 100644 --- a/docs/xtask.md +++ b/docs/xtask.md @@ -43,9 +43,9 @@ Native binary lookup and local release packaging read the effective output direc `cargo xtask check-dependencies` reads the locked, resolved Cargo graph. The manifests and `Cargo.lock` are the source of truth for versions: xtask does not maintain a second list of expected `wit-parser` or `tree-sitter` releases. -The checker requires one resolved semantic-parser version in each of the native language-server and Zed adapter normal/build dependency closures, and one Tree-sitter runtime version across the workspace. It does **not** require the adapter's transitive WIT parser to have the same version as the native semantic parser. The JSON report displays actual resolved versions, not pinned strings from xtask source. A version change still requires API/fixture/compatibility review and passing tests. +The checker requires exactly one resolved semantic-parser **package identity** in each of the native language-server and Zed adapter normal/build dependency closures, and one Tree-sitter runtime **package identity** across the workspace (even when distinct identities share a version). It does **not** require the adapter's transitive WIT parser to have the same version as the native semantic parser. The JSON report displays actual resolved versions, not pinned strings from xtask source. A version change still requires API/fixture/compatibility review and passing tests. -The checker also rejects unreviewed dependency sources and license expressions. The permitted `tree-sitter-wit` Git commit is derived from the two matching, 40-character revision pins in `crates/wit-syntax/Cargo.toml` and `extension.toml`; both the repository origin and the resolved Git source must match. The approved release-target matrix, target-filtered native normal/build closure, and `collect-licenses` redistribution notices remain independently enforced. Valid dependency updates must not bypass those checks. +The checker also rejects unreviewed dependency sources and license expressions. Crates.io registry sources are checked by exact identity. Packages whose Cargo metadata source is null must be recognized workspace members, and their canonicalized Cargo.toml paths must resolve inside the canonical repository boundary; external, unlisted and symlink-escaping local dependencies are rejected. The permitted `tree-sitter-wit` Git commit is derived from the two matching, 40-character revision pins in `crates/wit-syntax/Cargo.toml` and `extension.toml`; both the repository origin and the resolved Git source must match. The approved release-target matrix, target-filtered native normal/build closure, and `collect-licenses` redistribution notices remain independently enforced. Valid dependency updates must not bypass those checks. ## Local release preparation, not publication From 3374eb3452906244069bcd8e012268bcc574d1b0 Mon Sep 17 00:00:00 2001 From: Max T <25856189+maxtcodes@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:43:35 -0400 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20rustfmt=20correction?= =?UTF-8?q?s=20to=20dependency=20policy=20audit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/xtask/src/dependency_policy.rs | 33 ++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/xtask/src/dependency_policy.rs b/crates/xtask/src/dependency_policy.rs index 11b4219..334951c 100644 --- a/crates/xtask/src/dependency_policy.rs +++ b/crates/xtask/src/dependency_policy.rs @@ -119,7 +119,10 @@ fn approve_local_package( ) })?; if !canonical_manifest.starts_with(trusted_root) - || canonical_manifest.file_name().and_then(|name| name.to_str()) != Some("Cargo.toml") + || canonical_manifest + .file_name() + .and_then(|name| name.to_str()) + != Some("Cargo.toml") || !canonical_manifest.is_file() { return Err(format!( @@ -341,7 +344,11 @@ pub fn check(data: &Value) -> Result, String> { .and_then(Value::as_array) .ok_or_else(|| "cargo metadata omitted workspace_members".to_owned())? .iter() - .map(|member| member.as_str().ok_or_else(|| "invalid workspace member ID".to_owned())) + .map(|member| { + member + .as_str() + .ok_or_else(|| "invalid workspace member ID".to_owned()) + }) .collect::, _>>()?; for package in package_list { @@ -606,10 +613,16 @@ mod tests { std::thread::current().id() ); let outside = std::env::temp_dir().join(format!("{name}-outside")); - let inside = util::repo_root().join("target").join(format!("{name}-link")); + let inside = util::repo_root() + .join("target") + .join(format!("{name}-link")); fs::create_dir_all(&outside).unwrap(); fs::create_dir_all(inside.parent().unwrap()).unwrap(); - fs::write(outside.join("Cargo.toml"), "[package]\nname = \"external\"\nversion = \"0.1.0\"\n").unwrap(); + fs::write( + outside.join("Cargo.toml"), + "[package]\nname = \"external\"\nversion = \"0.1.0\"\n", + ) + .unwrap(); symlink(&outside, &inside).unwrap(); let mut data = graph_fixture("0.261.0", "0.26.13"); data["packages"][0]["manifest_path"] = json!(inside.join("Cargo.toml")); @@ -658,9 +671,17 @@ mod tests { check(&graph_fixture("0.261.0", "0.26.13")).unwrap(); let mut data = graph_fixture("0.261.0", "0.26.13"); data["packages"][3]["source"] = json!(format!("{CRATES_IO_SOURCE}-evil")); - assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + assert!( + check(&data) + .unwrap_err() + .contains("unreviewed dependency source") + ); data["packages"][3]["source"] = json!("registry+https://example.org/other-index"); - assert!(check(&data).unwrap_err().contains("unreviewed dependency source")); + assert!( + check(&data) + .unwrap_err() + .contains("unreviewed dependency source") + ); } #[test]