Repository navigation
Read BOM composer locks and pin that no lock pin drives a download - #2017
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Conversation
Composer: one leading UTF-8 BOM is now read past by every composer.lock reader that parsed it strictly (the lock inventory, the vendored backend's plan, revert and stranded scan, and the CLI's reinstall hints), through the shared `parse_json_manifest` / `parse_json_text` / `strip_bom` helpers lockfile discovery already uses. The vendored splice and its whole-document fallback keep the BOM on write, so vendor then `--revert` is byte-identical. Before, the stranded scan read a BOM lock as "nothing wired". A wet revert of an entry with no pre-vendor fragment (a `repair`-rebuilt entry) then drift-skipped, and in a lock that spells slashes `\/` the drift-keep's raw-text probe missed the path, so the revert deleted the artifact composer.lock still installs from. The new test reproduces it. Pristine fetch: verified that no vendored or repair path downloads what a lock entry pins. Vendored bytes come from the patch service by patch uuid; `recover_lock_entry` only feeds a yes/no refusal gate; registry downloads are the takeover's upstream restore (registry metadata) and the JVM backend's coordinate-built Maven Central fetches. A new architecture test pins those call sites, and the stale docs that said vendor fetches the lock's resolution are corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
Labeled Ready for review (burn-down agent).
Generated by Claude Code |
Final review briefWhat it does. Every composer.lock reader now skips one leading UTF-8 BOM before parsing. That covers the lock inventory, the vendored plan/prelude, Risk: low. About 10 lines of production code change: four strict Look here
Verified
Open questions (non-blocking)
Auto-merge is not armed: ci-ok has to go green first (after #2028 lands and main is merged in). Once it's green it gets armed, and approving then sends it straight to the merge queue. Generated by Claude Code |
Review findings on #2017: - Revert now refuses when composer.lock exists but cannot be read or parsed (merge conflict, trailing comma, a second BOM, a FIFO), in dry and wet runs alike. Before, the stranded scan read such a lock as "nothing wired", a record with no pre-vendor fragment drift-skipped, and the `\/`-blind drift-keep let the revert delete the artifact the lock still installs from. - composer.json readers (discovery's install preference, the upstream restore's repository gates, the crawler's installer-paths and vendor-dir) read past one leading BOM via `parse_json_text`. - The vendored lock write copies nothing extra when the lock has no BOM, and the parse memo keeps a BOM lock (it compares the render to the bytes past the BOM). - New `vendor::test_support::production_sources`: each production file read once, CRLF normalized, `#[cfg(test)]` / `cfg(all(test, ..))` items blanked in place (no truncation at the first one), plus a comment/literal-blind identifier scan. The BOM-helper guard and the no-lock-download guard share it. - The download guard pins every production reference to `registry_fetch::download`, `fetch_registry_bytes` and `recover_lock_entry` by file and line text, matched by name (any path, method call, `use`/alias, re-export, function value). Core checks only its own crate; the CLI's `recover_lock_entry` reference is pinned by a twin test in the CLI crate (the two downloads are `pub(crate)`). - Module doc: the "no download" paragraph follows the consumer list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 75b6ca3. Configure here.
|
Ready for review re-confirmed at the current head (burn-down agent).
Generated by Claude Code |
Final review brief (updated for 75b6ca3)What it does. Every composer.json/composer.lock reader now skips one leading UTF-8 BOM. Risk: low. Production changes are small, fail closed, and touch only composer handling; most of the diff is test-only ( Look here
Verified
Nits (non-blocking)
Auto-merge is not armed yet: ci-ok must be green first (after #2028 lands and the branch picks it up). Generated by Claude Code |
Second review round. - The production-source scan's `item_end` only stopped at a depth-0 `;` or the end of the first depth-0 block, so a `#[cfg(test)]` struct field, enum variant, match arm or parameter blanked code far past itself (a whole following `impl`), hiding production references from the no-lock-download and BOM guards. An item now ends at its `,` (with generic `<...>` counted so `Result<(), ()>` does not end it) or at the bracket enclosing it. - `code_only` failed open (an empty file) on a UTF-8 slip; it panics now, like `strip_test_items`. - The module docs no longer claim the file an out-of-line test module points at is dropped. - `vendor --revert` of a composer entry refused over any unparseable composer.lock. It still refuses when the lock names the entry's uuid, but a broken lock that never mentions it cannot route composer at the uuid dir and no longer blocks the revert. - The BOM memo test re-seeds with two BOMs before asserting nothing was stored; the CLI guard sorts its expected list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Composer lock with a UTF-8 BOM. Every composer.lock reader that parsed strictly now reads past exactly one leading BOM, using the shared helpers lockfile discovery already uses (
vendor::common::parse_json_manifest/parse_json_text,formats::text::strip_bom):vendor/lock_inventory/composer.rs)--revertrestore, and stranded scan (vendor/composer_lock.rs)commands/composer_hints.rs)The vendored splice (
lock_bytes_with_entry) and its whole-document fallback put the BOM back on write, so vendor followed by--revertrestores the lock byte for byte. composer.json readers are BOM-tolerant too: discovery'spreferred-install, the upstream restore's repository gates, and the crawler'sinstaller-paths/vendor-dir.Revert fails closed on an unreadable composer.lock. The stranded scan used to read a lock it could not parse as "nothing wired". That covered a BOM lock, but also a merge conflict, a trailing comma, a second BOM, or a FIFO. Take a
vendor --revertof an entry with no pre-vendor fragment, such as one rebuilt byrepair. That entry drift-skipped. Thenrevert::finish's drift-keep ran a raw-textcontains(".socket/vendor/composer/<uuid>"), which misses a\/-escaped path. So the revert deleted the artifact that composer.lock still installs from.Now a one-BOM lock parses. Any other lock that exists but cannot be read or parsed refuses the revert, in both dry and wet runs, with the reason and a fix-it hint. Tests:
revert_refuses_to_strand_a_bom_lock_entry,revert_refuses_over_an_unparseable_lock(merge markers).Other GC paths are not affected. The
scan --pruneGC and hosted-record liveness use discovery, which already strips the BOM; the inventory only adds negative evidence there, so a dropped lock could only make them keep more. The orphan sweep matches raw text and handles both slash spellings.Pristine fetch / repair (verified, no fix needed). No vendored or
repairpath downloads what a lock entry pins:repairtake their bytes from the patch service by patch uuid (service_fetch::fetch_verified_archive).recover_lock_entry's only production caller ispristine_fetch_is_verifiable, a yes/no refusal gate. Hosted-pinned purls are filtered out before that gate.original.lock_pinned_locations_never_drive_a_download(core) and its CLI twin pin every production reference toregistry_fetch::download,maven_repo::fetch_registry_bytesandrecover_lock_entry. I also corrected the stale docs that said vendor fetches the lock's resolution.Review findings (/code-review high)
stranded_wired_packagesnow returnsResult; an unreadable or unparseable lock refuses the revert.revert_refuses_over_an_unparseable_lockfailed before the fix, and passes after it.#[cfg(test)]: confirmed and fixed. The newvendor::test_support::production_sourcesnormalizes CRLF and blanks each#[cfg(test)]/cfg(all(test, ..))item in place: inline modules,mod x;, helpers,uses, and#[cfg(test)]files. It never truncates, and line numbers are kept. It is lexical, so it skips comments and string / raw-string / char literals while matching brackets.use, aliases or growth in allowlisted files: confirmed and fixed. References are now matched by identifier name in comment- and literal-blind code: calls by any path, method calls,useandasaliases (an alias must name the original once), re-exports, and the function passed as a value. Each one is pinned by file and line text.upstream::client's own methods); macro-generated calls; and an edit that keeps a pinned line's text while changing what feeds it.downloadnames are pinned explicitly.../socket-patch-cliwithunwrap(): confirmed and fixed. The core guard scans only its ownsrc/. A twin test in the CLI crate (commands::vendor::lock_download_guard_tests) pins the CLI'srecover_lock_entryreference. The two download functions arepub(crate)in core, so the compiler already keeps the CLI from calling them. A by-name scan there would only hit the CLI's owndownloadlocals.a_bom_composer_json_still_decides_the_preferencefailed before the fix, and passes after it.reseed_memocompares the render against the bytes past the BOM, so a BOM lock keeps the memo (the memo test is extended).formats/text.rsand both download guards useproduction_sources().Tests
bom_lock_vendors_keeps_its_bom_and_reverts_byte_identical,whole_document_rewrite_keeps_the_bom,stranded_scan_reads_a_bom_lock,revert_refuses_to_strand_a_bom_lock_entry,revert_refuses_over_an_unparseable_lock,composer_lock_with_a_bom_inventories_its_entries,a_bom_composer_json_still_decides_the_preference.memo_is_reseeded_only_with_the_docs_own_render. Unit tests forproduction_sources. Guard pins in core and CLI.cargo test -p socket-patch-core --lib -- vendor:: vex::discover:: formats::composer formats::gem patch::redirect:: formats::text crawlers::composer_crawler: 4310 passed. The golden corpus test passes with no golden changes.composer_hints/commands::vendor::/ repair (80 passed);mode_migration_composerande2e_vendor_composer_crlf(7 + 7 passed).--workspace --all-features -D warnings) and fmt are clean.Not done
any_live_file_references(the drift-keep probe) still misses\/-escaped paths; draft Keep vendored artifacts a lock references through escaped paths #2022 fixes that probe. Here the stranded refusal (now fail-closed) covers the composer case.Review findings, round 2 (
/code-review highon75b6ca3, cloud routine)Fixed in the commit after
75b6ca3. Each behavior test failed on75b6ca3.item_endignored a depth-0,and the enclosing}, so a#[cfg(test)]field, variant, arm or parameter blanked everything up to the next block, including a whole followingimpl. That hid productiondownloadreferences from the guard. An item now ends at its,or at the bracket that encloses it. Generic<…>count as brackets, soResult<(), ()>andHashMap<K, V>don't end it early. Test:list_items_end_at_their_comma_or_closer.code_onlyfailed open on a UTF-8 slip: fixed. It now usesexpect, likestrip_test_items.*tests.rs.revert_refuses_over_an_unparseable_lockandtest_stranded_scan_degrades_on_unreadable_or_odd_shapes, both extended.Declined:
downloadname matches updater lines. Renamingregistry_fetch::downloadis a wider refactor. The pinned list makes any change visible.#[cfg(test)]. They're outside this PR. Moving them toproduction_sourcesis a good follow-up.vendor::test_support. Both are layering cleanups and stay out of scope here.lock_bytes_with_entry. Style only.Tests (cloud run):
cargo test -p socket-patch-core --features test-fixtures --lib -- vendor::composer formats::text production_sources lock_inventory:: vex::discover::and-p socket-patch-cli --lib vendorpass. clippy and fmt are clean.Note
Medium Risk
Changes composer vendoring, revert safety, and lock parsing paths where incorrect behavior could strand installs or delete wired artifacts; mitigated by extensive tests and fail-closed revert logic.
Overview
Composer locks and manifests with a single leading UTF-8 BOM are now parsed through shared helpers (
parse_json_text/parse_json_manifest,strip_bom) across lock inventory, the vendored backend, CLI hints, crawler, upstream restore, and discovery. Lock rewrites preserve the BOM on disk, and the parse memo treats BOM-prefixed bytes as equivalent to the canonical render.vendor --revertno longer treats an unreadablecomposer.lockas “nothing wired.” The stranded-package scan returns an error for missing parse (merge conflicts, double BOM, etc.) and refuses revert instead of drift-skipping and deleting artifacts the lock still references—a regression that also affected BOM locks before the scan parsed them.Adds
production_sourcestest support and architecture tests (lock_pinned_locations_never_drive_a_downloadin core + CLI) that pin every production use of registry downloads andrecover_lock_entry, plus refactors the inline-BOM guard to use the same scanner. Module docs clarify that lock inventory / recovery are gates only, not pristine download sources.Reviewed by Cursor Bugbot for commit 75b6ca3. Configure here.
Generated by Claude Code