[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: E54.
Kind: bug. Source: new finding (register E54), part of review Part 5.4's "one line-ending policy" (E16).
Problem
toml_edit re-emits every newline as LF, so each Python lock rewriter has to restore line endings after rendering. The Poetry and PDM rewriters do this with two different rules, and they flip the edited package unit of a mixed-line-ending lock in opposite directions.
Both rewriters then splice only the changed fragments, the package unit and its boundary, back into the original text. So the rule decides the line endings of the unit's lines, including the lines the rewrite didn't otherwise change. Hosted (redirect/poetry.rs, redirect/pdm.rs) and vendored (vendor/pypi_poetry.rs, vendor/pypi_pdm.rs) share these functions, so both modes are affected.
Proof by execution (a throwaway integration test on 045d7ec, run twice, not committed). Input: the repository's own fixture (tests/fixtures/poetry/2.4.3/poetry.lock or tests/fixtures/pdm-native/2.29.2.lock), converted to CRLF, with the name = "urllib3" line of the patched unit left LF. Each was rewritten for a hosted urllib3 1.26.18 wheel:
|
input CRLF / LF lines |
output CRLF / LF lines |
edited unit's lines |
rewrite_poetry_lock |
22 / 1 |
24 / 0 |
all CRLF |
rewrite_pdm_lock |
21 / 1 |
12 / 8 |
all LF, inside a CRLF file |
On the same input shape, Poetry normalizes the unit to the majority ending, while PDM turns the unit's CRLF lines into LF (8 LF lines in a CRLF file). That leaves a CRLF lock with a block of LF lines, which shows up as whole-unit churn in git diff. Rollback replays the recorded fragments, so it restores the bytes exactly in both cases. The defect is the drift in the forward rewrite and the duplicated rule.
Symptoms
None filed. #467 is the same class of bug for yarn classic (mixed CRLF/LF converted to CRLF).
Impact: low severity. Only locks that already mix line endings are affected, and only the rewritten unit changes. It is the third and fourth spelling of the "toml_edit emits LF" fix (Part 5.4 lists line_endings refusing mixed files, python_lock's rule and cargo_manifest's LCS alignment), and each new rewriter picks a different one.
Proposed change
Give both rewriters one rule. Since only fragments are spliced, the rule that never flips an untouched line is per fragment: render each replacement fragment with the line ending that dominates the original fragment it replaces (an LF-only or CRLF-only file then behaves exactly as today).
Size and scope
utils/poetry_lock.rs, utils/pdm_lock.rs, utils/python_lock.rs. About 30 production lines plus tests.
- Out of scope: Cargo's
reconcile_line_endings, the npm family, and the repo-wide line-ending policy (E16).
Acceptance criteria
Dependencies
Independent. It is simpler after #694 (the shared fragment engine), because then it is fixed in one place.
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: E54.
Kind: bug. Source: new finding (register E54), part of review Part 5.4's "one line-ending policy" (E16).
Problem
toml_editre-emits every newline as LF, so each Python lock rewriter has to restore line endings after rendering. The Poetry and PDM rewriters do this with two different rules, and they flip the edited package unit of a mixed-line-ending lock in opposite directions.utils/poetry_lock.rs#L366-L369: if the input contains any\r\n, every line of the rendering becomes CRLF.utils/pdm_lock.rs#L268: callspython_lock::preserve_line_endings, which converts back only a CRLF-only input and leaves a mixed input as rendered (all LF).Both rewriters then splice only the changed fragments, the package unit and its boundary, back into the original text. So the rule decides the line endings of the unit's lines, including the lines the rewrite didn't otherwise change. Hosted (
redirect/poetry.rs,redirect/pdm.rs) and vendored (vendor/pypi_poetry.rs,vendor/pypi_pdm.rs) share these functions, so both modes are affected.Proof by execution (a throwaway integration test on
045d7ec, run twice, not committed). Input: the repository's own fixture (tests/fixtures/poetry/2.4.3/poetry.lockortests/fixtures/pdm-native/2.29.2.lock), converted to CRLF, with thename = "urllib3"line of the patched unit left LF. Each was rewritten for a hosted urllib3 1.26.18 wheel:rewrite_poetry_lockrewrite_pdm_lockOn the same input shape, Poetry normalizes the unit to the majority ending, while PDM turns the unit's CRLF lines into LF (8 LF lines in a CRLF file). That leaves a CRLF lock with a block of LF lines, which shows up as whole-unit churn in
git diff. Rollback replays the recorded fragments, so it restores the bytes exactly in both cases. The defect is the drift in the forward rewrite and the duplicated rule.Symptoms
None filed. #467 is the same class of bug for yarn classic (mixed CRLF/LF converted to CRLF).
Impact: low severity. Only locks that already mix line endings are affected, and only the rewritten unit changes. It is the third and fourth spelling of the "
toml_editemits LF" fix (Part 5.4 listsline_endingsrefusing mixed files,python_lock's rule andcargo_manifest's LCS alignment), and each new rewriter picks a different one.Proposed change
Give both rewriters one rule. Since only fragments are spliced, the rule that never flips an untouched line is per fragment: render each replacement fragment with the line ending that dominates the original fragment it replaces (an LF-only or CRLF-only file then behaves exactly as today).
replacepair.python_locknext topreserve_line_endings, or in the shared fragment engine from E13 (Share the poetry.lock and pdm.lock fragment-splice engine instead of keeping two copies #694) if that lands first.preserve_line_endingsfor the whole-file rewriters that use it (uv/pylock, manifests).Size and scope
utils/poetry_lock.rs,utils/pdm_lock.rs,utils/python_lock.rs. About 30 production lines plus tests.reconcile_line_endings, the npm family, and the repo-wide line-ending policy (E16).Acceptance criteria
native_formats_rewrite_and_reverse_byte_exactlyand the Poetry fixture tests, CRLF variants included).urlhosted,file/pathvendored).cargo test -p socket-patch-coreis green.Dependencies
Independent. It is simpler after #694 (the shared fragment engine), because then it is fixed in one place.