From bcf290b192decbbe0e7b6c5e75829c2757c47c30 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 11:13:41 -0400 Subject: [PATCH 01/25] Harden uv lockfile patch preservation after #238 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fail-closed revert for unwired Python entries: a ledger entry with no wiring (the shape `repair` re-synthesizes when state.json is lost) used to route into the uv / python-lock revert, iterate zero records, report success, and let the caller delete the vendored wheel and drop the entry while uv.lock, the pylock, the script, or requirements.txt still resolved through it. `revert_pypi_opts` now refuses while any Python project file references the uuid dir, mirroring the npm-family guard. Inventory: a PEP 723 script lock or PEP 751 lock supplements the project's poetry.lock / requirements.txt pins instead of hiding them; uv.lock keeps its exclusive precedence. Discovery: `python_lock_paths` follows symlinks and skips one unreadable entry instead of dropping every lock. Line endings: toml_edit re-emits every newline as LF, so CRLF pylock, script-lock and pyproject rewrites (and document-restore reverts) now restore the input's CRLF convention. Ledger recovery compares the purl name in PEP 503 form like `lookup`. Hosted pyproject edits render uv's own layout — header-less `[tool]` / `[tool.uv]` parents and a `[tool.uv.sources]` table after `[project]` — instead of dotted keys above `[project]`. The PEP 723 script block keeps fully dotted keys: only those vanish when empty after a parse round trip, which the out-of-order multi-package document revert relies on. `redirect_uv_missing_sha256` fires once per dep, not once per lock file; `repair` prefers uv.lock over an exported pylock when stamping flavor. Backtest harness: retry the cold-offline root-build failure with network for any uv version (not just 0.2.37), and pin first, latest, and every observed behaviour-boundary release of each 0.x family. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 16 ++ .../src/commands/repair_vendor.rs | 29 ++- .../src/patch/redirect/mod.rs | 71 +++++- .../src/utils/python_lock.rs | 128 ++++++++++- .../src/utils/python_script.rs | 148 ++++++++++++- .../src/vendor/lock_inventory.rs | 144 ++++++++++++- crates/socket-patch-core/src/vendor/pypi.rs | 203 ++++++++++++++++++ .../socket-patch-core/src/vendor/pypi_lock.rs | 7 +- scripts/backtest-uv.py | 51 ++++- 9 files changed, 752 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8153865a..ac7e7cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,22 @@ into the new version's section — see docs/releasing.md. ### Added +- **Python patches survive uv lockfiles in both hosted and vendored modes.** + `scan --mode hosted|vendored` now rewrites native `uv.lock` together with + the paired `pyproject.toml` source and metadata, PEP 723 script locks + (`*.py.lock` plus the script's inline metadata), PEP 751 `pylock*.toml`, + and uv-compiled hashed `requirements.txt`, so `uv sync --frozen|--locked`, + `uv run --script`, and `uv pip sync --require-hashes` install the patched + wheel instead of the registry artifact. Verified against real uv binaries + from every 0.x release family (0.0 through 0.12), first and latest release + of each — see `docs/testing/uv-compatibility.md`. Follow-up hardening: + `vendor --revert` refuses to delete a vendored Python wheel a lock still + references when the ledger entry has no wiring to replay (the shape + `repair` rebuilds), a script or PEP 751 lock supplements rather than hides + `poetry.lock`/`requirements.txt` pins, symlinked locks are discovered, CRLF + locks keep their line endings, and the hosted `[tool.uv.sources]` edit + renders as a header after `[project]` the way uv writes it. (#238) + - **Path targeting on `scan` and `rollback`.** `scan [PATHS]...` scopes discovery to packages with an installed copy under a matching glob (ancestor rule: `scan packages/foo` covers the subtree; `*` never crosses diff --git a/crates/socket-patch-cli/src/commands/repair_vendor.rs b/crates/socket-patch-cli/src/commands/repair_vendor.rs index 3445d40b..47ee31e5 100644 --- a/crates/socket-patch-cli/src/commands/repair_vendor.rs +++ b/crates/socket-patch-cli/src/commands/repair_vendor.rs @@ -15,9 +15,9 @@ //! from the lockfile path itself (the contract's uuid-in-path rule), the //! record from the manifest (or the patch API, yielding a detached entry), //! and a fresh ledger entry is re-synthesized so sweep/GC/revert know the -//! artifact again — stamped with the npm lockfile FLAVOR the reference was -//! found in, so a later `vendor --revert` routes to the backend whose -//! unwired-revert guard probes the right lockfile. WIRING reconstruction is +//! artifact again — stamped with the lockfile FLAVOR the reference was +//! found in (npm family and pypi), so a later `vendor --revert` routes to the +//! backend whose unwired-revert guard probes the right lockfile. WIRING reconstruction is //! per-ecosystem: gem recognizes //! its own Gemfile/lock wiring and rebuilds full revert-capable records //! ([`socket_patch_core::vendor::gem::reconstruct_gem_wiring`]); the other @@ -221,7 +221,13 @@ fn synth_entry(eco: &str, uuid: &str, artifact_path: &str, base_purl: &str) -> V async fn detect_reference_flavor(project_root: &Path, eco: &str, uuid: &str) -> Option { if eco == "pypi" { let needle = format!(".socket/vendor/pypi/{uuid}/"); - for file in socket_patch_core::utils::python_lock::python_lock_paths(project_root).ok()? { + let mut files = + socket_patch_core::utils::python_lock::python_lock_paths(project_root).ok()?; + // uv.lock outranks the standalone locks (the vendor backend's own + // precedence): a pylock EXPORTED from the wired project lock must not + // relabel the entry `python-lock`. Alphabetical order would. + files.sort_by_key(|file| file != "uv.lock"); + for file in files { if tokio::fs::read_to_string(project_root.join(&file)) .await .ok() @@ -1462,7 +1468,7 @@ mod tests { let references = scan_vendor_references(tmp.path()).await; assert_eq!( references, - vec![("pypi".to_string(), uuid.to_string(), path)] + vec![("pypi".to_string(), uuid.to_string(), path.clone())] ); assert_eq!( detect_reference_flavor(tmp.path(), "pypi", uuid) @@ -1470,6 +1476,19 @@ mod tests { .as_deref(), Some("python-lock") ); + // The project lock outranks a pylock exported from it. + tokio::fs::write( + tmp.path().join("uv.lock"), + format!("version = 1\n\n[[package]]\nname = \"requests\"\nversion = \"2.28.1\"\nsource = {{ path = \"{path}\" }}\n"), + ) + .await + .unwrap(); + assert_eq!( + detect_reference_flavor(tmp.path(), "pypi", uuid) + .await + .as_deref(), + Some("uv") + ); } /// pnpm writes vendored paths in THREE spellings — override values, diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index d4c09aea..0b4952a9 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -2696,16 +2696,29 @@ fn rewrite_uv_lock( complete_python_lock_metadata, is_python_lock_name, rewrite_python_lock, ArtifactSource, }; - for (path, original) in files.iter().filter(|(path, _)| is_python_lock_name(path)) { + let locks: Vec<(&String, &String)> = files + .iter() + .filter(|(path, _)| is_python_lock_name(path)) + .collect(); + if locks.is_empty() { + return; + } + // Intake gate ONCE per dep, not once per lock file: a project carrying + // uv.lock + pylock.toml + a script lock would otherwise repeat the same + // missing-integrity warning three times. + let mut usable: Vec<(&DepOverride, &str)> = Vec::new(); + for dep in overrides.iter().filter(|dep| dep.ecosystem == "pypi") { + match dep.integrity.sha256.as_deref() { + Some(sha256) => usable.push((dep, sha256)), + None => result.warnings.push(RewriteWarning { + code: "redirect_uv_missing_sha256".into(), + detail: format!("{} has no sha256 integrity", dep.name), + }), + } + } + for (path, original) in locks { let mut content = original.clone(); - for dep in overrides.iter().filter(|dep| dep.ecosystem == "pypi") { - let Some(sha256) = dep.integrity.sha256.as_deref() else { - result.warnings.push(RewriteWarning { - code: "redirect_uv_missing_sha256".into(), - detail: format!("{} has no sha256 integrity", dep.name), - }); - continue; - }; + for &(dep, sha256) in &usable { let rewritten = match rewrite_python_lock( &content, &dep.name, @@ -13262,3 +13275,43 @@ packages: ); } } + +#[cfg(test)] +mod python_lock_warning_tests { + use super::*; + + #[test] + fn missing_sha256_warns_once_across_python_lock_files() { + let dep = DepOverride { + ecosystem: "pypi".into(), + name: "requests".into(), + namespace: None, + version: "2.28.1".into(), + token: "11111111-1111-4111-8111-111111111111".into(), + patch_uuid: "22222222-2222-4222-8222-222222222222".into(), + artifact_url: "https://patch.socket.dev/requests-2.28.1-py3-none-any.whl".into(), + berry_zip_url: None, + registry_override: None, + integrity: Integrity::default(), + }; + let files = BTreeMap::from([ + ("uv.lock".to_string(), "version = 1\n".to_string()), + ( + "pylock.toml".to_string(), + "lock-version = \"1.0\"\n".to_string(), + ), + ("tool.py.lock".to_string(), "version = 1\n".to_string()), + ]); + let result = rewrite_registry_redirect(&files, &[dep]); + assert!(result.files.is_empty() && result.edits.is_empty()); + let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); + assert_eq!( + codes + .iter() + .filter(|code| **code == "redirect_uv_missing_sha256") + .count(), + 1, + "{codes:?}" + ); + } +} diff --git a/crates/socket-patch-core/src/utils/python_lock.rs b/crates/socket-patch-core/src/utils/python_lock.rs index 8bb4418b..965e9f94 100644 --- a/crates/socket-patch-core/src/utils/python_lock.rs +++ b/crates/socket-patch-core/src/utils/python_lock.rs @@ -36,21 +36,61 @@ pub fn is_python_lock_name(name: &str) -> bool { pub fn python_lock_paths(root: &Path) -> std::io::Result> { let mut paths = Vec::new(); for entry in std::fs::read_dir(root)? { - let entry = entry?; - if !entry.file_type()?.is_file() { + // One unreadable directory entry must not hide every other lock: + // the callers treat an `Err` as "no Python locks here", which would + // silently drop the whole project from inventory, repair, and the + // hosted candidate list. + let Ok(entry) = entry else { continue; - } + }; let Some(name) = entry.file_name().to_str().map(str::to_string) else { continue; }; - if is_python_lock_name(&name) { - paths.push(name); + if !is_python_lock_name(&name) { + continue; + } + // `DirEntry::file_type` does NOT follow symlinks, so a lock that is a + // symlink (a shared pylock, a checked-in link) was never discovered + // even though every reader opens it fine. `fs::metadata` follows the + // link; a link to a directory or FIFO is still excluded by `is_file`. + if !std::fs::metadata(entry.path()) + .map(|metadata| metadata.is_file()) + .unwrap_or(false) + { + continue; } + paths.push(name); } paths.sort(); Ok(paths) } +/// Re-apply the input's CRLF convention to a toml_edit rendering. +/// +/// toml_edit (0.25.x) re-emits every newline as `\n`: a CRLF document comes +/// back entirely LF even when nothing was edited, so a rewritten lock or +/// pyproject would churn on every line under git and a byte-exact revert +/// could never converge. When the input used CRLF exclusively, convert the +/// rendering back; mixed-ending files are left as rendered rather than +/// half-converted. +pub fn preserve_line_endings(original: &str, rendered: String) -> String { + let uses_crlf = original.contains("\r\n"); + let has_bare_lf = original.replace("\r\n", "").contains('\n'); + if !uses_crlf || has_bare_lf { + return rendered; + } + let mut output = String::with_capacity(rendered.len() + rendered.matches('\n').count()); + let mut previous = '\0'; + for character in rendered.chars() { + if character == '\n' && previous != '\r' { + output.push('\r'); + } + output.push(character); + previous = character; + } + output +} + fn inline(entries: &[(&str, Value)]) -> Value { let mut table = InlineTable::new(); for (key, value) in entries { @@ -405,7 +445,7 @@ pub fn complete_python_lock_metadata( } } } - Ok(document.to_string()) + Ok(preserve_line_endings(text, document.to_string())) } pub fn rewrite_python_lock( @@ -561,7 +601,7 @@ pub fn rewrite_python_lock( if !pep751 && !legacy { rewrite_manifest(&mut document, &name, artifact); } - Ok(Some(document.to_string())) + Ok(Some(preserve_line_endings(text, document.to_string()))) } #[cfg(test)] @@ -856,3 +896,77 @@ wheels = [{url = "https://pypi.org/urllib3.whl", hashes = {sha256 = "old"}}] } } } + +#[cfg(test)] +mod discovery_and_line_ending_tests { + use super::*; + + #[test] + fn crlf_locks_keep_their_line_endings_through_a_rewrite() { + let lock = "lock-version = \"1.0\"\r\ncreated-by = \"uv\"\r\n\r\n[[packages]]\r\nname = \"requests\"\r\nversion = \"2.28.1\"\r\nwheels = [{ name = \"requests-2.28.1-py3-none-any.whl\", url = \"https://pypi.org/requests-2.28.1-py3-none-any.whl\", hashes = { sha256 = \"old\" } }]\r\n"; + let url = "https://patch.socket.dev/requests-2.28.1-py3-none-any.whl"; + let out = rewrite_python_lock( + lock, + "requests", + "2.28.1", + ArtifactSource::Url(url), + &"a".repeat(64), + ) + .unwrap() + .expect("rewritten"); + assert!(out.contains(url) && !out.contains("pypi.org"), "{out}"); + assert!(!out.contains("\r\r"), "{out:?}"); + assert_eq!( + out.matches("\r\n").count(), + out.matches('\n').count(), + "every newline must stay CRLF: {out:?}" + ); + // Re-running over the CRLF output is byte-stable. + assert_eq!( + rewrite_python_lock( + &out, + "requests", + "2.28.1", + ArtifactSource::Url(url), + &"a".repeat(64) + ) + .unwrap() + .as_deref(), + Some(out.as_str()) + ); + } + + #[test] + fn line_endings_are_restored_only_for_pure_crlf_inputs() { + assert_eq!( + preserve_line_endings("a\r\nb\r\n", "a\nb\n".into()), + "a\r\nb\r\n" + ); + assert_eq!( + preserve_line_endings("a\r\nb\nc", "a\nb\nc".into()), + "a\nb\nc" + ); + assert_eq!(preserve_line_endings("a\nb\n", "a\nb\n".into()), "a\nb\n"); + assert_eq!( + preserve_line_endings("a\r\n", "a\r\nb\n".into()), + "a\r\nb\r\n" + ); + } + + #[cfg(unix)] + #[test] + fn python_lock_paths_follow_symlinks_and_skip_non_files() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + std::fs::write(root.join("shared.toml"), "lock-version = \"1.0\"\n").unwrap(); + std::os::unix::fs::symlink(root.join("shared.toml"), root.join("pylock.toml")).unwrap(); + std::fs::write(root.join("tool.py.lock"), "version = 1\n").unwrap(); + // A directory that merely carries a lock name, and a dangling link. + std::fs::create_dir(root.join("uv.lock")).unwrap(); + std::os::unix::fs::symlink(root.join("missing"), root.join("pylock.dev.toml")).unwrap(); + assert_eq!( + python_lock_paths(root).unwrap(), + vec!["pylock.toml".to_string(), "tool.py.lock".to_string()] + ); + } +} diff --git a/crates/socket-patch-core/src/utils/python_script.rs b/crates/socket-patch-core/src/utils/python_script.rs index 00d4384b..62940362 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -91,10 +91,44 @@ fn same_hosted_artifact(previous: &str, current: &str) -> bool { && uuid::Uuid::parse_str(current_path[grant_index + 1]).is_ok() } -fn dotted_table() -> Item { - let mut table = Table::new(); - table.set_dotted(true); - Item::Table(table) +/// How a freshly created `[tool.uv.sources]` is laid out. +/// +/// A PEP 723 script block is reverted by DOCUMENT restore +/// (`vendor::pypi_lock::restore_document`), which can only converge on the +/// exact pre-vendor bytes after an out-of-order multi-package revert when +/// the tables we created vanish once empty. Only DOTTED keys do that after a +/// parse round trip (dotted-ness is syntax; `implicit` is not), so the script +/// block keeps `tool.uv.sources.x = { … }` right after `dependencies`, where +/// placement was never a problem. +/// +/// A `pyproject.toml` is reverted by exact text replay of the recorded +/// original, so it can use uv's own layout: header-less `[tool]` / `[tool.uv]` +/// parents and a real `[tool.uv.sources]` table AFTER `[project]`. (Dotted +/// keys rendered as `tool.uv.sources.x = …` in the ROOT body — i.e. ABOVE +/// `[project]` — whenever the pyproject had no `[tool]` header yet.) +#[derive(Clone, Copy)] +enum SourcesLayout { + Dotted, + Tables, +} + +impl SourcesLayout { + fn parent(self) -> Item { + let mut table = Table::new(); + match self { + Self::Dotted => table.set_dotted(true), + Self::Tables => table.set_implicit(true), + } + Item::Table(table) + } + + fn leaf(self) -> Item { + let mut table = Table::new(); + if matches!(self, Self::Dotted) { + table.set_dotted(true); + } + Item::Table(table) + } } fn rewrite_sources( @@ -103,21 +137,22 @@ fn rewrite_sources( version: &str, artifact: ArtifactSource<'_>, direct: bool, + layout: SourcesLayout, ) -> Result<(), String> { let (key, location) = match artifact { ArtifactSource::Url(location) => ("url", location), ArtifactSource::Path(location) => ("path", location), }; - let tool = document.entry("tool").or_insert(dotted_table()); + let tool = document.entry("tool").or_insert(layout.parent()); let uv = tool .as_table_like_mut() .ok_or("Python tool metadata must be a table")? .entry("uv") - .or_insert(dotted_table()); + .or_insert(layout.parent()); let uv = uv .as_table_like_mut() .ok_or("Python tool.uv metadata must be a table")?; - let sources = uv.entry("sources").or_insert(dotted_table()); + let sources = uv.entry("sources").or_insert(layout.leaf()); let sources = sources .as_table_like_mut() .ok_or("Python tool.uv.sources must be a table")?; @@ -226,8 +261,15 @@ pub fn rewrite_project_metadata( "hosted sources for uv workspaces require a package-scoped source mapping".to_string(), ); } - rewrite_sources(&mut document, &name, version, artifact, direct)?; - let output = document.to_string(); + rewrite_sources( + &mut document, + &name, + version, + artifact, + direct, + SourcesLayout::Tables, + )?; + let output = crate::utils::python_lock::preserve_line_endings(text, document.to_string()); Ok((output != text).then_some(output)) } @@ -250,7 +292,14 @@ pub fn rewrite_script_metadata( .filter_map(Value::as_str) .any(|spec| dependency_name(spec) == name) }); - rewrite_sources(&mut document, &name, version, artifact, direct)?; + rewrite_sources( + &mut document, + &name, + version, + artifact, + direct, + SourcesLayout::Dotted, + )?; let output = replace_script_metadata(text, &document.to_string())?; Ok((output != text).then_some(output)) } @@ -345,3 +394,82 @@ mod tests { ); } } + +#[cfg(test)] +mod rendering_tests { + use super::*; + + const URL: &str = "https://patch.socket.dev/alpha-1.0.0-py3-none-any.whl"; + + /// Byte-level shape of a pyproject edit: uv's own layout — a + /// `[tool.uv.sources]` header AFTER `[project]`, never dotted keys at the + /// top of the file; a transitive dep adds `[tool.uv]` with its override. + #[test] + fn project_sources_render_as_uv_style_headers_after_project() { + let direct = rewrite_project_metadata( + "[project]\nname = \"p\"\nversion = \"0.1.0\"\ndependencies = [\"alpha==1.0.0\"]\n", + "alpha", + "1.0.0", + ArtifactSource::Url(URL), + ) + .unwrap() + .unwrap(); + assert_eq!( + direct, + format!("[project]\nname = \"p\"\nversion = \"0.1.0\"\ndependencies = [\"alpha==1.0.0\"]\n\n[tool.uv.sources]\nalpha = {{ url = \"{URL}\" }}\n") + ); + let transitive = rewrite_project_metadata( + "[project]\nname = \"p\"\ndependencies = [\"requests\"]\n", + "alpha", + "1.0.0", + ArtifactSource::Url(URL), + ) + .unwrap() + .unwrap(); + assert_eq!( + transitive, + format!("[project]\nname = \"p\"\ndependencies = [\"requests\"]\n\n[tool.uv]\noverride-dependencies = [\"alpha==1.0.0\"]\n\n[tool.uv.sources]\nalpha = {{ url = \"{URL}\" }}\n") + ); + // An existing `[tool.uv]` header gains the sources as its own + // sub-table; a CRLF pyproject stays CRLF. + let existing = rewrite_project_metadata( + "[project]\r\nname = \"p\"\r\ndependencies = [\"alpha==1.0.0\"]\r\n\r\n[tool.uv]\r\ndev-dependencies = [\"pytest\"]\r\n", + "alpha", + "1.0.0", + ArtifactSource::Url(URL), + ) + .unwrap() + .unwrap(); + assert_eq!( + existing, + format!("[project]\r\nname = \"p\"\r\ndependencies = [\"alpha==1.0.0\"]\r\n\r\n[tool.uv]\r\ndev-dependencies = [\"pytest\"]\r\n\r\n[tool.uv.sources]\r\nalpha = {{ url = \"{URL}\" }}\r\n") + ); + // An existing `[tool.uv.sources]` header is reused as-is. + let header = rewrite_project_metadata( + "[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\", \"beta\"]\n\n[tool.uv.sources]\nbeta = { git = \"https://example.test/beta\" }\n", + "alpha", + "1.0.0", + ArtifactSource::Url(URL), + ) + .unwrap() + .unwrap(); + assert_eq!( + header, + format!("[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\", \"beta\"]\n\n[tool.uv.sources]\nbeta = {{ git = \"https://example.test/beta\" }}\nalpha = {{ url = \"{URL}\" }}\n") + ); + } + + /// The PEP 723 block keeps the fully dotted shape so that document + /// restore can drop it without a trace once every source is reverted. + #[test] + fn script_sources_stay_dotted_inside_the_metadata_block() { + let script = "# /// script\n# dependencies = [\"alpha==1.0.0\"]\n# ///\nprint('x')\n"; + let out = rewrite_script_metadata(script, "alpha", "1.0.0", ArtifactSource::Url(URL)) + .unwrap() + .unwrap(); + assert_eq!( + out, + format!("# /// script\n# dependencies = [\"alpha==1.0.0\"]\n# tool.uv.sources.alpha = {{ url = \"{URL}\" }}\n# ///\nprint('x')\n") + ); + } +} diff --git a/crates/socket-patch-core/src/vendor/lock_inventory.rs b/crates/socket-patch-core/src/vendor/lock_inventory.rs index 73e55493..8fda3fce 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory.rs @@ -1093,24 +1093,35 @@ fn parse_gem_spec_line(line: &str) -> Option<(String, String)> { async fn inventory_pypi_locks(project_root: &Path) -> Option> { let mut out = Vec::new(); let mut found = false; + let mut uv_lock = false; if let Ok(paths) = crate::utils::python_lock::python_lock_paths(project_root) { for path in paths { - let Ok(text) = read_regular_to_string(&project_root.join(path)).await else { + let Ok(text) = read_regular_to_string(&project_root.join(&path)).await else { continue; }; if let Some(entries) = python_lock_inventory(&text) { found = true; + uv_lock |= path == "uv.lock"; out.extend(entries); } } } - if found { - return Some(dedup_prefer_integrity(out)); - } - if let Some(out) = inventory_poetry_lock(project_root).await { - return Some(out); + // uv.lock stays the EXCLUSIVE project inventory (its precedence over + // poetry.lock / requirements.txt predates standalone-lock support). A + // PEP 723 script lock or a PEP 751 lock is scoped to its own install, + // so it SUPPLEMENTS the project's tool lock: a stray `tool.py.lock` + // must not hide every poetry.lock / requirements.txt pin from scan's + // lockfile supplement and vendor's lookup. + if !uv_lock { + if let Some(entries) = inventory_poetry_lock(project_root).await { + found = true; + out.extend(entries); + } else if let Some(entries) = inventory_requirements_txt(project_root).await { + found = true; + out.extend(entries); + } } - inventory_requirements_txt(project_root).await + found.then(|| dedup_prefer_integrity(out)) } fn python_archive(archive: &dyn TableLike) -> Option<(String, String)> { @@ -1453,6 +1464,10 @@ pub async fn recover_lock_entry( .to_string(), ); } + // The inventory canonicalizes names (PEP 503); the purl may carry + // the project's own spelling (`PyYAML`, `typing_extensions`) — + // compare in normalized form like `lookup` does. + let canonical_name = canonicalize_pypi_name(&name); for wiring in entry .wiring .iter() @@ -1461,7 +1476,7 @@ pub async fn recover_lock_entry( if let Some(text) = wiring.original.as_ref().and_then(Value::as_str) { if let Some(entries) = python_lock_inventory(text) { if let Some(resolution) = entries.into_iter().find(|candidate| { - candidate.name == name + candidate.name == canonical_name && candidate.version == version && candidate.resolved.is_some() && candidate.integrity != LockIntegrity::None @@ -4400,3 +4415,116 @@ mod recover_tests { ); } } + +#[cfg(test)] +mod python_lock_union_tests { + use super::*; + + const WHEEL_SHA: &str = "abababababababababababababababababababababababababababababababab"; + + fn uv_style_lock(name: &str, version: &str) -> String { + format!( + "version = 1\n\n[[package]]\nname = \"{name}\"\nversion = \"{version}\"\nsource = {{ registry = \"https://pypi.org/simple\" }}\nwheels = [{{ url = \"https://files.pythonhosted.org/{name}-{version}-py3-none-any.whl\", hash = \"sha256:{WHEEL_SHA}\" }}]\n" + ) + } + + fn names(entries: &[LockfileEntry]) -> Vec<(String, String)> { + let mut pairs: Vec<_> = entries + .iter() + .map(|entry| (entry.name.clone(), entry.version.clone())) + .collect(); + pairs.sort(); + pairs + } + + /// A script lock is scoped to its script: it must ADD to the project's + /// requirements.txt / poetry.lock pins, not replace them (the base only + /// ever let uv.lock short-circuit the fallbacks). + #[tokio::test] + async fn script_lock_supplements_project_pins() { + let tmp = tempfile::tempdir().unwrap(); + tokio::fs::write(tmp.path().join("requirements.txt"), "requests==2.31.0\n") + .await + .unwrap(); + tokio::fs::write( + tmp.path().join("tool.py.lock"), + uv_style_lock("flask", "3.0.0"), + ) + .await + .unwrap(); + let entries = inventory_pypi_locks(tmp.path()).await.unwrap(); + assert_eq!( + names(&entries), + vec![ + ("flask".to_string(), "3.0.0".to_string()), + ("requests".to_string(), "2.31.0".to_string()), + ] + ); + } + + /// uv.lock keeps its exclusive precedence over the fallbacks. + #[tokio::test] + async fn uv_lock_still_hides_requirements_pins() { + let tmp = tempfile::tempdir().unwrap(); + tokio::fs::write(tmp.path().join("requirements.txt"), "requests==2.31.0\n") + .await + .unwrap(); + tokio::fs::write(tmp.path().join("uv.lock"), uv_style_lock("flask", "3.0.0")) + .await + .unwrap(); + let entries = inventory_pypi_locks(tmp.path()).await.unwrap(); + assert_eq!( + names(&entries), + vec![("flask".to_string(), "3.0.0".to_string())] + ); + } + + /// Ledger recovery must match a purl spelled the project's way + /// (`PyYAML`) against the PEP 503 names the inventory records. + #[tokio::test] + async fn python_document_recovery_canonicalizes_the_purl_name() { + let tmp = tempfile::tempdir().unwrap(); + let lock = format!( + "lock-version = '1.0'\n[[packages]]\nname = 'pyyaml'\nversion = '6.0.1'\narchive = {{ url = 'https://pypi.org/PyYAML-6.0.1-py3-none-any.whl', hashes = {{ sha256 = '{WHEEL_SHA}' }} }}\n" + ); + let entry = crate::vendor::state::VendorEntry { + ecosystem: "pypi".into(), + base_purl: "pkg:pypi/PyYAML@6.0.1".into(), + uuid: "11111111-1111-4111-8111-111111111111".into(), + artifact: crate::vendor::state::VendorArtifact { + path: ".socket/vendor/pypi/11111111-1111-4111-8111-111111111111/PyYAML-6.0.1-py3-none-any.whl".into(), + sha256: String::new(), + size: None, + platform_locked: None, + file_inventory: None, + }, + wiring: vec![crate::vendor::state::WiringRecord { + file: "pylock.toml".into(), + kind: "python_lock_document".into(), + action: crate::vendor::state::WiringAction::Rewritten, + key: Some("pyyaml".into()), + original: Some(serde_json::Value::String(lock)), + new: None, + }], + lock: None, + took_over_go_patches: false, + detached: false, + record: None, + flavor: Some("python-lock".into()), + uv: None, + pnpm: None, + poetry: None, + pdm: None, + pipenv: None, + }; + let recovered = recover_lock_entry(tmp.path(), &entry).await.unwrap(); + assert_eq!( + recovered.resolved.as_deref(), + Some("https://pypi.org/PyYAML-6.0.1-py3-none-any.whl") + ); + assert_eq!( + recovered.integrity, + LockIntegrity::Sha256Hex(WHEEL_SHA.into()) + ); + } +} diff --git a/crates/socket-patch-core/src/vendor/pypi.rs b/crates/socket-patch-core/src/vendor/pypi.rs index ecf73eab..c9253ee1 100644 --- a/crates/socket-patch-core/src/vendor/pypi.rs +++ b/crates/socket-patch-core/src/vendor/pypi.rs @@ -873,6 +873,84 @@ pub async fn revert_pypi(entry: &VendorEntry, project_root: &Path, dry_run: bool /// [`revert_pypi`] with full [`RevertOpts`]: `keep_artifact` skips the /// artifact deletion while the per-flavor wiring restore runs unchanged. +/// Fail-closed twin of [`super::npm_lock::guard_unwired_textual_revert`] +/// for the Python backends. A ledger entry with NO wiring records cannot +/// restore any project file — that is the shape `socket-patch repair` +/// re-synthesizes when state.json is lost (flavor stamped from the lock the +/// reference was found in, wiring not offline-recoverable). Routing such an +/// entry into a flavor revert that iterates zero records "succeeds", after +/// which the caller deletes the uuid dir and drops the entry while uv.lock, +/// the pylock, the script, or requirements.txt still resolve through the +/// vendored wheel — every later `--frozen` / `--offline` install fails. +/// Refuse whenever any Python project file still mentions the uuid dir, or +/// exists but cannot be read to prove it does not. With no reference left +/// the revert is a plain orphan cleanup and proceeds. +async fn guard_unwired_pypi_revert( + project_root: &Path, + uuid: &str, + uuid_dir_rel: &str, +) -> Option { + let needle = format!(".socket/vendor/pypi/{uuid}/"); + let mut names: Vec = [ + "pyproject.toml", + "requirements.txt", + "poetry.lock", + "pdm.lock", + "Pipfile", + "Pipfile.lock", + ] + .iter() + .map(|name| (*name).to_string()) + .collect(); + if let Ok(locks) = crate::utils::python_lock::python_lock_paths(project_root) { + for lock in locks { + if let Some(script) = lock.strip_suffix(".lock").filter(|s| s.ends_with(".py")) { + names.push(script.to_string()); + } + names.push(lock); + } + } + let mut clause = None; + for name in &names { + let path = project_root.join(name); + if matches!(tokio::fs::try_exists(&path).await, Ok(false)) { + continue; + } + match read_regular_to_string(&path).await { + Ok(text) if text.contains(&needle) => { + clause = Some(format!("{name} still resolves through it")); + break; + } + Ok(_) => {} + // Fail-closed: a file we cannot read may still reference it. + Err(_) => { + clause = Some(format!( + "{name} exists but could not be read to prove it no longer references it" + )); + break; + } + } + } + let clause = clause?; + let detail = format!( + "refusing to remove {uuid_dir_rel}: the ledger entry records no pre-vendor wiring to \ + replay (it was likely reconstructed by `socket-patch repair`; the pre-vendor Python \ + lock fragments are not offline-recoverable) and {clause} — deleting the artifact \ + would make every subsequent install fail; run `socket-patch repair` to keep the \ + vendored artifact healthy, and revert by restoring the pre-vendor files (or by \ + removing the dependency and re-locking) before re-running `vendor --revert`" + ); + Some(RevertOutcome { + success: false, + warnings: vec![VendorWarning::new( + "vendor_wiring_unknown_revert_blocked", + detail.clone(), + )], + error: Some(detail), + kept_artifact: false, + }) +} + pub async fn revert_pypi_opts( entry: &VendorEntry, project_root: &Path, @@ -882,6 +960,15 @@ pub async fn revert_pypi_opts( dry_run, keep_artifact, } = opts; + if entry.wiring.is_empty() { + let uuid_dir_rel = vendor_uuid_dir_rel("pypi", &entry.uuid) + .unwrap_or_else(|| format!(".socket/vendor/pypi/{:?}", entry.uuid)); + if let Some(blocked) = + guard_unwired_pypi_revert(project_root, &entry.uuid, &uuid_dir_rel).await + { + return blocked; + } + } let mut outcome = match entry.flavor.as_deref() { Some("uv") => revert_uv(entry, project_root, dry_run).await, Some("python-lock") => { @@ -2816,6 +2903,122 @@ wheels = [ } } + /// A ledger entry with NO wiring (the shape `socket-patch repair` + /// re-synthesizes when state.json is lost) cannot restore any file. + /// Routing it into a flavor revert that iterates zero records used to + /// "succeed", after which the caller deleted the uuid dir and dropped + /// the entry while the lock still resolved through the vendored wheel. + /// Both Python-lock backends must refuse while anything references it. + #[tokio::test] + async fn unwired_python_entry_revert_refuses_while_lock_references_artifact() { + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + let cases: Vec<(&str, Vec<(&str, String)>)> = vec![ + ( + "uv", + vec![ + ( + "pyproject.toml", + format!("[project]\nname = \"p\"\ndependencies = [\"six==1.16.0\"]\n\n[tool.uv.sources]\nsix = {{ path = \"{rel_wheel}\" }}\n"), + ), + ( + "uv.lock", + format!("version = 1\n\n[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = {{ path = \"{rel_wheel}\" }}\n"), + ), + ], + ), + ( + "python-lock", + vec![( + "pylock.toml", + format!("lock-version = \"1.0\"\n\n[[packages]]\nname = \"six\"\nversion = \"1.16.0\"\narchive = {{ path = \"{rel_wheel}\" }}\n"), + )], + ), + ( + "python-lock", + vec![ + ("tool.py.lock", "version = 1\n".to_string()), + ( + "tool.py", + format!("# /// script\n# dependencies = [\"six==1.16.0\"]\n# [tool.uv.sources]\n# six = {{ path = \"{rel_wheel}\" }}\n# ///\n"), + ), + ], + ), + ]; + for (flavor, files) in cases { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + for (name, text) in &files { + tokio::fs::write(root.join(name), text).await.unwrap(); + } + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry(flavor, &rel_wheel, Vec::new()); + for dry_run in [true, false] { + let outcome = revert_pypi(&entry, root, dry_run).await; + assert!( + !outcome.success, + "{flavor} dry_run={dry_run}: unwired revert must refuse: {outcome:?}" + ); + assert_eq!( + outcome.warnings.len(), + 1, + "{flavor}: {:?}", + outcome.warnings + ); + assert_eq!( + outcome.warnings[0].code, + "vendor_wiring_unknown_revert_blocked" + ); + assert!(!outcome.kept_artifact); + } + assert!( + wheel.is_file(), + "{flavor}: the referenced artifact must survive" + ); + for (name, text) in &files { + assert_eq!( + &tokio::fs::read_to_string(root.join(name)).await.unwrap(), + text, + "{flavor}: {name} must be untouched" + ); + } + } + } + + /// Nothing references the artifact any more (the user re-locked by + /// hand): an unwired entry's revert is a plain orphan cleanup and may + /// proceed — the guard is about live references, not about wiring. + #[tokio::test] + async fn unwired_python_entry_revert_proceeds_when_nothing_references_artifact() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + tokio::fs::write( + root.join("pyproject.toml"), + "[project]\nname = \"p\"\ndependencies = [\"six==1.16.0\"]\n", + ) + .await + .unwrap(); + tokio::fs::write( + root.join("uv.lock"), + "version = 1\n\n[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = { registry = \"https://pypi.org/simple\" }\n", + ) + .await + .unwrap(); + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry("uv", &rel_wheel, Vec::new()); + let outcome = revert_pypi(&entry, root, false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(!wheel.exists(), "the orphaned artifact dir is removed"); + } + const PIPENV_REGISTRY_LOCK: &str = r#"{ "_meta": { "hash": {"sha256": "x"}, diff --git a/crates/socket-patch-core/src/vendor/pypi_lock.rs b/crates/socket-patch-core/src/vendor/pypi_lock.rs index 2bde43c8..54bd2d56 100644 --- a/crates/socket-patch-core/src/vendor/pypi_lock.rs +++ b/crates/socket-patch-core/src/vendor/pypi_lock.rs @@ -290,7 +290,10 @@ pub(super) async fn wire_python_locks( } } } - rewritten = document.to_string(); + rewritten = crate::utils::python_lock::preserve_line_endings( + &file.text, + document.to_string(), + ); } } if rewritten != file.text { @@ -498,7 +501,7 @@ fn restore_document(live: &str, original: &str, new: &str) -> Result<(String, bo if drifted { current_text } else { - live.to_string() + crate::utils::python_lock::preserve_line_endings(¤t_text, live.to_string()) }, drifted, )) diff --git a/scripts/backtest-uv.py b/scripts/backtest-uv.py index 18173127..6b9f7065 100644 --- a/scripts/backtest-uv.py +++ b/scripts/backtest-uv.py @@ -15,21 +15,52 @@ import sys import urllib.request +# Every 0.x release family, first and latest release of each, plus the +# releases on either side of each behaviour boundary observed with real +# binaries (the lower one is the last release WITHOUT the feature): +# 0.1.23 / 0.1.24 `uv pip sync` accepts bare `./wheel` requirement paths +# 0.2.34 / 0.2.35 uv.lock `[[distribution]]` -> `[[package]]` grammar +# 0.4.0 / 0.4.1 `uv export` +# 0.5.16 / 0.5.17 `uv lock --script` +# 0.6.14 / 0.6.15 PEP 751 `pip compile -o pylock.toml`; lock revision 1 -> 2 +# 0.8.3 / 0.8.4 lock revision 2 -> 3 VERSIONS = [ '0.0.5', + '0.1.0', + '0.1.23', + '0.1.24', '0.1.45', + '0.2.0', + '0.2.34', + '0.2.35', '0.2.37', + '0.3.0', '0.3.5', + '0.4.0', + '0.4.1', '0.4.30', + '0.5.0', + '0.5.16', + '0.5.17', '0.5.31', '0.6.0', + '0.6.14', + '0.6.15', '0.6.17', + '0.7.0', '0.7.22', + '0.8.0', + '0.8.3', + '0.8.4', '0.8.24', + '0.9.0', '0.9.30', + '0.10.0', '0.10.12', + '0.11.0', '0.11.33', - '0.12.13', + '0.12.0', + '0.12.15', ] parser = argparse.ArgumentParser() parser.add_argument('--socket-patch', type=Path, required=True) @@ -40,7 +71,7 @@ args = parser.parse_args() ROOT = args.output.resolve() CLI = args.socket_patch.resolve() -BOOTSTRAP = ROOT / 'bin/0.12.13/uv' +BOOTSTRAP = ROOT / 'bin/0.12.15/uv' WHEEL = ROOT / 'urllib3-1.26.18-py2.py3-none-any.whl' ENV = { key: value @@ -78,7 +109,7 @@ def install_binaries(): raise ValueError('This backtest requires macOS or Linux') registry = fetch_json('https://pypi.org/pypi/uv/json') records = [] - for version in dict.fromkeys([*args.versions, '0.12.13']): + for version in dict.fromkeys([*args.versions, '0.12.15']): file = next( file for file in registry['releases'][version] @@ -362,7 +393,19 @@ def requirements_matrix(version): sync['installedResponseSha256'] = hashlib.sha256( target.read_bytes() ).hexdigest() - if version == '0.2.37': + # Older uv binaries (0.2.x, 0.3.0) cannot build the ROOT fixture from an + # empty cache under --offline: `setuptools>=40.8.0` is a build dependency + # of the fixture itself, not of the patched wheel. Distinguish that from a + # failure to install the patched wheel by retrying the frozen install with + # network access and recording it as its own row. + backtest = json.loads((base / 'backtest.json').read_text())['commands'] + offline_root_build_failure = any( + row['key'] == 'project-vendored-lock-sync' + and row['exitCode'] != 0 + and 'setuptools' in row['stderr'] + for row in backtest + ) + if offline_root_build_failure: case = base / 'project-vendored' sync = run( exe, From af9c79b8dabf266d991b0ceb5fac6732b06f70bb Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 11:34:48 -0400 Subject: [PATCH 02/25] Follow each uv `[[distribution]]` lock shape when redirecting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The experimental `[[distribution]]` grammar (uv 0.1.x–0.2.34) went through three shapes, and the rewriter keyed everything on the table name: - string sources + sub-table artifacts (`[distribution.sdist]`, `[[distribution.wheel]]`) through 0.2.5; - string sources + inline artifacts (`sdist = {…}`, `wheels = [...]`) from 0.2.6 through 0.2.17; - inline-table sources (`source = { registry = … }`) from 0.2.18. Emitting the string source into a 0.2.18–0.2.34 lock made uv reject it ("data did not match any variant of untagged enum SourceWire") and silently ignore the lock: an ordinary `uv sync` still installed the patch through the pyproject source, but `--frozen` / `--locked` failed. Emitting `[[distribution.wheel]]` into a 0.2.6–0.2.17 lock left the binary with a direct URL and no wheel, which it tried to build as a source archive ("Unsupported archive type: …whl"). Decide the source shape from the entry's own `source` syntax and the artifact shape from its own artifact keys (falling back to any sibling entry), independently. Vendored native wiring stays refused for the whole `[[distribution]]` era: every shape records absolute file paths for local artifacts. Reword the refusal to say so (it claimed "uv 0.1", but 0.2.34 refuses too). Add scripts/probe-uv-boundaries.py — the bisection tool behind every boundary pinned in scripts/backtest-uv.py (lock grammar, source and artifact shape, lock revision, `uv export`, `uv lock --script`, PEP 751 compilation, bare local wheel paths in requirements) — and pin the 0.2.5/0.2.6 and 0.2.17/0.2.18 pairs in the matrix. Document the exact boundaries. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 2 +- .../src/utils/python_lock.rs | 171 +++++++++++++++- .../socket-patch-core/src/vendor/pypi_uv.rs | 2 +- docs/testing/uv-compatibility.md | 82 ++++++-- scripts/backtest-uv.py | 9 + scripts/probe-uv-boundaries.py | 187 ++++++++++++++++++ 6 files changed, 425 insertions(+), 28 deletions(-) create mode 100644 scripts/probe-uv-boundaries.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7e7cf0..dd7aa557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ into the new version's section — see docs/releasing.md. `repair` rebuilds), a script or PEP 751 lock supplements rather than hides `poetry.lock`/`requirements.txt` pins, symlinked locks are discovered, CRLF locks keep their line endings, and the hosted `[tool.uv.sources]` edit - renders as a header after `[project]` the way uv writes it. (#238) + renders as a header after `[project]` the way uv writes it. (#238, #239) - **Path targeting on `scan` and `rollback`.** `scan [PATHS]...` scopes discovery to packages with an installed copy under a matching glob diff --git a/crates/socket-patch-core/src/utils/python_lock.rs b/crates/socket-patch-core/src/utils/python_lock.rs index 965e9f94..ac6e629e 100644 --- a/crates/socket-patch-core/src/utils/python_lock.rs +++ b/crates/socket-patch-core/src/utils/python_lock.rs @@ -505,6 +505,34 @@ pub fn rewrite_python_lock( .get_mut(*index) .expect("matching package index exists"); let original_source = package.get("source").cloned(); + // uv 0.2.20 through 0.2.34 kept the `[[distribution]]` table name but had + // already moved to inline-table sources (`source = { registry = … }`, + // `wheels = [{ … }]`, `dependencies = [{ name = … }]`). Those binaries + // reject the string grammar (`source = "direct+…"`, `[[distribution.wheel]]`) + // with "data did not match any variant of untagged enum SourceWire" and + // then IGNORE the lock: an ordinary `uv sync` re-resolves from the + // pyproject source (still the patch), but `--frozen` / `--locked` fail. + // Follow the entry's OWN source shape, not the table name. + let legacy_strings = legacy + && original_source + .as_ref() + .and_then(source_identity) + .is_some_and(|(kind, _)| kind == "legacy"); + // The artifact shape flipped separately: uv 0.2.14–0.2.17 still write + // string sources but already use inline `sdist = { … }` / `wheels = [ … ]` + // instead of `[distribution.sdist]` / `[[distribution.wheel]]` tables. + // uv 0.2.17 parses an unexpected `[[distribution.wheel]]` but ignores it, + // then treats the direct wheel URL as a source archive ("Unsupported + // archive type: …whl"). Decide from the entry's own artifact keys, then + // from any sibling entry in the document. + let legacy_artifact_tables = legacy + && (package.get("wheel").is_some_and(Item::is_array_of_tables) + || package.get("sdist").is_some_and(Item::is_table) + || (package.get("wheel").is_none() + && package.get("wheels").is_none() + && package.get("sdist").is_none() + && (text.contains("[[distribution.wheel]]") + || text.contains("[distribution.sdist]")))); if !pep751 && !original_source.as_ref().is_some_and(|source| { source_identity(source).is_some_and(|(kind, value)| { @@ -551,11 +579,14 @@ pub fn rewrite_python_lock( ])), ); } else { - let source = if legacy { - match artifact { - ArtifactSource::Url(_) => Item::Value(Value::from(format!("direct+{location}"))), - ArtifactSource::Path(_) => return Err("uv 0.1 lockfiles require absolute file URLs; portable vendoring needs uv >=0.2".to_string()), - } + let source = if legacy && matches!(artifact, ArtifactSource::Path(_)) { + // Both `[[distribution]]` shapes record ABSOLUTE paths/file URLs + // for local artifacts (uv 0.2.34 writes `source = { path = "/abs/…" }` + // and `wheels = [{ url = "file:///abs/…" }]`), so a committed + // relative wheel cannot be expressed portably before 0.2.35. + return Err("uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; portable vendoring needs uv >=0.2.35".to_string()); + } else if legacy_strings { + Item::Value(Value::from(format!("direct+{location}"))) } else { Item::Value(inline(&[(artifact.key(), Value::from(location.clone()))])) }; @@ -574,7 +605,7 @@ pub fn rewrite_python_lock( (artifact_key, Value::from(artifact_location)), ("hash", Value::from(format!("sha256:{sha256}"))), ]); - if legacy && wheel { + if legacy_artifact_tables && wheel { let mut table = Table::new(); table["url"] = toml_edit::value(artifact_location); table["hash"] = toml_edit::value(format!("sha256:{sha256}")); @@ -585,6 +616,11 @@ pub fn rewrite_python_lock( let mut array = Array::new(); array.push_formatted(entry); package.insert("wheels", Item::Value(Value::Array(array))); + } else if legacy_artifact_tables { + let mut table = Table::new(); + table["url"] = toml_edit::value(artifact_location); + table["hash"] = toml_edit::value(format!("sha256:{sha256}")); + package.insert("sdist", Item::Table(table)); } else { package.insert("sdist", Item::Value(entry)); } @@ -749,6 +785,129 @@ hash = "sha256:old" ); } + /// uv 0.2.20–0.2.34 (`uv lock` as shipped): `[[distribution]]` tables + /// with INLINE-TABLE sources. Emitting the string grammar here made those + /// binaries reject the lock ("data did not match any variant of untagged + /// enum SourceWire") and re-resolve, so `--frozen` / `--locked` failed. + #[test] + fn hybrid_distribution_lock_keeps_inline_table_sources() { + let text = r#"version = 1 +requires-python = ">=3.9" + +[[distribution]] +name = "socket-uv-patch-fixture" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "urllib3" }, +] + +[[distribution]] +name = "urllib3" +version = "1.26.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/urllib3-1.26.18.tar.gz", hash = "sha256:old", size = 305687 } +wheels = [ + { url = "https://files.pythonhosted.org/urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:old", size = 143835 }, +] +"#; + let rewritten = + rewrite_python_lock(text, "urllib3", "1.26.18", ArtifactSource::Url(URL), SHA256) + .unwrap() + .unwrap(); + assert!(rewritten.contains("[[distribution]]"), "{rewritten}"); + assert!( + rewritten.contains(&format!("source = {{ url = \"{URL}\" }}")), + "{rewritten}" + ); + assert!( + rewritten.contains(&format!("{{ url = \"{URL}\", hash = \"sha256:{SHA256}\" }}")), + "{rewritten}" + ); + assert!(!rewritten.contains("direct+"), "{rewritten}"); + assert!(!rewritten.contains("[[distribution.wheel]]"), "{rewritten}"); + assert!(!rewritten.contains("sdist"), "{rewritten}"); + assert!(!rewritten.contains("pythonhosted"), "{rewritten}"); + // The root entry's edge and source are untouched; re-run is a no-op. + assert!(rewritten.contains("source = { editable = \".\" }")); + assert_eq!( + rewrite_python_lock( + &rewritten, + "urllib3", + "1.26.18", + ArtifactSource::Url(URL), + SHA256 + ) + .unwrap() + .unwrap(), + rewritten + ); + // Vendoring stays refused for every `[[distribution]]` shape. + let refused = rewrite_python_lock( + text, + "urllib3", + "1.26.18", + ArtifactSource::Path(".socket/vendor/pypi/x/urllib3-1.26.18-py2.py3-none-any.whl"), + SHA256, + ) + .unwrap_err(); + assert!(refused.contains("0.2.35"), "{refused}"); + } + + /// uv 0.2.14–0.2.17: string sources, but INLINE `sdist = {…}` / + /// `wheels = [{…}]` artifacts and bare `[[distribution.dependencies]]` + /// edges. Emitting `[[distribution.wheel]]` here left the binary with a + /// direct URL and no wheel, which it tried to build as an sdist + /// ("Unsupported archive type: urllib3-….whl"). + #[test] + fn string_source_inline_artifact_lock_keeps_inline_wheels() { + let text = r#"version = 1 +requires-python = ">=3.9" + +[[distribution]] +name = "socket-uv-patch-fixture" +version = "0.1.0" +source = "editable+." + +[[distribution.dependencies]] +name = "urllib3" + +[[distribution]] +name = "urllib3" +version = "1.26.18" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/urllib3-1.26.18.tar.gz", hash = "sha256:old", size = 305687 } +wheels = [{ url = "https://files.pythonhosted.org/urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:old", size = 143835 }] +"#; + let rewritten = + rewrite_python_lock(text, "urllib3", "1.26.18", ArtifactSource::Url(URL), SHA256) + .unwrap() + .unwrap(); + assert!( + rewritten.contains(&format!("source = \"direct+{URL}\"")), + "{rewritten}" + ); + assert!( + rewritten.contains(&format!("wheels = [{{ url = \"{URL}\", hash = \"sha256:{SHA256}\" }}]")), + "{rewritten}" + ); + assert!(!rewritten.contains("[[distribution.wheel]]"), "{rewritten}"); + assert!(!rewritten.contains("sdist"), "{rewritten}"); + assert!(rewritten.contains("[[distribution.dependencies]]\nname = \"urllib3\""), "{rewritten}"); + assert_eq!( + rewrite_python_lock( + &rewritten, + "urllib3", + "1.26.18", + ArtifactSource::Url(URL), + SHA256 + ) + .unwrap() + .unwrap(), + rewritten + ); + } + #[test] fn pep751_replaces_registry_artifacts_with_one_archive() { let text = r#"lock-version = "1.0" diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index 9cd66557..2c8613d5 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -114,7 +114,7 @@ pub(super) async fn load_uv_project(root: &Path) -> Result=0.2 for portable native vendoring, or use a requirements.txt installation".to_string(), + "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation".to_string(), )); } diff --git a/docs/testing/uv-compatibility.md b/docs/testing/uv-compatibility.md index d8991ef9..7a926ec9 100644 --- a/docs/testing/uv-compatibility.md +++ b/docs/testing/uv-compatibility.md @@ -33,12 +33,33 @@ frozen, locked, and ordinary installation outcomes separately where supported. ## Limits - uv 0.0 has no native `uv.lock`; its compatibility lane is compiled requirements. - uv 0.0.5 rejects the bare local wheel paths emitted by vendored requirements; - use hosted mode or upgrade uv. Vendored requirements passed from uv 0.1.45 - onward in this matrix. + `uv pip sync` rejects the bare local wheel paths emitted by vendored + requirements through uv 0.1.23 (`Unexpected '.', expected '-c', '-e', '-r' + or the start of a requirement`) and accepts them from 0.1.24; use hosted mode + or upgrade uv for older binaries. +- uv 0.1.x and 0.2.0–0.2.34 write the experimental `[[distribution]]` lock + grammar; `[[package]]` starts at 0.2.35. The grammar went through three + shapes, and the hosted rewriter follows the entry's own shape on each axis: + string sources with sub-table artifacts (`source = "registry+…"`, + `[distribution.sdist]`, `[[distribution.wheel]]`, source-qualified + `[[distribution.dependencies]]`) through 0.2.5; string sources with inline + artifacts (`sdist = { … }`, `wheels = [ … ]`) from 0.2.6 through 0.2.17; and + inline-table sources (`source = { registry = … }`) from 0.2.18. Emitting the + wrong shape is not a parse error the user sees: 0.2.18–0.2.34 reject the + string source and silently ignore the lock (`--frozen` / `--locked` fail, + an ordinary `uv sync` still installs the patch via the pyproject source), + and 0.2.6–0.2.17 ignore an unexpected `[[distribution.wheel]]` and try to + build the direct wheel URL as a source archive. The `[[distribution]]` + grammar is hosted-only: vendored native wiring is refused with + `pypi_uv_legacy_lock_unsupported`, because every shape records absolute file + paths for local artifacts. - Native lock versions other than `version = 1`, and PEP 751 versions other than - `lock-version = "1.0"`, are refused. Native lock revisions and command - availability are measured separately by the matrix below. + `lock-version = "1.0"`, are refused. Lock `revision` values 1 (0.6.0–0.6.14), + 2 (0.6.15–0.8.3) and 3 (0.8.4 onward) are all covered by the matrix below. +- Command availability boundaries observed with real binaries: `uv export` + from 0.4.1; `uv lock --script` from 0.5.17; PEP 751 `uv pip compile + --output-file pylock.toml` from 0.6.15. Earlier binaries record those lanes + as unavailable, not as failures. - A script lock requires its paired script and a valid PEP 723 metadata block. Missing metadata or an incompatible existing source is reported before either file is rewritten. @@ -53,13 +74,23 @@ frozen, locked, and ordinary installation outcomes separately where supported. pins, opaque URLs, and ambiguous unpinned rows are reported as `redirect_requirements_version_ambiguous` and preserved. - A script lock does not replace the main project's lockfile selection merely - by sharing its directory. An unrelated script lock does not block vendoring - a package from the project's requirements or Poetry lock. When multiple + by sharing its directory: its packages supplement the `poetry.lock` or + `requirements.txt` inventory rather than hiding it, and an unrelated script + lock does not block vendoring a package from the project's requirements or + Poetry lock. `uv.lock` keeps its exclusive precedence. When multiple applicable package-manager locks coexist, the CLI reports its precedence choice and the locks it leaves unchanged. -- Vendored installation needs the committed artifact tree. Older uv versions - can also require build dependencies for the root project; an unavailable - offline build dependency is distinct from failure to install the patched wheel. +- Vendored installation needs the committed artifact tree. uv 0.2.x and 0.3.0 + cannot build the ROOT fixture from an empty cache under `--offline` + (`setuptools>=40.8.0` is a build dependency of the fixture, not of the + patched wheel); the harness retries that install with network access and + records it as `project-vendored-frozen-sync-root-build-networked`, distinct + from any failure to install the patched wheel. +- `vendor --revert` refuses to delete a vendored Python wheel while `uv.lock`, + a PEP 751 lock, a script, or `requirements.txt` still references it and the + ledger entry has no wiring to replay (the shape `socket-patch repair` + rebuilds when `state.json` is lost); `vendor_wiring_unknown_revert_blocked` + names the file. Restore the pre-vendor files (or re-lock) first. Revert state retains the original wiring. Script and lock edits are treated as a pair: conflicting changes preserve both files and their recovery state rather @@ -68,28 +99,39 @@ preserving another package's vendored entries. ## Reproduce the release-family matrix -The matrix pins these 14 binaries: `0.0.5`, `0.1.45`, `0.2.37`, `0.3.5`, -`0.4.30`, `0.5.31`, `0.6.0`, `0.6.17`, `0.7.22`, `0.8.24`, `0.9.30`, -`0.10.12`, `0.11.33`, and `0.12.13`. They cover uv 0.0 through 0.12 and the -additional 0.6 lock-revision boundary. This is release-family coverage, not a -claim that every patch release was tested. +The matrix pins 40 binaries: the first and the latest release of every uv 0.x +family (0.0 through 0.12), plus the releases on either side of every behaviour +boundary the probes found — `0.1.23`/`0.1.24` (local wheel paths in +requirements), `0.2.5`/`0.2.6` (sub-table → inline lock artifacts), +`0.2.17`/`0.2.18` (string → inline-table lock sources), +`0.2.34`/`0.2.35` (`[[distribution]]` → `[[package]]`), +`0.4.0`/`0.4.1` (`uv export`), `0.5.16`/`0.5.17` (`uv lock --script`), +`0.6.14`/`0.6.15` (PEP 751 export; lock revision 2) and `0.8.3`/`0.8.4` (lock +revision 3). The full list is `VERSIONS` in `scripts/backtest-uv.py`; the +boundaries were bisected with `scripts/probe-uv-boundaries.py`, which records +the lock grammar, lock revision, command availability, and local-wheel +requirement support of any set of uv releases. This is release-family plus +boundary coverage, not a claim that every patch release was tested. From the repository root on macOS or Linux: ```sh cargo build -p socket-patch-cli +cp target/debug/socket-patch /tmp/socket-patch-backtest-bin python3 scripts/backtest-uv.py \ - --socket-patch target/debug/socket-patch \ + --socket-patch /tmp/socket-patch-backtest-bin \ --socket-patch-revision "$(git rev-parse HEAD)" \ --python /path/to/python3 \ --output /tmp/socket-patch-uv-backtest ``` Use Python 3.9 to match the recorded probes; the fixtures declare it as their -minimum. -`--versions` can select a smaller diagnostic run. The script downloads pinned uv -binaries and the pristine urllib3 wheel from PyPI and verifies their registry -hashes. It runs against the public patch proxy without an API token. +minimum. Copy the CLI out of `target/` first so a rebuild cannot swap the binary +under a running matrix. The default list takes roughly half an hour with the +harness's four workers; `--versions` selects a smaller diagnostic run. The +script downloads pinned uv binaries and the pristine urllib3 wheel from PyPI and +verifies their registry hashes. It runs against the public patch proxy without +an API token. For each binary, the run records command lines, exit codes, output, artifact hashes, and installed `urllib3/response.py` hashes. It exercises native locks, diff --git a/scripts/backtest-uv.py b/scripts/backtest-uv.py index 6b9f7065..644aaec4 100644 --- a/scripts/backtest-uv.py +++ b/scripts/backtest-uv.py @@ -19,6 +19,11 @@ # releases on either side of each behaviour boundary observed with real # binaries (the lower one is the last release WITHOUT the feature): # 0.1.23 / 0.1.24 `uv pip sync` accepts bare `./wheel` requirement paths +# 0.2.5 / 0.2.6 `[[distribution]]` artifacts: `[distribution.sdist]` / +# `[[distribution.wheel]]` tables -> inline `sdist = {…}` / +# `wheels = [...]` values +# 0.2.17 / 0.2.18 `[[distribution]]` sources: `"registry+…"` strings -> +# inline tables (`{ registry = … }`) # 0.2.34 / 0.2.35 uv.lock `[[distribution]]` -> `[[package]]` grammar # 0.4.0 / 0.4.1 `uv export` # 0.5.16 / 0.5.17 `uv lock --script` @@ -31,6 +36,10 @@ '0.1.24', '0.1.45', '0.2.0', + '0.2.5', + '0.2.6', + '0.2.17', + '0.2.18', '0.2.34', '0.2.35', '0.2.37', diff --git a/scripts/probe-uv-boundaries.py b/scripts/probe-uv-boundaries.py new file mode 100644 index 00000000..bb01a3c7 --- /dev/null +++ b/scripts/probe-uv-boundaries.py @@ -0,0 +1,187 @@ +"""Probe uv behaviour boundaries with real binaries. + +For every requested uv release this downloads the PyPI wheel for the current +platform (hash-verified), extracts the `uv` binary, and records: + +- whether `uv lock` exists and which native lock grammar it writes + (`[[distribution]]` vs `[[package]]`), whether sources are strings or + inline tables, whether artifacts are sub-tables or inline values, plus the + lock `revision`; +- whether `uv export` and `uv lock --script` exist; +- whether `uv pip compile --output-file pylock.toml` writes PEP 751; +- whether `uv pip sync` accepts a bare `./wheel --hash=…` requirement line + (the shape vendored requirements use). + +It is the bisection tool behind the boundary versions pinned in +`scripts/backtest-uv.py`; see docs/testing/uv-compatibility.md. One JSON object +per version is printed to stdout. + + python3 scripts/probe-uv-boundaries.py --output /tmp/uv-probe \ + --python /path/to/python3.9 0.2.34 0.2.35 0.8.3 0.8.4 +""" +import argparse +import hashlib +import io +import json +import os +import platform +import shutil +import subprocess +import sys +import tempfile +import urllib.request +import zipfile +from pathlib import Path + +parser = argparse.ArgumentParser() +parser.add_argument('--output', type=Path, required=True) +parser.add_argument('--python', default=sys.executable) +parser.add_argument('versions', nargs='+') +args = parser.parse_args() +ROOT = args.output.resolve() +ROOT.mkdir(parents=True, exist_ok=True) +WHEEL_NAME = 'urllib3-1.26.18-py2.py3-none-any.whl' +ENV = { + key: value + for key, value in os.environ.items() + if not key.startswith(('UV_', 'PIP_', 'PYTHON')) and key != 'VIRTUAL_ENV' +} + + +def fetch_json(url): + with urllib.request.urlopen(url, timeout=90) as response: + return json.load(response) + + +def download(file): + with urllib.request.urlopen(file['url'], timeout=180) as response: + data = response.read() + if hashlib.sha256(data).hexdigest() != file['digests']['sha256']: + raise ValueError('download hash mismatch: ' + file['filename']) + return data + + +def platform_markers(): + system = platform.system() + machine = platform.machine().lower() + if system == 'Darwin': + return 'macosx', 'arm64' if machine in ['arm64', 'aarch64'] else 'x86_64' + if system == 'Linux': + return 'manylinux', 'aarch64' if machine in ['arm64', 'aarch64'] else 'x86_64' + raise ValueError('This probe requires macOS or Linux') + + +REGISTRY = fetch_json('https://pypi.org/pypi/uv/json') + + +def binary(version): + folder = ROOT / 'bin' / version + exe = folder / 'uv' + if exe.exists(): + return exe + marker, architecture = platform_markers() + file = next( + file + for file in REGISTRY['releases'][version] + if marker in file['filename'] and architecture in file['filename'] + ) + folder.mkdir(parents=True, exist_ok=True) + archive = zipfile.ZipFile(io.BytesIO(download(file))) + member = next(name for name in archive.namelist() if name.endswith('/uv')) + exe.write_bytes(archive.read(member)) + exe.chmod(0o755) + return exe + + +def wheel(): + path = ROOT / WHEEL_NAME + if not path.exists(): + release = fetch_json('https://pypi.org/pypi/urllib3/1.26.18/json') + file = next(file for file in release['urls'] if file['filename'] == WHEEL_NAME) + path.write_bytes(download(file)) + return path + + +def run(exe, arguments, cwd): + process = subprocess.run( + [str(exe), *arguments], + cwd=cwd, + env=dict(ENV, UV_CACHE_DIR=str(cwd / '.cache')), + capture_output=True, + text=True, + timeout=300, + ) + return process.returncode, process.stdout, process.stderr + + +def probe(version): + exe = binary(version) + with tempfile.TemporaryDirectory() as raw: + cwd = Path(raw) + (cwd / 'pyproject.toml').write_text( + '[project]\nname = "probe"\nversion = "0.1.0"\n' + 'requires-python = ">=3.9"\ndependencies = ["urllib3==1.26.18"]\n' + ) + lock_rc, _, _ = run(exe, ['lock', '--python', args.python], cwd) + lock = (cwd / 'uv.lock').read_text() if (cwd / 'uv.lock').exists() else '' + if '[[distribution]]' in lock: + schema = 'distribution' + elif '[[package]]' in lock: + schema = 'package' + else: + schema = None + revision = next( + (line.split('=')[1].strip() for line in lock.splitlines() if line.startswith('revision =')), + None, + ) + # uv 0.2.x switched `source = "registry+…"` strings (plus + # `[[distribution.dependencies]]` / `[[distribution.wheel]]` tables) to + # inline tables (`source = { registry = … }`, `wheels = [...]`) while + # still writing `[[distribution]]`; the rewriter must follow the + # entry's own shape, not the table name. + source_lines = [line for line in lock.splitlines() if line.startswith('source = ')] + source_style = None + if source_lines: + source_style = 'table' if source_lines[0].startswith('source = {') else 'string' + # …and, separately, whether artifacts are `[distribution.sdist]` / + # `[[distribution.wheel]]` tables or inline `sdist = { … }` / + # `wheels = [ … ]` values (the two flipped at different releases). + artifact_style = None + if '[[distribution.wheel]]' in lock or '[distribution.sdist]' in lock: + artifact_style = 'tables' + elif 'wheels = [' in lock or 'sdist = {' in lock: + artifact_style = 'inline' + _, help_out, _ = run(exe, ['--help'], cwd) + _, lock_help, _ = run(exe, ['lock', '--help'], cwd) + (cwd / 'requirements.in').write_text('urllib3==1.26.18\n') + run( + exe, + ['pip', 'compile', 'requirements.in', '--python-version', '3.9', '-o', 'pylock.toml'], + cwd, + ) + pylock = (cwd / 'pylock.toml').read_text() if (cwd / 'pylock.toml').exists() else '' + source = wheel() + shutil.copy(source, cwd / source.name) + sha = hashlib.sha256(source.read_bytes()).hexdigest() + (cwd / 'requirements.txt').write_text(f'./{source.name} --hash=sha256:{sha}\n') + venv_rc, _, _ = run(exe, ['venv', '.venv', '--python', args.python], cwd) + sync_rc, _, sync_err = run(exe, ['pip', 'sync', 'requirements.txt'], cwd) + local_ok = venv_rc == 0 and sync_rc == 0 + return { + 'uv': version, + 'lock': lock_rc == 0, + 'lockSchema': schema, + 'lockRevision': int(revision) if revision else None, + 'lockSourceStyle': source_style, + 'lockArtifactStyle': artifact_style, + 'export': 'export' in help_out, + 'lockScript': '--script' in lock_help, + 'pep751Compile': 'lock-version = ' in pylock, + 'localWheelRequirements': local_ok, + 'localWheelError': '' if local_ok else sync_err.strip().splitlines()[-1][:160] if sync_err.strip() else '', + } + + +if __name__ == '__main__': + for version in args.versions: + print(json.dumps(probe(version)), flush=True) From 310b9042abc8803aa5e302a5902b9f53584f6908 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 11:47:58 -0400 Subject: [PATCH 03/25] Vendor into uv 0.2.35/0.2.36 locks that predate [package.metadata] The first two `[[package]]`-grammar releases wrote no root `[package.metadata]`; the metadata step refused them with `pypi_uv_lock_package_missing` although there was nothing to repoint. Skip the requires-dist rewrite when the root unit has no metadata table at all (a lock that HAS metadata but lacks the entry is stale and still refuses), so vendored native wiring covers every `[[package]]` release. Verified with real `uv sync --frozen` / `--locked` / ordinary installs on 0.2.35, 0.2.36 and 0.2.37. Pin 0.2.36/0.2.37 as the metadata boundary in the matrix and document the exact grammar shapes and coverage. Co-Authored-By: Claude Fable 5.1 --- .../socket-patch-core/src/vendor/pypi_uv.rs | 27 ++++++ docs/ecosystems.md | 2 +- docs/testing/uv-compatibility.md | 93 ++++++++++++++----- scripts/backtest-uv.py | 2 + 4 files changed, 98 insertions(+), 26 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index 2c8613d5..aa1810fd 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -1035,6 +1035,15 @@ fn rewrite_root_metadata_entries( })?; let unit_start = unit_span.start; let unit_text = &lock_text[unit_span]; + // uv 0.2.35 and 0.2.36 — the first `[[package]]`-grammar releases — wrote + // no `[package.metadata]` at all (it arrived in 0.2.37). There is no + // requires-dist entry to repoint and nothing for `--locked` to compare; + // the package unit's source plus the pyproject `[tool.uv.sources]` entry + // carry the redirect alone. A lock that HAS metadata but lacks the entry + // is a stale lock and still refuses below. + if !unit_text.contains("[package.metadata]") { + return Ok(Vec::new()); + } let needle = format!("name = \"{canon}\""); let mut edits: Vec = Vec::new(); @@ -2408,6 +2417,24 @@ wheels = [ // ── path-source [package.metadata] reconstruction ────────────────── + /// uv 0.2.35/0.2.36 locks have a root `[[package]]` but no + /// `[package.metadata]`: the metadata step is a no-op rather than a + /// refusal. A lock that carries metadata without the entry still refuses. + #[test] + fn root_metadata_rewrite_is_a_noop_for_locks_without_package_metadata() { + let without = "version = 1\nrequires-python = \">=3.9\"\n\n[[package]]\nname = \"fixture\"\nversion = \"0.1.0\"\nsource = { editable = \".\" }\ndependencies = [\n { name = \"six\" },\n]\n\n[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/six-1.16.0-py2.py3-none-any.whl\", hash = \"sha256:old\" },\n]\n"; + let edits = rewrite_root_metadata_entries(without, "six", REL_WHEEL).unwrap(); + assert!(edits.is_empty()); + let stale = without.replace( + "dependencies = [\n { name = \"six\" },\n]\n", + "dependencies = [\n { name = \"six\" },\n]\n\n[package.metadata]\nrequires-dist = [{ name = \"other\", specifier = \"==1\" }]\n", + ); + let err = rewrite_root_metadata_entries(&stale, "six", REL_WHEEL) + .err() + .expect("metadata without the entry must refuse"); + assert_eq!(err.0, "pypi_uv_lock_package_missing"); + } + #[test] fn parse_requires_dist_pulls_apart_name_extras_specifier_marker() { // extra-gated dep with a bare specifier diff --git a/docs/ecosystems.md b/docs/ecosystems.md index eb1ea778..2923ac48 100644 --- a/docs/ecosystems.md +++ b/docs/ecosystems.md @@ -15,7 +15,7 @@ The backticked slug in each row is the value `-e`/`--ecosystems` accepts (e.g. | Ecosystem | agent (`--mode agent`) | vendored (`--mode vendored`) | hosted (`--mode hosted`) | |-----------|------------------------|------------------------------|--------------------------| | npm (`npm`) — pnpm / yarn / berry / bun | ✅ any install layout; `setup` postinstall hook | ✅ six lockfile flavors: package-lock, yarn classic, yarn berry (node-modules linker; PnP refused), pnpm v9, pnpm legacy v5.4/v6.0 (`pnpm 7/8` — frozen installs are path-bound because those majors absolutize `file:` override specifiers; moved checkouts run one `pnpm install --offline --no-frozen-lockfile`, surfaced as `vendor_pnpm_legacy_absolute_specifier`), bun `bun.lock` (binary `bun.lockb` refused with a `--save-text-lockfile` pointer). Rush monorepos refused (`vendor_rush_unsupported`) — see [Rush notes](#npm-rush-monorepos) | ✅ package-lock / npm-shrinkwrap, pnpm-lock.yaml (pnpm v5.4/v6.0/v9 — every major since pnpm 7), yarn classic, yarn berry, bun — pnpm, berry, and bun carry constraints, see [npm hosted-mode notes](#npm-hosted-mode-notes) | -| PyPI (`pypi`) — uv / poetry / pdm / pipenv / pip | ✅ `.pth` startup hook via `setup` | ✅ uv project/script locks, PEP 751 `pylock.toml` / `pylock..toml`, poetry, pdm, pipenv (lock rewired, but pipenv doesn't hash-check file entries — `vendor_integrity_unverified` warning; the committed wheel bytes are the protection), and requirements.txt. Native uv vendoring requires uv ≥ 0.2; see [uv compatibility](testing/uv-compatibility.md). | ✅ requirements.txt including hash continuations, uv project/script locks, and PEP 751 locks. Version/source ambiguity is refused; see [uv compatibility](testing/uv-compatibility.md). **poetry / pdm / pipenv locks are not rewritten** — use vendored | +| PyPI (`pypi`) — uv / poetry / pdm / pipenv / pip | ✅ `.pth` startup hook via `setup` | ✅ uv project/script locks, PEP 751 `pylock.toml` / `pylock..toml`, poetry, pdm, pipenv (lock rewired, but pipenv doesn't hash-check file entries — `vendor_integrity_unverified` warning; the committed wheel bytes are the protection), and requirements.txt. Native uv vendoring requires uv ≥ 0.2.35 (the `[[package]]` lock grammar); hosted mode covers every uv release since 0.1; see [uv compatibility](testing/uv-compatibility.md). | ✅ requirements.txt including hash continuations, uv project/script locks, and PEP 751 locks. Version/source ambiguity is refused; see [uv compatibility](testing/uv-compatibility.md). **poetry / pdm / pipenv locks are not rewritten** — use vendored | | Cargo (`cargo`) | ✅ in-place + `.cargo-checksum.json` rewrite (shared registry-cache caveat — see [Cargo: shared registry cache](#cargo-shared-registry-cache)) | ✅ `[patch.crates-io]` path entry | ✅ per-patch sparse registry (`[registries.socket-patch-]` + Cargo.lock source/checksum) | | RubyGems (`gem`) | ✅ Bundler plugin via `setup` — needs bundler ≥ 2.2 (1.x cannot load `plugin ... path:` directives; `setup` refuses below the floor and `setup --check` red-flags a wired 1.x project) | ✅ Gemfile + Gemfile.lock path pair (`Gemfile` spelling only — a `gems.rb` project cannot vendor yet) | ✅ per-dep `source` block — edits `gems.rb` + `gems.locked` when present (bundler prefers them over `Gemfile`; spellings that diverge beyond Socket's own edits fail closed with `redirect_gem_gemfile_spellings_diverge`); the `CHECKSUMS` pin needs bundler ≥ 2.6 (older locks get a `redirect_gem_no_checksums_section` warning); a stale pre-redirect materialization that `bundle install` would reuse instead of refetching is flagged `redirect_gem_stale_install` with a prescriptive remedy (see CLI_CONTRACT.md's "Gem stale-install guard") | | Go (`golang`) | ✅ `go.mod` `replace` → `.socket/go-patches/` — see [Go: directory replaces and go.sum](#go-directory-replaces-and-gosum) | ✅ `replace` → the committed vendor tree | ✅ (free tier) fork-style `replace` → `patch.socket.dev/gopatch/` + committed `go.sum` pin; see [golang-hosted.md](design/golang-hosted.md). Paid tier stays ❌ ([golang-hosted-no-go.md](design/golang-hosted-no-go.md)); `redirect_golang_unsupported` names the vendored remedy | diff --git a/docs/testing/uv-compatibility.md b/docs/testing/uv-compatibility.md index 7a926ec9..3d619128 100644 --- a/docs/testing/uv-compatibility.md +++ b/docs/testing/uv-compatibility.md @@ -17,7 +17,7 @@ managers. |-------|--------|----------| | `requirements.txt`, including uv-generated hash continuations | Exact version pins become direct artifact URLs with the patched SHA-256. Extras and markers are retained; hashes for the replaced artifact are removed. | Requirements refer to a committed wheel under `.socket/vendor/pypi/` with its hash. | | `uv.lock`, native `version = 1`, `[[package]]` | The package source and artifact entry agree on the hosted URL and hash. A paired `pyproject.toml` receives the corresponding uv source configuration. | The package source refers to the committed wheel. The paired `pyproject.toml` records that source. | -| `uv.lock`, experimental uv 0.1 `[[distribution]]` | Uses the legacy direct-source and artifact grammar, including source-qualified dependency references. | Refused: this grammar needs absolute file URLs, which cannot provide portable vendoring. Use uv 0.2 or newer. | +| `uv.lock`, experimental `[[distribution]]` (uv 0.1.x–0.2.34) | Follows the entry's own shape: `direct+` string or `{ url = … }` table source, `[[distribution.wheel]]` sub-table or inline `wheels` entry, and source-qualified dependency references where the lock carries them. | Refused (`pypi_uv_legacy_lock_unsupported`): every shape of this grammar records absolute file paths, which cannot provide portable vendoring. Use uv 0.2.35 or newer. | | `*.py.lock` with its PEP 723 `*.py` script | Rewrites the lock and the script's uv source metadata together. | Rewrites the lock and script metadata together and commits the patched wheel. | | `pylock.toml` and `pylock..toml`, PEP 751 `lock-version = "1.0"` | Uses one `archive` URL with the patched SHA-256. | Uses one `archive` path with the committed wheel's SHA-256. | @@ -53,6 +53,14 @@ frozen, locked, and ordinary installation outcomes separately where supported. grammar is hosted-only: vendored native wiring is refused with `pypi_uv_legacy_lock_unsupported`, because every shape records absolute file paths for local artifacts. +- Vendored native wiring covers every `[[package]]`-grammar release, uv 0.2.35 + onward. uv 0.2.35 and 0.2.36 wrote no root `[package.metadata]` yet (it + arrived in 0.2.37); on those locks the requires-dist repoint is skipped + rather than refused, and the package source plus the pyproject + `[tool.uv.sources]` entry carry the redirect (verified with `--frozen`, + `--locked`, and ordinary installs). A lock that has metadata but no entry + for the package is stale and is still refused with + `pypi_uv_lock_package_missing`. - Native lock versions other than `version = 1`, and PEP 751 versions other than `lock-version = "1.0"`, are refused. Lock `revision` values 1 (0.6.0–0.6.14), 2 (0.6.15–0.8.3) and 3 (0.8.4 onward) are all covered by the matrix below. @@ -99,12 +107,13 @@ preserving another package's vendored entries. ## Reproduce the release-family matrix -The matrix pins 40 binaries: the first and the latest release of every uv 0.x +The matrix pins 41 binaries: the first and the latest release of every uv 0.x family (0.0 through 0.12), plus the releases on either side of every behaviour boundary the probes found — `0.1.23`/`0.1.24` (local wheel paths in requirements), `0.2.5`/`0.2.6` (sub-table → inline lock artifacts), `0.2.17`/`0.2.18` (string → inline-table lock sources), -`0.2.34`/`0.2.35` (`[[distribution]]` → `[[package]]`), +`0.2.34`/`0.2.35` (`[[distribution]]` → `[[package]]`), `0.2.36`/`0.2.37` +(root `[package.metadata]` appears), `0.4.0`/`0.4.1` (`uv export`), `0.5.16`/`0.5.17` (`uv lock --script`), `0.6.14`/`0.6.15` (PEP 751 export; lock revision 2) and `0.8.3`/`0.8.4` (lock revision 3). The full list is `VERSIONS` in `scripts/backtest-uv.py`; the @@ -148,21 +157,23 @@ not just with a URL or a success message. ## Full matrix results -The complete run finished on **2026-09-14**, using macOS **26.6.2 arm64** and +The complete run finished on **2026-09-15**, using **macOS-26.6.2-arm64-arm-64bit** and Python **3.9.6**. It tested socket-patch source commit -`e11bd419ea9c01b3ecd1aa894b55874718a3ff0a` (`socket-patch 4.0.0`), with binary +`af9c79b8dabf266d991b0ceb5fac6732b06f70bb` (`socket-patch 4.0.0`), with binary SHA-256: ```text -eb5f6695a06c2124ac5c09f2117bf42e8777d767aec09c1de6ee16cd9dc4adee +7da27a3343d7007ddfdc275a2caae3e196894f540d6a9d2ec9ab3f0df6ebdc3b ``` -All **230 installed-byte comparisons passed**, with zero mismatches. All **99 -recorded lock-preservation checks passed**. This includes frozen and locked -installs where supported; ordinary installs also delivered the patched bytes. -The [machine-readable results](uv-compatibility/results.json) contain all 495 -observations and their command definitions. The [binary catalog](uv-compatibility/binaries.json) -records each uv wheel's public PyPI source and verified hash. +All **570 installed-byte comparisons passed**, with zero mismatches. All **234 +recorded lock-preservation checks passed** (`--frozen` and `--locked` installs +where the binary provides them; `--frozen` never writes the lock, so the +`--locked` rows are the ones that measure preservation). Ordinary installs +also delivered the patched bytes. The [machine-readable results](uv-compatibility/results.json) +contain all 1290 observations and their command definitions. The +[binary catalog](uv-compatibility/binaries.json) records each uv wheel's public +PyPI source and verified hash. Each paired result below is **hosted / vendored**. “Pass” means the installed `urllib3/response.py` matched the published patch; “—” means that uv binary did @@ -172,35 +183,67 @@ compilation. PEP 751 covers both standalone locks and exported locks. | uv | Native grammar | Native H/V | Requirements H/V | Requirements export H/V | Scripts H/V | PEP 751 H/V | Verified installs | |----|----------------|------------|------------------|-------------------------|-------------|-------------|-------------------| | 0.0.5 | No native lock | — / — | Pass / rejected path | — / — | — / — | — / — | 2 | +| 0.1.0 | No native lock | — / — | Pass / rejected path | — / — | — / — | — / — | 2 | +| 0.1.23 | No native lock | — / — | Pass / rejected path | — / — | — / — | — / — | 2 | +| 0.1.24 | No native lock | — / — | Pass / Pass | — / — | — / — | — / — | 4 | | 0.1.45 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.0 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.5 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.6 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.17 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.18 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | +| 0.2.34 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 7 | +| 0.2.35 | `package`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 7 | | 0.2.37 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | +| 0.3.0 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | | 0.3.5 | `package`, v1 | Pass / Pass | Pass / Pass | — / — | — / — | — / — | 10 | +| 0.4.0 | `package`, v1 | Pass / Pass | Pass / Pass | — / — | — / — | — / — | 10 | +| 0.4.1 | `package`, v1 | Pass / Pass | Pass / Pass | — / — | — / — | — / — | 10 | | 0.4.30 | `package`, v1 | Pass / Pass | Pass / Pass | Pass / Pass | — / — | — / — | 12 | +| 0.5.0 | `package`, v1 | Pass / Pass | Pass / Pass | Pass / Pass | — / — | — / — | 12 | +| 0.5.16 | `package`, v1 | Pass / Pass | Pass / Pass | Pass / Pass | — / — | — / — | 12 | +| 0.5.17 | `package`, v1 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | — / — | 18 | | 0.5.31 | `package`, v1 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | — / — | 18 | | 0.6.0 | `package`, v1 r1 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | — / — | 18 | +| 0.6.14 | `package`, v1 r1 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | — / — | 18 | +| 0.6.15 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.6.17 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.7.0 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.7.22 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.8.0 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.8.3 | `package`, v1 r2 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.8.4 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.8.24 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.9.0 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.9.30 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.10.0 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.10.12 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.11.0 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | | 0.11.33 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | -| 0.12.13 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.12.0 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | +| 0.12.15 | `package`, v1 r3 | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | Pass / Pass | 22 | The nonzero outcomes were the documented boundaries: -- uv 0.0.5 rejected vendored requirements' local wheel path syntax, although - both hosted requirements variants installed the patch. -- Native vendoring on uv 0.1.45 was refused with - `pypi_uv_legacy_lock_unsupported`; hosted native installs and both requirements - modes installed the patch. -- ¹ uv 0.2.37's cold offline native install could not build the root fixture - because `setuptools>=40.8.0` was absent from its empty cache. The network-enabled - retry installed the patched wheel. Its subsequent locked and ordinary - installation checks also passed. +- uv 0.0.5 through 0.1.23 rejected vendored requirements' local wheel path + syntax (`Unexpected '.', expected '-c', '-e', '-r' or the start of a + requirement`); 0.1.24 onward accepted it. Both hosted requirements variants + installed the patch on every binary. +- Native vendoring on every `[[distribution]]`-grammar binary (0.1.45 through + 0.2.34) was refused with `pypi_uv_legacy_lock_unsupported`; hosted native + installs — in all three shapes: sub-table artifacts (through 0.2.5), inline + artifacts with string sources (0.2.6–0.2.17), and inline-table sources + (0.2.18–0.2.34) — and both requirements modes installed the patch. +- ¹ uv 0.2.35, 0.2.37 and 0.3.0 cannot build the root fixture from an empty + cache under `--offline` (`setuptools>=40.8.0` was absent). The + network-enabled retry (`project-vendored-frozen-sync-root-build-networked`) + installed the patched wheel; the subsequent locked and ordinary installation + checks also passed. - Export, script-lock, and PEP 751 commands unavailable in older binaries were - recorded as unavailable, not installation successes. Some older uv binaries - accepted an output filename ending in `pylock.toml` but emitted requirements - text; those results have `formatSupported: false`. + recorded as unavailable, not installation successes (`uv export` from 0.4.1, + `uv lock --script` from 0.5.17, PEP 751 compilation from 0.6.15). Some older + uv binaries accepted an output filename ending in `pylock.toml` but emitted + requirements text; those results have `formatSupported: false`. ## Completed conditional-requirements and refusal checks diff --git a/scripts/backtest-uv.py b/scripts/backtest-uv.py index 644aaec4..3145de01 100644 --- a/scripts/backtest-uv.py +++ b/scripts/backtest-uv.py @@ -25,6 +25,7 @@ # 0.2.17 / 0.2.18 `[[distribution]]` sources: `"registry+…"` strings -> # inline tables (`{ registry = … }`) # 0.2.34 / 0.2.35 uv.lock `[[distribution]]` -> `[[package]]` grammar +# 0.2.36 / 0.2.37 root `[package.metadata]` (requires-dist) appears # 0.4.0 / 0.4.1 `uv export` # 0.5.16 / 0.5.17 `uv lock --script` # 0.6.14 / 0.6.15 PEP 751 `pip compile -o pylock.toml`; lock revision 1 -> 2 @@ -42,6 +43,7 @@ '0.2.18', '0.2.34', '0.2.35', + '0.2.36', '0.2.37', '0.3.0', '0.3.5', From 3726770e01052b90c8fd7c8ece6e0b9cb6d31873 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 11:54:16 -0400 Subject: [PATCH 04/25] Record the 41-release uv compatibility matrix for source 310b904 Regenerate results.json, binaries.json, and the results section of docs/testing/uv-compatibility.md from a full run of scripts/backtest-uv.py against the rebuilt CLI: 41 pinned uv releases (first and latest of every 0.x family plus every observed behaviour boundary), 583 installed-byte comparisons and 240 lock-preservation checks, zero mismatches and zero lock changes. Every non-zero exit is a documented uv boundary or the cold-offline root-build case retried with network. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 8 +- docs/testing/uv-compatibility.md | 13 +- docs/testing/uv-compatibility/binaries.json | 199 +- docs/testing/uv-compatibility/results.json | 5964 ++++++++++++++++++- 4 files changed, 5976 insertions(+), 208 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7aa557..84963fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,8 +58,12 @@ into the new version's section — see docs/releasing.md. and uv-compiled hashed `requirements.txt`, so `uv sync --frozen|--locked`, `uv run --script`, and `uv pip sync --require-hashes` install the patched wheel instead of the registry artifact. Verified against real uv binaries - from every 0.x release family (0.0 through 0.12), first and latest release - of each — see `docs/testing/uv-compatibility.md`. Follow-up hardening: + from every 0.x release family (0.0 through 0.12) — first and latest release + of each plus every observed behaviour boundary — see + `docs/testing/uv-compatibility.md`: hosted mode covers every uv release + since 0.1 (all three `[[distribution]]` lock shapes and `[[package]]`), + vendored native covers every `[[package]]` release (uv ≥ 0.2.35), vendored + requirements cover uv ≥ 0.1.24. Follow-up hardening: `vendor --revert` refuses to delete a vendored Python wheel a lock still references when the ledger entry has no wiring to replay (the shape `repair` rebuilds), a script or PEP 751 lock supplements rather than hides diff --git a/docs/testing/uv-compatibility.md b/docs/testing/uv-compatibility.md index 3d619128..ee7b3721 100644 --- a/docs/testing/uv-compatibility.md +++ b/docs/testing/uv-compatibility.md @@ -159,19 +159,19 @@ not just with a URL or a success message. The complete run finished on **2026-09-15**, using **macOS-26.6.2-arm64-arm-64bit** and Python **3.9.6**. It tested socket-patch source commit -`af9c79b8dabf266d991b0ceb5fac6732b06f70bb` (`socket-patch 4.0.0`), with binary +`310b9042abc8803aa5e302a5902b9f53584f6908` (`socket-patch 4.0.0`), with binary SHA-256: ```text -7da27a3343d7007ddfdc275a2caae3e196894f540d6a9d2ec9ab3f0df6ebdc3b +79e900f11714ee1575b57e7f494364094ca7d0ff953abddef597b839c5211379 ``` -All **570 installed-byte comparisons passed**, with zero mismatches. All **234 +All **583 installed-byte comparisons passed**, with zero mismatches. All **240 recorded lock-preservation checks passed** (`--frozen` and `--locked` installs where the binary provides them; `--frozen` never writes the lock, so the `--locked` rows are the ones that measure preservation). Ordinary installs also delivered the patched bytes. The [machine-readable results](uv-compatibility/results.json) -contain all 1290 observations and their command definitions. The +contain all 1324 observations and their command definitions. The [binary catalog](uv-compatibility/binaries.json) records each uv wheel's public PyPI source and verified hash. @@ -193,7 +193,8 @@ compilation. PEP 751 covers both standalone locks and exported locks. | 0.2.17 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | | 0.2.18 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 6 | | 0.2.34 | `distribution`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 7 | -| 0.2.35 | `package`, v1 | Pass / refused | Pass / Pass | — / — | — / — | — / — | 7 | +| 0.2.35 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | +| 0.2.36 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | | 0.2.37 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | | 0.3.0 | `package`, v1 | Pass / Pass¹ | Pass / Pass | — / — | — / — | — / — | 10 | | 0.3.5 | `package`, v1 | Pass / Pass | Pass / Pass | — / — | — / — | — / — | 10 | @@ -234,7 +235,7 @@ The nonzero outcomes were the documented boundaries: installs — in all three shapes: sub-table artifacts (through 0.2.5), inline artifacts with string sources (0.2.6–0.2.17), and inline-table sources (0.2.18–0.2.34) — and both requirements modes installed the patch. -- ¹ uv 0.2.35, 0.2.37 and 0.3.0 cannot build the root fixture from an empty +- ¹ uv 0.2.35, 0.2.36, 0.2.37 and 0.3.0 cannot build the root fixture from an empty cache under `--offline` (`setuptools>=40.8.0` was absent). The network-enabled retry (`project-vendored-frozen-sync-root-build-networked`) installed the patched wheel; the subsequent locked and ordinary installation diff --git a/docs/testing/uv-compatibility/binaries.json b/docs/testing/uv-compatibility/binaries.json index 5a97503d..459709d1 100644 --- a/docs/testing/uv-compatibility/binaries.json +++ b/docs/testing/uv-compatibility/binaries.json @@ -6,6 +6,27 @@ "uploaded": "2024-02-15T18:56:14.105999Z", "filename": "uv-0.0.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" }, + { + "version": "0.1.0", + "url": "https://files.pythonhosted.org/packages/0c/e3/415f40a86918316951b69b42661731bf6c5e5007f4063bc564773aec4815/uv-0.1.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", + "sha256": "71291b3b3222da25a6af5ab9dfaec3bdd97fd4f0d21cf38a04cbd63000fa9da5", + "uploaded": "2024-02-15T19:44:47.783250Z", + "filename": "uv-0.1.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" + }, + { + "version": "0.1.23", + "url": "https://files.pythonhosted.org/packages/e4/02/824e40366ecd913483d67723aa10079b5d127b43d7f8c3f164dcf19fb9c4/uv-0.1.23-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", + "sha256": "d9d1d6d2bca50b9ea2ed764a6931b53995f6b5dbf3def9eafbcec57a88b4a6e2", + "uploaded": "2024-03-21T03:09:59.075241Z", + "filename": "uv-0.1.23-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" + }, + { + "version": "0.1.24", + "url": "https://files.pythonhosted.org/packages/1e/ef/90fc17103183c23e8e5142c455b1ca8c1c38142c89c8aaa27f7ab37c34f5/uv-0.1.24-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", + "sha256": "33d74c4c67df34de18c4318a6c568efef9dfab6d05332b1d1eddc8e516fc8806", + "uploaded": "2024-03-22T20:15:08.127518Z", + "filename": "uv-0.1.24-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" + }, { "version": "0.1.45", "url": "https://files.pythonhosted.org/packages/7f/15/46efcaa86ebef51b8663d8beb95c8376fbdaaf45aeffbc269bf0a3527092/uv-0.1.45-py3-none-macosx_11_0_arm64.whl", @@ -13,6 +34,62 @@ "uploaded": "2024-05-20T21:05:54.623510Z", "filename": "uv-0.1.45-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.2.0", + "url": "https://files.pythonhosted.org/packages/cc/4a/7c3d702c5920487d481bd03a3ca821f8886ad2d6937e49f4efc0a16c6e61/uv-0.2.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "ba5e339a0fb32a02142346eea4d46f6ad618a390df5d70515ba0e1d3c6f7ce41", + "uploaded": "2024-05-22T19:11:42.407716Z", + "filename": "uv-0.2.0-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.5", + "url": "https://files.pythonhosted.org/packages/06/d2/ef1c65a0715731da623b430757a9b851167309fc7e326140d27d246e6541/uv-0.2.5-py3-none-macosx_11_0_arm64.whl", + "sha256": "650f81439c4f65e86fdba84f2cbb6700a074b95d70c5cc3d38e8ce2b87e43a45", + "uploaded": "2024-05-28T18:33:49.720762Z", + "filename": "uv-0.2.5-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.6", + "url": "https://files.pythonhosted.org/packages/a0/53/4c9a7370ec5bf75b89b82117fae68d7bb268d229deb82c51264ca27a1368/uv-0.2.6-py3-none-macosx_11_0_arm64.whl", + "sha256": "1ac3b96c6284ef2e5367e62f31472cc3f27ba4c7e14f579e7c6dbd081943e38b", + "uploaded": "2024-06-03T18:22:26.325121Z", + "filename": "uv-0.2.6-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.17", + "url": "https://files.pythonhosted.org/packages/90/c0/27b6fc7cc85984b0c86ffd0e42abf4c97aeff2cc3425b09ce86e679bedae/uv-0.2.17-py3-none-macosx_11_0_arm64.whl", + "sha256": "d6628bcb0d21f2f8489ed33818fa6c2da3a472adead076864701ae7a3bafb4de", + "uploaded": "2024-06-26T23:39:17.070293Z", + "filename": "uv-0.2.17-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.18", + "url": "https://files.pythonhosted.org/packages/60/0d/d462d188343464876ca196f064ba1682982446012c4893da0d7dbba01a8c/uv-0.2.18-py3-none-macosx_11_0_arm64.whl", + "sha256": "eee9773a0a9f02d084a584279891df1fdf4de32d067f273b9480f86c9f91dcdb", + "uploaded": "2024-06-29T18:49:23.499620Z", + "filename": "uv-0.2.18-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.34", + "url": "https://files.pythonhosted.org/packages/f2/a0/840b0fa6b9f884384b558b8690cad603bdc0da2b7a67cec2755b612839c9/uv-0.2.34-py3-none-macosx_11_0_arm64.whl", + "sha256": "4ce15beeba44e4ea052d83c89eb4ea3586dfd68bab039c5cdf44b90fbfc5698d", + "uploaded": "2024-08-07T20:59:42.011904Z", + "filename": "uv-0.2.34-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.35", + "url": "https://files.pythonhosted.org/packages/00/eb/c578ebb0f606be6a3a1c37e36a8b6068008ceab54db95a2ab70452c792fd/uv-0.2.35-py3-none-macosx_11_0_arm64.whl", + "sha256": "3ff91eb85e0804d5f609f18a911b33d81b06e11c1bced1bf1396a2d738e97bc0", + "uploaded": "2024-08-10T00:42:30.944714Z", + "filename": "uv-0.2.35-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.2.36", + "url": "https://files.pythonhosted.org/packages/80/40/e951bc5598dbd7d58a6daf33a93977ecbaad60fdaa584610349f00acf521/uv-0.2.36-py3-none-macosx_11_0_arm64.whl", + "sha256": "8820dd5b77ffcda07dde09712a43d969d39b0aace112d8074c540f19a4911cc2", + "uploaded": "2024-08-13T17:28:27.711187Z", + "filename": "uv-0.2.36-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.2.37", "url": "https://files.pythonhosted.org/packages/e6/84/2c973ddb320642d02d2d117123a61ec6666bbc0143f5263b1e0c791c1252/uv-0.2.37-py3-none-macosx_11_0_arm64.whl", @@ -20,6 +97,13 @@ "uploaded": "2024-08-16T02:45:56.044623Z", "filename": "uv-0.2.37-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.3.0", + "url": "https://files.pythonhosted.org/packages/61/95/b6603342b9a0a180776e7aec845079cdc66fb521157ed9bf62b4e4174983/uv-0.3.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "39a4276afe0808ca6c033e0cd6cb73249f934b4a0c9d7b18a944f3f8ea635e27", + "uploaded": "2024-08-20T17:52:38.258401Z", + "filename": "uv-0.3.0-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.3.5", "url": "https://files.pythonhosted.org/packages/83/8e/956ad3788cfa863cc8de148907e371b025acd97f7a0bb2a9e78ce63c2b1e/uv-0.3.5-py3-none-macosx_11_0_arm64.whl", @@ -27,6 +111,20 @@ "uploaded": "2024-08-27T17:10:36.293543Z", "filename": "uv-0.3.5-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.4.0", + "url": "https://files.pythonhosted.org/packages/2e/86/9844e8ab08e25cbf2094e2fa1a7ad66563036bfed77b46986b6a2489c10c/uv-0.4.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "02e0295566454289348de502677e2240ad86f2cb5fa058504e1b2ca2a2ebf7e1", + "uploaded": "2024-08-28T18:00:42.051786Z", + "filename": "uv-0.4.0-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.4.1", + "url": "https://files.pythonhosted.org/packages/46/ad/90e0d7a448b4c92846589c8bb92813ac45d2b123a2b9d4cab4167e805f9f/uv-0.4.1-py3-none-macosx_11_0_arm64.whl", + "sha256": "fcdc6503100e86fecf6a727d3149e54581e8e9ad6c10e814fc5d22c1e80fab8d", + "uploaded": "2024-08-30T14:42:40.152858Z", + "filename": "uv-0.4.1-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.4.30", "url": "https://files.pythonhosted.org/packages/67/37/8994c3d0be99851a21a6ee01bbf3cb35ddc4b202a2f6f4014098d5893660/uv-0.4.30-py3-none-macosx_11_0_arm64.whl", @@ -34,6 +132,27 @@ "uploaded": "2024-11-05T01:13:59.667063Z", "filename": "uv-0.4.30-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.5.0", + "url": "https://files.pythonhosted.org/packages/69/fb/e778bce57b12eac37c1e9e8e1efc8b190a2c72dee02b532d13706be447f4/uv-0.5.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "a3bc6911be7d86f3750bce1580e664877a3a88c126eb68afbb132cd0896fd109", + "uploaded": "2024-11-07T23:05:15.972013Z", + "filename": "uv-0.5.0-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.5.16", + "url": "https://files.pythonhosted.org/packages/0f/c4/74f4507f48ce5e38cef46cecd7716af0348dffcb3d40245189422226c7b7/uv-0.5.16-py3-none-macosx_11_0_arm64.whl", + "sha256": "3419888b178b82511faebd8d1ca9cb9f5920a7142406898d76878adaffe8dfb1", + "uploaded": "2025-01-08T16:40:50.904248Z", + "filename": "uv-0.5.16-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.5.17", + "url": "https://files.pythonhosted.org/packages/f1/7e/4c8b7ca07945fe6ffd1a7e5d1f992b72534be69e97e20a2536d192734adc/uv-0.5.17-py3-none-macosx_11_0_arm64.whl", + "sha256": "12789bf19457e3c5fc20767960203ab60222124afe2cbdfde92a657318651a64", + "uploaded": "2025-01-10T21:13:54.034599Z", + "filename": "uv-0.5.17-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.5.31", "url": "https://files.pythonhosted.org/packages/1f/5a/1eb42f481a9f9010c8c194d70ab375a6eda96d67ca1fd011bf869d4016c8/uv-0.5.31-py3-none-macosx_11_0_arm64.whl", @@ -48,6 +167,20 @@ "uploaded": "2025-02-14T18:20:31.778738Z", "filename": "uv-0.6.0-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.6.14", + "url": "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", + "sha256": "9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", + "uploaded": "2025-04-09T21:56:12.061201Z", + "filename": "uv-0.6.14-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.6.15", + "url": "https://files.pythonhosted.org/packages/f6/88/ad35ecc3b986274ff560267cb6e3b9119ca0f8cc82c61e2acae28c8b9ffe/uv-0.6.15-py3-none-macosx_11_0_arm64.whl", + "sha256": "f113d1746ce7fdffada6f9e12b2f667d2b9069a3c3d5b05b680836102f2586c1", + "uploaded": "2025-04-22T00:50:26.375142Z", + "filename": "uv-0.6.15-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.6.17", "url": "https://files.pythonhosted.org/packages/a5/4f/66c7153120c155446f319647c1bafec2d9288f2b48d769cd9f9da39aa1f2/uv-0.6.17-py3-none-macosx_11_0_arm64.whl", @@ -55,6 +188,13 @@ "uploaded": "2025-04-25T18:51:15.410083Z", "filename": "uv-0.6.17-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.7.0", + "url": "https://files.pythonhosted.org/packages/ed/1d/cad304d6107208fdd90adf5ca519e86a2e35786cb7229d6cd39cf29bec3d/uv-0.7.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "1f48eeaeb46a5f81be873c1662c999e78ff267798ecea9dd48c7082e022048ec", + "uploaded": "2025-04-29T22:03:35.477030Z", + "filename": "uv-0.7.0-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.7.22", "url": "https://files.pythonhosted.org/packages/64/f5/0ee734f5e988fd8f26aad1f150703fe8c7d664029c9c677b989b69caf104/uv-0.7.22-py3-none-macosx_11_0_arm64.whl", @@ -62,6 +202,27 @@ "uploaded": "2025-07-17T17:00:10.010548Z", "filename": "uv-0.7.22-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.8.0", + "url": "https://files.pythonhosted.org/packages/9d/98/9a89983caa05cf998eea3dac1e6cff2e0ab8099be0695fd8b9dc6a5038a0/uv-0.8.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "2d0ebf05eaee75921b3f23e7401a56bc0732bcdabb7469081ab00769340a93b4", + "uploaded": "2025-07-17T22:51:04.941749Z", + "filename": "uv-0.8.0-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.8.3", + "url": "https://files.pythonhosted.org/packages/9c/ba/8ceec5d6a1adf6b827db557077d8059e573a84c3708a70433d22a0470fab/uv-0.8.3-py3-none-macosx_11_0_arm64.whl", + "sha256": "3f904f574dc2d7aa1d96ddf2483480ecd121dc9d060108cadd8bff100b754b64", + "uploaded": "2025-07-24T21:13:57.570096Z", + "filename": "uv-0.8.3-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.8.4", + "url": "https://files.pythonhosted.org/packages/16/39/7d4b68132868c550ae97c3b2c348c55db47a987dff05ab0e5f577bf0e197/uv-0.8.4-py3-none-macosx_11_0_arm64.whl", + "sha256": "edc813645348665a3b4716a7d5e961cf7c8d1d3bfb9d907a4f18cf87c712a430", + "uploaded": "2025-07-30T17:10:20.417141Z", + "filename": "uv-0.8.4-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.8.24", "url": "https://files.pythonhosted.org/packages/ea/00/08f4e93989129bb3378f20315dddcac6f8cf26a12bdd90443a340e7ecdb4/uv-0.8.24-py3-none-macosx_11_0_arm64.whl", @@ -69,6 +230,13 @@ "uploaded": "2025-10-07T03:33:24.533046Z", "filename": "uv-0.8.24-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.9.0", + "url": "https://files.pythonhosted.org/packages/8e/06/f5e38314e318bfaa20ccce966f6d0a69b093854648d31085b2d8b2097aab/uv-0.9.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "b900e84d992a657e16371426dbb030ab031c0322a604b632dada34401ebe7145", + "uploaded": "2025-10-07T23:44:30.076333Z", + "filename": "uv-0.9.0-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.9.30", "url": "https://files.pythonhosted.org/packages/42/5f/3ccc9415ef62969ed01829572338ea7bdf4c5cf1ffb9edc1f8cb91b571f3/uv-0.9.30-py3-none-macosx_11_0_arm64.whl", @@ -76,6 +244,13 @@ "uploaded": "2026-02-04T21:45:40.881824Z", "filename": "uv-0.9.30-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.10.0", + "url": "https://files.pythonhosted.org/packages/ee/77/ec8f24f8d0f19c4fda0718d917bb78b9e6f02a4e1963b401f1c4f4614a54/uv-0.10.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "aefea608971f4f23ac3dac2006afb8eb2b2c1a2514f5fee1fac18e6c45fd70c4", + "uploaded": "2026-02-05T20:57:10.581974Z", + "filename": "uv-0.10.0-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.10.12", "url": "https://files.pythonhosted.org/packages/ce/db/c41ace81b8ef5d5952433df38e321c0b6e5f88ce210c508b14f84817963f/uv-0.10.12-py3-none-macosx_11_0_arm64.whl", @@ -83,6 +258,13 @@ "uploaded": "2026-03-19T21:50:53.693778Z", "filename": "uv-0.10.12-py3-none-macosx_11_0_arm64.whl" }, + { + "version": "0.11.0", + "url": "https://files.pythonhosted.org/packages/d9/1c/6ddd0febcea06cf23e59d9bff90d07025ecfd600238807f41ed2bdafd159/uv-0.11.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "4b0ebbd7ae019ea9fc4bff6a07d0c1e1d6784d1842bbdcb941982d30e2391972", + "uploaded": "2026-03-23T22:05:48.771767Z", + "filename": "uv-0.11.0-py3-none-macosx_11_0_arm64.whl" + }, { "version": "0.11.33", "url": "https://files.pythonhosted.org/packages/4d/e5/17a4e36299e9bd5e8101680be697c7832afac686d1fe8b28be28046c1d95/uv-0.11.33-py3-none-macosx_11_0_arm64.whl", @@ -91,10 +273,17 @@ "filename": "uv-0.11.33-py3-none-macosx_11_0_arm64.whl" }, { - "version": "0.12.13", - "url": "https://files.pythonhosted.org/packages/2a/33/ef14dc7c9c4cfaf0c3a4aed5a298b76b5dea94c75b233f7620c49c9b2e09/uv-0.12.13-py3-none-macosx_11_0_arm64.whl", - "sha256": "f86e5f02883c2e7a21bf522f4aa520c20d4f779d9a3368fbd6cdfe4a8f9549b5", - "uploaded": "2026-09-10T19:25:08.355776Z", - "filename": "uv-0.12.13-py3-none-macosx_11_0_arm64.whl" + "version": "0.12.0", + "url": "https://files.pythonhosted.org/packages/a5/7b/15d6865264120bd30c738b4bf63ddff66d087087cadeb2a6b88c6284a446/uv-0.12.0-py3-none-macosx_11_0_arm64.whl", + "sha256": "009758d8fde2da2b90900f5fe863c71d0e1b8b28bbdba59863ceb967973a3735", + "uploaded": "2026-07-28T18:56:32.904863Z", + "filename": "uv-0.12.0-py3-none-macosx_11_0_arm64.whl" + }, + { + "version": "0.12.15", + "url": "https://files.pythonhosted.org/packages/84/62/82e86e03e111463ab224c132c0f0e6b649d98d22710b177fbc000d379103/uv-0.12.15-py3-none-macosx_11_0_arm64.whl", + "sha256": "03b2c763f8b3c5595fa103221bc667e3af0146f8cb327aa06630ebcf5cfe16e9", + "uploaded": "2026-09-15T12:07:07.611580Z", + "filename": "uv-0.12.15-py3-none-macosx_11_0_arm64.whl" } ] diff --git a/docs/testing/uv-compatibility/results.json b/docs/testing/uv-compatibility/results.json index bbc3323e..e5daa4be 100644 --- a/docs/testing/uv-compatibility/results.json +++ b/docs/testing/uv-compatibility/results.json @@ -1,9 +1,9 @@ { - "date": "2026-09-14", - "scope": "14 pinned uv releases on macOS-26.6.2-arm64-arm-64bit; interpreter /usr/bin/python3", - "socketPatchRevision": "e11bd419ea9c01b3ecd1aa894b55874718a3ff0a", + "date": "2026-09-15", + "scope": "41 pinned uv releases on macOS-26.6.2-arm64-arm-64bit; interpreter /usr/bin/python3", + "socketPatchRevision": "310b9042abc8803aa5e302a5902b9f53584f6908", "socketPatchVersion": "socket-patch 4.0.0", - "socketPatchBinarySha256": "eb5f6695a06c2124ac5c09f2117bf42e8777d767aec09c1de6ee16cd9dc4adee", + "socketPatchBinarySha256": "79e900f11714ee1575b57e7f494364094ca7d0ff953abddef597b839c5211379", "patchUuid": "e828efa5-5c6d-43f3-9909-03f5ac232b98", "originalWheelSha256": "34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", "patchedWheelSha256": "ccc9a9e0b18a5efc7038c504cfc580e47d2e02e5390f2e29cad833cbccb956b6", @@ -260,6 +260,16 @@ ], "cwd": "/matrix//project-hosted" }, + "project-hosted-locked-install": { + "args": [ + "/bin//uv", + "sync", + "--locked", + "--python", + "/usr/bin/python3" + ], + "cwd": "/matrix//project-unfrozen-hosted" + }, "project-vendored-export-requirements-txt": { "args": [ "/bin//uv", @@ -305,16 +315,6 @@ ], "cwd": "/matrix//project-vendored" }, - "project-hosted-locked-install": { - "args": [ - "/bin//uv", - "sync", - "--locked", - "--python", - "/usr/bin/python3" - ], - "cwd": "/matrix//project-unfrozen-hosted" - }, "project-vendored-locked-install": { "args": [ "/bin//uv", @@ -685,57 +685,15 @@ ] }, { - "version": "0.1.45", - "lockSchema": "distribution", - "lockVersion": 1, + "version": "0.1.0", + "lockSchema": null, + "lockVersion": null, "lockRevision": null, "observations": [ { "command": "lock", - "exitCode": 0 - }, - { - "command": "project-hosted-socket-patch", - "exitCode": 0, - "rewrittenFiles": [ - "pyproject.toml", - "uv.lock" - ], - "redirected": 1, - "warnings": [] - }, - { - "command": "project-hosted-export-requirements-txt", - "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" - }, - { - "command": "project-hosted-export-pylock.toml", "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" - }, - { - "command": "project-hosted-lock-sync", - "exitCode": 0, - "lockUnchanged": true, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true - }, - { - "command": "project-vendored-socket-patch", - "exitCode": 1, - "vendorSummary": { - "applied": 0, - "failed": 1 - }, - "vendorErrors": [ - { - "action": "failed", - "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", - "errorCode": "pypi_uv_legacy_lock_unsupported", - "error": "uv 0.1 lockfiles require absolute file URLs; upgrade to uv >=0.2 for portable native vendoring, or use a requirements.txt installation" - } - ] + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "requirements-hosted-socket-patch", @@ -763,9 +721,8 @@ }, { "command": "requirements-vendored-pip-sync", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true + "exitCode": 2, + "diagnostic": "error: Unexpected '.', expected '-c', '-e', '-r' or the start of a requirement in `requirements.txt` at position 144\n" }, { "command": "compile-plain", @@ -801,19 +758,18 @@ }, { "command": "requirements-plain-vendored-pip-sync", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true + "exitCode": 2, + "diagnostic": "error: Unexpected '.', expected '-c', '-e', '-r' or the start of a requirement in `requirements.txt` at position 126\n" }, { "command": "script-lock-hosted", "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "script-lock-vendored", "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "compile-pylock-hosted", @@ -824,54 +780,74 @@ "command": "compile-pylock-vendored", "exitCode": 0, "formatSupported": false - }, - { - "command": "project-hosted-unfrozen-install", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true } ] }, { - "version": "0.2.37", - "lockSchema": "package", - "lockVersion": 1, + "version": "0.1.23", + "lockSchema": null, + "lockVersion": null, "lockRevision": null, "observations": [ { "command": "lock", - "exitCode": 0 + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { - "command": "project-hosted-socket-patch", + "command": "requirements-hosted-socket-patch", "exitCode": 0, "rewrittenFiles": [ - "pyproject.toml", - "uv.lock" + "requirements.txt" ], "redirected": 1, "warnings": [] }, { - "command": "project-hosted-export-requirements-txt", - "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { - "command": "project-hosted-export-pylock.toml", + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + "diagnostic": "error: Unexpected '.', expected '-c', '-e', '-r' or the start of a requirement at requirements.txt:3:1\n" }, { - "command": "project-hosted-lock-sync-variant-2", + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", "exitCode": 0, - "lockUnchanged": true, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, { - "command": "project-vendored-socket-patch", + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", "exitCode": 0, "vendorSummary": { "applied": 1, @@ -880,20 +856,42 @@ "vendorErrors": [] }, { - "command": "project-vendored-export-requirements-txt", + "command": "requirements-plain-vendored-pip-sync", "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + "diagnostic": "error: Unexpected '.', expected '-c', '-e', '-r' or the start of a requirement at requirements.txt:3:1\n" }, { - "command": "project-vendored-export-pylock.toml", + "command": "script-lock-hosted", "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { - "command": "project-vendored-lock-sync", + "command": "script-lock-vendored", "exitCode": 2, - "lockUnchanged": true, - "diagnostic": "warning: `uv sync` is experimental and may change without warning\nUsing Python 3.9.6 interpreter at: /Applications/Xcode.app/Contents/Developer/usr/bin/python3\nCreating virtualenv at: .venv\nerror: Failed to prepare distributions\n Caused by: Failed to fetch wheel: socket-uv-patch-fixture @ file:///matrix/0.2.37/project-vendored\n Caused by: Failed to install requirements from setup.py build (resolve)\n Caused by: No solution found when resolving: setuptools>=40.8.0\n Caused by: Because setuptools was not found in the cache and you require setuptools>=40.8.0, we can conclude that your requirements are unsatisfiable.\n\nhint: Packages were unavailable because the network was disabled\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + } + ] + }, + { + "version": "0.1.24", + "lockSchema": null, + "lockVersion": null, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "requirements-hosted-socket-patch", @@ -963,21 +961,15 @@ "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, - { - "command": "project-vendored-frozen-sync-root-build-networked", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true - }, { "command": "script-lock-hosted", "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "script-lock-vendored", "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "diagnostic": "error: unrecognized subcommand 'lock'\n\n tip: a similar subcommand exists: 'uv pip compile'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { "command": "compile-pylock-hosted", @@ -988,38 +980,12 @@ "command": "compile-pylock-vendored", "exitCode": 0, "formatSupported": false - }, - { - "command": "project-hosted-locked-install", - "exitCode": 0, - "lockUnchanged": true, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true - }, - { - "command": "project-hosted-unfrozen-install", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true - }, - { - "command": "project-vendored-locked-install", - "exitCode": 0, - "lockUnchanged": true, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true - }, - { - "command": "project-vendored-unfrozen-install", - "exitCode": 0, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true } ] }, { - "version": "0.3.5", - "lockSchema": "package", + "version": "0.1.45", + "lockSchema": "distribution", "lockVersion": 1, "lockRevision": null, "observations": [ @@ -1048,7 +1014,7 @@ "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" }, { - "command": "project-hosted-lock-sync-variant-3", + "command": "project-hosted-lock-sync", "exitCode": 0, "lockUnchanged": true, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", @@ -1056,29 +1022,19 @@ }, { "command": "project-vendored-socket-patch", - "exitCode": 0, + "exitCode": 1, "vendorSummary": { - "applied": 1, - "failed": 0 + "applied": 0, + "failed": 1 }, - "vendorErrors": [] - }, - { - "command": "project-vendored-export-requirements-txt", - "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" - }, - { - "command": "project-vendored-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" - }, - { - "command": "project-vendored-lock-sync-variant-2", - "exitCode": 0, - "lockUnchanged": true, - "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", - "installedPatch": true + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] }, { "command": "requirements-hosted-socket-patch", @@ -1169,27 +1125,5462 @@ "formatSupported": false }, { - "command": "project-hosted-locked-install-variant-2", + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.0", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.5", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.6", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.17", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.18", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.34", + "lockSchema": "distribution", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 1, + "vendorSummary": { + "applied": 0, + "failed": 1 + }, + "vendorErrors": [ + { + "action": "failed", + "purl": "pkg:pypi/urllib3@1.26.18?artifact_id=py2-py3-none-any-whl", + "errorCode": "pypi_uv_legacy_lock_unsupported", + "error": "uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; upgrade to uv >=0.2.35 for portable native vendoring, or use a requirements.txt installation" + } + ] + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.35", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync", + "exitCode": 2, + "lockUnchanged": true, + "diagnostic": "warning: `uv sync` is experimental and may change without warning\nUsing Python 3.9.6 interpreter at: /Applications/Xcode.app/Contents/Developer/usr/bin/python3\nCreating virtualenv at: .venv\nerror: Failed to prepare distributions\n Caused by: Failed to fetch wheel: socket-uv-patch-fixture @ file:///matrix/0.2.35/project-vendored\n Caused by: Failed to build: `socket-uv-patch-fixture @ file:///matrix/0.2.35/project-vendored`\n Caused by: Failed to install requirements from setup.py build (resolve)\n Caused by: No solution found when resolving: setuptools>=40.8.0\n Caused by: Because setuptools was not found in the cache and you require setuptools>=40.8.0, we can conclude that the requirements are unsatisfiable.\n\nhint: Packages were unavailable because the network was disabled\n" + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-frozen-sync-root-build-networked", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.36", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync", + "exitCode": 2, + "lockUnchanged": true, + "diagnostic": "warning: `uv sync` is experimental and may change without warning\nUsing Python 3.9.6 interpreter at: /Applications/Xcode.app/Contents/Developer/usr/bin/python3\nCreating virtualenv at: .venv\nerror: Failed to prepare distributions\n Caused by: Failed to fetch wheel: socket-uv-patch-fixture @ file:///matrix/0.2.36/project-vendored\n Caused by: Failed to build: `socket-uv-patch-fixture @ file:///matrix/0.2.36/project-vendored`\n Caused by: Failed to install requirements from setup.py build (resolve)\n Caused by: No solution found when resolving: setuptools>=40.8.0\n Caused by: Because setuptools was not found in the cache and you require setuptools>=40.8.0, we can conclude that the requirements are unsatisfiable.\n\nhint: Packages were unavailable because the network was disabled\n" + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-frozen-sync-root-build-networked", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.2.37", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync", + "exitCode": 2, + "lockUnchanged": true, + "diagnostic": "warning: `uv sync` is experimental and may change without warning\nUsing Python 3.9.6 interpreter at: /Applications/Xcode.app/Contents/Developer/usr/bin/python3\nCreating virtualenv at: .venv\nerror: Failed to prepare distributions\n Caused by: Failed to fetch wheel: socket-uv-patch-fixture @ file:///matrix/0.2.37/project-vendored\n Caused by: Failed to install requirements from setup.py build (resolve)\n Caused by: No solution found when resolving: setuptools>=40.8.0\n Caused by: Because setuptools was not found in the cache and you require setuptools>=40.8.0, we can conclude that your requirements are unsatisfiable.\n\nhint: Packages were unavailable because the network was disabled\n" + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-frozen-sync-root-build-networked", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.3.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync", + "exitCode": 2, + "lockUnchanged": true, + "diagnostic": "Using Python 3.9.6 interpreter at: /Applications/Xcode.app/Contents/Developer/usr/bin/python3\nCreating virtualenv at: .venv\nerror: Failed to prepare distributions\n Caused by: Failed to fetch wheel: socket-uv-patch-fixture @ file:///matrix/0.3.0/project-vendored\n Caused by: Failed to install requirements from setup.py build (resolve)\n Caused by: No solution found when resolving: setuptools>=40.8.0\n Caused by: Because setuptools was not found in the cache and you require setuptools>=40.8.0, we can conclude that your requirements are unsatisfiable.\n\nhint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.\n" + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-frozen-sync-root-build-networked", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.3.5", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.4.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unrecognized subcommand 'export'\n\nUsage: uv [OPTIONS] \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.4.1", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--output-file' found\n\nUsage: uv export --frozen --format \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--output-file' found\n\nUsage: uv export --frozen --format \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--output-file' found\n\nUsage: uv export --frozen --format \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--output-file' found\n\nUsage: uv export --frozen --format \n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.4.30", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.5.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.5.16", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "script-lock-vendored", + "exitCode": 2, + "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.5.17", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.5.31", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": null, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.6.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 1, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": false + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.6.14", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 1, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 2, + "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 2, + "formatSupported": false, + "diagnostic": "error: TOML is not a supported output format for `uv pip compile` (only `requirements.txt`-style output is supported)\n" + }, + { + "command": "compile-pylock-vendored", + "exitCode": 2, + "formatSupported": false, + "diagnostic": "error: TOML is not a supported output format for `uv pip compile` (only `requirements.txt`-style output is supported)\n" + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.6.15", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.6.17", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.7.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.7.22", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.8.0", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + } + ] + }, + { + "version": "0.8.3", + "lockSchema": "package", + "lockVersion": 1, + "lockRevision": 2, + "observations": [ + { + "command": "lock", + "exitCode": 0 + }, + { + "command": "project-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pyproject.toml", + "uv.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "project-hosted-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-hosted-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-hosted-lock-sync-variant-3", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "project-vendored-export-requirements-txt", + "exitCode": 0 + }, + { + "command": "project-vendored-export-pylock.toml", + "exitCode": 0 + }, + { + "command": "project-vendored-lock-sync-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain", + "exitCode": 0 + }, + { + "command": "requirements-plain-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "requirements.txt" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "requirements-plain-hosted-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-plain-variant-2", + "exitCode": 0 + }, + { + "command": "requirements-plain-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "requirements-plain-vendored-pip-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "requirements-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-hosted", + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-lock-vendored", + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-hosted", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "compile-pylock-vendored", + "exitCode": 0, + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-hosted-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-locked-install-variant-2", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "project-vendored-unfrozen-install-variant-2", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-locked-install", "exitCode": 0, "lockUnchanged": true, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, { - "command": "project-hosted-unfrozen-install-variant-2", + "command": "script-hosted-unfrozen-install", "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, { - "command": "project-vendored-locked-install-variant-2", + "command": "script-vendored-locked-install", "exitCode": 0, "lockUnchanged": true, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, { - "command": "project-vendored-unfrozen-install-variant-2", + "command": "script-vendored-unfrozen-install", "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true @@ -1197,10 +6588,10 @@ ] }, { - "version": "0.4.30", + "version": "0.8.4", "lockSchema": "package", "lockVersion": 1, - "lockRevision": null, + "lockRevision": 3, "observations": [ { "command": "lock", @@ -1222,8 +6613,7 @@ }, { "command": "project-hosted-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-hosted-lock-sync-variant-3", @@ -1247,8 +6637,7 @@ }, { "command": "project-vendored-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-vendored-lock-sync-variant-2", @@ -1331,31 +6720,106 @@ "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "requirements-vendored-export-sync", "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "script-lock-hosted", - "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "exitCode": 0 + }, + { + "command": "script-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "example.py", + "example.py.lock" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "script-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "script-lock-vendored", - "exitCode": 2, - "diagnostic": "error: unexpected argument '--script' found\n\nUsage: uv lock [OPTIONS]\n\nFor more information, try '--help'.\n" + "exitCode": 0 + }, + { + "command": "script-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "script-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "compile-pylock-hosted", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "compile-pylock-vendored", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "project-hosted-locked-install-variant-2", @@ -1382,14 +6846,40 @@ "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true + }, + { + "command": "script-hosted-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-hosted-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-locked-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, + { + "command": "script-vendored-unfrozen-install", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true } ] }, { - "version": "0.5.31", + "version": "0.8.24", "lockSchema": "package", "lockVersion": 1, - "lockRevision": null, + "lockRevision": 3, "observations": [ { "command": "lock", @@ -1411,8 +6901,7 @@ }, { "command": "project-hosted-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-hosted-lock-sync-variant-3", @@ -1436,8 +6925,7 @@ }, { "command": "project-vendored-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-vendored-lock-sync-variant-2", @@ -1520,12 +7008,24 @@ "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "requirements-vendored-export-sync", "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "script-lock-hosted", "exitCode": 0 @@ -1570,12 +7070,44 @@ { "command": "compile-pylock-hosted", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "compile-pylock-vendored", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "project-hosted-locked-install-variant-2", @@ -1632,10 +7164,10 @@ ] }, { - "version": "0.6.0", + "version": "0.9.0", "lockSchema": "package", "lockVersion": 1, - "lockRevision": 1, + "lockRevision": 3, "observations": [ { "command": "lock", @@ -1657,8 +7189,7 @@ }, { "command": "project-hosted-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-hosted-lock-sync-variant-3", @@ -1682,8 +7213,7 @@ }, { "command": "project-vendored-export-pylock.toml", - "exitCode": 2, - "diagnostic": "error: invalid value 'pylock.toml' for '--format '\n [possible values: requirements-txt]\n\nFor more information, try '--help'.\n" + "exitCode": 0 }, { "command": "project-vendored-lock-sync-variant-2", @@ -1766,12 +7296,24 @@ "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-hosted-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "requirements-vendored-export-sync", "exitCode": 0, "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", "installedPatch": true }, + { + "command": "pylock-vendored-export-sync", + "exitCode": 0, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true + }, { "command": "script-lock-hosted", "exitCode": 0 @@ -1816,12 +7358,44 @@ { "command": "compile-pylock-hosted", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-hosted-socket-patch", + "exitCode": 0, + "rewrittenFiles": [ + "pylock.toml" + ], + "redirected": 1, + "warnings": [] + }, + { + "command": "pylock-direct-hosted-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "compile-pylock-vendored", "exitCode": 0, - "formatSupported": false + "formatSupported": true + }, + { + "command": "pylock-direct-vendored-socket-patch", + "exitCode": 0, + "vendorSummary": { + "applied": 1, + "failed": 0 + }, + "vendorErrors": [] + }, + { + "command": "pylock-direct-vendored-install", + "exitCode": 0, + "lockUnchanged": true, + "installedResponseSha256": "21d9a7810de52973c88d9170f437e98921456bce445ab0618576987478a6a6e4", + "installedPatch": true }, { "command": "project-hosted-locked-install-variant-2", @@ -1878,10 +7452,10 @@ ] }, { - "version": "0.6.17", + "version": "0.9.30", "lockSchema": "package", "lockVersion": 1, - "lockRevision": 2, + "lockRevision": 3, "observations": [ { "command": "lock", @@ -2166,10 +7740,10 @@ ] }, { - "version": "0.7.22", + "version": "0.10.0", "lockSchema": "package", "lockVersion": 1, - "lockRevision": 2, + "lockRevision": 3, "observations": [ { "command": "lock", @@ -2454,7 +8028,7 @@ ] }, { - "version": "0.8.24", + "version": "0.10.12", "lockSchema": "package", "lockVersion": 1, "lockRevision": 3, @@ -2742,7 +8316,7 @@ ] }, { - "version": "0.9.30", + "version": "0.11.0", "lockSchema": "package", "lockVersion": 1, "lockRevision": 3, @@ -3030,7 +8604,7 @@ ] }, { - "version": "0.10.12", + "version": "0.11.33", "lockSchema": "package", "lockVersion": 1, "lockRevision": 3, @@ -3318,7 +8892,7 @@ ] }, { - "version": "0.11.33", + "version": "0.12.0", "lockSchema": "package", "lockVersion": 1, "lockRevision": 3, @@ -3606,7 +9180,7 @@ ] }, { - "version": "0.12.13", + "version": "0.12.15", "lockSchema": "package", "lockVersion": 1, "lockRevision": 3, From 1e5a62ae41495d91979dea36a106db8fa371c980 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:24:33 -0400 Subject: [PATCH 05/25] utils/fs: export read_regular_to_string and is_symlink helpers Shared FIFO-safe reader (the shape every ecosystem module re-declared privately) and an lstat-based symlink probe, exported so the CLI crate's raw read_to_string sites and the Python lock writers can use them. Co-Authored-By: Claude Fable 5.1 --- crates/socket-patch-core/src/utils/fs.rs | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/socket-patch-core/src/utils/fs.rs b/crates/socket-patch-core/src/utils/fs.rs index f6f83b3b..971ef6eb 100644 --- a/crates/socket-patch-core/src/utils/fs.rs +++ b/crates/socket-patch-core/src/utils/fs.rs @@ -134,6 +134,33 @@ pub(crate) async fn open_regular_file( Ok((file, metadata)) } +/// Read a regular file to a `String` through [`open_regular_file`]: the +/// FIFO-safe reader (non-blocking open, fstat regular-file check on the +/// opened descriptor) that the ecosystem modules had each re-declared +/// privately. Follows a symlink to a regular file; a FIFO, directory or +/// socket fails fast with `InvalidInput` instead of wedging in open(2). +/// `pub` so the CLI crate's raw `read_to_string` sites can share it. +pub async fn read_regular_to_string(path: &Path) -> std::io::Result { + use tokio::io::AsyncReadExt as _; + + let (mut file, metadata) = open_regular_file(path).await?; + let mut content = String::with_capacity(metadata.len() as usize); + file.read_to_string(&mut content).await?; + Ok(content) +} + +/// True when `path` ITSELF is a symbolic link (lstat; the link target is not +/// consulted, so a dangling link is still `true`). Writers that stage a +/// replacement next to `path` and rename over it would replace the link with +/// a regular file (leaving the target stale) — they use this to refuse +/// fail-closed before any write, mirroring the hosted replay guard. +pub async fn is_symlink(path: &Path) -> bool { + tokio::fs::symlink_metadata(path) + .await + .map(|metadata| metadata.file_type().is_symlink()) + .unwrap_or(false) +} + /// Return the raw `FileType` for `entry`, swallowing stat errors. /// /// Use this instead of `entry_is_dir` when the caller needs to From da2f1882b5a76b954fb5406e87e21765a56733aa Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:40:03 -0400 Subject: [PATCH 06/25] uv vendor: classify [tool.uv] dev-dependencies as direct; repoint every metadata entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit classify_dependency scanned project.dependencies, optional-dependencies and [dependency-groups] but not the legacy `[tool.uv] dev-dependencies` array, which uv still honours (0.12.15 warns but records it exactly like `dependency-groups.dev`, in the root unit's [package.metadata.requires-dev]). Such a target classified Transitive: wire_uv took the override branch, [package.metadata.requires-dev] kept `specifier = "==…"`, and after a "successful" vendored scan every uv >= 0.2.37 `uv sync --locked` / `uv lock --check` exited non-zero while a plain `uv sync` rewrote the lock (ledger drift) and on 0.2.37/0.4.30 reinstalled the PRISTINE wheel. rewrite_root_metadata_entries also repointed only the FIRST matching element per array (a `break` in the requires-dist loop and per group in requires-dev) although its doc promised ALL: a package declared in both dependencies and an optional-dependencies extra has two requires-dist entries (the extra's with `marker = "extra == '…'"`) and uv repoints both, so the stale second entry kept --locked red. Fix: extend `declared` with the [tool.uv] dev-dependencies string members (pep508_name); repoint every matching requires-dist element as its own edit/wiring record (revert already replays N records per package), and rebuild each requires-dev group line once with every matching element rewritten so a group naming the package twice never yields overlapping spans. Both new fixtures are byte-exact uv 0.12.15 output. wire_uv now also returns a Vec of wiring-time advisories, threaded into the vendor outcome's warnings by pypi.rs (the channel the next commit's override-branch advisory uses). Co-Authored-By: Claude Fable 5.1 --- crates/socket-patch-core/src/vendor/pypi.rs | 5 +- .../socket-patch-core/src/vendor/pypi_uv.rs | 367 +++++++++++++++--- 2 files changed, 316 insertions(+), 56 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/pypi.rs b/crates/socket-patch-core/src/vendor/pypi.rs index c9253ee1..32d30628 100644 --- a/crates/socket-patch-core/src/vendor/pypi.rs +++ b/crates/socket-patch-core/src/vendor/pypi.rs @@ -763,7 +763,10 @@ pub async fn vendor_pypi( &record.uuid, ) .await - .map(|(wiring, meta)| (wiring, MetaSlot::Uv(Some(meta)))), + .map(|(wiring, meta, advisories)| { + warnings.extend(advisories); + (wiring, MetaSlot::Uv(Some(meta))) + }), WiringPlan::PythonLocks(project) => super::pypi_lock::wire_python_locks( &project, project_root, diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index aa1810fd..a583fb1c 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -237,9 +237,14 @@ pub(super) async fn load_uv_project(root: &Path) -> Result= 0.2.37. fn classify_dependency(p: &UvProject, canon_name: &str) -> UvDepClass { let mut declared: Vec = Vec::new(); pep621_declared_names(&p.pyproject, &mut declared); @@ -260,6 +265,19 @@ fn classify_dependency(p: &UvProject, canon_name: &str) -> UvDepClass { } } } + if let Some(dev) = p + .pyproject + .get("tool") + .and_then(|t| item_get(t, "uv")) + .and_then(|u| item_get(u, "dev-dependencies")) + .and_then(Item::as_array) + { + declared.extend( + dev.iter() + .filter_map(Value::as_str) + .map(|s| pep508_name(s).to_string()), + ); + } if declared .iter() .any(|n| canonicalize_pypi_name(n) == canon_name) @@ -426,7 +444,8 @@ pub(super) fn wired_pin( /// Wire the pair for the vendored wheel. Writes `pyproject.toml` FIRST, then /// `uv.lock`; a failed lock write unwinds the pyproject from the recorded /// original so the pair is never left half-wired (either half alone is a -/// silent no-op or a silent revert — spike claims 7/9). +/// silent no-op or a silent revert — spike claims 7/9). The third element +/// carries wiring-time advisories (non-fatal, surfaced with the outcome). #[allow(clippy::too_many_arguments)] pub(super) async fn wire_uv( p: &UvProject, @@ -437,7 +456,7 @@ pub(super) async fn wire_uv( wheel_file_name: &str, wheel_sha256_hex: &str, record_uuid: &str, -) -> Result<(Vec, UvMeta), (&'static str, String)> { +) -> Result<(Vec, UvMeta, Vec), (&'static str, String)> { match check_target_guards(p, canon_name, record_uuid)? { // Defensive: the orchestrator short-circuits in-sync pre-flight and // never calls wire on it (we must never re-record our own edit as an @@ -456,6 +475,7 @@ pub(super) async fn wire_uv( } let class = classify_dependency(p, canon_name); let mut wiring: Vec = Vec::new(); + let mut advisories: Vec = Vec::new(); // ── pyproject.toml (computed in memory; committed before the lock) ──── let mut doc = p.pyproject.clone(); @@ -653,7 +673,7 @@ pub(super) async fn wire_uv( created_sources_table, lock_revision: p.lock_revision, }; - Ok((wiring, meta)) + Ok((wiring, meta, advisories)) } /// Reverse the wiring: restore verbatim originals (or delete added fragments) @@ -1009,18 +1029,23 @@ struct RequiresDistEdit { kind: &'static str, } -/// Find + transform EVERY root-package metadata entry for `canon`: the -/// `[package.metadata]` `requires-dist` array (project.dependencies / -/// optional-dependencies) AND each `[package.metadata.requires-dev]` group -/// array (PEP 735 `[dependency-groups]` — uv records group deps there, never -/// in requires-dist, and rewrites ALL entries to the path shape when a -/// source applies; spike-verified against uv 0.11.19). Each entry: -/// `{ name = "x", specifier = "==v" }` → `{ name = "x", path = "" }` -/// (uv DROPS the specifier for path sources — recorded for revert). Returns -/// absolute byte spans, ascending, so the caller splices by range, never by -/// string search (a bare `{ name = "x" }` entry would collide with -/// `dependencies` arrays elsewhere in the lock). requires-dev fragments span -/// the whole ` = […]` line so identically-pinned groups stay +/// Find + transform EVERY root-package metadata entry for `canon`: each +/// matching element of the `[package.metadata]` `requires-dist` array +/// (project.dependencies / optional-dependencies — a package in both has +/// TWO entries, the extra's with `marker = "extra == '…'"`) AND each +/// `[package.metadata.requires-dev]` group array (PEP 735 +/// `[dependency-groups]` and the legacy `[tool.uv] dev-dependencies` — uv +/// records those there, never in requires-dist). uv rewrites ALL of them to +/// the path shape when a source applies (verified against uv 0.11.19 and +/// 0.12.15), so one left with its specifier keeps `--locked` red. Each +/// entry: `{ name = "x", specifier = "==v" }` → `{ name = "x", path = +/// "" }` (uv DROPS the specifier for path sources — recorded for +/// revert). Returns absolute byte spans, ascending and non-overlapping, so +/// the caller splices by range, never by string search (a bare `{ name = "x" +/// }` entry would collide with `dependencies` arrays elsewhere in the lock). +/// Each requires-dist element is its own edit (one wiring record per +/// element); a requires-dev fragment spans the whole ` = […]` line +/// with every matching element rewritten, so identically-pinned groups stay /// distinguishable when revert matches fragments by text. fn rewrite_root_metadata_entries( lock_text: &str, @@ -1067,6 +1092,10 @@ fn rewrite_root_metadata_entries( continue; } let (new_entry, specifier) = path_source_entry(entry, rel_wheel); + // No early exit: a package in both project.dependencies and an + // optional-dependencies extra has TWO entries here (the second + // carries `marker = "extra == '…'"`) and uv repoints both; one + // left with its specifier keeps `--locked` red. edits.push(RequiresDistEdit { span: (unit_start + arr_open + s)..(unit_start + arr_open + e), old_entry: entry.to_string(), @@ -1074,7 +1103,6 @@ fn rewrite_root_metadata_entries( specifier, kind: "uv_lock_requires_dist", }); - break; } } @@ -1097,28 +1125,39 @@ fn rewrite_root_metadata_entries( ) })?; let array_text = &unit_text[arr_open..arr_end]; + // Rebuild the whole group array with EVERY matching element + // repointed (a group may name the package twice under different + // markers), so one group is always one non-overlapping edit. + let mut new_array = String::with_capacity(array_text.len()); + let mut last = 0; + let mut specifier: Option = None; + let mut matched = false; for (s, e) in top_level_brace_groups(array_text) { let entry = &array_text[s..e]; if !entry.contains(&needle) { continue; } - let (new_entry, specifier) = path_source_entry(entry, rel_wheel); + let (new_entry, spec) = path_source_entry(entry, rel_wheel); + if specifier.is_none() { + specifier = spec; + } + new_array.push_str(&array_text[last..s]); + new_array.push_str(&new_entry); + last = e; + matched = true; + } + if matched { + new_array.push_str(&array_text[last..]); // Fragment from the group key so revert's text match can't // confuse two groups pinning the same entry. let key_start = unit_text[..arr_open].rfind('\n').map_or(0, |i| i + 1); edits.push(RequiresDistEdit { span: (unit_start + key_start)..(unit_start + arr_end), old_entry: unit_text[key_start..arr_end].to_string(), - new_entry: format!( - "{}{}{}", - &unit_text[key_start..arr_open + s], - new_entry, - &unit_text[arr_open + e..arr_end] - ), + new_entry: format!("{}{}", &unit_text[key_start..arr_open], new_array), specifier, kind: "uv_lock_requires_dev", }); - break; } cursor = arr_end; } @@ -1757,7 +1796,7 @@ wheels = [ assert!(p.warnings.is_empty()); assert_eq!(classify_dependency(&p, "six"), UvDepClass::Direct); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -1804,7 +1843,7 @@ wheels = [ let p = load_uv_project(tmp.path()).await.unwrap(); assert_eq!(classify_dependency(&p, "six"), UvDepClass::Transitive); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2081,7 +2120,7 @@ wheels = [ async fn revert_direct_restores_originals_byte_identically() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2110,7 +2149,7 @@ wheels = [ async fn revert_override_restores_originals_byte_identically() { let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, TRANSITIVE_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2197,7 +2236,7 @@ wheels = [ }; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2231,7 +2270,7 @@ wheels = [ async fn revert_dry_run_changes_nothing() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2259,7 +2298,7 @@ wheels = [ async fn revert_warns_and_skips_on_drifted_lock_fragment() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2619,7 +2658,7 @@ wheels = [ let p = load_uv_project(tmp.path()).await.unwrap(); assert_eq!(classify_dependency(&p, "widget"), UvDepClass::Direct); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "widget", @@ -2681,7 +2720,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &empty_overrides_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2910,7 +2949,7 @@ wheels = [ let p = load_uv_project(tmp.path()).await.unwrap(); assert_eq!(classify_dependency(&p, "six"), UvDepClass::Direct); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -2959,7 +2998,7 @@ wheels = [ let p = load_uv_project(tmp.path()).await.unwrap(); assert_eq!(classify_dependency(&p, "six"), UvDepClass::Direct); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3094,7 +3133,7 @@ wheels = [ assert!(p.warnings.is_empty(), "{:?}", p.warnings); assert_eq!(p.lock_revision, None); - let (_, meta) = wire_uv( + let (_, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3165,7 +3204,7 @@ wheels = [ let p = load_uv_project(tmp.path()).await.unwrap(); assert_eq!(classify_dependency(&p, "six"), UvDepClass::Transitive); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3225,7 +3264,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &input_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3284,7 +3323,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &input_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3334,7 +3373,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &input_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3393,7 +3432,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &input_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3458,7 +3497,7 @@ wheels = [ async fn revert_warns_and_skips_when_sources_line_was_edited() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3521,7 +3560,7 @@ wheels = [ ] { let tmp = write_pair(registry_py, registry_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), target, @@ -3567,7 +3606,7 @@ wheels = [ async fn revert_pypi_converged_uv_cleans_up_the_artifact() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3615,7 +3654,7 @@ wheels = [ async fn revert_pypi_drifted_uv_keeps_artifact() { let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3672,7 +3711,7 @@ wheels = [ async fn revert_warns_and_skips_when_added_override_line_was_edited() { let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, TRANSITIVE_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3803,7 +3842,7 @@ wheels = [ use std::os::unix::fs::PermissionsExt; let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -3873,7 +3912,7 @@ wheels = [ let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, &sdist_only_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -4317,7 +4356,7 @@ wheels = [ async fn revert_warns_when_created_manifest_section_still_routes_after_reshape() { let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, TRANSITIVE_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -4387,7 +4426,7 @@ wheels = [ ); let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, &input_lock).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -4426,7 +4465,7 @@ wheels = [ ); let tmp = write_pair(&pyproject, TRANSITIVE_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -4461,7 +4500,7 @@ wheels = [ ); let tmp = write_pair(&pyproject, TRANSITIVE_REGISTRY_LOCK).await; let p = load_uv_project(tmp.path()).await.unwrap(); - let (wiring, meta) = wire_uv( + let (wiring, meta, _) = wire_uv( &p, tmp.path(), "six", @@ -4663,4 +4702,222 @@ wheels = [ assert_eq!(err.0, "pypi_uv_lock_parse_failed"); assert!(err.1.contains("overrides array is unbalanced"), "{}", err.1); } + + // ── legacy `[tool.uv] dev-dependencies` (uv 0.12.15 ground truth) ─── + // uv still honours the deprecated array (with a warning) and records it + // EXACTLY like `[dependency-groups] dev`: a `[package.dev-dependencies] + // dev` edge plus a `[package.metadata.requires-dev] dev` entry, never a + // requires-dist one. The lock shapes are therefore the dev-group ones. + + const TOOL_UV_DEV_REGISTRY_PYPROJECT: &str = r#"[project] +name = "proj" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = [] + +[tool.uv] +dev-dependencies = ["six==1.16.0"] +"#; + + const TOOL_UV_DEV_PATH_PYPROJECT: &str = r#"[project] +name = "proj" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = [] + +[tool.uv] +dev-dependencies = ["six==1.16.0"] + +[tool.uv.sources] +six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl" } +"#; + + /// A target declared ONLY in the legacy `[tool.uv] dev-dependencies` + /// array is a Direct dependency: `[tool.uv.sources]` applies to it and + /// the lock keeps it in `[package.metadata.requires-dev]`. Classifying + /// it Transitive took the override branch and left the requires-dev + /// `specifier` in place, so every uv >= 0.2.37 `uv sync --locked` / + /// `uv lock --check` failed after a "successful" vendored scan (and a + /// plain `uv sync` on 0.2.37/0.4.30 reinstalled the pristine wheel). + #[tokio::test] + async fn tool_uv_dev_dependencies_classify_direct_and_repoint_requires_dev() { + let tmp = write_pair(TOOL_UV_DEV_REGISTRY_PYPROJECT, DEV_GROUP_REGISTRY_LOCK).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + assert_eq!( + classify_dependency(&p, "six"), + UvDepClass::Direct, + "[tool.uv] dev-dependencies is a declaration surface sources apply to" + ); + + let (wiring, meta, _) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + + let kinds: Vec<&str> = wiring.iter().map(|w| w.kind.as_str()).collect(); + assert_eq!( + kinds, + vec!["uv_sources_entry", "uv_lock_package", "uv_lock_requires_dev"], + "a requires-dev repoint and NO override record" + ); + assert_eq!(meta.dep_class, "direct"); + assert_eq!(meta.original_specifier.as_deref(), Some("==1.16.0")); + let (pyproject, lock) = read_pair(tmp.path()).await; + assert!( + !pyproject.contains("override-dependencies"), + "no override pin may be layered on a declared dependency:\n{pyproject}" + ); + assert_eq!(pyproject, TOOL_UV_DEV_PATH_PYPROJECT); + assert_eq!( + lock, DEV_GROUP_PATH_LOCK, + "the requires-dev entry must carry `path` (uv 0.12.15 shape)" + ); + + let entry = entry_for(wiring, meta); + let outcome = revert_uv(&entry, tmp.path(), false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(outcome.warnings.is_empty(), "{:?}", outcome.warnings); + let (pyproject, lock) = read_pair(tmp.path()).await; + assert_eq!(pyproject, TOOL_UV_DEV_REGISTRY_PYPROJECT); + assert_eq!(lock, DEV_GROUP_REGISTRY_LOCK); + } + + // ── extras duplicate (uv 0.12.15 ground truth) ─────────────────────── + // A package in BOTH project.dependencies and an optional-dependencies + // extra yields two requires-dist entries (the second carries + // `marker = "extra == '…'"`); a path source repoints BOTH. + + const EXTRAS_DUP_REGISTRY_PYPROJECT: &str = r#"[project] +name = "proj" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = ["six==1.16.0"] + +[project.optional-dependencies] +socks = ["six==1.16.0"] +"#; + + const EXTRAS_DUP_REGISTRY_LOCK: &str = r#"version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "proj" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "six" }, +] + +[package.optional-dependencies] +socks = [ + { name = "six" }, +] + +[package.metadata] +requires-dist = [ + { name = "six", specifier = "==1.16.0" }, + { name = "six", marker = "extra == 'socks'", specifier = "==1.16.0" }, +] +provides-extras = ["socks"] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, +] +"#; + + const EXTRAS_DUP_PATH_LOCK: &str = r#"version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "proj" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "six" }, +] + +[package.optional-dependencies] +socks = [ + { name = "six" }, +] + +[package.metadata] +requires-dist = [ + { name = "six", path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl" }, + { name = "six", marker = "extra == 'socks'", path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl" }, +] +provides-extras = ["socks"] + +[[package]] +name = "six" +version = "1.16.0" +source = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl" } +wheels = [ + { filename = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" }, +] +"#; + + /// Both requires-dist entries for one package (bare + extra-marker) are + /// repointed — one wiring record each — and revert restores both. The + /// first-match `break` left the marker entry with its `specifier`, so + /// `uv lock --check` / `uv sync --locked` failed after vendoring. + #[tokio::test] + async fn duplicate_requires_dist_entries_all_repointed() { + let tmp = write_pair(EXTRAS_DUP_REGISTRY_PYPROJECT, EXTRAS_DUP_REGISTRY_LOCK).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + assert_eq!(classify_dependency(&p, "six"), UvDepClass::Direct); + + let (wiring, meta, _) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + + let kinds: Vec<&str> = wiring.iter().map(|w| w.kind.as_str()).collect(); + assert_eq!( + kinds, + vec![ + "uv_sources_entry", + "uv_lock_package", + "uv_lock_requires_dist", + "uv_lock_requires_dist" + ], + "one record per repointed requires-dist entry" + ); + let (_, lock) = read_pair(tmp.path()).await; + assert_eq!( + lock, EXTRAS_DUP_PATH_LOCK, + "both the bare and the extra-marker entries must carry `path`" + ); + + let entry = entry_for(wiring, meta); + let outcome = revert_uv(&entry, tmp.path(), false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(outcome.warnings.is_empty(), "{:?}", outcome.warnings); + let (pyproject, lock) = read_pair(tmp.path()).await; + assert_eq!(pyproject, EXTRAS_DUP_REGISTRY_PYPROJECT); + assert_eq!(lock, EXTRAS_DUP_REGISTRY_LOCK); + } } From 8ffe535786c89426a00f2ab35ef1c749d9d33a26 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:41:19 -0400 Subject: [PATCH 07/25] uv vendor: advise that override-dependencies wiring needs uv >= 0.5.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module doc's "claim 8" said [tool.uv.sources] applies to override-dependencies. Against real binaries that holds only from uv 0.5.6: on 0.2.35–0.5.3 a Transitive target wired through the override branch is silently reinstalled from the registry by a plain `uv sync` (the lock still names the vendored path, so `--frozen` installs it, which is why the matrix looked green). No lock-shape marker separates 0.5.3 from 0.5.6, so an offline refusal is impossible. Fix: the Transitive branch of wire_uv pushes an advisory VendorWarning with the stable code `pypi_uv_override_requires_uv_0_5_6` ("pin uv or make the package a direct dependency") through the outcome's warnings, and the module doc states the >= 0.5.6 boundary. The Direct branch stays silent. Co-Authored-By: Claude Fable 5.1 --- .../socket-patch-core/src/vendor/pypi_uv.rs | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index a583fb1c..5d87cb81 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -6,7 +6,10 @@ //! to the registry by a plain `uv sync`. So vendor always writes BOTH — the //! pyproject sources entry (plus, for transitive deps, a //! `[tool.uv] override-dependencies` pin, which sources DO apply to — claim -//! 8) and the lock's `[[package]]` / `requires-dist` / `[manifest]` fragments. +//! 8 — but only on uv >= 0.5.6: 0.2.35–0.5.3 ignore sources for overrides +//! and a plain `uv sync` there reinstalls the registry wheel, hence the +//! `pypi_uv_override_requires_uv_0_5_6` advisory on that branch) and the +//! lock's `[[package]]` / `requires-dist` / `[manifest]` fragments. //! //! All lock edits are targeted text surgery rather than a TOML re-serialize: //! the spike proved a surgical edit reproduces uv's own serializer output @@ -487,6 +490,19 @@ pub(super) async fn wire_uv( .is_none(); if class == UvDepClass::Transitive { + // uv 0.2.35–0.5.3 do NOT apply [tool.uv.sources] to + // override-dependencies: a plain `uv sync` on those releases silently + // reinstalls the registry wheel (the lock still names the vendored + // path, so `--frozen` installs it). No lock-shape marker separates + // 0.5.3 from 0.5.6, so this cannot be an offline refusal — advise. + advisories.push(VendorWarning::new( + "pypi_uv_override_requires_uv_0_5_6", + format!( + "transitive wiring of {canon_name} via [tool.uv] override-dependencies is \ + honored only by uv >= 0.5.6; older uv reinstalls the registry wheel on a \ + plain `uv sync` — pin uv or make the package a direct dependency" + ), + )); let spec = format!("{canon_name}=={version}"); let uv_table = ensure_table(&mut doc, &["tool", "uv"])?; if !had_uv_table { @@ -4920,4 +4936,56 @@ wheels = [ assert_eq!(pyproject, EXTRAS_DUP_REGISTRY_PYPROJECT); assert_eq!(lock, EXTRAS_DUP_REGISTRY_LOCK); } + + /// uv 0.2.35–0.5.3 do NOT apply `[tool.uv.sources]` to + /// override-dependencies: a transitive target wired through the override + /// branch is silently reinstalled from the registry by a plain `uv sync` + /// on those releases, and no lock-shape marker separates 0.5.3 from + /// 0.5.6, so an offline refusal is impossible. The branch must surface a + /// stable advisory; the Direct branch must stay silent. + #[tokio::test] + async fn override_branch_emits_uv_version_advisory() { + let tmp = write_pair(TRANSITIVE_REGISTRY_PYPROJECT, TRANSITIVE_REGISTRY_LOCK).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + let (_, meta, advisories) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + assert_eq!(meta.dep_class, "override"); + let codes: Vec<&str> = advisories.iter().map(|w| w.code).collect(); + assert_eq!(codes, vec!["pypi_uv_override_requires_uv_0_5_6"]); + let detail = &advisories[0].detail; + assert!( + detail.contains("0.5.6") && detail.contains("override-dependencies"), + "{detail}" + ); + + let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + let (_, meta, advisories) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + assert_eq!(meta.dep_class, "direct"); + assert!( + advisories.is_empty(), + "a direct dependency needs no override advisory: {advisories:?}" + ); + } } From 01326eb37ba5f42b4a24f786a7578fd963955e29 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:44:55 -0400 Subject: [PATCH 08/25] uv: repoint [manifest] constraints / build-constraints for the redirected package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the target is named in `[tool.uv] constraint-dependencies` (or build-constraint-dependencies), uv.lock carries `[manifest] constraints = [{ name = "urllib3", specifier = "==1.26.18" }]` (resp. build-constraints). Once the package has a source, uv >= 0.5.6 re-serializes that element as `{ name, path|url }` with the specifier dropped (uv 0.12.15 ground truth), so after vendoring or a hosted redirect `uv lock --check` / `uv sync --locked` exited 2 and a plain sync churned the lock. Neither backend touched those arrays: the vendored surgery only knew requires-dist / requires-dev / overrides, and the hosted rewrite_manifest returned early whenever the (script-lock-only) `requirements` key was absent. Vendored: rewrite_manifest_constraints repoints every matching element of both arrays, one whole-line ` = […]` fragment per key recorded as the new wiring kind `uv_lock_manifest_constraints` (unknown kinds are warned+skipped on revert, so the arm is added to the replace_fragment group). The records are pushed last so the reverse-order revert restores them before searching for a requires-dist element that may be byte-identical. Hosted: rewrite_manifest repoints matching `constraints` / `build-constraints` elements before the `requirements` early return, and still invents no `overrides` array for a direct dependency. Co-Authored-By: Claude Fable 5.1 --- .../src/utils/python_lock.rs | 102 +++++++++ .../socket-patch-core/src/vendor/pypi_uv.rs | 210 +++++++++++++++++- 2 files changed, 311 insertions(+), 1 deletion(-) diff --git a/crates/socket-patch-core/src/utils/python_lock.rs b/crates/socket-patch-core/src/utils/python_lock.rs index ac6e629e..56e03258 100644 --- a/crates/socket-patch-core/src/utils/python_lock.rs +++ b/crates/socket-patch-core/src/utils/python_lock.rs @@ -220,6 +220,32 @@ fn rewrite_manifest(document: &mut DocumentMut, name: &str, artifact: ArtifactSo else { return; }; + // `[tool.uv] constraint-dependencies` / `build-constraint-dependencies` + // land here as `{ name, specifier }`; uv >= 0.5.6 re-serializes a sourced + // package's element as `{ name, url|path }` (specifier dropped), so a + // stale specifier keeps `uv lock --check` / `uv sync --locked` at exit 2 + // and a plain sync churns the lock. Repoint every matching element in + // both arrays, independent of the `requirements` key below (a project + // lock has none — only script locks do). + for key in ["constraints", "build-constraints"] { + if let Some(constraints) = manifest.get_mut(key).and_then(Item::as_array_mut) { + for constraint in constraints + .iter_mut() + .filter_map(Value::as_inline_table_mut) + { + if constraint + .get("name") + .and_then(Value::as_str) + .is_some_and(|value| canonicalize_pypi_name(value) == name) + { + constraint.remove("specifier"); + constraint.remove("url"); + constraint.remove("path"); + constraint.insert(artifact.key(), Value::from(artifact.location())); + } + } + } + } if !manifest.contains_key("requirements") { return; } @@ -1129,3 +1155,79 @@ mod discovery_and_line_ending_tests { ); } } + +#[cfg(test)] +mod manifest_constraints_tests { + use super::{rewrite_python_lock, ArtifactSource}; + + const URL: &str = "https://patch.socket.dev/pkg/urllib3-1.26.18-py2.py3-none-any.whl"; + const SHA256: &str = "ccc9a9e0b18a5efc7038c504cfc580e47d2e02e5390f2e29cad833cbccb956b6"; + + /// `[tool.uv] constraint-dependencies` / `build-constraint-dependencies` + /// land in `[manifest] constraints` / `build-constraints` as + /// `{ name, specifier }`; uv >= 0.5.6 re-serializes a sourced package's + /// element as `{ name, url|path }`, so a stale specifier keeps + /// `uv lock --check` / `uv sync --locked` at exit 2 after a hosted scan. + /// Both arrays are repointed even when the project lock has no + /// `requirements` key (only script locks carry one), and no `overrides` + /// array is invented for a direct dependency. + #[test] + fn hosted_rewrite_repoints_manifest_constraints_and_build_constraints() { + let text = r#"version = 1 +revision = 3 +requires-python = ">=3.9" + +[manifest] +constraints = [{ name = "urllib3", specifier = "==1.26.18" }] +build-constraints = [ + { name = "other", specifier = "==1.0" }, + { name = "urllib3", specifier = ">=1.26" }, +] + +[[package]] +name = "fixture" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "urllib3" }, +] + +[package.metadata] +requires-dist = [{ name = "urllib3", specifier = "==1.26.18" }] + +[[package]] +name = "urllib3" +version = "1.26.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://pypi.org/urllib3-1.26.18.tar.gz", hash = "sha256:old", size = 123 } +wheels = [ + { url = "https://pypi.org/urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:old", size = 123 }, +] +"#; + let rewritten = rewrite_python_lock( + text, + "urllib3", + "1.26.18", + ArtifactSource::Url(URL), + SHA256, + ) + .unwrap() + .unwrap(); + let document: toml_edit::DocumentMut = rewritten.parse().unwrap(); + let manifest = &document["manifest"]; + let constraint = manifest["constraints"][0].as_inline_table().unwrap(); + assert_eq!(constraint["url"].as_str(), Some(URL)); + assert!(constraint.get("specifier").is_none(), "{rewritten}"); + let build = manifest["build-constraints"].as_array().unwrap(); + let foreign = build.get(0).unwrap().as_inline_table().unwrap(); + assert_eq!(foreign["specifier"].as_str(), Some("==1.0")); + assert!(foreign.get("url").is_none(), "a foreign constraint is untouched"); + let ours = build.get(1).unwrap().as_inline_table().unwrap(); + assert_eq!(ours["url"].as_str(), Some(URL)); + assert!(ours.get("specifier").is_none(), "{rewritten}"); + assert!( + manifest.as_table_like().unwrap().get("overrides").is_none(), + "a direct dependency must not gain an overrides array: {rewritten}" + ); + } +} diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index 5d87cb81..e82395e6 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -653,6 +653,25 @@ pub(super) async fn wire_uv( } } + // [manifest] constraints / build-constraints naming the target (either + // class). Recorded LAST so the reverse-order revert restores these + // whole-line fragments before the requires-dist element they may be + // byte-identical to is searched for. + let constraint_edits = rewrite_manifest_constraints(&new_lock, canon_name, rel_wheel)?; + for edit in constraint_edits.iter().rev() { + new_lock.replace_range(edit.span.clone(), &edit.new_entry); + } + for edit in constraint_edits { + wiring.push(record( + "uv.lock", + edit.kind, + WiringAction::Rewritten, + canon_name, + Some(edit.old_entry), + edit.new_entry, + )); + } + // ── commit: pyproject first, then the lock; unwind on lock failure ──── // Mode-preserving: both are user-owned files we merely edit, so the // swapped-in inode must keep its permission bits rather than reset them @@ -734,7 +753,10 @@ pub(super) async fn revert_uv(entry: &VendorEntry, root: &Path, dry_run: bool) - ) }; match rec.kind.as_str() { - "uv_lock_package" | "uv_lock_requires_dist" | "uv_lock_requires_dev" => { + "uv_lock_package" + | "uv_lock_requires_dist" + | "uv_lock_requires_dev" + | "uv_lock_manifest_constraints" => { match replace_fragment(&lock_text, new_text, original_text) { Some(t) => lock_text = t, None => { @@ -1191,6 +1213,92 @@ fn rewrite_root_metadata_entries( Ok(edits) } +/// Find + transform every `[manifest] constraints` / `build-constraints` +/// element naming `canon` (`[tool.uv] constraint-dependencies` / +/// `build-constraint-dependencies`). uv >= 0.5.6 re-serializes a sourced +/// package's element as `{ name, path }` (specifier dropped — uv 0.12.15 +/// ground truth), so an element left with its specifier keeps `uv lock +/// --check` / `uv sync --locked` at exit 2 and a plain sync churns the +/// lock. One edit per key, spanning the whole ` = […]` line with every +/// matching element rewritten: the key prefix keeps revert's text match from +/// confusing the element with a byte-identical requires-dist one. A lock +/// without `[manifest]` or without the keys yields no edits (most locks). +fn rewrite_manifest_constraints( + lock_text: &str, + canon: &str, + rel_wheel: &str, +) -> Result, (&'static str, String)> { + let index = line_index(lock_text); + let Some(h) = index.iter().position(|(_, l)| l.trim_end() == "[manifest]") else { + return Ok(Vec::new()); + }; + // Section spans until the next top-level header. + let section_end_line = index[h + 1..] + .iter() + .position(|(_, l)| l.starts_with('[')) + .map(|i| h + 1 + i) + .unwrap_or(index.len()); + let section_start = index[h].0; + let section_end = index + .get(section_end_line) + .map(|(off, _)| *off) + .unwrap_or(lock_text.len()); + let section = &lock_text[section_start..section_end]; + let needle = format!("name = \"{canon}\""); + let mut edits: Vec = Vec::new(); + + for key in ["constraints", "build-constraints"] { + let prefix = format!("{key} = ["); + // Keys sit at line start, so `constraints = [` can never match inside + // `build-constraints = [`. + let Some(line_off) = line_index(section) + .iter() + .find(|(_, l)| l.starts_with(&prefix)) + .map(|(off, _)| *off) + else { + continue; + }; + let arr_open = line_off + prefix.len() - 1; + let arr_end = balanced_span(section, arr_open).ok_or_else(|| { + ( + "pypi_uv_lock_parse_failed", + format!("uv.lock [manifest] {key} array is unbalanced"), + ) + })?; + let array_text = §ion[arr_open..arr_end]; + let mut new_array = String::with_capacity(array_text.len()); + let mut last = 0; + let mut specifier: Option = None; + let mut matched = false; + for (s, e) in top_level_brace_groups(array_text) { + let entry = &array_text[s..e]; + if !entry.contains(&needle) { + continue; + } + let (new_entry, spec) = path_source_entry(entry, rel_wheel); + if specifier.is_none() { + specifier = spec; + } + new_array.push_str(&array_text[last..s]); + new_array.push_str(&new_entry); + last = e; + matched = true; + } + if !matched { + continue; + } + new_array.push_str(&array_text[last..]); + edits.push(RequiresDistEdit { + span: (section_start + line_off)..(section_start + arr_end), + old_entry: section[line_off..arr_end].to_string(), + new_entry: format!("{}{}", §ion[line_off..arr_open], new_array), + specifier, + kind: "uv_lock_manifest_constraints", + }); + } + Ok(edits) +} + /// Build the path-source requires-dist entry from the registry one: keep /// every other key (extras, markers) in place, drop `specifier`, append /// `path` — matching uv's own serialization of a sources-path dep. @@ -4988,4 +5096,104 @@ wheels = [ "a direct dependency needs no override advisory: {advisories:?}" ); } + + // ── [tool.uv] constraint-dependencies (uv 0.12.15 ground truth) ───── + // A constrained package is recorded in `[manifest] constraints` + // (resp. `build-constraints` for build-constraint-dependencies) as + // `{ name, specifier }`; once the package has a path source uv >= 0.5.6 + // re-serializes the element as `{ name, path }` — a stale specifier + // keeps `uv lock --check` / `uv sync --locked` at exit 2. + + const CONSTRAINTS_REGISTRY_PYPROJECT: &str = r#"[project] +name = "proj" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = ["six==1.16.0"] + +[tool.uv] +constraint-dependencies = ["six==1.16.0"] +"#; + + const CONSTRAINTS_PATH_PYPROJECT: &str = r#"[project] +name = "proj" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = ["six==1.16.0"] + +[tool.uv] +constraint-dependencies = ["six==1.16.0"] + +[tool.uv.sources] +six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl" } +"#; + + /// `[manifest] constraints` AND `build-constraints` elements naming the + /// target are repointed to the path shape (one whole-line record per + /// key, kind `uv_lock_manifest_constraints`) and revert restores them + /// byte-exactly. Fixtures: the DIRECT pair with the uv 0.12.15 + /// `[manifest]` block prepended. + #[tokio::test] + async fn constraint_dependencies_manifest_entries_repointed_and_reverted() { + let manifest = "[manifest]\nconstraints = [{ name = \"six\", specifier = \"==1.16.0\" }]\nbuild-constraints = [{ name = \"six\", specifier = \">=1.16\" }]\n\n"; + let registry_lock = DIRECT_REGISTRY_LOCK.replacen("[[package]]", &format!("{manifest}[[package]]"), 1); + let path_manifest = format!( + "[manifest]\nconstraints = [{{ name = \"six\", path = \"{REL_WHEEL}\" }}]\nbuild-constraints = [{{ name = \"six\", path = \"{REL_WHEEL}\" }}]\n\n" + ); + let path_lock = DIRECT_PATH_LOCK.replacen("[[package]]", &format!("{path_manifest}[[package]]"), 1); + + let tmp = write_pair(CONSTRAINTS_REGISTRY_PYPROJECT, ®istry_lock).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + let (wiring, meta, _) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + + let (pyproject, lock) = read_pair(tmp.path()).await; + assert_eq!(pyproject, CONSTRAINTS_PATH_PYPROJECT); + assert_eq!( + lock, path_lock, + "both [manifest] constraint arrays must carry `path` (uv 0.12.15 shape)" + ); + let kinds: Vec<&str> = wiring.iter().map(|w| w.kind.as_str()).collect(); + assert_eq!( + kinds, + vec![ + "uv_sources_entry", + "uv_lock_package", + "uv_lock_requires_dist", + "uv_lock_manifest_constraints", + "uv_lock_manifest_constraints", + ] + ); + let constraint_records: Vec<&WiringRecord> = wiring + .iter() + .filter(|w| w.kind == "uv_lock_manifest_constraints") + .collect(); + assert_eq!( + constraint_records[0].original.as_ref().and_then(|v| v.as_str()), + Some("constraints = [{ name = \"six\", specifier = \"==1.16.0\" }]"), + "the fragment spans the whole key line so revert cannot confuse it \ + with an identical requires-dist element" + ); + assert_eq!( + constraint_records[1].original.as_ref().and_then(|v| v.as_str()), + Some("build-constraints = [{ name = \"six\", specifier = \">=1.16\" }]") + ); + + let entry = entry_for(wiring, meta); + let outcome = revert_uv(&entry, tmp.path(), false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(outcome.warnings.is_empty(), "{:?}", outcome.warnings); + let (pyproject, lock) = read_pair(tmp.path()).await; + assert_eq!(pyproject, CONSTRAINTS_REGISTRY_PYPROJECT); + assert_eq!(lock, registry_lock); + } } From a7579ceb4af8249b4b0aa2e675d7316d808884fd Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:48:09 -0400 Subject: [PATCH 09/25] uv vendor: keep a CRLF pyproject.toml / uv.lock pair CRLF through wire and revert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wire_uv wrote `doc.to_string()` for pyproject.toml, and toml_edit re-emits every newline as LF: a pure-CRLF pyproject (git autocrlf on Windows) came back all-LF — whole-file churn — and revert_uv, which splices fragments rather than rewriting the file, could never restore it. The lock side had the same class in three places: rewrite_target_package_unit rebuilt the target unit with `join("\n")` and appended the reconstructed [package.metadata] block with "\n\n" (mixed endings inside a CRLF lock); add_manifest_override created `[manifest]\n…\n\n`, `overrides = […]\n` and the multi-line `,\n]` tail with LF; and revert_uv removed those fragments with hardcoded "{new}\n\n" / "{new}\n", so a CRLF lock's created overrides were left in place with a drift warning. Fix: render pyproject through preserve_line_endings, record the uv_override Rewritten old/new array fragments in the file's convention (a multi-line array renders LF and replace_fragment would miss), detect the lock's terminator once (newline_of) and build every spliced, appended and removed lock fragment with it; the target unit's span is trimmed of the trailing `\r` find_unit_span includes so the rebuilt unit splices back in front of the same `\r\n`. The regression test CRLF-converts the Direct, Transitive (created override + created [manifest]), Rewritten-override (multi-line arrays in both files) and requires-dev fixtures, asserts the wired pair has no bare LF and equals the LF wiring modulo terminators, and reverts byte-exactly. Co-Authored-By: Claude Fable 5.1 --- .../socket-patch-core/src/vendor/pypi_uv.rs | 168 ++++++++++++++++-- 1 file changed, 157 insertions(+), 11 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index e82395e6..cf6c9e66 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -23,6 +23,7 @@ use toml_edit::{DocumentMut, Item, Table, Value}; use crate::crawlers::python_crawler::canonicalize_pypi_name; use crate::utils::fs::atomic_write_bytes_preserving_mode; +use crate::utils::python_lock::preserve_line_endings; use super::common::{item_get, pep508_name, pep621_declared_names, record}; use super::state::{UvMeta, VendorEntry, WiringAction, WiringRecord}; @@ -557,6 +558,10 @@ pub(super) async fn wire_uv( .and_then(Item::as_value) .map(|v| v.to_string().trim().to_string()) .unwrap_or_default(); + // Multi-line arrays render LF; record both fragments in the + // file's own convention or revert's exact-text splice misses. + let old_text = preserve_line_endings(&p.pyproject_text, old_text); + let new_text = preserve_line_endings(&p.pyproject_text, new_text); wiring.push(record( "pyproject.toml", "uv_override", @@ -591,7 +596,9 @@ pub(super) async fn wire_uv( None, format!("{canon_name} = {{ path = \"{rel_wheel}\" }}"), )); - let new_pyproject = doc.to_string(); + // toml_edit re-emits every newline as LF; a CRLF pyproject would come + // back all-LF (whole-file churn, and revert splices never restore it). + let new_pyproject = preserve_line_endings(&p.pyproject_text, doc.to_string()); // ── uv.lock text surgery (fully computed before any write) ──────────── let mut new_lock = p.lock_text.clone(); @@ -782,10 +789,12 @@ pub(super) async fn revert_uv(entry: &VendorEntry, root: &Path, dry_run: bool) - }; // A created [manifest] section was inserted with a blank // separator line; a created overrides key is one line. + // Both were terminated with the lock's own newline. + let nl = newline_of(&lock_text); let removed = if new.starts_with("[manifest]") { - remove_substring(&lock_text, &format!("{new}\n\n")) + remove_substring(&lock_text, &format!("{new}{nl}{nl}")) } else { - remove_substring(&lock_text, &format!("{new}\n")) + remove_substring(&lock_text, &format!("{new}{nl}")) }; match removed { Some(t) => lock_text = t, @@ -921,6 +930,18 @@ pub(super) async fn revert_uv(entry: &VendorEntry, root: &Path, dry_run: bool) - // ── helpers ────────────────────────────────────────────────────────────── +/// The lock's line terminator. uv writes LF, but git autocrlf on Windows +/// hands us a CRLF file; every fragment we splice, append or remove must be +/// built with the file's own terminator or the lock comes back with mixed +/// endings and revert's exact-text removals miss. +fn newline_of(text: &str) -> &'static str { + if text.contains("\r\n") { + "\r\n" + } else { + "\n" + } +} + /// Walk/create the table chain, marking CREATED intermediates implicit so /// they never render stray `[tool]` headers. fn ensure_table<'a>( @@ -992,13 +1013,17 @@ fn rewrite_target_package_unit( wheel_sha256_hex: &str, metadata_block: Option<&str>, ) -> Result<(String, String), (&'static str, String)> { + let nl = newline_of(lock_text); let span = find_unit_span(lock_text, |lines| unit_has_name(lines, canon)).ok_or_else(|| { ( "pypi_uv_lock_package_missing", format!("uv.lock has no [[package]] entry for {canon}"), ) })?; - let old_unit = lock_text[span].to_string(); + // `find_unit_span` ends at the last line's content, which in a CRLF lock + // is its trailing `\r`; keep the fragment CR-free at both ends so the + // rebuilt unit splices back in front of the same `\r\n`. + let old_unit = lock_text[span].trim_end_matches('\r').to_string(); let unit: Vec<&str> = old_unit.lines().collect(); let wheels_lines = [ "wheels = [".to_string(), @@ -1046,12 +1071,18 @@ fn rewrite_target_package_unit( } out.splice(pos..pos, wheels_lines.iter().cloned()); } - let mut new_unit = out.join("\n"); + let mut new_unit = out.join(nl); if let Some(block) = metadata_block { // uv emits [package.metadata] as a sub-table after the [[package]] // body, separated by one blank line (fixture shape: `]\n\n[package…`). - new_unit.push_str("\n\n"); - new_unit.push_str(block); + // The block is rendered LF; re-terminate it for a CRLF lock. + new_unit.push_str(nl); + new_unit.push_str(nl); + if nl == "\n" { + new_unit.push_str(block); + } else { + new_unit.push_str(&block.replace('\n', nl)); + } } Ok((old_unit, new_unit)) } @@ -1332,6 +1363,9 @@ fn add_manifest_override( rel_wheel: &str, ) -> Result<(WiringRecord, String), (&'static str, String)> { let element = format!("{{ name = \"{canon}\", path = \"{rel_wheel}\" }}"); + // Every created/spliced fragment is built with the lock's own terminator + // (revert removes `{new}{nl}` / `{new}{nl}{nl}` with the same detection). + let nl = newline_of(lock_text); let index = line_index(lock_text); let manifest_line = index.iter().position(|(_, l)| l.trim_end() == "[manifest]"); @@ -1348,9 +1382,9 @@ fn add_manifest_override( "uv.lock has no [[package]] entries".to_string(), ) })?; - let section = format!("[manifest]\noverrides = [{element}]"); + let section = format!("[manifest]{nl}overrides = [{element}]"); let mut text = lock_text.to_string(); - text.insert_str(first_pkg, &format!("{section}\n\n")); + text.insert_str(first_pkg, &format!("{section}{nl}{nl}")); return Ok(( record( "uv.lock", @@ -1389,7 +1423,7 @@ fn add_manifest_override( let new_array = if old_array.contains('\n') { // multi-line: add an indented element before the closing bracket let body = &old_array[..old_array.rfind(']').unwrap_or(old_array.len())]; - format!("{body} {element},\n]") + format!("{body} {element},{nl}]") } else if old_array[1..old_array.len() - 1].trim().is_empty() { // `overrides = []` (hand-edited; uv omits the key when empty): // no existing element to comma-separate from @@ -1423,7 +1457,7 @@ fn add_manifest_override( .map(|(off, _)| *off) .unwrap_or(lock_text.len()); let mut text = lock_text.to_string(); - text.insert_str(insert_at, &format!("{line}\n")); + text.insert_str(insert_at, &format!("{line}{nl}")); Ok(( record( "uv.lock", @@ -5196,4 +5230,116 @@ six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.1 assert_eq!(pyproject, CONSTRAINTS_REGISTRY_PYPROJECT); assert_eq!(lock, registry_lock); } + + /// Whether `text` holds a `\n` that is not part of a `\r\n` pair. + fn has_bare_lf(text: &str) -> bool { + let bytes = text.as_bytes(); + bytes + .iter() + .enumerate() + .any(|(i, &b)| b == b'\n' && (i == 0 || bytes[i - 1] != b'\r')) + } + + /// A pure-CRLF pair (git autocrlf on Windows) must wire to a pure-CRLF + /// pair and revert byte-exactly. `doc.to_string()` re-emits every + /// pyproject newline as LF, the lock surgery joined rebuilt units and + /// created `[manifest]` fragments with LF (mixed endings), and revert's + /// hardcoded `"{new}\n"` removals then missed. Covers the Direct, + /// Transitive (created override + created `[manifest]`), Rewritten + /// override (multi-line arrays in both files) and requires-dev shapes. + #[tokio::test] + async fn crlf_pyproject_and_lock_round_trip_byte_exact() { + let existing_override_pyproject = format!( + "{TRANSITIVE_REGISTRY_PYPROJECT}\n[tool.uv]\noverride-dependencies = [\n \"attrs==23.1.0\",\n]\n" + ); + let existing_override_lock = TRANSITIVE_REGISTRY_LOCK.replacen( + "[[package]]", + "[manifest]\noverrides = [\n { name = \"attrs\", specifier = \"==23.1.0\" },\n]\n\n[[package]]", + 1, + ); + let cases: [(&str, &str, &str); 4] = [ + ("direct", DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK), + ( + "transitive", + TRANSITIVE_REGISTRY_PYPROJECT, + TRANSITIVE_REGISTRY_LOCK, + ), + ( + "existing-override", + &existing_override_pyproject, + &existing_override_lock, + ), + ("dev-group", DEV_GROUP_REGISTRY_PYPROJECT, DEV_GROUP_REGISTRY_LOCK), + ]; + for (label, pyproject_lf, lock_lf) in cases { + let pyproject_crlf = pyproject_lf.replace('\n', "\r\n"); + let lock_crlf = lock_lf.replace('\n', "\r\n"); + let tmp = write_pair(&pyproject_crlf, &lock_crlf).await; + let p = load_uv_project(tmp.path()).await.unwrap(); + let (wiring, meta, _) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap_or_else(|e| panic!("{label}: {e:?}")); + + let (pyproject, lock) = read_pair(tmp.path()).await; + assert!( + !has_bare_lf(&pyproject), + "{label}: wired pyproject.toml must stay pure CRLF:\n{pyproject:?}" + ); + assert!( + !has_bare_lf(&lock), + "{label}: wired uv.lock must stay pure CRLF:\n{lock:?}" + ); + assert!( + lock.contains(REL_WHEEL) && pyproject.contains(REL_WHEEL), + "{label}: the pair must actually be wired" + ); + // Modulo line endings the wired pair is the LF pair's wiring. + let tmp_lf = write_pair(pyproject_lf, lock_lf).await; + let p_lf = load_uv_project(tmp_lf.path()).await.unwrap(); + wire_uv( + &p_lf, + tmp_lf.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap(); + let (pyproject_expect, lock_expect) = read_pair(tmp_lf.path()).await; + assert_eq!( + pyproject.replace("\r\n", "\n"), + pyproject_expect, + "{label}: pyproject wiring differs beyond line endings" + ); + assert_eq!( + lock.replace("\r\n", "\n"), + lock_expect, + "{label}: lock wiring differs beyond line endings" + ); + + let entry = entry_for(wiring, meta); + let outcome = revert_uv(&entry, tmp.path(), false).await; + assert!(outcome.success, "{label}: {:?}", outcome.error); + assert!( + outcome.warnings.is_empty(), + "{label}: {:?}", + outcome.warnings + ); + let (pyproject, lock) = read_pair(tmp.path()).await; + assert_eq!(pyproject, pyproject_crlf, "{label}: pyproject not restored"); + assert_eq!(lock, lock_crlf, "{label}: lock not restored"); + } + } } From 433af3f513e5223ceeaedf184d5ec31e4ea0b238 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:49:22 -0400 Subject: [PATCH 10/25] uv: say what a [[distribution]] lock actually cannot do instead of "records absolute paths" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both refusals (load_uv_project and the vendored ArtifactSource::Path arm of rewrite_python_lock) claimed uv < 0.2.35 "records absolute file paths". Against the real 0.1.45–0.2.34 binaries that is not the limitation: <= 0.2.6 cannot parse a relative path source at all; 0.2.17–0.2.34 install one under --frozen / plain sync, but `--locked` rejects any non-canonical spelling, plain sync (0.2.34) and every `uv lock` absolutize it, resolution is CWD-relative, and 0.2.17/0.2.18 never verify the wheel hash. A user reading the old text would look for an absolute path to fix rather than upgrade or switch to a requirements.txt install. Both messages now read: uv `[[distribution]]` lockfiles (uv < 0.2.35, experimental `uv lock`) cannot carry a portable local wheel: `--locked` rejects relative paths and `uv lock`/`uv sync` rewrite them to absolute ones; upgrade to uv >=0.2.35 for native vendoring, or use a requirements.txt installation. The vendored refusal gains a pinning test; the existing hosted assertion (`contains("0.2.35")`) still holds. Co-Authored-By: Claude Fable 5.1 --- .../src/utils/python_lock.rs | 2 +- .../socket-patch-core/src/vendor/pypi_uv.rs | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/socket-patch-core/src/utils/python_lock.rs b/crates/socket-patch-core/src/utils/python_lock.rs index 56e03258..5e7d0997 100644 --- a/crates/socket-patch-core/src/utils/python_lock.rs +++ b/crates/socket-patch-core/src/utils/python_lock.rs @@ -610,7 +610,7 @@ pub fn rewrite_python_lock( // for local artifacts (uv 0.2.34 writes `source = { path = "/abs/…" }` // and `wheels = [{ url = "file:///abs/…" }]`), so a committed // relative wheel cannot be expressed portably before 0.2.35. - return Err("uv `[[distribution]]` lockfiles (uv < 0.2.35) record absolute file paths; portable vendoring needs uv >=0.2.35".to_string()); + return Err("uv `[[distribution]]` lockfiles (uv < 0.2.35, experimental `uv lock`) cannot carry a portable local wheel: `--locked` rejects relative paths and `uv lock`/`uv sync` rewrite them to absolute ones; upgrade to uv >=0.2.35 for native vendoring, or use a requirements.txt installation".to_string()); } else if legacy_strings { Item::Value(Value::from(format!("direct+{location}"))) } else { diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index cf6c9e66..e7ba00c4 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -115,10 +115,15 @@ pub(super) async fn load_uv_project(root: &Path) -> Result=0.2.35 for portable native vendoring, or use a requirements.txt installation".to_string(), + "uv `[[distribution]]` lockfiles (uv < 0.2.35, experimental `uv lock`) cannot carry a portable local wheel: `--locked` rejects relative paths and `uv lock`/`uv sync` rewrite them to absolute ones; upgrade to uv >=0.2.35 for native vendoring, or use a requirements.txt installation".to_string(), )); } @@ -5342,4 +5347,25 @@ six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.1 assert_eq!(lock, lock_crlf, "{label}: lock not restored"); } } + + /// The `[[distribution]]` refusal names the REAL limitation measured + /// against the 0.1.45–0.2.34 binaries (relative path sources are + /// rejected by `--locked` and absolutized by `uv lock` / `uv sync`), + /// not the old "records absolute file paths" folklore, and points at + /// both exits (uv >= 0.2.35, or a requirements.txt install). + #[tokio::test] + async fn legacy_distribution_lock_refusal_names_the_real_limitation() { + let legacy_lock = "version = 1\nrequires-python = \">=3.9\"\n\n[[distribution]]\nname = \"proj\"\nversion = \"0.1.0\"\nsource = { editable = \".\" }\n"; + let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, legacy_lock).await; + let (code, detail) = load_uv_project(tmp.path()).await.unwrap_err(); + assert_eq!(code, "pypi_uv_legacy_lock_unsupported"); + assert!( + detail.contains("`--locked` rejects relative paths") + && detail.contains("rewrite them to absolute ones") + && detail.contains("uv >=0.2.35") + && detail.contains("requirements.txt"), + "{detail}" + ); + assert!(!detail.contains("record absolute"), "{detail}"); + } } From 34cea6a66fadc817168b6021ce2deb71f73fcdb3 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:50:26 -0400 Subject: [PATCH 11/25] uv vendor: gate the metadata no-op on the sub-tables too, not the bare header The 0.2.35/0.2.36 early return in rewrite_root_metadata_entries checked `unit_text.contains("[package.metadata]")`. The requires-dev sub-table header `[package.metadata.requires-dev]` does NOT contain that substring (`.` where the bare header has `]`), so a root unit carrying only the sub-table would have been treated as "no metadata": the requires-dev entry kept its specifier, `uv lock --check` / `uv sync --locked` went red, and nothing refused. Every uv release since 0.2.37 happens to write the empty bare header first, which is why this never fired. The no-op now requires the absence of BOTH the bare header and any `[package.metadata.` sub-table; the later scan already works from the sub-table header alone, so such a unit is repointed rather than skipped. Pinned by a test whose root unit has only [package.metadata.requires-dev]. Co-Authored-By: Claude Fable 5.1 --- .../socket-patch-core/src/vendor/pypi_uv.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index e7ba00c4..9f688422 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -1140,7 +1140,13 @@ fn rewrite_root_metadata_entries( // the package unit's source plus the pyproject `[tool.uv.sources]` entry // carry the redirect alone. A lock that HAS metadata but lacks the entry // is a stale lock and still refuses below. - if !unit_text.contains("[package.metadata]") { + // + // "No metadata" means neither the bare header NOR any `[package.metadata.` + // sub-table: `"[package.metadata.requires-dev]".contains("[package.metadata]")` + // is false (`.` vs `]`), so keying on the bare header alone would + // silently skip the requires-dev repoint should a uv release ever omit + // the empty header line — a stale specifier with no refusal. + if !unit_text.contains("[package.metadata]") && !unit_text.contains("[package.metadata.") { return Ok(Vec::new()); } let needle = format!("name = \"{canon}\""); @@ -5368,4 +5374,27 @@ six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.1 ); assert!(!detail.contains("record absolute"), "{detail}"); } + + /// `"[package.metadata.requires-dev]".contains("[package.metadata]")` is + /// FALSE (`.` vs `]`), so a no-op gate keyed on the bare header alone + /// would silently skip the requires-dev repoint if a uv release ever + /// omitted the empty `[package.metadata]` line — a stale specifier and a + /// red `--locked` with no refusal. A root unit carrying ONLY the + /// sub-table must still have its group entry repointed. + #[test] + fn requires_dev_is_repointed_without_a_bare_package_metadata_header() { + let lock = DEV_GROUP_REGISTRY_LOCK.replacen("[package.metadata]\n\n", "", 1); + assert!( + !lock.contains("[package.metadata]\n"), + "fixture must lack the bare header" + ); + let edits = rewrite_root_metadata_entries(&lock, "six", REL_WHEEL).unwrap(); + assert_eq!(edits.len(), 1, "the requires-dev group entry must be repointed"); + assert_eq!(edits[0].kind, "uv_lock_requires_dev"); + assert_eq!( + edits[0].new_entry, + format!("dev = [{{ name = \"six\", path = \"{REL_WHEEL}\" }}]") + ); + assert_eq!(edits[0].specifier.as_deref(), Some("==1.16.0")); + } } From fb5b584c24883802c6fbaabc20bfff194888df37 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:53:36 -0400 Subject: [PATCH 12/25] uv vendor: refuse a symlinked pyproject.toml / uv.lock before any write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit atomic_write_bytes_preserving_mode stages the new content next to the path and renames over it. uv itself writes THROUGH a symlinked uv.lock / pyproject.toml, but our rename REPLACES the link with a regular file: the target keeps the pre-vendor bytes, git shows a typechange, and a revert (which also renames) can never put the link back. wire_uv now refuses fail-closed before either write, and revert_uv keeps the artifact and fails, both with `pypi_uv_symlink_unsupported` naming the offending file (lstat via crate::utils::fs::is_symlink — the link itself, not its target). The orchestrator sweeps the freshly built wheel on a wiring refusal, so no `.socket/vendor` residue is left. The module's private FIFO-safe reader is replaced by the shared crate::utils::fs::read_regular_to_string. Co-Authored-By: Claude Fable 5.1 --- .../socket-patch-core/src/vendor/pypi_uv.rs | 143 ++++++++++++++++-- 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index 9f688422..7a15576a 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -22,7 +22,13 @@ use std::path::Path; use toml_edit::{DocumentMut, Item, Table, Value}; use crate::crawlers::python_crawler::canonicalize_pypi_name; -use crate::utils::fs::atomic_write_bytes_preserving_mode; +// `read_regular_to_string` is the FIFO-safe guarded reader (`O_NONBLOCK` +// open + fstat regular-file check): a FIFO planted as `pyproject.toml` or +// `uv.lock` fails fast instead of wedging every uv-project vendor run (and +// revert) forever in an `open(2)` that waits for a writer — the +// flavor-routing probes ahead of the load are metadata-only, so these are +// the first opens. +use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_string}; use crate::utils::python_lock::preserve_line_endings; use super::common::{item_get, pep508_name, pep621_declared_names, record}; @@ -43,21 +49,6 @@ const HIGHEST_TESTED_LOCK_REVISION: u64 = 3; /// header block (real ones are a few KiB). const MAX_WHEEL_METADATA_BYTES: u64 = 4 * 1024 * 1024; -/// Guarded read shared in shape with the sibling backend twins: -/// `open_regular_file` opens with `O_NONBLOCK` and rejects non-regular -/// files, so a FIFO planted as `pyproject.toml` or `uv.lock` fails fast -/// instead of wedging every uv-project vendor run (and revert) forever in -/// an `open(2)` that waits for a writer — the flavor-routing probes ahead -/// of the load are metadata-only, so these are the first opens. -async fn read_regular_to_string(path: &Path) -> std::io::Result { - use tokio::io::AsyncReadExt as _; - - let (mut file, metadata) = crate::utils::fs::open_regular_file(path).await?; - let mut content = String::with_capacity(metadata.len() as usize); - file.read_to_string(&mut content).await?; - Ok(content) -} - /// How the target package is declared, which picks the wiring strategy. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum UvDepClass { @@ -466,6 +457,8 @@ pub(super) async fn wire_uv( wheel_sha256_hex: &str, record_uuid: &str, ) -> Result<(Vec, UvMeta, Vec), (&'static str, String)> { + // Before ANY write: a symlinked half would be replaced by the rename. + refuse_symlinked_pair(root).await?; match check_target_guards(p, canon_name, record_uuid)? { // Defensive: the orchestrator short-circuits in-sync pre-flight and // never calls wire on it (we must never re-record our own edit as an @@ -733,6 +726,16 @@ pub(super) async fn wire_uv( pub(super) async fn revert_uv(entry: &VendorEntry, root: &Path, dry_run: bool) -> RevertOutcome { let pyproject_path = root.join("pyproject.toml"); let lock_path = root.join("uv.lock"); + // A symlinked half would be replaced by the rename-over write: keep the + // artifact (the wiring still routes through it) and fail the revert. + if let Err((code, detail)) = refuse_symlinked_pair(root).await { + return RevertOutcome { + kept_artifact: true, + success: false, + warnings: Vec::new(), + error: Some(format!("{code}: {detail}")), + }; + } let mut pyproject_text = match read_regular_to_string(&pyproject_path).await { Ok(t) => t, Err(e) => return RevertOutcome::failed(format!("cannot read pyproject.toml: {e}")), @@ -935,6 +938,27 @@ pub(super) async fn revert_uv(entry: &VendorEntry, root: &Path, dry_run: bool) - // ── helpers ────────────────────────────────────────────────────────────── +/// Refuse when `pyproject.toml` or `uv.lock` is itself a symlink. The +/// writers stage a replacement next to the path and rename over it, which +/// would REPLACE the link with a regular file — the target left stale, git +/// showing a typechange — so both wire and revert check before any write +/// (uv itself writes through the link). `Err` names the offending file. +async fn refuse_symlinked_pair(root: &Path) -> Result<(), (&'static str, String)> { + for name in ["pyproject.toml", "uv.lock"] { + if crate::utils::fs::is_symlink(&root.join(name)).await { + return Err(( + "pypi_uv_symlink_unsupported", + format!( + "{name} is a symbolic link; the atomic rewrite would replace the link with \ + a regular file and leave its target stale — vendor the real file's \ + directory instead" + ), + )); + } + } + Ok(()) +} + /// The lock's line terminator. uv writes LF, but git autocrlf on Windows /// hands us a CRLF file; every fragment we splice, append or remove must be /// built with the file's own terminator or the lock comes back with mixed @@ -5397,4 +5421,91 @@ six = { path = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.1 ); assert_eq!(edits[0].specifier.as_deref(), Some("==1.16.0")); } + + /// atomic_write_bytes_preserving_mode stages a file next to the path and + /// renames over it: a symlinked pyproject.toml / uv.lock would be + /// REPLACED by a regular file (target left stale, git shows a + /// typechange). Wire refuses before ANY write with + /// `pypi_uv_symlink_unsupported` naming the file; revert keeps the + /// artifact and fails. The link stays a link, its target keeps its bytes, + /// and nothing under `.socket/` appears. + #[cfg(unix)] + #[tokio::test] + async fn symlinked_uv_lock_or_pyproject_refused_before_write() { + for linked in ["pyproject.toml", "uv.lock"] { + let tmp = write_pair(DIRECT_REGISTRY_PYPROJECT, DIRECT_REGISTRY_LOCK).await; + let real = tmp.path().join("real"); + tokio::fs::create_dir(&real).await.unwrap(); + let target = real.join(linked); + tokio::fs::rename(tmp.path().join(linked), &target) + .await + .unwrap(); + std::os::unix::fs::symlink(&target, tmp.path().join(linked)).unwrap(); + let target_before = tokio::fs::read(&target).await.unwrap(); + let other = if linked == "pyproject.toml" { + "uv.lock" + } else { + "pyproject.toml" + }; + let other_before = tokio::fs::read(tmp.path().join(other)).await.unwrap(); + + let p = load_uv_project(tmp.path()).await.unwrap(); + let (code, detail) = wire_uv( + &p, + tmp.path(), + "six", + "1.16.0", + REL_WHEEL, + WHEEL_NAME, + WHEEL_SHA, + UUID, + ) + .await + .unwrap_err(); + assert_eq!(code, "pypi_uv_symlink_unsupported", "{linked}: {detail}"); + assert!(detail.contains(linked), "{linked}: {detail}"); + let meta = tokio::fs::symlink_metadata(tmp.path().join(linked)) + .await + .unwrap(); + assert!(meta.file_type().is_symlink(), "{linked}: link replaced"); + assert_eq!( + tokio::fs::read(&target).await.unwrap(), + target_before, + "{linked}: target rewritten through the link" + ); + assert_eq!( + tokio::fs::read(tmp.path().join(other)).await.unwrap(), + other_before, + "{linked}: the sibling file must be untouched" + ); + assert!( + !tmp.path().join(".socket").exists(), + "{linked}: no vendor dir may appear" + ); + + // Revert against a symlinked pair: keep the artifact, fail. + let entry = entry_for( + Vec::new(), + UvMeta { + dep_class: "direct".into(), + original_specifier: None, + created_sources_table: true, + lock_revision: Some(3), + }, + ); + let outcome = revert_uv(&entry, tmp.path(), false).await; + assert!(!outcome.success, "{linked}: revert must fail"); + assert!(outcome.kept_artifact, "{linked}: artifact must be kept"); + let error = outcome.error.unwrap_or_default(); + assert!( + error.contains("pypi_uv_symlink_unsupported") && error.contains(linked), + "{linked}: {error}" + ); + let meta = tokio::fs::symlink_metadata(tmp.path().join(linked)) + .await + .unwrap(); + assert!(meta.file_type().is_symlink(), "{linked}: link replaced by revert"); + assert_eq!(tokio::fs::read(&target).await.unwrap(), target_before); + } + } } From a5438997112c7a6549bc83e822c6daf93e52ca6d Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:47:54 -0400 Subject: [PATCH 13/25] vendor/pypi: fail-closed listing, include probing and orphan reclaim for the unwired-revert guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unwired-revert guard added after #238 protects a `repair`-reconstructed pypi ledger entry (no wiring records) from being deleted while a Python project file still resolves through the vendored wheel. Four gaps remained: - `keep_artifact` (`rollback/remove --preserve-state`) was still guarded, so a preserve-state revert of an unwired entry refused with `vendor_wiring_unknown_revert_blocked` although it would have deleted nothing. npm skips the guard there because the refusal exists only to protect the deletion; the pypi guard now does the same. - The lock enumeration failed OPEN: a `read_dir` error on the project root was read as "no Python locks here", `python_lock_paths` silently dropped any lock whose (followed) metadata failed, and the static probe list lacked uv.lock and pylock.toml. On a 0311 (execute-only) root, a listing-denied ACL or a transient EMFILE, `vendor --revert` deleted the referenced wheel and dropped the entry while uv.lock still resolved through it. The guard now lists the root itself (lstat only, so a symlinked lock with an unreadable target is still probed and fails closed on the read), and an unlistable root refuses with its own clause. - Once the guard cleared, the entry was still dispatched by flavor, which made a true orphan unreclaimable forever: flavor `uv` with uv.lock gone failed "cannot read uv.lock", and flavor `None` (what `repair` stamps for requirements/poetry/pdm/pipenv reconstructions) was unknown to the dispatch. An empty-wiring entry that passes the guard now skips the dispatch (it has nothing to replay) and proceeds to the uuid-dir removal. An UNKNOWN non-None flavor still fails closed: a newer backend's files may reference the artifact from a place this guard does not probe. - The requirements planner writes vendored pins into `-r` includes, but the guard probed only the root requirements.txt. `requirements_include_names` (new, exported from the core crate for the CLI's repair/orphan sweep) walks the same include tree as the planner — FIFO-safe, `Err` when a reached include exists but cannot be read — and the guard probes every in-root include, failing closed on an unreadable tree. Verified against real uv 0.9.30 and 0.5.31 through the built CLI: the refusal on a 0311 root keeps the wheel and `uv sync --frozen --offline` still installs from it; after `uv remove` the orphan is reclaimed and the ledger entry dropped. Co-Authored-By: Claude Fable 5.1 --- crates/socket-patch-core/src/vendor/mod.rs | 1 + crates/socket-patch-core/src/vendor/pypi.rs | 576 ++++++++++++++++-- .../src/vendor/pypi_requirements.rs | 207 +++++-- 3 files changed, 700 insertions(+), 84 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/mod.rs b/crates/socket-patch-core/src/vendor/mod.rs index 72fef912..9da89589 100644 --- a/crates/socket-patch-core/src/vendor/mod.rs +++ b/crates/socket-patch-core/src/vendor/mod.rs @@ -84,6 +84,7 @@ pub(crate) mod yarn_classic_lock; mod yarn_layering_tests; pub use path::{ecosystem_dir_for_purl, parse_vendor_path}; +pub use pypi_requirements::requirements_include_names; pub use state::{ carry_forward_wiring, load_state, lookup_entry, save_state, VendorEntry, VendorState, VENDOR_STATE_REL, diff --git a/crates/socket-patch-core/src/vendor/pypi.rs b/crates/socket-patch-core/src/vendor/pypi.rs index 32d30628..d6c1c8f5 100644 --- a/crates/socket-patch-core/src/vendor/pypi.rs +++ b/crates/socket-patch-core/src/vendor/pypi.rs @@ -886,17 +886,51 @@ pub async fn revert_pypi(entry: &VendorEntry, project_root: &Path, dry_run: bool /// the pylock, the script, or requirements.txt still resolve through the /// vendored wheel — every later `--frozen` / `--offline` install fails. /// Refuse whenever any Python project file still mentions the uuid dir, or -/// exists but cannot be read to prove it does not. With no reference left -/// the revert is a plain orphan cleanup and proceeds. +/// exists but cannot be read to prove it does not — or the project cannot +/// be enumerated to know which files to probe. With no reference left the +/// revert is a plain orphan cleanup and proceeds. async fn guard_unwired_pypi_revert( project_root: &Path, uuid: &str, uuid_dir_rel: &str, ) -> Option { + let clause = unwired_pypi_reference_clause(project_root, uuid).await?; + let detail = format!( + "refusing to remove {uuid_dir_rel}: the ledger entry records no pre-vendor wiring to \ + replay (it was likely reconstructed by `socket-patch repair`; the pre-vendor Python \ + lock fragments are not offline-recoverable) and {clause} — deleting the artifact \ + would make every subsequent install fail; run `socket-patch repair` to keep the \ + vendored artifact healthy, and revert by restoring the pre-vendor files (or by \ + removing the dependency and re-locking) before re-running `vendor --revert`" + ); + Some(RevertOutcome { + success: false, + warnings: vec![VendorWarning::new( + "vendor_wiring_unknown_revert_blocked", + detail.clone(), + )], + error: Some(detail), + kept_artifact: false, + }) +} + +/// The in-use probe behind [`guard_unwired_pypi_revert`]: `None` when every +/// Python project file was read and none mentions the uuid dir; otherwise +/// the human clause naming what blocks the revert. The probe list is the +/// statically named project files, the root `requirements.txt` plus every +/// `-r` include the planner may have written a pin into, and every Python +/// lock the root directory LISTS (`uv.lock`, `pylock*.toml`, `*.py.lock` +/// with its paired script). Every step fails closed: a root that cannot be +/// listed, an include tree that cannot be read, or a listed lock (a symlink +/// included — lstat only, so an unreadable target is still probed) that +/// exists but cannot be read all block the revert, because none of them +/// can prove the absence of a reference. +async fn unwired_pypi_reference_clause(project_root: &Path, uuid: &str) -> Option { let needle = format!(".socket/vendor/pypi/{uuid}/"); let mut names: Vec = [ "pyproject.toml", - "requirements.txt", + "uv.lock", + "pylock.toml", "poetry.lock", "pdm.lock", "Pipfile", @@ -905,15 +939,61 @@ async fn guard_unwired_pypi_revert( .iter() .map(|name| (*name).to_string()) .collect(); - if let Ok(locks) = crate::utils::python_lock::python_lock_paths(project_root) { - for lock in locks { - if let Some(script) = lock.strip_suffix(".lock").filter(|s| s.ends_with(".py")) { - names.push(script.to_string()); - } - names.push(lock); + match super::pypi_requirements::requirements_include_names(project_root).await { + Ok(includes) => names.extend(includes), + Err(_) => { + return Some( + "the requirements.txt include tree could not be read to prove no requirements \ + file references it" + .to_string(), + ) + } + } + // Enumerate the locks ourselves instead of through + // `python_lock_paths`, which follows symlinks and DROPS every entry + // whose target cannot be stat'ed — and whose `Err` the previous shape + // read as "no Python locks here". Neither may fail open here. + let listing = match std::fs::read_dir(project_root) { + Ok(listing) => listing, + Err(_) => { + return Some( + "the project directory could not be listed to prove no Python lock references \ + it" + .to_string(), + ) + } + }; + for entry in listing { + let Ok(entry) = entry else { + return Some( + "the project directory could not be listed to prove no Python lock references \ + it" + .to_string(), + ); + }; + let Some(name) = entry.file_name().to_str().map(str::to_string) else { + continue; + }; + if !crate::utils::python_lock::is_python_lock_name(&name) { + continue; + } + // lstat only: a regular file or ANY symlink is probed (the read + // below fails closed on a target that cannot be opened); dirs, + // FIFOs and sockets under a lock name are not locks. A failed + // file_type() is probed too. + if entry + .file_type() + .is_ok_and(|ft| !ft.is_file() && !ft.is_symlink()) + { + continue; + } + if let Some(script) = name.strip_suffix(".lock").filter(|s| s.ends_with(".py")) { + names.push(script.to_string()); + } + if !names.contains(&name) { + names.push(name); } } - let mut clause = None; for name in &names { let path = project_root.join(name); if matches!(tokio::fs::try_exists(&path).await, Ok(false)) { @@ -921,39 +1001,32 @@ async fn guard_unwired_pypi_revert( } match read_regular_to_string(&path).await { Ok(text) if text.contains(&needle) => { - clause = Some(format!("{name} still resolves through it")); - break; + return Some(format!("{name} still resolves through it")); } Ok(_) => {} // Fail-closed: a file we cannot read may still reference it. Err(_) => { - clause = Some(format!( + return Some(format!( "{name} exists but could not be read to prove it no longer references it" )); - break; } } } - let clause = clause?; - let detail = format!( - "refusing to remove {uuid_dir_rel}: the ledger entry records no pre-vendor wiring to \ - replay (it was likely reconstructed by `socket-patch repair`; the pre-vendor Python \ - lock fragments are not offline-recoverable) and {clause} — deleting the artifact \ - would make every subsequent install fail; run `socket-patch repair` to keep the \ - vendored artifact healthy, and revert by restoring the pre-vendor files (or by \ - removing the dependency and re-locking) before re-running `vendor --revert`" - ); - Some(RevertOutcome { - success: false, - warnings: vec![VendorWarning::new( - "vendor_wiring_unknown_revert_blocked", - detail.clone(), - )], - error: Some(detail), - kept_artifact: false, - }) + None } +/// `VendorEntry::flavor` values the dispatch below knows how to revert — +/// the set an UNWIRED entry must belong to (or be `None`) before it is +/// treated as a reclaimable orphan. +const KNOWN_PYPI_FLAVORS: [&str; 6] = [ + "uv", + "python-lock", + "requirements", + "poetry", + "pdm", + "pipenv", +]; + pub async fn revert_pypi_opts( entry: &VendorEntry, project_root: &Path, @@ -963,7 +1036,37 @@ pub async fn revert_pypi_opts( dry_run, keep_artifact, } = opts; - if entry.wiring.is_empty() { + let mut outcome = if entry.wiring.is_empty() { + // Nothing to replay (a `repair`-reconstructed entry): no project + // file can be restored, so the only work left is the artifact + // deletion below. Under `keep_artifact` (`--preserve-state`) even + // that is skipped — the revert is a no-op and the in-use guard, + // which exists only to protect the deletion, has nothing to + // protect (npm's precedent). Otherwise the artifact may only go + // when no Python project file provably resolves through it. Once + // the guard clears, the entry is a plain orphan: the flavor + // dispatch is skipped on purpose — flavor `uv` with uv.lock gone + // fails "cannot read uv.lock", and flavor `None` (what `repair` + // stamps for requirements/poetry/pdm/pipenv reconstructions) is + // unknown to the dispatch — and either would leave the orphan + // unreclaimable forever. + if keep_artifact { + return RevertOutcome::ok(); + } + // An UNKNOWN flavor (a newer binary's backend) still fails closed + // even here: its project files may reference the artifact from a + // place this guard does not know to probe. `None` is `repair`'s + // own stamp and every known flavor's files are probed. + if let Some(flavor) = entry + .flavor + .as_deref() + .filter(|f| !KNOWN_PYPI_FLAVORS.contains(f)) + { + return RevertOutcome::failed(format!( + "unknown pypi vendor flavor {:?}; cannot revert", + Some(flavor) + )); + } let uuid_dir_rel = vendor_uuid_dir_rel("pypi", &entry.uuid) .unwrap_or_else(|| format!(".socket/vendor/pypi/{:?}", entry.uuid)); if let Some(blocked) = @@ -971,20 +1074,26 @@ pub async fn revert_pypi_opts( { return blocked; } - } - let mut outcome = match entry.flavor.as_deref() { - Some("uv") => revert_uv(entry, project_root, dry_run).await, - Some("python-lock") => { - super::pypi_lock::revert_python_locks(entry, project_root, dry_run).await - } - Some("requirements") => revert_requirements(entry, project_root, dry_run).await, - Some("poetry") => super::pypi_poetry::revert_poetry(entry, project_root, dry_run).await, - Some("pdm") => super::pypi_pdm::revert_pdm(entry, project_root, dry_run).await, - Some("pipenv") => super::pypi_pipenv::revert_pipenv(entry, project_root, dry_run).await, - other => { - return RevertOutcome::failed(format!( - "unknown pypi vendor flavor {other:?}; cannot revert" - )) + RevertOutcome::ok() + } else { + match entry.flavor.as_deref() { + Some("uv") => revert_uv(entry, project_root, dry_run).await, + Some("python-lock") => { + super::pypi_lock::revert_python_locks(entry, project_root, dry_run).await + } + Some("requirements") => revert_requirements(entry, project_root, dry_run).await, + Some("poetry") => { + super::pypi_poetry::revert_poetry(entry, project_root, dry_run).await + } + Some("pdm") => super::pypi_pdm::revert_pdm(entry, project_root, dry_run).await, + Some("pipenv") => { + super::pypi_pipenv::revert_pipenv(entry, project_root, dry_run).await + } + other => { + return RevertOutcome::failed(format!( + "unknown pypi vendor flavor {other:?}; cannot revert" + )) + } } }; if !outcome.success || dry_run { @@ -3022,6 +3131,379 @@ wheels = [ assert!(!wheel.exists(), "the orphaned artifact dir is removed"); } + /// An unwired entry with a wheel still referenced by the project files. + /// Returns the wheel path (so the caller can assert survival/removal). + async fn unwired_uv_fixture(root: &Path, rel_wheel: &str) -> PathBuf { + tokio::fs::write( + root.join("pyproject.toml"), + format!("[project]\nname = \"p\"\ndependencies = [\"six==1.16.0\"]\n\n[tool.uv.sources]\nsix = {{ path = \"{rel_wheel}\" }}\n"), + ) + .await + .unwrap(); + tokio::fs::write( + root.join("uv.lock"), + format!("version = 1\n\n[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = {{ path = \"{rel_wheel}\" }}\n"), + ) + .await + .unwrap(); + let wheel = root.join(rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + wheel + } + + /// `rollback/remove --preserve-state` (`keep_artifact`) never deletes the + /// artifact, and an unwired entry has nothing to restore — so there is + /// nothing for the in-use guard to protect. It used to refuse with + /// `vendor_wiring_unknown_revert_blocked` although the revert would + /// have touched nothing (npm skips the guard under `keep_artifact` for + /// exactly this reason: the refusal exists only to protect the deletion). + #[tokio::test] + async fn unwired_entry_preserve_state_skips_guard() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + let wheel = unwired_uv_fixture(root, &rel_wheel).await; + let entry = revert_entry("uv", &rel_wheel, Vec::new()); + for dry_run in [true, false] { + let outcome = revert_pypi_opts( + &entry, + root, + RevertOpts { + dry_run, + keep_artifact: true, + }, + ) + .await; + assert!( + outcome.success, + "dry_run={dry_run}: preserve-state revert of an unwired entry must succeed: \ + {outcome:?}" + ); + assert!( + outcome.warnings.is_empty(), + "dry_run={dry_run}: no refusal warning: {:?}", + outcome.warnings + ); + assert!(!outcome.kept_artifact, "keep_artifact is not a drift-keep"); + assert!(wheel.is_file(), "dry_run={dry_run}: the wheel is kept"); + } + } + + /// Restores a directory mode on drop so a failing assertion never leaves + /// an unlistable/unreadable tempdir behind for `TempDir` to choke on. + #[cfg(unix)] + struct ModeGuard(PathBuf); + #[cfg(unix)] + impl Drop for ModeGuard { + fn drop(&mut self) { + use std::os::unix::fs::PermissionsExt as _; + let _ = std::fs::set_permissions(&self.0, std::fs::Permissions::from_mode(0o755)); + } + } + + /// The guard used to treat a `read_dir` failure on the project root as + /// "no Python locks here" (and its static list lacked uv.lock and + /// pylock.toml), so on an execute-only root nothing was probed and the + /// referenced wheel was deleted while uv.lock / pylock.toml still + /// resolved through it. An unlistable root cannot prove the absence of + /// a reference: refuse, fail-closed. + #[cfg(unix)] + #[tokio::test] + async fn unwired_python_entry_revert_refuses_when_root_unlistable() { + use std::os::unix::fs::PermissionsExt as _; + if unsafe { libc::geteuid() } == 0 { + eprintln!("skipping: running as root, directory perms are not enforced"); + return; + } + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + // Only the LOCK references the wheel (pyproject.toml was already + // hand-restored): the reference is reachable through the listing + // alone, not through a statically named project file. + let cases: Vec<(&str, Vec<(&str, String)>)> = vec![ + ( + "uv", + vec![ + ( + "pyproject.toml", + "[project]\nname = \"p\"\ndependencies = [\"six==1.16.0\"]\n".to_string(), + ), + ( + "uv.lock", + format!("version = 1\n\n[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = {{ path = \"{rel_wheel}\" }}\n"), + ), + ], + ), + ( + "python-lock", + vec![( + "pylock.toml", + format!("lock-version = \"1.0\"\n\n[[packages]]\nname = \"six\"\nversion = \"1.16.0\"\narchive = {{ path = \"{rel_wheel}\" }}\n"), + )], + ), + ]; + for (flavor, files) in cases { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + for (name, text) in &files { + tokio::fs::write(root.join(name), text).await.unwrap(); + } + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry(flavor, &rel_wheel, Vec::new()); + // Execute-only: paths under the root still resolve (the wheel + // and every known lock name), only the listing is denied. + std::fs::set_permissions(root, std::fs::Permissions::from_mode(0o311)).unwrap(); + let _restore = ModeGuard(root.to_path_buf()); + assert!( + std::fs::read_dir(root).is_err(), + "0o311 must make the root unlistable on this host" + ); + let outcome = revert_pypi(&entry, root, false).await; + assert!( + !outcome.success, + "{flavor}: revert under an unlistable root must refuse: {outcome:?}" + ); + assert_eq!(outcome.warnings.len(), 1, "{flavor}: {:?}", outcome.warnings); + assert_eq!( + outcome.warnings[0].code, + "vendor_wiring_unknown_revert_blocked" + ); + assert!( + outcome.warnings[0].detail.contains("could not be listed"), + "{flavor}: {}", + outcome.warnings[0].detail + ); + assert!(!outcome.kept_artifact); + assert!( + wheel.is_file(), + "{flavor}: the referenced artifact must survive" + ); + } + } + + /// A lock that is a SYMLINK whose target cannot be stat'ed used to be + /// dropped from the probe list (the lister follows the link and drops + /// any entry whose metadata fails), so the guard never saw it and the + /// wheel it may reference was deleted. Listing must keep symlinks on + /// lstat alone; the unreadable target then hits the fail-closed read. + /// Both a static-list name and a listing-only name are covered. + #[cfg(unix)] + #[tokio::test] + async fn unwired_python_entry_revert_refuses_when_lock_target_unstatable() { + use std::os::unix::fs::PermissionsExt as _; + if unsafe { libc::geteuid() } == 0 { + eprintln!("skipping: running as root, directory perms are not enforced"); + return; + } + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + for lock_name in ["pylock.toml", "pylock.dev.toml"] { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let locked = root.join("locked"); + tokio::fs::create_dir(&locked).await.unwrap(); + tokio::fs::write( + locked.join(lock_name), + format!("lock-version = \"1.0\"\n\n[[packages]]\nname = \"six\"\nversion = \"1.16.0\"\narchive = {{ path = \"{rel_wheel}\" }}\n"), + ) + .await + .unwrap(); + tokio::fs::symlink(format!("locked/{lock_name}"), root.join(lock_name)) + .await + .unwrap(); + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry("python-lock", &rel_wheel, Vec::new()); + std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000)).unwrap(); + let _restore = ModeGuard(locked.clone()); + assert!( + std::fs::metadata(root.join(lock_name)).is_err(), + "{lock_name}: the link target must be unstatable on this host" + ); + let outcome = revert_pypi(&entry, root, false).await; + assert!( + !outcome.success, + "{lock_name}: revert with an unreadable lock target must refuse: {outcome:?}" + ); + assert_eq!( + outcome.warnings.len(), + 1, + "{lock_name}: {:?}", + outcome.warnings + ); + assert_eq!( + outcome.warnings[0].code, + "vendor_wiring_unknown_revert_blocked" + ); + assert!( + outcome + .warnings[0] + .detail + .contains(&format!("{lock_name} exists but could not be read")), + "{lock_name}: {}", + outcome.warnings[0].detail + ); + assert!( + wheel.is_file(), + "{lock_name}: the referenced artifact must survive" + ); + } + } + + /// Once the guard finds no reference, an unwired entry is a plain + /// orphan and must be reclaimable. Dispatching it by flavor used to + /// fail forever: flavor `uv` with uv.lock gone → "cannot read uv.lock". + #[tokio::test] + async fn unwired_uv_entry_without_uv_lock_reclaims_orphan() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + tokio::fs::write( + root.join("pyproject.toml"), + "[project]\nname = \"p\"\ndependencies = [\"six==1.16.0\"]\n", + ) + .await + .unwrap(); + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry("uv", &rel_wheel, Vec::new()); + let dry = revert_pypi(&entry, root, true).await; + assert!(dry.success, "dry run previews the orphan cleanup: {dry:?}"); + assert!(wheel.is_file(), "dry run deletes nothing"); + let outcome = revert_pypi(&entry, root, false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(!outcome.kept_artifact); + assert!( + !root.join(format!(".socket/vendor/pypi/{UUID}")).exists(), + "the orphaned artifact dir is removed" + ); + } + + /// Same reclaim contract for flavor `None` — the shape `repair` stamps + /// for requirements/poetry/pdm/pipenv reconstructions, which the + /// dispatch used to reject with "unknown pypi vendor flavor None". + #[tokio::test] + async fn unwired_entry_flavor_none_reclaims_orphan() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + tokio::fs::write(root.join("requirements.txt"), "six==1.16.0\n") + .await + .unwrap(); + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let mut entry = revert_entry("uv", &rel_wheel, Vec::new()); + entry.flavor = None; + let outcome = revert_pypi(&entry, root, false).await; + assert!(outcome.success, "{:?}", outcome.error); + assert!(!outcome.kept_artifact); + assert!( + !root.join(format!(".socket/vendor/pypi/{UUID}")).exists(), + "the orphaned artifact dir is removed" + ); + // An UNKNOWN flavor fails closed whether wired or not: a newer + // backend's project files may reference the artifact from a place + // this guard does not probe. + let mut unknown = revert_entry("uv", &rel_wheel, Vec::new()); + unknown.flavor = Some("frobnicate".into()); + let outcome = revert_pypi(&unknown, root, false).await; + assert!(!outcome.success, "{outcome:?}"); + assert!( + outcome + .error + .as_deref() + .is_some_and(|e| e.contains("unknown pypi vendor flavor")), + "{outcome:?}" + ); + let mut wired = revert_entry("uv", &rel_wheel, Vec::new()); + wired.flavor = Some("frobnicate".into()); + wired.wiring.push(WiringRecord { + file: "requirements.txt".into(), + kind: "requirements_line".into(), + action: WiringAction::Added, + key: None, + original: None, + new: None, + }); + let outcome = revert_pypi(&wired, root, false).await; + assert!(!outcome.success, "{outcome:?}"); + assert!( + outcome + .error + .as_deref() + .is_some_and(|e| e.contains("unknown pypi vendor flavor")), + "{outcome:?}" + ); + } + + /// The requirements planner writes vendored pins into `-r` includes, so + /// a reference may live ONLY in an include. The guard used to probe the + /// root requirements.txt alone and let the include-referenced wheel go. + #[tokio::test] + async fn unwired_requirements_entry_refuses_on_include_reference() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let rel_wheel = format!(".socket/vendor/pypi/{UUID}/six-1.16.0-py2.py3-none-any.whl"); + tokio::fs::write(root.join("requirements.txt"), "-r requirements/base.txt\n") + .await + .unwrap(); + tokio::fs::create_dir(root.join("requirements")).await.unwrap(); + let include = format!( + "./{rel_wheel} --hash=sha256:{} # socket-patch vendor: six==1.16.0\n", + "0".repeat(64) + ); + tokio::fs::write(root.join("requirements/base.txt"), &include) + .await + .unwrap(); + let wheel = root.join(&rel_wheel); + tokio::fs::create_dir_all(wheel.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&wheel, b"wheel bytes").await.unwrap(); + let entry = revert_entry("requirements", &rel_wheel, Vec::new()); + for dry_run in [true, false] { + let outcome = revert_pypi(&entry, root, dry_run).await; + assert!( + !outcome.success, + "dry_run={dry_run}: include-referenced revert must refuse: {outcome:?}" + ); + assert_eq!(outcome.warnings.len(), 1, "{:?}", outcome.warnings); + assert_eq!( + outcome.warnings[0].code, + "vendor_wiring_unknown_revert_blocked" + ); + assert!( + outcome.warnings[0] + .detail + .contains("requirements/base.txt still resolves through it"), + "{}", + outcome.warnings[0].detail + ); + } + assert!(wheel.is_file(), "the include-referenced wheel must survive"); + assert_eq!( + tokio::fs::read_to_string(root.join("requirements/base.txt")) + .await + .unwrap(), + include, + "the include is untouched" + ); + } + const PIPENV_REGISTRY_LOCK: &str = r#"{ "_meta": { "hash": {"sha256": "x"}, diff --git a/crates/socket-patch-core/src/vendor/pypi_requirements.rs b/crates/socket-patch-core/src/vendor/pypi_requirements.rs index 6af1a1ca..6b7c7dde 100644 --- a/crates/socket-patch-core/src/vendor/pypi_requirements.rs +++ b/crates/socket-patch-core/src/vendor/pypi_requirements.rs @@ -603,6 +603,82 @@ fn vendor_line( /// must never edit them. The root file is always element 0. async fn collect_requirements_files(root: &Path) -> Result, (&'static str, String)> { let mut out: Vec = Vec::new(); + walk_requirements_tree(root, |rel, path, read| match read { + Ok(content) => { + // Out-of-root (`../`) and absolute includes resolve outside any + // committable root — readable so a pin inside can refuse, never + // editable. (`Path::join` passes an absolute `rel` through + // verbatim.) + let editable = is_in_root_rel(rel); + out.push(ReqFile { + rel: rel.to_string(), + content, + editable, + }); + Ok(true) + } + Err(_) if out.is_empty() => Err(( + "pypi_no_requirements", + format!("cannot read {}", path.display()), + )), + // A broken include is pip's error to report; vendor just can't see + // inside it. Skip. + Err(_) => Ok(false), + }) + .await?; + // Depth-first stack order put the root last among pushes; restore "root + // first" deterministically. + out.sort_by_key(|f| f.rel != "requirements.txt"); + Ok(out) +} + +/// Every requirements file the vendor planner may have written a pin into: +/// the root `requirements.txt` plus each IN-ROOT `-r`/`--requirement` +/// include it reaches (same walk as the planner, so a vendored pin hosted in +/// an include is found where the planner put it). Names are root-relative +/// (`requirements/base.txt`), the root first; a file that does not exist is +/// still named (so a caller probing it sees a clean "absent"), it is just +/// not descended into. FIFO-safe. `Err` when a reached file EXISTS but +/// cannot be read (a permission-denied include, a FIFO in its place): the +/// tree is then unknowable, and callers that must prove the absence of a +/// reference — the unwired-revert guard — fail closed on it. Out-of-root +/// and absolute includes are never editable, so they are neither named nor +/// followed. +pub async fn requirements_include_names(root: &Path) -> std::io::Result> { + let mut names: Vec = Vec::new(); + walk_requirements_tree(root, |rel, _path, read| { + if !is_in_root_rel(rel) { + return Ok(false); + } + names.push(rel.to_string()); + match read { + Ok(_) => Ok(true), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e), + } + }) + .await?; + names.sort_by_key(|rel| rel != "requirements.txt"); + Ok(names) +} + +/// A root-relative requirements path that stays inside the project root +/// (not `../…`, not absolute) — the only files the planner may edit. +fn is_in_root_rel(rel: &str) -> bool { + !rel.starts_with("../") && !Path::new(rel).is_absolute() +} + +/// The shared include walk behind [`collect_requirements_files`] and +/// [`requirements_include_names`]: depth-first from the root +/// `requirements.txt`, each `-r`/`--requirement` target resolved against the +/// INCLUDING file's directory and lexically normalized, visited-set cycle +/// guard, FIFO-safe reads. `visit` sees every reached file with its read +/// result and answers whether to descend into its includes (`Ok(true)`), or +/// aborts the walk with its own error. +async fn walk_requirements_tree( + root: &Path, + mut visit: impl FnMut(&str, &Path, std::io::Result) -> Result, +) -> Result<(), E> { let mut visited: HashSet = HashSet::new(); let mut stack: Vec<(String, PathBuf)> = vec![( "requirements.txt".to_string(), @@ -612,47 +688,37 @@ async fn collect_requirements_files(root: &Path) -> Result, (&'stat if !visited.insert(rel.clone()) { continue; } - let Ok(content) = read_regular_to_string(&path).await else { - if out.is_empty() { - return Err(( - "pypi_no_requirements", - format!("cannot read {}", path.display()), - )); + let read = read_regular_to_string(&path).await; + // Parse the includes BEFORE handing the content over (the visitor + // takes it by value); nothing is pushed unless it asks to descend. + let includes: Vec = match &read { + Ok(content) => { + let include_dir = match rel.rfind('/') { + Some(i) => rel[..i].to_string(), + None => String::new(), + }; + logical_lines(content) + .iter() + .filter_map(|ll| include_target(&ll.text)) + .map(|target| { + let joined = if include_dir.is_empty() { + target.to_string() + } else { + format!("{include_dir}/{target}") + }; + normalize_rel_path(&joined) + }) + .collect() } - // A broken include is pip's error to report; vendor just can't - // see inside it. Skip. - continue; + Err(_) => Vec::new(), }; - // Out-of-root (`../`) and absolute includes resolve outside any - // committable root — readable so a pin inside can refuse, never - // editable. (`Path::join` passes an absolute `rel` through verbatim.) - let editable = !rel.starts_with("../") && !Path::new(&rel).is_absolute(); - let include_dir = match rel.rfind('/') { - Some(i) => rel[..i].to_string(), - None => String::new(), - }; - for ll in logical_lines(&content) { - let Some(target) = include_target(&ll.text) else { - continue; - }; - let joined = if include_dir.is_empty() { - target.to_string() - } else { - format!("{include_dir}/{target}") - }; - let normalized = normalize_rel_path(&joined); - stack.push((normalized.clone(), root.join(&normalized))); + if visit(&rel, &path, read)? { + for normalized in includes { + stack.push((normalized.clone(), root.join(&normalized))); + } } - out.push(ReqFile { - rel, - content, - editable, - }); } - // Depth-first stack order put the root last among pushes; restore "root - // first" deterministically. - out.sort_by_key(|f| f.rel != "requirements.txt"); - Ok(out) + Ok(()) } /// The `-r`/`--requirement` include target of a logical line, if any. @@ -832,6 +898,73 @@ mod tests { use super::*; use crate::vendor::state::VendorArtifact; + /// [`requirements_include_names`] names every file the planner may + /// have pinned into — root first, nested includes resolved against the + /// including file, a missing include still named but not descended — + /// and never an out-of-root include (never editable). A reached + /// include that exists but cannot be read (a FIFO here) is an `Err`, + /// so a fail-closed caller can refuse. + #[tokio::test] + async fn requirements_include_names_walks_in_root_includes() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + tokio::fs::create_dir(root.join("requirements")).await.unwrap(); + tokio::fs::write( + root.join("requirements.txt"), + "-r requirements/base.txt\n-c constraints.txt\n-r ../shared.txt\n", + ) + .await + .unwrap(); + tokio::fs::write(root.join("constraints.txt"), "six<2\n") + .await + .unwrap(); + tokio::fs::write( + root.join("requirements/base.txt"), + "--requirement=dev.txt\n-r missing.txt\nsix==1.16.0\n", + ) + .await + .unwrap(); + tokio::fs::write(root.join("requirements/dev.txt"), "pytest\n") + .await + .unwrap(); + let names = requirements_include_names(root).await.unwrap(); + assert_eq!(names[0], "requirements.txt", "{names:?}"); + let mut rest = names[1..].to_vec(); + rest.sort(); + assert_eq!( + rest, + vec![ + "requirements/base.txt".to_string(), + "requirements/dev.txt".to_string(), + "requirements/missing.txt".to_string(), + ], + "constraints and out-of-root includes are never named" + ); + + // No root requirements.txt at all: still names the root (absent). + let empty = tempfile::tempdir().unwrap(); + assert_eq!( + requirements_include_names(empty.path()).await.unwrap(), + vec!["requirements.txt".to_string()] + ); + + #[cfg(unix)] + { + tokio::fs::remove_file(root.join("requirements/dev.txt")) + .await + .unwrap(); + let fifo = std::ffi::CString::new( + root.join("requirements/dev.txt").to_str().unwrap(), + ) + .unwrap(); + assert_eq!(unsafe { libc::mkfifo(fifo.as_ptr(), 0o644) }, 0); + let err = requirements_include_names(root) + .await + .expect_err("an include that exists but cannot be read is an Err"); + assert_ne!(err.kind(), std::io::ErrorKind::NotFound, "{err:?}"); + } + } + const UUID: &str = "9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f"; const REL_WHEEL: &str = ".socket/vendor/pypi/9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f/six-1.16.0-py2.py3-none-any.whl"; From 94693ed398a73b38ff57df8933af4daede02e257 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Tue, 15 Sep 2026 17:47:54 -0400 Subject: [PATCH 14/25] repair/vendor: FIFO-safe reference scan that also sees `-r` include pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `repair`'s ledger reconstruction and `vendor --revert`'s orphan sweep share `scan_vendor_references`, which read every wiring-file candidate with a plain `tokio::fs::read_to_string`. A FIFO under one of those names — most easily the paired `