Repository navigation
Vendored Poetry wires a 2.x lock through two different splicers depending on its line endings #936
Description
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: register comment.
Kind: refactor (a duplicated splicer whose behavior has drifted, proven by execution). Source: new finding; register E66 (related to E23).
Problem
wire_poetry picks its forward splicer by the lock's line endings:
- legacy locks (
0/1.0/1.1) and any CRLF lock go through the sharedtoml_editengineutils::poetry_lock::rewrite_poetry_lock_with_edits, which hosted mode uses too; - LF 2.x locks go through a private line scanner,
rewrite_target_package_unit, built ontoml_surgery::{find_unit_span, package_unit_lines, replace_files_array}.
pypi_poetry.rs#L316-L349:``
let edits =
if matches!(p.lock_version.as_str(), "0" | "1.0" | "1.1") || p.lock_text.contains("\r\n") {
let rewrite = crate::utils::poetry_lock::rewrite_poetry_lock_with_edits(/* … */)
// …
} else {
vec[rewrite_target_package_unit(/* … */)?]
};- The line scanner:
pypi_poetry.rs#L432-L476andtoml_surgery.rs#L82-L123.`` - The engine:
poetry_lock.rs#L243-L346.
The two have drifted. Proof: a throwaway test in pypi_poetry.rs's test module, run twice on 9c43dfc. It wired each of the module's 2.x fixtures (LOCK21_DIRECT_REGISTRY, LOCK21_TRANSITIVE_REGISTRY, LOCK20_DIRECT_REGISTRY) once as LF and once as CRLF, and compared the results after normalizing CRLF to LF.
- The output shape differs for every fixture. LF writes Poetry's own multi-line array, which the fixture tests pin:
CRLF (and hosted mode, which uses the same engine) writes the
files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:0bf5…"}, ]
toml_editinline rendering:So the same project gets a differentfiles = [{ file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:0bf5…" }]
poetry.lockdiff depending oncore.autocrlf, and only the LF shape is fixture-proven. - The engine's input gates are skipped on LF.
- The engine refuses a wheel whose filename doesn't match the locked package (
poetry_lock.rs#L262). It also lowercases the digest, because "an uppercase digest would fail every install" (#L259-L261).`` - In the test,
wire_poetrywith wheelevil-9.9-py3-none-any.whlsucceeded on LF but failed on CRLF ("Poetry patch wheel does not match the locked package"). - An uppercase digest was written uppercase on LF and lowercase on CRLF.
- Today's orchestrator passes its own lowercase sha and a matching wheel name, so these two are latent. They show that the gates live in only one of the two paths.
- The engine refuses a wheel whose filename doesn't match the locked package (
- The name matcher differs. The line scanner matches only the literal
name = "…"spelling (common::unit_has_canon_name), sowire_fails_closed_when_text_surgery_cannot_find_the_parsed_unitpins a refusal forname="six". The engine reads the parsed TOML and accepts the same lock when it is CRLF. This was read from the code, not executed.
Symptoms / Impact
No open issue tracks this. The impact is moderate:
- Vendored diffs depend on line endings, and hosted and vendored write different shapes for the same 2.x lock.
- Two forward rewriters for one format must each be fixed when Poetry's shape changes.
- The line scanner is the last Poetry-only text-surgery path after Fix Poetry/PDM lock splice drift (#694, #695) #703 unified the Poetry/PDM engine.
Proposed change
- Make the shared engine emit Poetry's own multi-line
files = [\n {file = …, hash = …},\n]shape for 2.x locks.utils::poetry_lock::is_multiline_arrayand the legacylegacy_files_entryalready do this for[metadata.files]. This also aligns hosted output with what Poetry writes. - Route every vendored Poetry lock through
rewrite_poetry_lock_with_edits, and delete the LF branch. - Delete:
rewrite_target_package_unit(pypi_poetry.rs),toml_surgery::replace_files_arrayandtoml_surgery::package_unit_lines(Poetry-only), andcommon::unit_has_canon_name(Poetry-only). uv keepsfind_unit_spanand its other helpers.
Size and scope
- Files:
vendor/pypi_poetry.rs,vendor/toml_surgery.rs,vendor/common.rsandutils/poetry_lock.rs; roughly −120 / +40 production lines. - Out of scope: PDM (already engine-only), the revert path (
revert_lock_fragment_splice_atomic, already shared) and the wider backend skeleton (E23). - Coordinate with open PR Fix per-patch Poetry/PDM lock re-parse (#760, #762) #877, which touches
utils/poetry_lock.rsfor parse reuse.
Acceptance criteria
-
wire_poetrycalls onlyutils::poetry_lockfor every lock version and line ending. -
wiring_matches_fixtures_byte_identically_both_lock_versionsstays green, with byte-identical fixture output. - New test: each 2.x fixture wired as LF and as CRLF gives the same lock after CRLF→LF normalization.
- New test: hosted
rewrite_poetry_lockon a 2.x fixture keeps the multi-linefilesshape. -
wire_fails_closed_when_text_surgery_cannot_find_the_parsed_unitis rewritten to the engine's behavior forname="six"(accept, or refuse with a stated reason). - The CRLF, revert, legacy and
files-keyed sub-table tests inpypi_poetry.rsandutils/poetry_lock.rsstay green.
Dependencies
- Blocked by: none. Sequence after Fix per-patch Poetry/PDM lock re-parse (#760, #762) #877 if that merges first.
- Blocks: E23 (the shared PyPI lock-backend envelope) is simpler once Poetry has one forward path.
Backlog review — 2026-10-08
Priority: P1 → P3. Demonstrated effects are formatting/parser differences; the bad-wheel/uppercase-hash cases are prevented by the current orchestrator. No live urgent exploit or wrong attestation is established.
Activity
audit-ecosystems: file E23 (#937), new finding E66 (#936)
Refactor queue: re-rank at 12:58Z
mikolalysenko commentedon Oct 8, 2026
[agent] Re-checked on main @
e2d9633. Nothing has changed:wire_poetrystill picks the sharedtoml_editengine for legacy or CRLF locks and the privaterewrite_target_package_unitline scanner for LF 2.x locks (pypi_poetry.rs#L318-L347,`` scanner at#L432). No open PR touches this branch point.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Claiming this issue for the architecture refactor routine (highest leverage: the best item whose files no open PR blocks; deletes vendored Poetry's private LF splicer so every lock goes through
utils::poetry_lock). Branch: arch-refactor/936-poetry-one-splicer. Claim-ID: 2026-10-08T21:55:59Z-7c3e19Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Draft PR: #1185. It takes the whole issue: the engine writes 2.x
filesone file per line, every vendored Poetry lock goes through it, and the LF line scanner and its helpers are deleted.Generated by Claude Code
refactor: run 2026-10-08T22Z, PR #1185 (#936), record merged #1141 #1145