Skip to content

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 shared toml_edit engine utils::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 on toml_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 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.

  1. The output shape differs for every fixture. LF writes Poetry's own multi-line array, which the fixture tests pin:
    files = [
        {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:0bf5…"},
    ]
    CRLF (and hosted mode, which uses the same engine) writes the toml_edit inline rendering:
    files = [{ file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:0bf5…" }]
    So the same project gets a different poetry.lock diff depending on core.autocrlf, and only the LF shape is fixture-proven.
  2. 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_poetry with wheel evil-9.9-py3-none-any.whl succeeded 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.
  3. The name matcher differs. The line scanner matches only the literal name = "…" spelling (common::unit_has_canon_name), so wire_fails_closed_when_text_surgery_cannot_find_the_parsed_unit pins a refusal for name="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_array and the legacy legacy_files_entry already 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_array and toml_surgery::package_unit_lines (Poetry-only), and common::unit_has_canon_name (Poetry-only). uv keeps find_unit_span and its other helpers.

Size and scope

  • Files: vendor/pypi_poetry.rs, vendor/toml_surgery.rs, vendor/common.rs and utils/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.rs for parse reuse.

Acceptance criteria

  • wire_poetry calls only utils::poetry_lock for every lock version and line ending.
  • wiring_matches_fixtures_byte_identically_both_lock_versions stays 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_lock on a 2.x fixture keeps the multi-line files shape.
  • wire_fails_closed_when_text_surgery_cannot_find_the_parsed_unit is rewritten to the engine's behavior for name="six" (accept, or refuse with a stated reason).
  • The CRLF, revert, legacy and files-keyed sub-table tests in pypi_poetry.rs and utils/poetry_lock.rs stay green.

Dependencies

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)pm:poetryPoetrypriority:p1refactorStructural change: duplicated code or logic, missing abstraction, layering, dead code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions