Repository navigation
Read BOM composer locks and pin that no lock pin drives a download - #2017
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 |
|
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>
Main (#2015) moved pristine_fetch_is_verifiable from the CLI's vendor.rs into socket-patch-core's vendor/refusals.rs. Keep main's move and carry this PR's intent there: the doc comment explaining why the gate never downloads a lock-pinned (possibly Socket-hosted) artifact now sits on the core function, core's download guard pins its recover_lock_entry call in vendor/refusals.rs, and the CLI guard now pins that the CLI has no recover_lock_entry reference at all. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011f9aePNUT56baX6u17CKZ6
|
Merge conflict resolved by the burn-down agent (head Main's #2015 moved bugbot run Generated by Claude Code |
|
bugbot run Generated by Claude Code |
strip_test_items treated any #![cfg(test)] as test-only for the whole
file. One inside an inline mod x { ... } therefore blanked the
production code after that module too, so the download / BOM guards
could miss a real reference there (Bugbot on 455e201). A top-level
inner attribute still blanks the file; a nested one blanks only the
body of the innermost enclosing brace.
Co-Authored-By: Claude <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 10c665c. Configure here.
|
Ready for review (burn-down agent).
Generated by Claude Code |
|
Enqueued by the final reviewer (head Generated by Claude Code |
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.Bugbot on
455e2010(burn-down agent)#[cfg(test)]blanked the whole file: confirmed and fixed in10c665c5. A top-level one still does; one inside an inlinemod x { … }now blanks only that module's body, so production references after it stay visible to the download / BOM guards. Test:test_items_are_removed_in_place_not_truncated(extended).cargo test -p socket-patch-core --features test-fixtures --lib -- production_sources lock_pinned_locations_never_drive_a_download formats::text: 9 passed. Bugbot's re-run on10c665c5found no new issues.Note
Medium Risk
Touches vendor revert/inventory paths and adds stricter failure modes around corrupted locks, so regressions could block reverts or mis-handle edge cases; download-reference guards are test-only but sensitive to future refactors.
Overview
Composer lockfiles and manifests with a UTF-8 BOM are now read consistently: one leading BOM is stripped via shared helpers (
parse_json_text/parse_json_manifest,strip_bom), across lock inventory, vendored backend (parse, memo reseed, splice writes), discovery/crawler/upstream gates, and CLI vendored-package hints—so BOM-prefixed files behave like normal JSON instead of disappearing or failing spuriously.Revert and stranded scans fail closed when
composer.lockexists but is unreadable or unparseable (merge conflicts, double BOM, etc.) if the lock could still wire the artifact—preventing dangerous reverts that deleted vendor dirs while Composer still pointed at them; locks that never mention the UUID no longer block unrelated reverts.Architecture guardrails add
production_sources-based scans and paired core/CLI tests that pin production references to registrydownload/fetch_registry_bytesandrecover_lock_entry, with docs clarifying that lock “pins” must not drive pristine downloads; supporting tests cover BOM round-trips, memo behavior, and inventory edge cases.Reviewed by Cursor Bugbot for commit 10c665c. Configure here.