Skip to content

Omnibus: 2026-08 single-file review sweep — 100 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends - #223

Merged
Mikola Lysenko (mikolalysenko) merged 14 commits into
mainfrom
cleanup/dead-package-mode-and-before-blobs
Aug 20, 2026
Merged

Omnibus: 2026-08 single-file review sweep — 100 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends#223
Mikola Lysenko (mikolalysenko) merged 14 commits into
mainfrom
cleanup/dead-package-mode-and-before-blobs

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Omnibus: 2026-08-19/20 single-file review sweep — 99 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends

This PR lands the complete 2026-08-19/20 single-file line-by-line review sweep: ~50 production files reviewed, 100 distinct fixes, every one pinned by a regression test that was verified RED against the pre-fix code. It also carries the branch's original commit (dead --download-mode package removal, previously the sole content of this PR).

How to review this

The diff is large but highly regular: 42 of the 100 fixes are the same one-line-shape fix (the FIFO-wedge guard, below) applied to every raw filesystem read in the codebase, and each fix comes with a focused regression test named after the failure. The per-file map below links every fix to its test. Suggested order: read utils/fs.rs first (the shared guard helpers), then review by section.

Dominant bug classes

  • FIFO-wedge on raw reads (42 fixes) — every fs::read/read_to_string of a workspace-controlled path (lockfiles, manifests, configs) blocks forever in open(2) if that path is a FIFO, wedging scan/apply/vendor/setup and in two cases every CLI command (update-check.json, socket-cli config.json). Fixed via the shared open_regular_file guard (O_NONBLOCK + is_file on unix, metadata gate elsewhere): reads either refuse fast with a real error or degrade to the documented "missing/malformed" contract, per call site.
  • Data corruption / wrong-rewrite (8) — duplicate-key yarn.lock blocks spliced the wrong entry while reporting success (now a pre-flight vendor_lock_entry_ambiguous refusal); poetry files-keyed sub-tables corrupted by the shared TOML array rewriter; pnpm peer deps literally named version/deprecated dropped from the lock; wheel version escaping mangled +/! into names pip/uv reject; and more.
  • Fail-open probes (6) — in-use/in-sync probes that treated unreadable state as "not in use", letting reverts/sweeps proceed blind (yarn/npm post-restore probes, CRLF lock in-use probe, bundled-deps object form).
  • CRLF churn/blindness (4) — LF-normalizing rewriters caused full-file churn or silent no-ops on CRLF lockfiles/manifests (toml_surgery byte-span splice fix + pnpm/go twins).
  • Silent --silent failures (4)get/repair/apply-path download failures exited 1 with zero output on both streams; --silent now means errors-only, not errors-hidden.
  • Multi-copy gates (2) — apply's blob top-up and rollback's before-blob gate probed only the first copy of a duplicated package, wedging or partially applying the rest (completes the fix(apply): patch every on-disk copy of a duplicated package (multi-copy silent partial P0) #216 multi-copy campaign).
  • Locking (2) — wet GC ran its manifest read-modify-write and orphan sweep without the apply lock; a parked apply-lock waiter kept flocking an inode that repair had unlinked.
  • Plus a long tail: maven <properties> coordinate leak, launcher AppData fallback miss, zip declared-size cap bypass (3 sites), GOPROXY | fallback splitting, gem remove deleting the plugin dir with the directive still live, eco-dir symlink sweep escape, empty-uuid redirect anchor matching everything, pnpm suffixed-instance takeover misses, BOM-blind requirements.txt parsing, and dangling go.mod replace directives after version-bump skew.

Provenance & verification

  • Every fix was made in an isolated single-file review pass with the failure first reproduced by a RED test; per-file review notes (including candidates rejected as non-bugs) are preserved in the project memory.
  • This PR was re-reviewed slice-by-slice (10 independent review passes over the final diff, cross-checked against the original review notes) before assembly: 0 blockers; 3 test-hygiene follow-ups from that re-review are included here (un-ignored stale RED e2e pin for the fetch-stage fix, cwd restore-on-drop guard in the composer crawler e2e, O_NONBLOCK FIFO writer-release in four vendor test timeout branches).

Testing

  • Full workspace suite over the assembled diff: cargo test --workspace --no-fail-fast5036 passed, 0 failed across 179 test binaries (ignored = opt-in docker/setup-matrix suites and RED pins for known-open bugs tracked separately).
  • Every fix carries a regression test that was verified RED against the pre-fix code at fix time; the map below names each test.
  • Post-assembly adversarial re-review: 10 independent slice reviews over the final diff → 0 blockers; 3 test-hygiene improvements from that pass are folded in.
  • Multi-ecosystem docker e2e smoke: all 11 suites green (npm, pypi, gem, golang, composer, cargo, maven, nuget, deno, vendored gem/composer — 26/26 tests). Full breakdown in a PR comment below.
  • Hands-on usability probe of the built binary (planted FIFOs in gem/python/npm/cargo fixtures): all crawler-path checks pass — scans complete in ~1s with graceful degradation and clear errors. The probe also caught one live residual of the same class (manifest/operations.rs — a FIFO at .socket/manifest.json hung list/apply, i.e. npm install via the install hook), fixed in this PR as the 100th fix.

Fix map (by area)

Manifest engine (found by this PR's smoke pass)

  • crates/socket-patch-core/src/manifest/operations.rsread_manifest raw-read the manifest, so a FIFO squatting .socket/manifest.json wedged list/apply/remove/repair forever in open(2) → now opens via open_regular_file, failing fast with InvalidInput/manifest_unreadable while NotFound keeps mapping to Ok(None) (apply runs from install hooks, so this hung npm install with zero output; tested by test_read_manifest_fifo_fails_fast_instead_of_wedging, RED-verified)

CLI commands (apply/get/remove/repair/rollback/fetch_stage/scan-gc) — 9 fixes

  • crates/socket-patch-cli/src/commands/apply.rs — two fixes:
    • Multi-copy mismatch top-up probed only the first copy of each PURL (regression from fix(apply): patch every on-disk copy of a duplicated package (multi-copy silent partial P0) #216) → mismatch_blob_gaps now takes Vec<PathBuf> per PURL and probes every physical copy, breaking per file once the hash is queued (a clean root copy no longer masks a drifted nested npm duplicate, which previously failed to apply with exit 1; tested by mismatch_blob_gaps_probes_every_copy in-module and mismatch_blob_topup_probes_every_copy_of_a_duplicated_package in tests/apply_network.rs).
    • Yarn-PnP refusal fired unconditionally on the cwd layout → both gates are now scoped by npm_in_local_scope() (local mode + npm passes the exact cli_name filter partition_purls uses) and the with-manifest gate additionally requires manifest_targets_npm() (--global/--global-prefix runs, non-npm --ecosystems runs, and polyglot PnP repos with no npm patches were refused wholesale and patched nothing; tested by the 3 un-ignored RED tests in tests/e2e_safety_yarn_pnp.rs, with the live no-manifest and npm-in-scope refusal counter-guards unchanged).
  • crates/socket-patch-cli/src/commands/fetch_stage.rs — the post-download failure gate used aggregate fetch counters → the offline gate's per-patch coverage rule is factored into patches_without_source() and re-run against the staged dirs on any failure, bailing only when some patch is truly uncovered (a patch covered by a local .socket/packages/<uuid>.tar.gz or diff archive was Unavailable online whenever downloads failed — online strictly worse than --offline on the same cache; tested by 3 new in-module tests incl. the no-local-source Unavailable overshoot guard).
  • crates/socket-patch-cli/src/commands/get.rs--silent swallowed the download loops' error output (8 [fail]/[error] sites gated on !json && !silent) → error prints now gate on !json only, per the --silent = "errors only" contract (get <purl> --silent with a failed patch fetch exited 1 with zero output anywhere; tested by tests/cli_get_silent_errors.rs — NEW file, wiremock search-200 + view-500, with loud control and informational-stays-suppressed guard).
  • crates/socket-patch-cli/src/commands/remove.rs — two fixes:
    • Every manifest read error was labeled manifest_unreadableErrorKind::InvalidData (exists-but-unparseable) now emits the contract's manifest_invalid, mirroring list (JSON consumers saw corrupt manifests as transient I/O failures; tested by the flipped pin plus schema-invalid and directory-as-manifest cases in tests/remove_invariants.rs).
    • Detached-only "Removal cancelled." ignored --silent → now gated on !json && !silent like the manifest path (interactive decline under --silent printed suppressed-class chatter; tested by remove_detached_interactive_n_cancel_message_respects_silent (PTY + loud control) in tests/interactive_prompts_e2e.rs).
  • crates/socket-patch-cli/src/commands/repair.rsrepair --silent with failed downloads exited 1 completely mute → a failures-only reprint (FetchMissingBlobsResult with zeroed success counters through format_fetch_result) now reaches stderr; --json keeps carrying the failure in the envelope and loud-mode stdout is untouched (tested by repair_silent_still_reports_download_failures_on_stderr in tests/repair_invariants.rs).
  • crates/socket-patch-cli/src/commands/rollback.rs — the before-blob gate verified only the representative first copy → it now probes every copy in all_packages_multi, breaking per file on the first MissingBlob; exact twin of apply's mismatch_blob_gaps fix (an online rollback of a diverged npm duplicate skipped the on-demand before-blob download and wedged mid-run with MissingBlob, leaving the nested copy patched on every retry; tested by tests/rollback_multicopy_blob_gate.rs — NEW file: blob-GET + exit-0 + restore assertions plus an all-copies-original offline anti-overshoot guard).
  • crates/socket-patch-cli/src/commands/scan/gc.rs — wet GC pruned the manifest and swept blobs with no apply lock → run_apply_gc now takes a non-blocking apply_lock::acquire (after the self-locking vendored half), and contention skips the pass without failing the scan (a concurrent get/apply mid-write could have its new manifest entry clobbered and its fresh blobs deleted; tested by run_apply_gc_skips_prune_and_sweep_while_apply_lock_is_held in-module).

Reviewer note: tests/apply_network.rs:916 still #[ignore]s apply_online_uses_cached_package_archive_when_downloads_fail with a now-stale reason — the fetch_stage.rs change in this PR is exactly the fix it pins; recommend un-ignoring it in this PR after a green run.

Crawlers (composer/maven/python/ruby) — 4 fixes

  • crates/socket-patch-core/src/crawlers/maven_crawler.rs<properties> was not in parse_pom_group_artifact_version's skip_sections, so a schema-valid property literally named version/groupId/artifactId (free-form xs:any; Maven warns but permits it) won first-match extraction over the project's own coordinates → now the whole <properties> block is depth-skipped. Unlike the parser's None failures this produced a wrong Some that suppressed the directory-path fallback, so scan emitted a bogus purl — a silent patch miss or worst-case a wrong-version patch applied in place. (tested by test_parse_pom_property_named_coordinate_does_not_leak, in-module)
  • crates/socket-patch-core/src/crawlers/python_crawler.rsparse_metadata_headers plain-opened <pkg>.dist-info/METADATA via tokio::fs::read_to_string; a planted FIFO there blocks open(2) forever, wedging scan (crawl_all) and apply (find_by_purls) with no error or timeout → now opens through utils::fs::open_regular_file (O_NONBLOCK + fstat is_file), and a rejected METADATA degrades into the dir-name fallback so the package stays discoverable. (one malicious build-hook FIFO no longer hangs the whole crawl; tested by read_python_metadata_rejects_fifo_metadata_without_hanging in tests/crawler_python_e2e.rs)
  • crates/socket-patch-core/src/crawlers/ruby_crawler.rsapp_config_bundle_path plain-opened .bundle/config ($BUNDLE_APP_CONFIG/config) — the open_regular_file twin fix(gem): discover flat BUNDLE_PATH gem layouts — bundler-1 env-var installs were invisible to scan/get/apply #218 introduced after the composer fix → same non-blocking guard; a rejected config only drops the app-config root while the unconditional vendor/bundle probe still runs. (a FIFO .bundle/config wedged scan/get/apply forever for ANY project with a Bundler manifest; tested by app_config_fifo_does_not_wedge_discovery, in-module)
  • crates/socket-patch-core/src/crawlers/composer_crawler.rs — under the CLI default --cwd ., resolve_project_root normalizes the containment boundary to the EMPTY path, and starts_with("") holds for every path — so the install-path escape gate was vacuous in exactly the default invocation and a tampered installed.json absolute install-path resolved out of tree as a patch WRITE target → resolve_install_path now rejects any anchored/unanchored mismatch between the resolved path and the boundary (is_anchored covers RootDir and Windows drive-relative C:evil prefixes); strictly narrowing, absolute-root behavior unchanged. Note: a legitimately-absolute in-project install-path is now also refused under a relative cwd — fail-closed by design, consistent with the existing no-fallback rule. (tested by test_resolve_install_path_rejects_absolute_escape_from_relative_root, in-module, and end-to-end by install_path_absolute_escape_rejected_under_relative_cwd in tests/crawler_composer_e2e.rs, which drives the real get_vendor_pathscrawl_all/find_by_purls chain from a relative cwd with the escape target existing on disk)
  • crates/socket-patch-core/tests/crawler_npm_e2e.rs — rustfmt reflow of two assert_eq! calls only; no behavior change.

Setup writers + package.json discovery (composer/gem/pypi/npm-family) — 10 fixes

  • crates/socket-patch-core/src/package_json/find.rs — workspace discovery read pnpm-workspace.yaml and every glob-discovered member package.json with a plain read_to_string, so one planted FIFO wedged setup discovery forever; both sites now go through a new read_project_file_to_string (O_NONBLOCK open_regular_file + regular-file gate), falling back cleanly (FIFO pnpm-workspace.yaml degrades to the package.json workspaces field) (silent infinite hang → fast error; tested by pnpm_workspace_fifo_does_not_wedge_discovery, member_package_json_fifo_does_not_wedge_discovery)
  • crates/socket-patch-core/src/package_json/update.rsupdate_package_json/remove_package_json opened a discovery-listed FIFO package.json and blocked forever; both reads now use find.rs's shared guarded reader and surface Error status instead (wedged setup AND setup --remove → clean per-file error; tested by test_update_fifo_package_json_does_not_wedge, test_remove_fifo_package_json_does_not_wedge)
  • crates/socket-patch-core/src/setup/composer/mod.rsedit() (add/remove hook) read composer.json raw; a FIFO there wedged setup/--remove in PHP projects. Now guarded via read_composer_json_to_string; the missing-file-on-remove no-op contract is preserved (NotFound still returns Ok(false)) (infinite hang → Error status; tested by test_add_fifo_composer_json_does_not_wedge, test_remove_fifo_composer_json_does_not_wedge)
  • crates/socket-patch-core/src/setup/gem/mod.rs — three fixes:
    • FIFO wedge closed across all six raw read sites (needs_write, is_generated, stamp_ignore_missing, add_stamp_gitignore, remove_stamp_artifacts, bundler plugin-index read) via read_regular_to_string, and write_file switched to atomic_write_bytes_preserving_mode so the wedge can't just move to the write — rename-over never opens the destination, replacing a squatting FIFO instead of blocking on it (setup/--check/--remove hung forever → fail fast or sync over; tested by test_check_fifo_plugins_rb_does_not_wedge, test_add_fifo_plugins_rb_does_not_wedge_and_replaces_it, test_remove_fifo_plugins_rb_does_not_wedge, test_remove_registration_fifo_index_reports_residue_not_wedge)
    • discover_bundler_project accepted any path whose metadata stat'd; now gates on is_file() like bundler's own File.file?, so a directory/FIFO named Gemfile is skipped in favor of the ancestor manifest bundle actually loads (setup errored where bundle install works → matches bundler; tested by test_discover_skips_directory_named_gemfile, test_discover_skips_fifo_named_gemfile)
    • add_stamp_gitignore treated any read error on an existing .socket/.gitignore as "empty" and rewrote it, destroying user lines (non-UTF-8 gitignore bytes are legal); now only NotFound defaults to empty, other errors fail setup with the file untouched (silent user-data loss → hard error; tested by test_add_never_clobbers_unreadable_stamp_gitignore, test_add_fifo_stamp_gitignore_errors_not_wedges)
  • crates/socket-patch-core/src/setup/gem/update.rs--remove on a Gemfile whose managed block was mutated (e.g. stripped final newline) reported "not configured" and deleted .socket/bundler-plugin while the live plugin ... path: directive stayed in the Gemfile, breaking every later bundle install (exit 13); the marker-present-but-unmatchable case is now an Error naming the manual remedy, and the orchestrator's Error early-return keeps the plugin files (broken installs → safe refusal with remedy; tested by test_remove_keeps_plugin_files_when_managed_block_is_unmatchable)
  • crates/socket-patch-core/src/setup/gem/version.rsprobe_bundler read Gemfile.lock/gems.locked raw; a FIFO at the lock path wedged setup/--check and every Gemfile-block update inside the version probe; now routed through the parent module's guarded reader, failing fast to the time-bounded bundle --version fallback (infinite hang → bounded fallback probe; tested by test_probe_fifo_lockfile_does_not_wedge)
  • crates/socket-patch-core/src/setup/pypi/detect.rsdetect_python_pm read pyproject.toml raw behind a metadata-only project gate; a FIFO there (no lockfile present) wedged setup/--check and the configured-ecosystems probe; now guarded, degrading to the Pip fallback (infinite hang → Pip fallback; tested by test_detect_fifo_pyproject_does_not_wedge)
  • crates/socket-patch-core/src/setup/pypi/edit.rsadd_hook_dependency/remove_hook_dependency read the manifest raw, and detection never opens the file it hands the edit path, so a FIFO at requirements.txt/pyproject.toml wedged setup/setup --remove; now guarded, failing fast to PthStatus::Error while preserving the NotFound contracts (add+Requirements creates, remove no-ops) and never replacing the squatting FIFO (infinite hang → clean error, FIFO untouched; tested by test_edit_fifo_manifest_does_not_wedge)

Reviewer notes: the CLI-side raw manifest reads in crates/socket-patch-cli/src/commands/setup.rs (composer/gem/pypi --check/detect paths and state probes) are known unfixed twins, deliberately out of this slice; the plain Gemfile reads remaining in setup/gem/update.rs are deliberate — the new discovery is_file gate closes their production route, leaving race-only exposure.

Patch engine core (apply_lock, rollback, redirect state/takeover/golang_local) — 7 fixes

  • crates/socket-patch-core/src/patch/apply_lock.rsacquire opened apply.lock once before the retry loop and re-flocked that stale handle for the whole --lock-timeout budget → the open now lives inside the loop, binding every attempt to whatever inode the path names now (a waiter parked across repair's sanctioned unlink could flock the orphaned pre-deletion inode and hand out a second live "exclusive" guard — concurrent manifest/package corruption; tested by waiter_does_not_lock_orphaned_inode_after_lock_file_deleted, previously RED #[ignore], now enabled)
  • crates/socket-patch-core/src/patch/rollback.rs — before-blob probe/read were entry-type blind (metadata follows symlinks, bare tokio::fs::read) → verify now lstats the blobs entry and refuses non-regular files as MissingBlob ("not a regular file", target_hash kept so re-download self-heals), with a defense-in-depth lstat twin at the read site (a hex-named symlink committed at blobs/<hash> passed the string guard and leaked an out-of-tree file's existence + content hash via the "Got:" mismatch error; a FIFO hung rollback forever; tested by test_verify_file_rollback_rejects_symlinked_blob_entry, test_rollback_package_patch_symlinked_blob_entry_blocked, test_verify_file_rollback_rejects_fifo_blob_entry)
  • crates/socket-patch-core/src/patch/redirect/golang_local.rs — two fixes:
    • reconcile_go_redirects loop (b) pruned copy dirs by exact purl while loop (a) kept the directive whenever the module was desired at another version → (b) now drops the GoPatches-owned directive first iff its path exactly equals the pruned copy's replace_target_path (path-exact, dry-run-aware), so a not-yet-applied version bump no longer leaves a dangling replace that bricks every go build with "replacement directory does not exist" (tested by test_reconcile_version_bump_never_leaves_dangling_directive + over-drop guard test_reconcile_prunes_stale_copy_keeps_repointed_directive)
    • the hosted-owned-replace refusal was unscoped and also blocked the vendor backend's local-build leg → gated on base_rel == GO_PATCHES_DIR (apply's sole production copy base), restoring the documented vendor-takes-over-hosted policy; vendor --vendor-source=local/offline/auto-fallback can now vendor hosted-scanned Go modules (fix owned by the vendor/golang.rs pass; tested by vendor::golang::tests::test_local_vendor_takes_over_hosted_replace and the new apply-side pin test_apply_refuses_hosted_owned_replace)
  • crates/socket-patch-core/src/patch/redirect/state.rs — two fixes:
    • load_redirect_state read the untrusted ledger with bare tokio::fs::read → new read_ledger_bytes opens via open_regular_file (O_NONBLOCK + fstat on unix; plain open + is_file elsewhere), so a planted FIFO fails loudly as CorruptRedirectState instead of wedging every ledger-consulting flow (scan, vex, list, vendor) forever; NotFound → Ok(None) unchanged, symlink-to-regular-file still loads (tested by load_fifo_ledger_fails_fast_instead_of_wedging)
    • drop_superseded_purl used record uuids as artifact anchors without filtering empties, so a "uuid": "" record (hand-repaired ledger — a workflow the corrupt-ledger message itself instructs) made contains("") claim every package's edits → empty uuids are filtered and the claim falls back to version-exact-only, so dropping one purl no longer destroys other packages' only revert data (tested by drop_superseded_purl_empty_uuid_record_claims_no_anchored_edits)
  • crates/socket-patch-core/src/patch/redirect/takeover.rs — the pnpm takeover claim matched edits by key == lock_key exactly, missing v6 resolved-peer (name@ver(peer@x)) and v5 suffixed (name@ver_suffix) instance keys → pnpm claims (kind redirect_pnpm_resolution only) also accept a (/_ peer boundary after the exact version (never -/./alnum, which would swallow a prerelease sibling), so takeover no longer "succeeds" as a silent half revert that strands peered edits in the ledger and leaves the expiring hosted tarball wired for every dependent resolving through the peered instance (tested by npm_pnpm_v6_peered_instance_takeover_reverts_every_instance, npm_pnpm_v5_suffixed_instance_takeover_reverts_every_instance, boundary guard npm_pnpm_prerelease_sibling_peered_edit_is_not_claimed)
  • crates/socket-patch-core/src/patch/redirect/mod.rs — no behavior change: rustfmt-only churn in two test assertions (file was reviewed clean; safe to skim)

Self-update + shared utils (channel/state/swap, fs, socket_cli_config) — 5 fixes

  • crates/socket-patch-core/src/utils/fs.rsatomic_write_bytes_as swallowed stage-write failures: tokio's write_all only buffers (up to 2 MiB) and its sync_all stores the background-write error back into the handle instead of returning it, so ENOSPC/EIO/EFBIG during the stage write returned Ok(()) and renamed the truncated stage over the intact destination. Now an explicit file.flush().await between write_all and sync_all surfaces the error and the stage is cleaned up (silent data loss on every user-owned file we edit — package.json, go.mod, lockfiles, vendor/state.json — via both writer variants; tested by atomic_write_failed_stage_write_errors_and_keeps_target, deterministic via an RLIMIT_FSIZE cap). Context for the FIFO fixes below: this file also hosts the sweep's shared async open_regular_file helper (pre-existing) — O_NONBLOCK open on unix so a FIFO can't wedge open(2), with fstat taken from the open handle so size/bytes can't come from different inodes, rejecting FIFOs/devices/dirs with InvalidInput.
  • crates/socket-patch-core/src/update/state.rsload_state() used bare std::fs::read, so a FIFO planted at <state_dir>/update-check.json wedged the open forever → now reads via sync helper read_state_bytes (O_NONBLOCK + handle-based is_file) and degrades to never-checked like any other unreadable state (one special file in the cache dir hung every CLI command with no output, since the passive update notifier loads this synchronously at command start; tested by load_state_fifo_state_file_degrades_instead_of_wedging).
  • crates/socket-patch-core/src/update/swap.rsacquire_update_lock() opened update.lock with a plain blocking O_WRONLY open, so a FIFO planted there wedged --update forever before it did anything → now opens with O_NONBLOCK on unix (FIFO-no-reader → immediate ENXIO → SwapFailed) plus an unconditional handle-based is_file check giving a clear "not a regular file; remove it and retry" error. The fd is only ever flock(2)ed, so O_NONBLOCK is a no-op for the normal regular-file case (tested by update_lock_fifo_lock_file_errors_instead_of_wedging; regular path re-pinned by the pre-existing update_lock_is_exclusive_and_released_on_drop).
  • crates/socket-patch-core/src/utils/socket_cli_config.rsread_from_disk used raw std::fs::read on the user-writable socket-cli config path, so a FIFO planted at <data dir>/socket/settings/config.json wedged every networked command during API-client construction → now reads via private read_regular_file (same O_NONBLOCK + is_file guard); a non-regular file warns and is treated as absent per the existing warn-and-stop probe contract (tested by fifo_config_file_is_ignored_instead_of_wedging).
  • crates/socket-patch-core/src/update/channel.rslauncher_cache_roots missed the gem launcher's Windows fallback cache root ~/AppData/Local (launcher.rb: ENV[\"LOCALAPPDATA\"] || File.join(Dir.home, \"AppData\", \"Local\")), so with LOCALAPPDATA stripped a launcher-cached binary misdetected as Standalone and --update swapped the per-version SHA-verified cache entry in place, desyncing the launcher's version pin → the root is now probed unconditionally, keeping the pure-function/table-testable design (worst case on Unix is a contrived-layout refusal with the gem upgrade hint; tested by launcher_cache_detected_via_home_appdata_fallback, forward-slash spelling so it runs on Unix runners).

Vendor: npm family (npm/bun/yarn classic/yarn berry/berry_zip) — 14 fixes

  • crates/socket-patch-core/src/vendor/npm_common.rsdeclares_bundled_deps ignored the OBJECT form bundleDependencies: {"dep": "…"}, which npm honors (Object.keys(bd) for any truthy non-array) → new Value::Object(o) => !o.is_empty() arm makes the refusal fire (a bundled-deps package previously vendored "successfully" with its bundled node_modules pruned, breaking every install; tested by declares_bundled_deps_matches_npm_value_shapes + bundled_deps_refusal_covers_object_form in npm_lock.rs)
  • crates/socket-patch-core/src/vendor/npm_flavor.rs — FIFO-wedge on the flavor/in-use probes: read_lock and lock_text_mentions_uuid raw-read lockfiles, so a planted FIFO wedged flavor detection (every npm vendor) and the GC in-use probe forever in open(2) → both now read via a module-local read_regular_to_string over open_regular_file (O_NONBLOCK + fstat is_file; plain-open fallback on non-unix; symlinks still followed). Probe refuses vendor_lockfile_missing naming the file; in-use probe returns None = fail-safe keep (tested by fifo_lockfiles_fail_fast_instead_of_wedging, unix-only)
  • crates/socket-patch-core/src/vendor/npm_lock.rs — three fixes:
    • select_lockfile raw-read the npm locks — the FIRST open on the vendor path (flavor detection is existence-only for npm locks), so a FIFO wedged every npm vendor → now read_regular; NotFound-continue unchanged (tested by fifo_lockfile_fails_fast_instead_of_wedging_vendor_and_revert)
    • revert_npm's wired-lock read had the same wedge → same fix; FIFO → InvalidInput → existing RevertOutcome::failed, artifact survives (same test, revert half)
    • shrinkwrap-rename revert brick: npm shrinkwrap renames package-lock.json → npm-shrinkwrap.json carrying the file: entries, so the wired revert deleted the artifact while the surviving lock still resolved through it, failing every later install with ENOENT → new post-restore, pre-delete probe via lock_text_mentions_uuid(&[SHRINKWRAP, PACKAGE_LOCK]) refuses with new code vendor_lock_still_wired_revert_blocked, artifact kept (tested by revert_refuses_when_a_renamed_lock_still_resolves_through_the_artifact)
  • crates/socket-patch-core/src/vendor/bun_lock.rs — both bun.lock write sites (vendor rewrite + revert restore) used plain atomic_write_bytes, resetting a 0600/0640 lock to umask 0644 → swapped to atomic_write_bytes_preserving_mode (the last vendor backend on the plain writer; tested by lock_writes_preserve_file_mode, unix-only, covers both sites). Known follow-up: bun's wired revert still lacks the post-restore in-use probe its npm/yarn siblings gained in this sweep.
  • crates/socket-patch-core/src/vendor/yarn_berry_lock.rs — three fixes:
    • wired revert removed the uuid dir while yarn still resolved through it via an entry the wiring never named (hand-copied lock entry, or a re-keyed resolutions selector reading as converged) → post-restore probe over BOTH wired files (yarn.lock AND package.json, independently) refuses vendor_lock_still_wired_revert_blocked (tested by revert_refuses_when_an_unrecorded_lock_entry_still_resolves_through_the_artifact + revert_refuses_when_a_rekeyed_resolutions_entry_still_references_the_artifact)
    • the empty-wiring (repair-reconstructed) guard probed only yarn.lock, blind to a dangling package.json resolutions file: spec → loops guard_unwired_textual_revert per-file over [yarn.lock, package.json] (the helper's multi-name semantics are precedence, not OR; tested by empty_wiring_revert_refuses_while_resolutions_still_references_the_artifact)
    • FIFO-wedge on 5 raw-read sites — vendor: shared read_yarn_lock (lives in yarn_classic_lock.rs; classic vendor benefits), .yarnrc.yml, project package.json; revert: yarn.lock + package.json → all read via new pub(super) read_regular/read_regular_to_string in yarn_classic_lock.rs; every arm maps through pre-existing NotFound/else handling (tested by fifo_files_fail_fast_instead_of_wedging_vendor_and_revert, unix-only, 5 scenarios)
  • crates/socket-patch-core/src/vendor/yarn_classic_lock.rs — three fixes (plus hosting the shared guarded-read helpers above):
    • wired revert twin of the berry/npm brick: post-restore probe over yarn.lock only (classic never writes package.json) refuses vendor_lock_still_wired_revert_blocked (tested by revert_refuses_when_an_unrecorded_lock_entry_still_resolves_through_the_artifact, incl. heal-then-converge re-run)
    • revert's yarn.lock read was raw → read_regular_to_string; FIFO → InvalidInput → existing RevertOutcome::failed, artifact survives (tested by fifo_lock_fails_fast_instead_of_wedging_revert, unix-only)
    • NEW CLASS — duplicate-key blocks (mangled merge; yarn parses last-wins): the by-key rewrite spliced the FIRST same-key block, leaving yarn's winner on the registry while reporting success (silent unpatched package) → pre-flight refusal before staging with new code vendor_lock_entry_ambiguous ("run yarn install to re-lock"; tested by duplicate_key_blocks_are_refused_before_any_write, both dup flavors, asserts nothing written)
  • crates/socket-patch-core/src/vendor/berry_zip.rs — two fail-closed contract holes (neither could commit a wrong checksum — verify paths compare against the lock value — but both mislabeled rejections as "checksum mismatch" instead of "cannot rebuild deterministically"):
    • file/dir path collision: seen_files/seen_dirs never cross-checked, so package/a as both file and directory emitted a zip yarn's zipfs cannot hold → mkdirp now Errs on a dir stem that is a known file, and the file branch mirrors via seen_dirs (tested by file_and_directory_path_collisions_fail_closed, all three orderings)
    • a Regular-typed tar entry named with a trailing / (pre-POSIX dir marker; GNU tar/node-tar read it as a directory) was silently hashed as a file → refused with "directory-style name" (tested by regular_entry_with_trailing_slash_fails_closed)

Reviewer note: the new vendor_lock_still_wired_revert_blocked refusals cannot appear in dry-run previews (the probe runs after the real restore; a pre-restore probe would false-positive on every healthy revert) — accepted inaccuracy, wet runs are safe and converge on re-run.

Vendor: pnpm + shared vendor infrastructure (state/inventory/common/path/mod) — 17 fixes

  • crates/socket-patch-core/src/vendor/pnpm_lock.rs — six fixes plus half of a cross-file invariant:
    • FIFO wedge: all 7 raw pair-file reads (vendor pkg/lock/ws, revert lock/pkg, revert_workspace, pnpm_entry_in_use) now go through new pub(super) read_regular/read_regular_string over open_regular_file — a planted FIFO fails fast instead of wedging vendor/revert forever (tested by fifo_pair_files_fail_fast_instead_of_wedging_vendor_and_revert).
    • Workspace read fail-open: an existing-but-unreadable pnpm-workspace.yaml no longer reads as "no file" (.ok()), which routed into the create path and overwrote the user's workspace definition with the root-only scaffold; now only NotFound → None, anything else refuses vendor_lockfile_missing (tested by unreadable_workspace_file_refuses_instead_of_scaffold_overwrite).
    • CRLF workspace, vendor side: refuses vendor_lockfile_crlf_unsupported instead of appending a duplicate overrides: section that pnpm rejects as a duplicated mapping key, bricking the project (tested by crlf_workspace_file_refuses_instead_of_duplicating_overrides). Note: an LF-append to a CRLF file with no existing section used to succeed benignly and now also refuses — deliberate tightening.
    • CRLF workspace, revert side: reads as drift (drift-keep gate holds artifact + ledger entry) instead of "already converged" followed by deleting a tarball the live override still points at (tested by crlf_workspace_revert_is_a_drift_keep_not_silent_convergence).
    • CRLF lock in pnpm_entry_in_use: returns None (undeterminable) instead of Some(false) ("provably orphaned"), so the unwired-revert guard refuses instead of deleting a still-referenced artifact (tested by crlf_lock_is_undeterminable_for_in_use_and_unwired_revert_refuses).
    • edit_packages peer-line drop: exact 4-space " version:"/" deprecated:" match replaces trim_start().starts_with, so 6-indent peer-dep lines for the real npm packages version/deprecated survive the rewrite verbatim (tested by packages_rewrite_keeps_peer_deps_named_version_or_deprecated).
    • Generation-stable wiring key (with pnpm_lock_legacy.rs + state.rs): edit_packages/edit_snapshot_rekey always record the registry key instead of block.key, whose file: form embeds the stale uuid on a new-uuid re-vendor and broke carry_forward_wiring's file+kind+key original-fill — revert then warned "no recorded pre-vendor original" forever. Safe because revert_block locates the live block via rec.new's embedded key, never via key (tested by new_uuid_revendor_revert_removes_created_workspace_and_tables).
  • crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rs — three fixes plus the legacy half of the key invariant:
    • FIFO wedge ×5 (vendor pkg/lock, in-use lock, revert lock/pkg) via v9's shared guarded readers (tested by fifo_pair_files_fail_fast_instead_of_wedging_vendor_and_revert).
    • CRLF in-use fail-open: pnpm_legacy_entry_in_use returns None for a CRLF lock — sniff_lock_grammar tolerates \r but the LF-exact section probes miss, so it previously read as provably orphaned and the guard deleted a still-referenced tarball (tested by crlf_lock_is_undeterminable_for_in_use_and_unwired_revert_refuses).
    • Missing drift-keep gate (legacy-only, residual fix(redirect): fail closed on non-npm: berry descriptors + yarn layering regression suite #131): revert_pnpm_legacy now gates remove_tree on drift_skipped()keep_artifact like the v9/npm/bun backends, instead of deleting the tarball a drifted lock still resolves through — every later install ENOENT'd (tested by drifted_lock_revert_keeps_the_artifact).
    • Legacy edit_packages records the registry key (same invariant as above; tested by new_uuid_revendor_revert_restores_the_packages_block).
  • crates/socket-patch-core/src/vendor/state.rs — two fixes plus the consumer side of the key invariant:
    • FIFO wedge in load_state: ledger read via new read_state_bytes over open_regular_file; NotFound → empty-ledger arm unchanged. Previously a FIFO at .socket/vendor/state.json wedged every vendor-adjacent command (tested by load_fifo_state_fails_fast_instead_of_wedging).
    • carry_forward_wiring pnpm created-flags OR-merge moved above the prev.uuid != entry.uuid early return: "vendor created this table/file/section" is generation-independent, so a new-uuid re-vendor no longer makes --revert leave the vendor-created pnpm-workspace.yaml and an empty "pnpm":{"overrides":{}} husk behind. The wiring UNION stays same-uuid-scoped (tested by carry_forward_merges_pnpm_created_flags_across_uuid_generations + the pnpm_lock.rs e2e above).
  • crates/socket-patch-core/src/vendor/lock_inventory.rs — FIFO wedge on all 15 raw lockfile reads (Cargo.lock, go.sum, npm shrinkwrap/package-lock, pnpm/rush/subspace locks, yarn ×2, bun, composer, Gemfile.lock ×2, uv/poetry/requirements, wired_vendor_integrity ×2, gem_remotes): module-local guarded readers; inventories stay fail-soft (non-regular reads as absent) and symlinked lockfiles still read (handle-based is_file follows the link). Previously wedged scan's supplement, vendor's auto-fetch, and repair's reconstruction (tested by fifo_lockfiles_fail_fast_instead_of_wedging).
  • crates/socket-patch-core/src/vendor/mod.rs — FIFO wedge in yarn_classic_berry_migration_risk: the probe runs unconditionally at envelope-finalize on every vendor / scan --vendor run, so a FIFO at yarn.lock or package.json wedged the whole run even in non-npm projects. New sync read_regular_file_to_string (O_NONBLOCK + handle is_file on unix; plain open + is_file elsewhere); semantics preserved — unreadable yarn.lock stays silent, unreadable package.json still fails toward the warning (tested by fifo_yarn_lock_never_wedges_probe / fifo_package_json_never_wedges_probe).
  • crates/socket-patch-core/src/vendor/common.rs — two FIFO wedges:
    • zip_matches_after_hashes reads the committed .jar/.nupkg via open_regular_file; a non-regular file reads as out-of-sync instead of wedging the maven/nuget in-sync probes — and every apply — forever (tested by fifo_archive_fails_fast_in_zip_matches_after_hashes).
    • revert_lock_fragment_splice guards the lock read and fails loudly with "cannot read {lock_file}"; a FIFO as poetry.lock/pdm.lock previously wedged remove/rollback (tested by fifo_lock_fails_fast_in_revert_lock_fragment_splice).
  • crates/socket-patch-core/src/vendor/path.rs — SECURITY: sweep_vendor_dirs lstat-guards the ecosystem root before read_dir. A committed .socket/vendor/npm -> <outside> symlink previously made the sweep enumerate uuid-shaped dirs at the target and let the CLI orphan sweep delete through the link (tested by sweep_never_follows_a_symlinked_ecosystem_dir). Known deliberate residual: only the final path component is lstat-checked per level — a symlinked .socket or .socket/vendor parent still resolves through, same as the pre-existing uuid-level guard.

Vendor: PyPI family (pip/poetry/pdm/pipenv/uv/wheel) + toml_surgery — 12 fixes

  • crates/socket-patch-core/src/vendor/pypi.rs — FIFO planted as pyproject.toml wedged detect_pypi_flavor (and every lockless-project vendor run) forever in open(2); the read now goes through open_regular_file (O_NONBLOCK + regular-file check) and a FIFO reads as "no pyproject", falling through to requirements routing (tested by fifo_pyproject_does_not_wedge_flavor_detection).
  • crates/socket-patch-core/src/vendor/pypi_pdm.rs — FIFO wedge on both load_pdm_project raw reads; pdm.lock FIFO now refuses fast (pypi_pdm_lock_parse_failed), pyproject.toml FIFO degrades to no-pyproject/"transitive" diagnostics (tested by fifo_lock_or_pyproject_does_not_wedge_load).
  • crates/socket-patch-core/src/vendor/pypi_pipenv.rs — FIFO wedge on both Pipfile.lock reads (load + revert); load refuses fast (pypi_pipenv_lock_parse_failed, NotFound still maps to pypi_pipenv_no_lockfile), revert fails fast with "cannot read" (tested by fifo_lock_does_not_wedge_load_or_revert).
  • crates/socket-patch-core/src/vendor/pypi_poetry.rs — FIFO wedge on both load_poetry_project raw reads; lock FIFO refuses fast (pypi_poetry_lock_parse_failed), pyproject FIFO degrades to no-pyproject (tested by fifo_lock_or_pyproject_does_not_wedge_load; also hosts the byte-exact end-to-end regression wiring_leaves_files_keyed_subtable_lines_verbatim for the shared toml_surgery sub-table fix below).
  • crates/socket-patch-core/src/vendor/pypi_requirements.rs — three fixes:
    • FIFO wedge on both raw reads (collect_requirements_files + revert_requirements): FIFO root refuses fast (pypi_no_requirements), FIFO include is skipped, revert fails fast (tested by fifo_requirements_does_not_wedge_wire_or_revert).
    • BOM-blind first-line parse: \u{feff}six==1.16.0 classified Absent → duplicate transitive line appended → pip install -r double-requirement error; the parser now strips one leading BOM from the first logical line's parse text only (records/revert stay byte-identical — tested by bom_first_line_pin_is_rewritten_not_duplicated).
    • Attached -rdev.txt include missed (pip's optparse accepts the no-space short form): the include was invisible, causing the same duplicate-append; include_target now follows -r<path> (tested by attached_short_form_include_is_followed).
  • crates/socket-patch-core/src/vendor/pypi_uv.rs — FIFO wedge on all four raw reads (load_uv_project and revert_uv, pyproject + lock each); load refuses fast (pypi_uv_lock_parse_failed), revert fails fast — closes the last pypi FIFO twin (tested by fifo_lock_or_pyproject_does_not_wedge_load_or_revert, all four legs).
  • crates/socket-patch-core/src/vendor/pypi_wheel.rs — two fixes:
    • FIFO wedge on five raw-read sites (RECORD + WHEEL in locate_installed_dist; entry_points.txt, direct_url.json, and every RECORD-member staging read in build_patched_wheel): FIFOs now surface as the existing refusals / fail the build closed / fall through as absent; the member read also reuses the open handle's metadata for the exec bit — one fewer stat (tested by fifo_record_or_wheel_does_not_wedge_locate, fifo_record_member_fails_closed_instead_of_wedging_build, fifo_direct_url_json_does_not_wedge_editable_probe, fifo_entry_points_does_not_wedge_build).
    • Version escaping mangled PEP 440 +/! into _ in the wheel filename (2.0.0+cu1182.0.0_cu118), which pip/uv reject as an invalid wheel filename — the vendored artifact was uninstallable; escape_wheel_version now keeps + and ! literally, matching bdist_wheel (tested by wheel_version_keeps_local_and_epoch_separators).
  • crates/socket-patch-core/src/vendor/toml_surgery.rs — two fixes in the shared pypi text-surgery helpers:
    • replace_files_array rewrote ANY unindented files = [ line — including [package.dependencies]/[package.extras] entries literally keyed files (poetry emits both; the PyPI package files exists) — silently corrupting sub-tables into duplicate wheel arrays; it now passes everything from the first [package. header through verbatim and fails closed when only a sub-table files exists (tested by the extended replace_files_array_handles_multi_line_inline_and_absent + poetry's wiring_leaves_files_keyed_subtable_lines_verbatim; pdm units carry no sub-tables, so pdm output is byte-identical).
    • remove_exact_line/remove_table_if_empty rebuilt the file via lines()+join("\n"), LF-normalizing a CRLF pyproject.toml wholesale on every uv vendor --revert (their only callers); both now splice by byte span with EOL-insensitive matching so untouched bytes survive verbatim — with two deliberate, test-pinned edge changes (a removed newline-less final line keeps the prior terminator; all-blank remainders are kept) (tested by removal_helpers_preserve_foreign_line_endings).

Vendor: Cargo + Gem + Composer — 8 fixes

  • crates/socket-patch-core/src/vendor/cargo.rs — two fixes:
    • FIFO wedge: hosted_redirect_residue read the project Cargo.toml with a raw read_to_string → now a guarded read (open_regular_file: O_NONBLOCK + fstat is_file), so a FIFO planted as the manifest fails fast and reads as "no residue" instead of wedging every wet cargo vendor run forever in open(2) (tested by test_fifo_cargo_toml_does_not_wedge_vendor).
    • Husk cleanup: prune_empty_vendor_dirs stopped on ANY remove_dir(uuid_dir) error, but unwind paths remove_tree the uuid dir first, so NotFound aborted the prune → NotFound now continues to the parent levels while any other error (non-empty = live state) still stops; failed fresh vendors no longer leave empty .socket/vendor/cargo/ + .socket/vendor/ husks to commit (tested by test_failed_fresh_vendor_leaves_no_vendor_husk).
  • crates/socket-patch-core/src/vendor/cargo_config.rs — FIFO wedge: all three .cargo/config(.toml) read sites (edit_config, read_patch_entries, socket_registry_indexes) swapped to the guarded read; a FIFO squatting the config no longer wedges scan or wet vendor apply/remove, reads-only paths degrade to "no entries", and edit_config errors loudly — never treats the squatted path as an empty config to rename a fresh file over (tested by test_read_patch_entries_fifo_config_does_not_wedge, test_ensure_patch_entry_fifo_legacy_config_fails_fast, test_socket_registry_indexes_fifo_configs_do_not_wedge).
  • crates/socket-patch-core/src/vendor/cargo_lock.rs — FIFO wedge: read_lock (single choke point for all five public fns) swapped to the guarded read; a FIFO planted as Cargo.lock no longer wedges scan's probe, vendor mode detection, the version cross-check, or wet detach/restore — probes stay fail-safe (NoLockfile/None/0), edits refuse loudly with Io (tested by fifo_lock_fails_fast_instead_of_wedging, which exercises all five fns).
  • crates/socket-patch-core/src/vendor/composer_lock.rs — FIFO wedge: all three composer.lock read sites (vendor's presence read, stranded_wired_packages, restore_lock_entry) swapped to the guarded read; vendor now refuses vendor_lockfile_missing/"unreadable" and revert fails loudly before deleting any artifacts instead of hanging forever (tested by fifo_lock_fails_fast_instead_of_wedging).
  • crates/socket-patch-core/src/vendor/gem.rs — three fixes:
    • FIFO wedge: all 9 Gemfile / Gemfile.lock / stub-gemspec read sites (vendor pair read, stub probes, reconstruct_gem_wiring ×2, revert ×3) swapped to the guarded read, with a NotFound-vs-"unreadable" refusal-detail split mirroring composer (refusal codes unchanged); vendor, revert, and reconstruction no longer hang on a planted FIFO (tested by fifo_lock_fails_fast_instead_of_wedging).
    • Duplicate-declaration fail-open: valid-Ruby declarations the strict line grammar can't see (gem"rack" no separator, gem ("rack") space before paren) fell through to the transitive Append plan, writing a SECOND declaration that makes bundler hard-fail every install → a looser gem_call_mentions_name probe (the redirect rewriter's declared_re twin) now gates the Append and refuses gemfile_declaration_not_editable (tested by unrecognized_gem_call_refuses_instead_of_duplicating).
    • Spurious drift warning: re-vendor over a lock whose CHECKSUMS entry was already bare pre-vendor rides a checksum record with original: None that carry-forward can never fill; revert reported vendor_lock_entry_drifted on a byte-perfect restore → revert_lock_checksum_record now returns Ok(true) when original is None and the bare written line still stands (nothing to restore = pre-vendor state) (tested by revendor_over_already_bare_checksum_reverts_without_drift).

Vendor: Go + Maven + NuGet + registry_fetch — 13 fixes

  • crates/socket-patch-core/src/vendor/golang.rs — test-only: pins vendor-takes-over-hosted on the local-build leg (test_local_vendor_takes_over_hosted_replace: takeover, wiring original records the hosted target verbatim, revert names the go.sum recovery). The production change — scoping the hosted-owned-replace refusal to apply's GO_PATCHES_DIR copy base — is in patch/redirect/golang_local.rs (see that file's section), which also gains test_apply_refuses_hosted_owned_replace since the refusal is now conditional.
  • crates/socket-patch-core/src/vendor/go_mod_edit.rs — two fixes:
    • FIFO planted as go.mod wedged every caller (apply redirect + reconcile, --check verify, vex scan) forever in open(2) → all three read paths now go through open_regular_file (O_NONBLOCK + is_file); reads stay fail-safe (empty vec / None), edits refuse loudly, NotFound legs unchanged (tested by test_fifo_go_mod_fails_fast_instead_of_wedging).
    • Edits LF-normalized every line of a CRLF go.mod (git autocrlf on Windows), breaking the byte-identical ensure→drop round-trip → joins and the upsert append path now use detect_eol (tested by test_crlf_go_mod_preserves_line_endings).
  • crates/socket-patch-core/src/vendor/go_sum_edit.rs — two fixes (pure &str transform; depscan TS twins need matching ports):
    • All three joins LF-normalized CRLF go.sum files wholesale on every hosted golang apply/revert → detect_eol at all three joins (tested by crlf_go_sum_preserves_line_endings).
    • "Already applied" short-circuited on a bare match-count of 2, so a stale same-key line with a different hash survived (go fatals SECURITY ERROR and the owner refused to repair) and a duplicated zip line masked a missing /go.mod line → now requires exactly one of each wanted line and no other key-prefixed lines (tested by upsert_rewrites_when_stale_duplicate_key_line_coexists).
  • crates/socket-patch-core/src/vendor/maven_repo.rs — FIFO-wedge guards on all 6 raw-read sites (project pom in vendor + revert, ~/.m2 pom + jar, committed leaf + .sha1 in sidecar_matches — the unguarded other half of artifact_in_sync); a FIFO squatting the committed pom now reads as stale and the rebuild atomically renames a real pom over it; NotFound semantics preserved (fail fast instead of wedging vendor/hot-path/rebuild/revert forever; tested by 5 mkfifo+timeout tests: fifo_vendored_pom_fails_fast_and_rebuilds_on_hot_path, fifo_project_pom_fails_fast_in_vendor, fifo_cached_jar_fails_fast_in_local_rebuild, fifo_local_pom_fails_fast_in_acquire_upstream_pom, fifo_project_pom_fails_fast_in_revert).
  • crates/socket-patch-core/src/vendor/nuget_feed.rs — two fixes:
    • FIFO-wedge guards on all 6 raw-read sites (vendor's config + lock, hot-path committed-nupkg lock-hash probe, local_rebuild's cached nupkg, revert's config + lock), the maven twin; a FIFO squatting the committed nupkg reads as stale → atomic rebuild; a revert lock failure leaves the config wiring untouched for retry (tested by 6 mkfifo+timeout tests mirroring the maven set).
    • Self-closing <packageSources /> made parse_config_source_keys scan to EOF and harvest <add key> entries from unrelated sections as phantom catch-all sources — mapping * to a nonexistent source hard-fails every restore and suppressed the nuget.org seeding → no close tag now yields an empty key list, fail-closed (tested by self_closing_sources_do_not_harvest_foreign_add_keys).
  • crates/socket-patch-core/src/vendor/registry_fetch.rs — five fixes:
    • stage_local_artifact raw-read the committed vendored .tgz; the CLI caller only probes metadata(), which a FIFO passes → fresh-clone re-vendor wedged forever; now guarded via open_regular_file (tested by stage_local_artifact_fifo_fails_fast_instead_of_wedging, which runs its own runtime on a detached thread so the unfixed code fails instead of hanging the harness).
    • goproxy_base split GOPROXY only on ,; the Go spec also allows | — pipe-separated values built an unparseable https://a|b/... URL → splits on both (tested by goproxy_base_splits_on_pipe_separator).
    • Berry foreign-cacheKey (non-10c0/) was refused only after downloading the whole tarball, breaking the "Unverifiable = decided before network I/O" contract, wasting a full download per package on yarn 2/3 locks, and flipping to Failed when the registry was unreachable → hoisted above download, still gated on verify (tested by berry_foreign_cachekey_refuses_before_network against an unroutable URL).
    • New bug class — zip declared-size caps were bypassable: zip 8.6 does not bound an entry's read by its declared uncompressed size, so file.size()-based caps were void against a crafted (poisoned-lockfile) artifact (~1000× deflate disk/CPU bomb). All 3 sites bounded: extract_zip copies via take(declared+1) + refuses on copied != declared; extract_zip_with_prefix (previously had no caps at all, reachable without the dirhash pre-pass via the golang service-download path when the service reports no dirhashH1) gains MAX_ENTRIES + declared per-entry/total caps + the same actual-bytes refusal; go_h1_of_zip counts ACTUAL bytes per entry and in total while hashing (tested by zip_entry_lying_declared_size_fails_closed, module_zip_extraction_enforces_size_caps, go_h1_caps_actual_decompressed_bytes, via the patch_declared_uncompressed_size header-patching helper).
    • The pypi no-wheel-URL message carried a literal 14-space run from a missing \ string continuation → single-spaced (tested by pypi_no_wheel_url_message_is_single_spaced).

Follow-ups flagged by the review notes (not in this diff): berry_zip.rs gunzip actual-bytes cap twin (modified in another slice — confirm there); depscan TS ports of the go.mod/go.sum CRLF + already-check fixes; nuget NuGet.config middle-casing probe gap (needs a Linux RED first).

🤖 Generated with Claude Code

The mode built GET /patch/package/<uuid> (proxy) or GET
/v0/orgs/<slug>/patches/package/<uuid> (authenticated) — routes no
deployed server has served since the on-demand package flow was
dropped (depscan e2e test 80 pins their absence). Every package-mode
fetch failed per-UUID and silently degraded to per-file blob
downloads: slower, noisier, and never what the flag promised.

Now `--download-mode package` / `SOCKET_DOWNLOAD_MODE=package` fails
fast with a removal notice instead of degrading. The local
`.socket/packages/` read path in the apply pipeline is intentionally
untouched — pre-staged archives still apply.

Removed: DownloadMode::Package, ArchiveKind (collapsed to diff-only
fetch), ApiClient::fetch_package. Tests updated to pin the removal
error and that repair in package mode hard-fails without touching the
package archive route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nfig read

- fs.rs: atomic_write_bytes_as now flushes before sync_all, so tokio's
  buffered background-write errors (e.g. ENOSPC) fail the stage write
  instead of committing a truncated file over the target.
- socket_cli_config.rs: read config.json via open_regular_file so a FIFO
  planted at the user-writable path cannot wedge every networked command.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tate/lock

- channel.rs: launcher_cache_roots also probes ~/AppData/Local (the gem
  launcher's Windows fallback) so --update cannot clobber the launcher
  cache when LOCALAPPDATA is stripped.
- state.rs: update-check.json read through a sync open_regular_file twin;
  a planted FIFO no longer wedges every command.
- swap.rs: update.lock acquired with O_NONBLOCK + is_file guard; a FIFO
  there no longer wedges --update.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt, FIFO wedges

- maven_crawler.rs: <properties> added to skip_sections so a property named
  version/groupId/artifactId can no longer win first-match extraction over
  the project's real coordinates.
- composer_crawler.rs: containment boundary under --cwd . normalized to an
  absolute root; install-path escapes are rejected instead of vacuously
  contained by starts_with("").
- python_crawler.rs / ruby_crawler.rs: dist-info METADATA and
  .bundle/config reads routed through open_regular_file; planted FIFOs no
  longer wedge scan/apply.
- crawler e2e suites extended accordingly (incl. cwd restore-on-drop guard).

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrectness

- package_json find.rs/update.rs, setup composer/pypi (detect, edit),
  gem version.rs: every raw manifest/lock read (pnpm-workspace.yaml,
  package.json, composer.json, pyproject.toml, requirements.txt,
  Gemfile.lock) routed through guarded regular-file reads; planted FIFOs
  fail fast instead of wedging setup/--remove.
- setup/gem/mod.rs: six raw-read sites guarded; discover_bundler_project
  requires a regular file (matching bundler's File.file? gate); a read
  error on an existing .socket/.gitignore no longer clobbers it via
  unwrap_or_default.
- setup/gem/update.rs: remove with the managed marker present but no
  matchable block now errors (exit 13) instead of deleting the plugin dir
  while the Gemfile directive stays live.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t fixes

- apply_lock.rs: the lock open moved inside the retry loop so a parked
  waiter re-opens the current inode instead of flocking one repair
  unlinked.
- rollback.rs: before-blob verify uses symlink_metadata + is_file so a
  symlink/FIFO squatting blobs/<hash> cannot pass verify or wedge the read.
- redirect/golang_local.rs: reconcile drops the replace directive for
  copies it prunes on version-bump skew (no more dangling directives
  bricking go build); the hosted-owned-replace refusal is scoped to the
  apply-managed GO_PATCHES_DIR so the vendor local-build leg is not
  blocked.
- redirect/state.rs: ledger read guarded against non-regular files;
  empty-uuid records no longer act as contains("") wildcard anchors.
- redirect/takeover.rs: pnpm claims match v6 resolved-peer and v5 suffixed
  instance keys, ending silent half-takeovers.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…FIFO wedges

- npm_lock.rs / yarn_classic_lock.rs / yarn_berry_lock.rs: wired revert now
  probes post-restore whether the lock (and for berry, package.json
  resolutions) still resolves through the uuid dir before deleting it —
  covers npm's shrinkwrap rename and hand-copied/re-keyed lock entries.
- yarn_classic_lock.rs: duplicate-key lock blocks now refuse pre-flight
  with vendor_lock_entry_ambiguous instead of splicing the first block and
  leaving yarn's last-wins winner unpatched while reporting success.
- npm_common.rs: object-form bundleDependencies honored (npm-bundled
  Object.keys ground truth) — no more fail-open on bundled deps.
- bun_lock.rs: both bun.lock write sites preserve file mode.
- berry_zip.rs: file/dir path collisions and trailing-slash Regular tar
  entries now fail closed instead of emitting a zip with a guessed
  checksum.
- FIFO-wedge guards on all raw lock/config reads across the family
  (npm_flavor read_lock + in-use probe, npm select_lockfile + revert,
  yarn shared read_yarn_lock, .yarnrc.yml, classic revert).

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…IFO wedges

- pnpm_lock.rs + pnpm_lock_legacy.rs + state.rs: packages-snapshot wiring
  keys no longer embed the stale uuid, so cross-generation original-fill
  and revert work after a re-vendor under a new uuid; carry_forward keeps
  pnpm created-flags across uuid changes.
- pnpm_lock.rs: workspace read treats only NotFound as "no file" (no more
  scaffold-overwrite of a user's pnpm-workspace.yaml on read errors); CRLF
  workspace/lock files refuse or count as drift instead of duplicating
  overrides:, silently converging, or failing open on the in-use probe;
  peer deps literally named version/deprecated survive edit_packages.
- pnpm_lock_legacy.rs: gains the drift-keep lossiness gate on revert plus
  the CRLF in-use fix.
- lock_inventory.rs (all 15 lockfile reads), mod.rs (migration-risk
  probe), common.rs (jar/nupkg in-sync probe, lock fragment splice),
  pnpm pair reads: FIFO-wedge guards throughout.
- path.rs: eco-dir symlink lstat guard — the orphan sweep can no longer
  follow a planted .socket/vendor/<eco> symlink and delete through it.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ption, BOM/include parsing, FIFO wedges

- pypi_wheel.rs: version component keeps PEP 440 + and ! (2.0.0+cu118 no
  longer becomes 2.0.0_cu118, which pip/uv reject as InvalidWheelFilename);
  five raw-read sites guarded.
- toml_surgery.rs: replace_files_array only rewrites the real
  [package.files] array — deps/extras literally keyed "files" are no
  longer corrupted into duplicate wheel arrays; remove_exact_line/
  remove_table_if_empty splice by byte span, preserving CRLF files.
- pypi_requirements.rs: line-1 BOM stripped before pin matching (pip/uv
  ground truth) and pip's attached -rdev.txt include form recognized — no
  more duplicate transitive appends.
- FIFO-wedge guards on every raw read across the family: flavor detection
  (pypi.rs), pdm/pipenv/poetry/uv load + revert, requirements collect +
  revert.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sk prune, FIFO wedges

- gem.rs: unparseable-but-valid-Ruby gem declarations (gem"rack") refuse
  instead of falling through to a duplicate append; re-vendor over a
  pre-bare CHECKSUMS entry no longer warns of spurious drift; all 9
  Gemfile/lock/stub read sites guarded.
- cargo.rs: prune_empty_vendor_dirs skips NotFound husks left by unwind
  paths instead of aborting the prune; hosted-redirect residue read
  guarded.
- cargo_config.rs / cargo_lock.rs / composer_lock.rs: every raw
  config/lock read guarded against non-regular files (scan probe, vendor
  pre-flight, wet apply/remove, revert).

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…preservation, GOPROXY pipes, FIFO wedges

- registry_fetch.rs: zip extraction bounds reads with take() — the zip
  crate does not bound an entry read by its declared size, so the
  MAX_ENTRY/MAX_TOTAL decompression caps were bypassable at 3 sites;
  GOPROXY now splits on | as well as , per the Go spec; berry foreign
  cacheKey refusal hoisted before any network I/O; pypi Unverifiable
  message garble fixed.
- go_mod_edit.rs / go_sum_edit.rs: joins preserve the file's own EOL so
  CRLF go.mod/go.sum stop churning wholesale; go.sum upsert's
  already-applied check requires exactly the wanted lines and no
  conflicting stale sums.
- golang.rs: pins vendor-takes-over-hosted on the local-build leg (the
  refusal scoping lives in redirect/golang_local.rs).
- maven_repo.rs (6 sites) / nuget_feed.rs (6 sites): FIFO-wedge guards on
  every pom/jar/config/lock/nupkg raw read; self-closing
  <packageSources /> no longer harvests phantom keys that brick restore.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…usal scoping, GC locking

- get.rs / repair.rs: download failures under --silent now reach stderr
  (errors-only, not errors-hidden) instead of a mute exit 1.
- apply.rs / rollback.rs: the mismatch-blob top-up and the before-blob
  gate probe EVERY on-disk copy of a duplicated package, completing the
  #216 multi-copy campaign — no more wedged rollbacks or partial applies
  when copies diverge.
- apply.rs: both yarn-PnP refusal gates are scoped to npm-in-local-scope
  (and manifest npm targeting), so --global runs, non-npm --ecosystems
  runs, and polyglot PnP repos stop being refused wholesale; the three
  RED pins for this are un-ignored.
- fetch_stage.rs: online fetch failures re-run the offline coverage
  filter against staged dirs — a patch fully satisfied by a local
  .socket/packages archive no longer goes Unavailable; the pre-existing
  RED e2e pin is un-ignored and green.
- remove.rs: corrupt manifests report manifest_invalid per CLI_CONTRACT
  (not manifest_unreadable); the detached-cancel message respects
  --silent.
- scan/gc.rs: wet GC takes the apply lock (non-blocking; contention skips
  the pass) before its manifest read-modify-write and orphan sweep.

Part of the 2026-08-19/20 single-file review sweep (see PR body).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title chore: remove dead --download-mode package Omnibus: 2026-08 single-file review sweep — 99 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends Aug 20, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Known residuals & follow-ups surfaced by the post-assembly re-review

None of these block this PR; they are recorded here so they are not lost. [minor] = worth a follow-up PR, [note] = FYI/known-unfixed twin.

  • [minor] crates/socket-patch-cli/tests/apply_network.rs — Line 916: apply_online_uses_cached_package_archive_when_downloads_fail still carries #[ignore = "RED: ... The cache-fallback fix was not part of this change."] — but the fetch_stage.rs fix in THIS sweep IS that cache-fallback fix (the two 2026-08-19 sessions ran independently; the fetch_stage session added in-module unit tests but never un-ignored this pre-existing e2e RED pin). The ignore reason is now stale and the end-to-end pin for the fetch_stage fix stays dark. Un-ignore it and verify it goes green (its scenario — no archives served, blob GET fails, local .socket/packages/.tar.gz present — is exactly what stage_online_fetch_failure_accepts_local_package_archive proves Ready at the unit level).
  • [note] crates/socket-patch-cli/src/commands/scan/gc.rs — FYI for reviewers: the new acquire match uses Err(_), so a genuine I/O error creating/locking apply.lock (not just contention) also silently skips the wet GC pass. Safe direction (fail-closed, nothing pruned or swept) and consistent with the documented skip-not-fail posture of the vendored half — and the invisibility of skips in gc JSON is an already-triaged deferred item (absorb_vendor_gc drops the skip marker) — but worth knowing the two cases are conflated.
  • [note] crates/socket-patch-cli/src/commands/apply.rs — Known residuals the memory notes deliberately left open (do not treat as regressions of this diff): apply --check with no manifest in a PnP checkout is still layout-refused (pre-manifest gate fires before the check branch — open design question); pnpm/bun informational notes still print regardless of --ecosystems scope; rollback's variant-by-multi-venv narrowing (select_installed_variants on entries[0].1) is an unconfirmed systemic twin shared with apply, to revisit only with a real multi-venv repro.
  • [note] crates/socket-patch-core/tests/crawler_npm_e2e.rs — Both hunks are pure rustfmt reflow of two assert_eq! calls (arguments unchanged) — no fix implemented, no memory note needed; benign formatting churn riding the sweep.
  • [note] crates/socket-patch-core/src/crawlers/composer_crawler.rs — Deliberate fail-closed tightening worth stating in the PR: a LEGITIMATELY absolute install-path (Composer writes one for some path repositories) that points INSIDE the project is now rejected whenever the crawler runs with a relative cwd (the CLI default), because containment cannot be verified lexically against an empty root. Consistent with the documented no-fallback design for out-of-boundary entries, but such packages become undiscoverable under --cwd . where the vacuous check previously (unsafely) admitted them; running with an absolute --cwd restores discovery.
  • [minor] crates/socket-patch-core/tests/crawler_composer_e2e.rs — install_path_absolute_escape_rejected_under_relative_cwd restores the process cwd before its asserts (good), but the .unwrap() calls on get_vendor_paths/find_by_purls sit between set_current_dir(&root) and the restore — a panic there leaves the test binary's cwd pointing into a soon-deleted TempDir, cascading confusing failures into later tests in the same binary. A restore-on-drop guard would make failures debuggable; only manifests when the test is already failing.
  • [note] crates/socket-patch-core/src/crawlers/maven_crawler.rs — Possible unfixed twin of the FIFO-wedge class: scan_maven_repo still raw-reads POM files with std::fs::read_to_string (maven_crawler.rs:522). The 7th-pass memory rejected this site only as a sync-walkdir perf nit and its rejected list never evaluated the hang angle. Threat model is weaker than site-packages (~/.m2 content is registry-written, no install hooks), so flagging as FYI only — not a defect in this diff.
  • [note] crates/socket-patch-cli/src/commands/setup.rs — Known UNFIXED twins the memory notes call out as out of this slice's scope, worth naming in the PR body so reviewers don't assume the class is fully closed: CLI-side raw read_to_string of discovered manifests in commands/setup.rs (composer ~:391/:748, gem ~:383, pypi ~:372, and the state-probe reads ~:370/:476) still wedge on a FIFO, and the CLI setup --check raw package.json parse remains BOM-blind/unguarded.
  • [note] crates/socket-patch-core/src/setup/gem/update.rs — Reviewer FYI: the Gemfile reads in edit_gemfile_add/edit_gemfile_remove deliberately stay plain read_to_string — the new discover_bundler_project is_file gate in gem/mod.rs closes the production route for non-regular Gemfiles, leaving only race-window exposure (documented rejected candidate in the memory notes). Not a defect in this diff.
  • [note] crates/socket-patch-core/src/patch/redirect/golang_local.rs — The base_rel == GO_PATCHES_DIR hosted-refusal scoping hunk implements a fix from OUTSIDE this slice's five assigned notes — it belongs to the vendor/golang.rs pass (memory note golang-vendor-hosted-local-takeover-fixed, same sweep). Verified legitimate and test-pinned on both sides; flagging so the slice owner for vendor/golang.rs does not double-count it and no one mistakes it for a stray change.
  • [minor] crates/socket-patch-core/src/patch/redirect/golang_local.rs — The new dangling-directive drop in reconcile loop (b) swallows drop_replace_entry errors (let _ =) and still prunes the copy afterward, so an unwritable/unreadable go.mod at that instant leaves the directive dangling at the deleted copy anyway. This matches the loop's pre-existing best-effort convention (loop (a) swallows the same call) and is strictly better than pre-fix behavior (which always dangled), but a follow-up could skip remove_tree when the directive drop fails, matching remove_go_redirect's fail-closed order.
  • [note] crates/socket-patch-core/src/patch/rollback.rs — Known UNFIXED twin called out by the rollback memory note: apply.rs:927 still does a bare tokio::fs::read of the after-blob. Forward-apply blob writes are hash-gated, so the 'Got: ' oracle channel and FIFO wedge are the same class; deliberately out of scope for this single-file pass but worth its own pass.
  • [note] crates/socket-patch-core/src/patch/rollback.rs — test_verify_file_rollback_rejects_fifo_blob_entry spawns the mkfifo binary via std::process::Command, while the state.rs test in this same sweep deliberately switched to direct libc::mkfifo because fork/exec flakes under heavy parallel test load (see the sanity_exec ETXTBSY memory). Cosmetic consistency/robustness nit for a follow-up, not a correctness issue.
  • [note] crates/socket-patch-core/src/patch/apply_lock.rs — waiter_does_not_lock_orphaned_inode_after_lock_file_deleted is not cfg(unix)-gated: it remove_file()s apply.lock while the waiter thread holds an open handle. Rust std opens with FILE_SHARE_DELETE and modern Windows (10 1903+/Server 2019+, all current GH runners) uses POSIX delete semantics, so the immediately-following create-open should succeed — but on older Windows the delete-pending window could make the fresh acquire fail spuriously. FYI only if this crate's unit tests ever run on legacy Windows.
  • [note] crates/socket-patch-core/src/patch/redirect/takeover.rs — Design-level residual the takeover memory note documents as NOT fixed (do not expect it in this diff): redirect_pnpm_workspace_trust (global trustLockfile key, CLI-side scan/hosted.rs) belongs to no purl, so taking over the LAST pnpm purl leaves trustLockfile: true in pnpm-workspace.yaml plus an orphaned ledger edit; only a full --revert unwinds it.
  • [note] crates/socket-patch-core/src/utils/fs.rs — The new RLIMIT_FSIZE regression test caps the whole process at 256 KiB file writes for its window; it is #[serial_test::serial], but non-serial tests in the same socket-patch-core test binary still run concurrently on other threads and would EFBIG-flake if any of them writes a >=256 KiB file during that window. No such test is known today (core unit fixtures are small) — flagging only as a latent flake vector to remember if a large-file unit test is ever added.
  • [note] crates/socket-patch-core/src/update/state.rs — read_state_bytes (state.rs) and read_regular_file (socket_cli_config.rs) are near-identical sync twins of utils::fs::open_regular_file. The memory notes describe the local-helper design deliberately (the shared helper is async, these call sites are sync), so this is not a deviation — but with two copies now in tree, a third sync guarded-read call site should trigger consolidating a sync variant into utils/fs.rs.
  • [note] crates/socket-patch-core/src/vendor/toml_surgery.rs — The byte-span rewrite of remove_exact_line/remove_table_if_empty deliberately changes two edge behaviors (documented in the memory note and pinned by removal_helpers_preserve_foreign_line_endings): removing a final newline-less line now keeps the prior line's terminator ("a\nb" -> "a\n", was "a"), and all-blank remainders are no longer dropped. Callers are revert_uv only; worth calling out in the PR so reviewers don't read it as accidental drift.
  • [note] crates/socket-patch-core/src/vendor/pypi_requirements.rs — Designed side effect of the BOM fix: when the BOM'd first line IS the rewritten pin, the wire drops the BOM from the file (the wheel line replaces the raw physical line); pip/uv are indifferent and revert restores the BOM byte-identically from the record — both directions are test-pinned.
  • [note] crates/socket-patch-core/src/vendor/pypi_uv.rs — One raw read remains at pypi_uv.rs:1099 (wheel_metadata_block reading the vendored wheel) — this is memory-rejected candidate Refactor: Centralize manifest and blob management operations #4 (the orchestrator wrote that artifact moments earlier in the same run; shared vendor TOCTOU class), left unguarded deliberately, not a missed FIFO site.
  • [note] crates/socket-patch-core/src/vendor/bun_lock.rs — Known UNFIXED twin, deliberately out of this sweep's scope per the npm_lock and yarn-berry memory notes: bun's wired revert still has no post-restore in-use probe before artifact removal (no shrinkwrap-rename analogue — single lock name — but the hoisted/hand-copied-entry scenario applies). pnpm backends have their own guard structure and are likewise unchecked for this probe class. Worth naming in the PR as known follow-ups so reviewers don't mistake the asymmetry for an oversight.
  • [note] crates/socket-patch-core/src/vendor/npm_lock.rs — Accepted dry-run inaccuracy (pre-adjudicated in the memory notes, restating for the PR body): the new post-restore vendor_lock_still_wired_revert_blocked refusals in npm_lock/yarn_classic/yarn_berry cannot be predicted by a dry-run preview — the probe runs after the real restore, and a pre-restore probe would false-positive on every healthy revert. Wet runs are safe and converge on re-run.
  • [note] crates/socket-patch-core/src/vendor/path.rs — Deliberate documented residual (do not treat as a defect): the sweep's lstat guards refuse only the FINAL path component per level — a symlinked .socket or .socket/vendor PARENT still resolves through. Same property as the pre-existing uuid-level guard; a fully no-follow walk was out of minimal-fix scope per the memory note. Worth stating in the PR body so reviewers don't assume full no-follow semantics.
  • [note] crates/socket-patch-core/src/vendor/pnpm_lock.rs — Deliberate posture tightening riding the CRLF-workspace fix: appending an LF overrides: section to a CRLF pnpm-workspace.yaml WITHOUT an existing section previously succeeded benignly (mixed endings are valid YAML, revert byte-restored); it now refuses with vendor_lockfile_crlf_unsupported. Intentional supersession per the memory note, but it is a user-visible behavior change for CRLF checkouts that used to vendor successfully.
  • [minor] crates/socket-patch-core/src/vendor/cargo.rs — Test-robustness inconsistency: the timeout-branch FIFO writer-release in cargo.rs, cargo_lock.rs, composer_lock.rs, and gem.rs uses a plain blocking write-open (std::fs::OpenOptions::new().write(true).open(path)), while cargo_config.rs uses the O_NONBLOCK release_fifo_reader helper precisely because a plain write-open of a FIFO with no pending reader blocks the test itself (gotcha explicitly called out in the cargo-config memory note). In the expected regression mode a reader IS pending so the plain open succeeds, but if the 5s timeout ever fires without a wedged reader (pathologically slow CI, future dropped before reaching the FIFO), the failing test hangs the suite instead of panicking. Follow-up: share the O_NONBLOCK helper across the four tests.
  • [note] crates/socket-patch-core/src/vendor/gem.rs — read_regular_to_string is now copy-pasted file-locally in all five slice files (and more across the sweep). This matches the sweep's deliberate file-local idiom per the memory notes, but is a candidate for later consolidation into utils::fs next to open_regular_file.
  • [note] crates/socket-patch-core/src/vendor/cargo.rs — The diff carries a handful of rustfmt-only reflow hunks in existing tests (cargo.rs lines ~1539/1626/1688/1717 and cargo_config.rs path_is_socket_owned + one test string). Pure formatting, no behavior change — flagged only so the reviewer doesn't hunt for meaning in them.
  • [note] crates/socket-patch-core/src/vendor/golang.rs — This file's hunk is test-only by design: the production fix it pins (gating the hosted-owned-replace refusal on base_rel == GO_PATCHES_DIR) lives in crates/socket-patch-core/src/patch/redirect/golang_local.rs, which belongs to another slice and was verified present in the working tree along with its own test_apply_refuses_hosted_owned_replace. Reviewers of this slice should not read the golang.rs diff as a missing fix; the two files must land together or the new test fails.
  • [note] crates/socket-patch-core/src/vendor/registry_fetch.rs — goproxy_base_splits_on_pipe_separator mutates process-wide env (GOPROXY/SOCKET_GOPROXY set_var/remove_var) without a #[serial] guard, in a parallel test binary. Safe today — the only runtime reader is fetch_golang's resolved-None fallback, and no other test in this binary exercises it — but it is flake bait if a future test adds a resolved-less golang fetch. The crate already uses serial_test elsewhere (utils/fs.rs); a #[serial] attribute would be a one-line hardening follow-up.
  • [note] crates/socket-patch-core/src/vendor/registry_fetch.rs — Known UNFIXED twin the memory note flags for the zip cap-bypass class: berry_zip.rs gunzips the downloaded tarball per-entry with plain read_to_end — whether its caps bound ACTUAL bytes is an open question. berry_zip.rs is modified elsewhere in this sweep; its slice reviewer should confirm the twin was addressed (vendor/common.rs zip_matches_after_hashes was assessed fail-safe-direction, likely fine).
  • [note] crates/socket-patch-core/src/vendor/go_sum_edit.rs — Cross-repo residual called out by the memory notes: depscan's TS twins mirror these transforms byte-identically, so the CRLF fixes (go_sum_edit ×3 joins, go_mod_edit join/append) and the strengthened go.sum already-applied check all likely need TS ports. Not addressable in this repo's PR; worth listing as follow-up.
  • [note] crates/socket-patch-core/src/vendor/nuget_feed.rs — Deliberately left unfixed (memory: no local RED possible on case-insensitive macOS): existing_config_path probes only nuget.config + NuGet.Config, missing NuGet's non-Windows middle casing NuGet.config — on Linux a project with only NuGet.config would get a competing created nuget.config that NuGet prefers. Needs a docker/Linux RED before fixing; not a regression in this diff.

…fest consumer

read_manifest used a bare tokio read_to_string, so a FIFO planted at the
manifest path blocked open(2) forever — list/apply/remove/repair all hung
with zero output, and apply runs from install hooks, so this wedged
`npm install` indefinitely. Now opens via open_regular_file: non-regular
files fail fast with InvalidInput (labeled manifest_unreadable by the
CLI), missing files keep mapping to Ok(None).

Found live by this PR's post-assembly multi-ecosystem usability probe of
the FIFO-wedge class; RED-verified (the new regression test wedged its
full 5s deadline against the unfixed code, passes in ms after).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title Omnibus: 2026-08 single-file review sweep — 99 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends Omnibus: 2026-08 single-file review sweep — 100 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends Aug 20, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Multi-ecosystem smoke test results (post-assembly)

Run locally against the assembled branch (docker daemon, --features docker-e2e), after the full workspace suite (5036 passed / 0 failed).

Docker e2e suites — 11/11 green

Suite Result Time
docker_e2e_npm 6 passed / 0 failed 3.8s
docker_e2e_pypi 5 passed / 0 failed 3.8s
docker_e2e_gem 2 passed / 0 failed 2.9s
docker_e2e_golang 1 passed / 0 failed 3.1s
docker_e2e_composer 2 passed / 0 failed 2.7s
docker_e2e_cargo 1 passed / 0 failed 1.5s
docker_e2e_maven 1 passed / 0 failed 23.0s
docker_e2e_nuget 2 passed / 0 failed 4.2s
docker_e2e_deno 3 passed / 0 failed 2.2s
docker_e2e_vendor_gem 2 passed / 0 failed 4.2s
docker_e2e_vendor_composer 1 passed / 0 failed 3.0s

Every suite drives the real CLI through a real package-manager install → patch-applied → (where applicable) revert chain.

Hands-on usability probe (built binary, planted FIFOs)

The sweep's headline class is the FIFO-wedge; the probe planted FIFOs where the CLI expects regular files and drove the real binary:

  • Sanity: --version/--help clean; empty-project list gives an actionable "Manifest not found" error; scan --offline refuses with a clear strict-airgap message. No panics anywhere.
  • gem (.bundle/config as FIFO): scan completes in ~1s, finds all 31 ruby packages, degrades the config read gracefully. Previously: infinite hang.
  • python (dist-info/METADATA as FIFO): scan completes in ~1s and still identifies the package from the dir name.
  • npm (package-lock.json as FIFO): scan completes in ~1s, finds the package.
  • cargo (Cargo.lock as FIFO): completes instantly (that path only stats the lock, by design).
  • Error quality: every hard error is clear and actionable; FIFO configs are silently skipped with normal scan output — no backtraces, no gibberish, no mute exit-1 in loud mode.

Residual found by the probe → fixed as the 100th fix (49508d5)

An extension probe of the same class found that a FIFO squatting .socket/manifest.json still hung list and apply forever with zero outputread_manifest (manifest/operations.rs) was the one manifest raw-read the sweep missed, and apply runs from install hooks, so this wedged npm install indefinitely. Fixed via the standard open_regular_file guard, RED-verified (the new regression test wedged its full 5s deadline pre-fix, passes in ms post-fix), and re-verified live: both commands now fail fast in ~1s with Error: ./.socket/manifest.json is not a regular file. Manifest-consumer contracts re-checked (remove_invariants, repair_invariants green; a directory manifest still labels manifest_unreadable).

🤖 Generated with Claude Code

…M_UUIDS

Production extended pkg:gem/activestorage@6.0.3 with a second advisory's
patch on 2026-08-19T21:19Z (GHSA-w749-p3v6-hccq / CVE-2022-21831, uuid
6c4141c5-1535-4fd2-9db1-b5f8e4834bdb) and the server-ranked hosted
selection now wires it, failing the pinned any-of assert exactly as
designed. The new patch was live-verified before extending the pin: the
served .gem matches the /info checksum, carries the Socket patch header
in image_processing_transformer.rb, and every other file is
byte-identical to stock rubygems 6.0.3. Both advisories' uuids stay in
the set (the batch API still publishes both).

Verified live: preflight_required_patches_are_published +
gem_bundler_hosted_install_proof green against production.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

hosted-e2e failure on ee6488c — root-caused and fixed in f842a21 (not a sweep regression)

gem_bundler_hosted_install_proof failed because production extended pkg:gem/activestorage@6.0.3 with a second advisory's patch on 2026-08-19T21:19Z — GHSA-w749-p3v6-hccq / CVE-2022-21831, uuid 6c4141c5-1535-4fd2-9db1-b5f8e4834bdb — and the server-ranked hosted selection now wires that one, tripping the pinned any-of UUID assert exactly as it was designed to.

Per the assert's own instruction, the new patch was verified before extending the pin:

  • registry healthy: /versions 200, /info/activestorage 200 with the full dep list;
  • served .gem matches the /info sha256 (b7ebb857…d6df);
  • lib/active_storage/transformers/image_processing_transformer.rb carries the Socket Community Patch header (dated exactly at publishedAt) and the CVE-2022-21831 fix;
  • every other file is byte-identical to stock rubygems 6.0.3;
  • the batch API still publishes both advisories' patches, so both uuids stay in the any-of set.

Re-verified live against production after the pin extension: preflight_required_patches_are_published + gem_bundler_hosted_install_proof both green.

🤖 Generated with Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit a2f07e0 into main Aug 20, 2026
62 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the cleanup/dead-package-mode-and-before-blobs branch August 20, 2026 14:14
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Aug 20, 2026
…lass-split fallback-home failure semantics

Review-round fixes for the two verified Bugbot findings on #222.

Finding 1 (Medium): the containment guard's
`gem_bundle_config_path_ignored` was a bare eprintln inside the crawler —
it never reached any --json `warnings[]` and printed under --silent,
violating the repo-wide warning conventions (#219/#220 + the #223
omnibus silent fixes). The crawler is now print-free: the refusal is
RECORDED on `BundleStoreDiscovery.skipped_config_path`, and a shared
`config_path_ignored_warning(value)` builder feeds the CLI channels —
scan pushes it onto the same run-level channel as the PnP layout
refusals (JSON `warnings[]` on both the zero-package and >=1-package
envelopes; one stderr line gated on !json && !silent), and apply carries
it in its Envelope `warnings[]` plus one gated stderr line. Scoped like
the crawl that hit it: local mode, gem in --ecosystems/manifest scope.

Finding 2 (High, with nuance): with an env/config bundle root,
get_gem_paths appends the gem-env fallback homes and the multi-copy
fan-out patched EVERY copy with per-copy loud-fail — so a gem present in
both the bundle store and a shared home (rvm @global, root-owned system
dir) failed the WHOLE run on a permission failure or variant mismatch
THERE, even though the copy bundler loads patched fine. Patching a
shared home's copy is not itself wrong (plain apply always patched
GEM_HOME when no store existed); the defect was failure semantics
crossing store classes. Fix: discovery's store list is exposed
(`RubyCrawler::discover_bundle_stores`, fs-probes only) and apply's gem
fan-out classes each copy — bundle-path store copies stay PRIMARY
(loud-fail, unchanged); gem-env fallback-home copies become BEST-EFFORT
once at least one store copy applied: a variant mismatch or write
failure there is a per-copy non-fatal Skipped event
(`gem_fallback_home_skipped`, detail names the path and reason; gated
stderr twin), never a run failure. Parity edge kept: with NO bundle-store
copy (the historic fallback-only layout, and every --global run) the
home copy IS primary and keeps loud-fail exactly as pre-#218 apply.

TDD evidence (red -> green on the rebased tip): scan/apply --json
missing the warnings[] entry and the --silent leak (3 tests,
in_process_gem_config_warning.rs); mismatched-home-copy exit 1 and
Failed-event-on-strict-refusal (in_process_gem_fallback_home.rs), with
fallback-only loud parity and both-copies-patched pinned green
throughout. CLI_CONTRACT.md documents the copy classes and the warning
channels.

Gates: touched files rustfmt-clean; cargo clippy --workspace
--all-features -D warnings clean; core --lib 2535, cli --lib 436;
crawlers::ruby 50, crawler_ruby_e2e 25; gem+npm multicopy 2+2;
apply_network 11, apply_invariants 4, cli_gem_variant_mismatch_policy 6,
cli_apply_silent 2, e2e_gem hermetic 8 (6 shown +cache selftests),
e2e_scan, cli_scan_silent, docker_e2e_gem — all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants