From ee8f1109526fd5f88f1b90935e5064ecd144df2f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 23:24:09 +0000 Subject: [PATCH 1/5] Start fix for #1847 Assisted-by: Claude Code:claude-opus-5-5 From 6abc8c5e43ce0e205bd4b37d232a1c0835a4c220 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 23:34:57 +0000 Subject: [PATCH 2/5] Read the Poetry lock writer through one helper Hosted redirects, vendored wiring and the source-pin refusal each decided "was this poetry.lock written by an old Poetry" with their own copy of the same rule, while the helper built for that question had no caller. Add utils::poetry_lock::lock_writer, which reads the @generated stamp, else the lock format, and route all three through it so the warnings and refusals can't drift apart. No change for any lock a real Poetry writes. Fixes #1847 Assisted-by: Claude Code:claude-opus-5-5 --- .../src/patch/redirect/poetry.rs | 45 ++---- .../src/utils/poetry_lock.rs | 146 +++++++++++++++--- .../src/vendor/pypi_poetry.rs | 14 +- 3 files changed, 134 insertions(+), 71 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/poetry.rs b/crates/socket-patch-core/src/patch/redirect/poetry.rs index b058b0d3e..d845aeaea 100644 --- a/crates/socket-patch-core/src/patch/redirect/poetry.rs +++ b/crates/socket-patch-core/src/patch/redirect/poetry.rs @@ -9,39 +9,11 @@ use serde_json::Value; use super::{DepOverride, FileEdit, RewriteResult, RewriteWarning}; use crate::crawlers::python_crawler::canonicalize_pypi_name; use crate::utils::poetry_lock::{ - generated_by_version, lock_version, lock_wiring, pyproject_source_constraints, - rewrite_poetry_lock_all_with_version, source_constraint_advisory, source_constraint_break, - source_constraint_refusal, LockBatch, LockStep, PoetryLockDep, + lock_wiring, lock_writer, pyproject_source_constraints, rewrite_poetry_lock_all_with_version, + source_constraint_advisory, source_constraint_break, source_constraint_refusal, LockBatch, + LockStep, LockWriter, PoetryLockDep, }; -/// Whether the lock was written by a Poetry release older than 1.4. Those -/// installers neither verify hashes for local wheels nor replace an -/// already-installed package at the same version, so a warm virtualenv keeps -/// serving the upstream bytes after the redirect. Formats `0`/`1.0`/`1.1` are -/// only written by such releases; lock `2.0` is written by 1.3 through 1.8, so -/// the `@generated by Poetry X.Y.Z` header (present from 1.4) decides there. -/// -/// `version` is the lock's `lock_version` when the caller already has it; -/// otherwise the lock is parsed for it. -fn pre_1_4_writer(lock_text: &str, version: Option<&str>) -> Option<&'static str> { - let parsed; - let version = match version { - Some(version) => version, - None => { - // The batch already parsed (and shared) these bytes. - parsed = crate::utils::lock_fragments::parse_lock(lock_text).ok()?; - lock_version(&parsed).ok()? - } - }; - match version { - "0" => Some("0"), - "1.0" => Some("1.0"), - "1.1" => Some("1.1"), - "2.0" if !matches!(generated_by_version(lock_text), Some(v) if v >= (1, 4)) => Some("2.0"), - _ => None, - } -} - pub(super) fn rewrite_poetry( files: &BTreeMap, overrides: &[DepOverride], @@ -139,10 +111,10 @@ pub(super) fn rewrite_poetry( batch_lock_version, ) = rewrite_poetry_lock_all_with_version(original, &deps); let mut stale_warned = false; - // `pre_1_4_writer` reads only the first line and `[metadata] + // `lock_writer` reads only the first line and `[metadata] // lock-version`, which no package rewrite touches: judged once, from // the batch's parse when it has one. - let mut writer_format: Option> = None; + let mut stale_writer: Option> = None; for (&(dep, _), step) in usable.iter().zip(steps) { match step { LockStep::Rewritten(edits) => { @@ -171,8 +143,9 @@ pub(super) fn rewrite_poetry( }); } if !stale_warned { - if let Some(format) = *writer_format.get_or_insert_with(|| { - pre_1_4_writer(&content, batch_lock_version.as_deref()) + if let Some(writer) = *stale_writer.get_or_insert_with(|| { + lock_writer(&content, batch_lock_version.as_deref()) + .filter(|writer| writer.is_pre_1_4()) }) { stale_warned = true; // Poetry 1.0 installs url sources through pip, which reads @@ -180,7 +153,7 @@ pub(super) fn rewrite_poetry( // rest of the fragment (`&#egg=`, appended by Poetry) // as part of the digest and refuse the install (measured; // pip <= 22.2 and >= 23.1 install and verify). - let pip_note = if format == "1.0" { + let pip_note = if writer == LockWriter::Format("1.0") { " Poetry 1.0 installs through pip: pip 22.3–23.0 misparse the \ hash fragment and refuse the install (fail-closed) — use pip \ <= 22.2 or >= 23.1 in the virtualenv." diff --git a/crates/socket-patch-core/src/utils/poetry_lock.rs b/crates/socket-patch-core/src/utils/poetry_lock.rs index 8e06c0c58..9ec7774bd 100644 --- a/crates/socket-patch-core/src/utils/poetry_lock.rs +++ b/crates/socket-patch-core/src/utils/poetry_lock.rs @@ -360,19 +360,61 @@ pub fn source_constraint_advisory( ) } -/// The `(major, minor)` of the Poetry that wrote the lock, when the lock -/// says: the version in the `@generated by Poetry X.Y` stamp (1.4 on), else -/// 1.3 for `lock-version = "2.0"`. Only 1.3 – 1.8 write lock 2.0, and 1.4 – -/// 1.8 stamp their version, so an unversioned 2.0 lock is 1.3's (it stamps -/// the sentence without a version, #1447) or one whose stamp was edited -/// away; both read as the oldest writer of that format, as -/// `pre_1_4_writer` reads them. `None` for older formats. -pub fn lock_writer_version(lock_text: &str) -> Option<(u64, u64)> { - if let Some(version) = generated_by_version(lock_text) { - return Some(version); +/// Who wrote a `poetry.lock`, as far as the lock itself says: see +/// [`lock_writer`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LockWriter { + /// The `(major, minor)` of the Poetry that wrote it. + Poetry(u64, u64), + /// An unstamped lock in a format only Poetry < 1.3 writes: `"0"` (the + /// legacy `[metadata.hashes]` layout), `"1.0"` or `"1.1"`. + Format(&'static str), +} + +impl LockWriter { + /// Whether that Poetry is older than 1.4. Those installers neither + /// verify hashes for local wheels nor replace an already-installed + /// package at the same version, so a warm virtualenv keeps serving the + /// upstream bytes after the lock is rewired. + pub fn is_pre_1_4(self) -> bool { + match self { + LockWriter::Poetry(major, minor) => (major, minor) < (1, 4), + LockWriter::Format(_) => true, + } + } +} + +/// The Poetry that wrote the lock, when the lock says. The version in the +/// `@generated by Poetry X.Y` stamp (1.4 on) decides without parsing: none +/// of those releases writes a format older than 2.0. Otherwise the format +/// decides: only 1.3 – 1.8 write `lock-version = "2.0"`, and 1.4 – 1.8 +/// stamp their version, so an unstamped 2.0 lock is 1.3's (it stamps the +/// sentence without a version, #1447) or one whose stamp was edited away; +/// both read as 1.3, the oldest writer of that format. Formats 0, 1.0 and +/// 1.1 read as [`LockWriter::Format`]. An unstamped 2.1+ lock (or an +/// unreadable one) has no known writer. +/// +/// `lock_version` is the lock's [`lock_version`] when the caller already +/// has it; otherwise the lock is parsed for it (only when unstamped). +pub fn lock_writer(lock_text: &str, lock_version: Option<&str>) -> Option { + if let Some((major, minor)) = generated_by_version(lock_text) { + return Some(LockWriter::Poetry(major, minor)); + } + let parsed; + let format = match lock_version { + Some(format) => format, + None => { + parsed = crate::utils::lock_fragments::parse_lock(lock_text).ok()?; + self::lock_version(&parsed).ok()? + } + }; + match format { + "2.0" => Some(LockWriter::Poetry(1, 3)), + "0" => Some(LockWriter::Format("0")), + "1.0" => Some(LockWriter::Format("1.0")), + "1.1" => Some(LockWriter::Format("1.1")), + _ => None, } - let lock = lock_text.parse::().ok()?; - (lock_version(&lock).ok()? == "2.0").then_some((1, 3)) } /// Why a lock can't source a pyproject-source-pinned package from a URL or @@ -394,7 +436,7 @@ pub enum SourcePinBreak { /// Whether a lock that sources a pyproject `source = "…"`-pinned package /// from a URL or file breaks `poetry install` ("Repository \"pypi\" does /// not exist."; 1.3 and 1.4 report a solver failure), and why: its writer -/// is Poetry 1.3 – 2.2 ([`lock_writer_version`]), or its format is older +/// is Poetry 1.3 – 2.2 ([`lock_writer`]), or its format is older /// than 2.1. Poetry 2.3+'s own locks install it. The writer is usually but /// not always the installer, so a 2.1 lock is admitted (with the /// [`source_constraint_advisory`]); a 1.0 / 1.1 lock is refused whichever @@ -403,18 +445,14 @@ pub enum SourcePinBreak { /// A versioned stamp decides without parsing (only 1.4+ write one, and /// none of them writes a format older than 2.0); otherwise the lock is /// parsed once for its `lock-version`, which also names Poetry 1.3 (the -/// only unstamped writer of 2.0, as [`lock_writer_version`] reads it). +/// only unstamped writer of 2.0, as [`lock_writer`] reads it). pub fn source_constraint_break(lock_text: &str) -> Option { - if let Some(writer) = generated_by_version(lock_text) { - return ((1, 3)..(2, 3)) - .contains(&writer) - .then_some(SourcePinBreak::Writer); - } - let lock = lock_text.parse::().ok()?; - match lock_version(&lock).ok()? { - "2.0" => Some(SourcePinBreak::Writer), - format @ ("1.0" | "1.1") => Some(SourcePinBreak::Format(format.into())), - _ => None, + match lock_writer(lock_text, None)? { + LockWriter::Poetry(major, minor) => ((1, 3)..(2, 3)) + .contains(&(major, minor)) + .then_some(SourcePinBreak::Writer), + LockWriter::Format(format @ ("1.0" | "1.1")) => Some(SourcePinBreak::Format(format.into())), + LockWriter::Format(_) => None, } } @@ -1435,6 +1473,66 @@ grouped = {version = "*", source = "pypi"} assert_eq!(generated_by_version(&fixture("1.2.2")), None); } + /// One reader answers "who wrote this lock" for the hosted and vendored + /// pre-1.4 advisories and the source-pin refusal (#1847): the stamp, + /// else 1.3 for an unstamped 2.0 lock, else the pre-2.0 format. + #[test] + fn lock_writer_reads_the_stamp_then_the_format() { + use LockWriter::{Format, Poetry}; + for (version, want) in [ + ("0.12.17", Some(Format("0"))), + ("1.0.10", Some(Format("1.0"))), + ("1.1.15", Some(Format("1.1"))), + ("1.2.2", Some(Format("1.1"))), + ("1.3.2", Some(Poetry(1, 3))), + ("1.4.2", Some(Poetry(1, 4))), + ("1.8.5", Some(Poetry(1, 8))), + ("2.3.4", Some(Poetry(2, 3))), + ("2.4.3", Some(Poetry(2, 4))), + ] { + let lock = fixture(version); + assert_eq!(lock_writer(&lock, None), want, "{version}"); + let format = lock_version(&crate::utils::lock_fragments::parse_lock(&lock).unwrap()) + .unwrap() + .to_owned(); + assert_eq!(lock_writer(&lock, Some(&format)), want, "{version}"); + assert_eq!(want.unwrap().is_pre_1_4(), version < "1.4", "{version}"); + } + + let stamp = |v: &str| { + format!("# This file is automatically @generated by Poetry {v} and should not be changed by hand.\n") + }; + let body = |format: &str| format!("[metadata]\nlock-version = \"{format}\"\n"); + for (lock, want) in [ + // An unstamped (or versionless-stamped) 2.0 lock is 1.3's. + (body("2.0"), Some(Poetry(1, 3))), + (format!("{}{}", stamp(""), body("2.0")), Some(Poetry(1, 3))), + // A stamp decides without the format. + (stamp("1.4.0"), Some(Poetry(1, 4))), + (stamp("2.3.0"), Some(Poetry(2, 3))), + ( + format!("{}{}", stamp("2.3.0"), body("2.1")), + Some(Poetry(2, 3)), + ), + // An unstamped 2.1+ lock, or an unreadable one, has no known + // writer. + (body("2.1"), None), + (body("2.2"), None), + ("[[package]]\n".to_owned(), None), + ("not = [toml".to_owned(), None), + ] { + assert_eq!(lock_writer(&lock, None), want, "{lock:?}"); + assert_eq!( + lock_writer(&lock.replace('\n', "\r\n"), None), + want, + "CRLF {lock:?}" + ); + } + assert!(Poetry(1, 3).is_pre_1_4()); + assert!(!Poetry(1, 4).is_pre_1_4()); + assert!(!Poetry(2, 3).is_pre_1_4()); + } + /// The edits a rewrite hands back are exactly `poetry_lock_edits` of its /// input and output — whether reused from the rewrite or re-derived. #[test] diff --git a/crates/socket-patch-core/src/vendor/pypi_poetry.rs b/crates/socket-patch-core/src/vendor/pypi_poetry.rs index 373fd8323..cfecec6ba 100644 --- a/crates/socket-patch-core/src/vendor/pypi_poetry.rs +++ b/crates/socket-patch-core/src/vendor/pypi_poetry.rs @@ -125,17 +125,9 @@ pub(super) async fn load_poetry_project( } // Poetry < 1.4 neither verifies local wheel hashes nor replaces an // already-installed package at the same version (a warm virtualenv keeps - // the upstream bytes after the lock is rewired). Formats 0/1.0/1.1 are - // only written by those releases; lock 2.0 is written by 1.3 through 1.8, - // so the `@generated by Poetry X.Y.Z` header (present from 1.4) decides. - let pre_1_4_writer = match lock_version.as_str() { - "0" | "1.0" | "1.1" => true, - "2.0" => !matches!( - crate::utils::poetry_lock::generated_by_version(&lock_text), - Some(v) if v >= (1, 4) - ), - _ => false, - }; + // the upstream bytes after the lock is rewired). + let pre_1_4_writer = poetry_lock::lock_writer(&lock_text, Some(&lock_version)) + .is_some_and(|writer| writer.is_pre_1_4()); if pre_1_4_writer { warnings.push(VendorWarning::new( "pypi_poetry_integrity_unverified", From 672ab67f229cafe5f022f40ab066a64b15104fc7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 00:03:47 +0000 Subject: [PATCH 3/5] Let an old lock format outrank the Poetry stamp A poetry.lock in format 0, 1.0 or 1.1 is only ever written by Poetry older than 1.3. If a hand-merged or copied header claimed Poetry 1.4+, the shared writer reader trusted the header and dropped the "written by Poetry < 1.4" install warnings that hosted and vendored modes gave before. Read the format first, as those warnings always did, and test a stamp that disagrees with the format. Assisted-by: Claude Code:claude-opus-5-5 --- .../src/utils/poetry_lock.rs | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/crates/socket-patch-core/src/utils/poetry_lock.rs b/crates/socket-patch-core/src/utils/poetry_lock.rs index 9ec7774bd..f1d6d986d 100644 --- a/crates/socket-patch-core/src/utils/poetry_lock.rs +++ b/crates/socket-patch-core/src/utils/poetry_lock.rs @@ -179,7 +179,7 @@ fn is_2x(version: &str) -> bool { /// Advisory only — a lock can be consumed by a different Poetry than the one /// that wrote it — but it lets warnings about the WRITER's installer be /// precise instead of blaming every lock-2.0 project for Poetry 1.3. -pub fn generated_by_version(lock_text: &str) -> Option<(u64, u64)> { +pub(crate) fn generated_by_version(lock_text: &str) -> Option<(u64, u64)> { let first = lock_text.lines().next()?; let rest = first.split("@generated by Poetry ").nth(1)?; let version = rest.split_whitespace().next()?; @@ -384,37 +384,38 @@ impl LockWriter { } } -/// The Poetry that wrote the lock, when the lock says. The version in the -/// `@generated by Poetry X.Y` stamp (1.4 on) decides without parsing: none -/// of those releases writes a format older than 2.0. Otherwise the format -/// decides: only 1.3 – 1.8 write `lock-version = "2.0"`, and 1.4 – 1.8 -/// stamp their version, so an unstamped 2.0 lock is 1.3's (it stamps the -/// sentence without a version, #1447) or one whose stamp was edited away; -/// both read as 1.3, the oldest writer of that format. Formats 0, 1.0 and -/// 1.1 read as [`LockWriter::Format`]. An unstamped 2.1+ lock (or an -/// unreadable one) has no known writer. +/// The Poetry that wrote the lock, when the lock says. A format older than +/// 2.0 decides first: only Poetry < 1.3 write formats 0, 1.0 and 1.1, so +/// they read as [`LockWriter::Format`] whatever the first line claims (a +/// stamp is advisory, and a hand-merged header must not hide an old lock). +/// Otherwise the version in the `@generated by Poetry X.Y` stamp (1.4 on) +/// decides. An unstamped `lock-version = "2.0"` reads as 1.3: only 1.3 – +/// 1.8 write that format, and 1.4 – 1.8 stamp their version, so it is +/// 1.3's (it stamps the sentence without a version, #1447) or one whose +/// stamp was edited away, read as the oldest writer of that format. An +/// unstamped 2.1+ lock (or an unreadable one) has no known writer. /// -/// `lock_version` is the lock's [`lock_version`] when the caller already -/// has it; otherwise the lock is parsed for it (only when unstamped). -pub fn lock_writer(lock_text: &str, lock_version: Option<&str>) -> Option { - if let Some((major, minor)) = generated_by_version(lock_text) { - return Some(LockWriter::Poetry(major, minor)); - } +/// `known_format` is the lock's [`lock_version`] when the caller already +/// has it; otherwise the lock is parsed for it. +pub fn lock_writer(lock_text: &str, known_format: Option<&str>) -> Option { let parsed; - let format = match lock_version { - Some(format) => format, + let format = match known_format { + Some(format) => Some(format), None => { - parsed = crate::utils::lock_fragments::parse_lock(lock_text).ok()?; - self::lock_version(&parsed).ok()? + parsed = lock_text.parse::().ok(); + parsed.as_ref().and_then(|lock| lock_version(lock).ok()) } }; match format { - "2.0" => Some(LockWriter::Poetry(1, 3)), - "0" => Some(LockWriter::Format("0")), - "1.0" => Some(LockWriter::Format("1.0")), - "1.1" => Some(LockWriter::Format("1.1")), - _ => None, + Some("0") => return Some(LockWriter::Format("0")), + Some("1.0") => return Some(LockWriter::Format("1.0")), + Some("1.1") => return Some(LockWriter::Format("1.1")), + _ => {} + } + if let Some((major, minor)) = generated_by_version(lock_text) { + return Some(LockWriter::Poetry(major, minor)); } + (format == Some("2.0")).then_some(LockWriter::Poetry(1, 3)) } /// Why a lock can't source a pyproject-source-pinned package from a URL or @@ -442,10 +443,9 @@ pub enum SourcePinBreak { /// [`source_constraint_advisory`]); a 1.0 / 1.1 lock is refused whichever /// Poetry installs it, since only Poetry 1.0 – 1.2 would. /// -/// A versioned stamp decides without parsing (only 1.4+ write one, and -/// none of them writes a format older than 2.0); otherwise the lock is -/// parsed once for its `lock-version`, which also names Poetry 1.3 (the -/// only unstamped writer of 2.0, as [`lock_writer`] reads it). +/// The lock is parsed once for its `lock-version`; a versioned stamp then +/// names the writer of a 2.x lock, and an unstamped 2.0 lock is Poetry +/// 1.3's (as [`lock_writer`] reads it). pub fn source_constraint_break(lock_text: &str) -> Option { match lock_writer(lock_text, None)? { LockWriter::Poetry(major, minor) => ((1, 3)..(2, 3)) @@ -1474,10 +1474,11 @@ grouped = {version = "*", source = "pypi"} } /// One reader answers "who wrote this lock" for the hosted and vendored - /// pre-1.4 advisories and the source-pin refusal (#1847): the stamp, - /// else 1.3 for an unstamped 2.0 lock, else the pre-2.0 format. + /// pre-1.4 advisories and the source-pin refusal (#1847): a + /// pre-2.0 format first, then the stamp, else 1.3 for an unstamped 2.0 + /// lock. #[test] - fn lock_writer_reads_the_stamp_then_the_format() { + fn lock_writer_reads_old_formats_then_the_stamp() { use LockWriter::{Format, Poetry}; for (version, want) in [ ("0.12.17", Some(Format("0"))), @@ -1496,7 +1497,8 @@ grouped = {version = "*", source = "pypi"} .unwrap() .to_owned(); assert_eq!(lock_writer(&lock, Some(&format)), want, "{version}"); - assert_eq!(want.unwrap().is_pre_1_4(), version < "1.4", "{version}"); + let pre_1_4 = matches!(version, "0.12.17" | "1.0.10" | "1.1.15" | "1.2.2" | "1.3.2"); + assert_eq!(want.unwrap().is_pre_1_4(), pre_1_4, "{version}"); } let stamp = |v: &str| { From f3a01625ae2f3eedabbe29b1e4c14e5dec5e5666 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 02:07:53 +0000 Subject: [PATCH 4/5] Test that an old lock format wins over a 1.4+ stamp The PR's one behaviour change is that a 1.0/1.1 lock carrying a hand-merged 1.4+ stamp now reads as its format (and source_constraint_break refuses it) instead of trusting the stamp. The description says this is covered, but no test row exercised it. Add rows to lock_writer's and source_constraint_break's tests; the latter fail on main, where the stamp decided. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/socket-patch-core/src/utils/poetry_lock.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/socket-patch-core/src/utils/poetry_lock.rs b/crates/socket-patch-core/src/utils/poetry_lock.rs index f1d6d986d..c1f154ba7 100644 --- a/crates/socket-patch-core/src/utils/poetry_lock.rs +++ b/crates/socket-patch-core/src/utils/poetry_lock.rs @@ -1029,6 +1029,8 @@ grouped = {version = "*", source = "pypi"} } let versionless = "# This file is automatically @generated by Poetry and should not be \ changed by hand.\n\n"; + let stamped_1_4 = "# This file is automatically @generated by Poetry 1.4.0 and should not \ + be changed by hand.\n\n"; let lock_body = |lock_version: &str| { format!("[metadata]\nlock-version = \"{lock_version}\"\npython-versions = \"*\"\ncontent-hash = \"x\"\n") }; @@ -1038,6 +1040,9 @@ grouped = {version = "*", source = "pypi"} ("", "2.0", Some(Writer)), ("# a comment above the stamp\n", "2.0", Some(Writer)), (versionless, "1.1", format("1.1")), + // A 1.4+ stamp on a pre-2.1 format doesn't hide it. + (stamped_1_4, "1.1", format("1.1")), + (stamped_1_4, "1.0", format("1.0")), ("", "1.1", format("1.1")), ("", "1.0", format("1.0")), (versionless, "2.1", None), @@ -1516,6 +1521,15 @@ grouped = {version = "*", source = "pypi"} format!("{}{}", stamp("2.3.0"), body("2.1")), Some(Poetry(2, 3)), ), + // A pre-2.0 format decides over a (hand-merged) 1.4+ stamp. + ( + format!("{}{}", stamp("1.4.0"), body("1.1")), + Some(Format("1.1")), + ), + ( + format!("{}{}", stamp("2.2.1"), body("1.0")), + Some(Format("1.0")), + ), // An unstamped 2.1+ lock, or an unreadable one, has no known // writer. (body("2.1"), None), From 3143a07990a6884db163efc4d3716cb4a30a6334 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 05:44:38 +0000 Subject: [PATCH 5/5] Read the lock format through the memoized parse in lock_writer lock_writer parsed the lock with an uncached DocumentMut parse. Before this PR, source_constraint_break skipped parsing for any stamped lock, and the hosted pre-1.4 check used lock_fragments::parse_lock. Now every source-constrained package re-parsed the whole lock, and the PARSES counter that many_patches_render_the_lock_once relies on never saw it. Use the shared parse_lock so the parse is shared and counted. Also fix LockWriter::Format's doc (a format now wins over a stamp), and cover the six Poetry fixture locks the writer table skipped. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011f9aeS41XjBLw8LQsUUTYU --- crates/socket-patch-core/src/utils/poetry_lock.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/socket-patch-core/src/utils/poetry_lock.rs b/crates/socket-patch-core/src/utils/poetry_lock.rs index c1f154ba7..5c14c8fa3 100644 --- a/crates/socket-patch-core/src/utils/poetry_lock.rs +++ b/crates/socket-patch-core/src/utils/poetry_lock.rs @@ -366,8 +366,8 @@ pub fn source_constraint_advisory( pub enum LockWriter { /// The `(major, minor)` of the Poetry that wrote it. Poetry(u64, u64), - /// An unstamped lock in a format only Poetry < 1.3 writes: `"0"` (the - /// legacy `[metadata.hashes]` layout), `"1.0"` or `"1.1"`. + /// A lock in a format only Poetry < 1.3 writes, stamped or not: `"0"` + /// (the legacy `[metadata.hashes]` layout), `"1.0"` or `"1.1"`. Format(&'static str), } @@ -402,7 +402,7 @@ pub fn lock_writer(lock_text: &str, known_format: Option<&str>) -> Option Some(format), None => { - parsed = lock_text.parse::().ok(); + parsed = crate::utils::lock_fragments::parse_lock(lock_text).ok(); parsed.as_ref().and_then(|lock| lock_version(lock).ok()) } }; @@ -1492,7 +1492,13 @@ grouped = {version = "*", source = "pypi"} ("1.2.2", Some(Format("1.1"))), ("1.3.2", Some(Poetry(1, 3))), ("1.4.2", Some(Poetry(1, 4))), + ("1.5.1", Some(Poetry(1, 5))), + ("1.6.1", Some(Poetry(1, 6))), + ("1.7.1", Some(Poetry(1, 7))), ("1.8.5", Some(Poetry(1, 8))), + ("2.0.1", Some(Poetry(2, 0))), + ("2.1.4", Some(Poetry(2, 1))), + ("2.2.1", Some(Poetry(2, 2))), ("2.3.4", Some(Poetry(2, 3))), ("2.4.3", Some(Poetry(2, 4))), ] {