Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ command's integration tests check its output.
| `maven_trusted_checksums_left` / `nuget_default_config_left` / `upstream_uv_override_removed` | rollback/remove `warnings[]`; vendor advisory event (takeover) | upstream restore (v5.0): `.mvn` config keeps the trusted-checksums resolver lines because it holds more than hosted mode writes; `nuget.config` now holds only the nuget.org source (delete it if hosted mode created it); a transitive `override-dependencies` entry hosted mode added to `pyproject.toml` (the one under its `# socket-patch hosted` comment) was removed; a user-authored override is never removed and never reported. |
| `hosted_resolution_orphaned` | rollback/remove `warnings[]`; list `warnings[]` (human: `Warning: …` on stderr) | yarn berry (#1203): the root `package.json` keeps a Socket-hosted `resolutions` selector that no live `yarn.lock` entry resolves and no lock descriptor matches (left behind by `yarn remove` / `yarn up`). `rollback` / `remove` removed it (the lock is untouched; an emptied `resolutions` object is dropped). uv (#1445): `pyproject.toml` (or a script's PEP 723 block) keeps a hosted `override-dependencies` pin and its `[tool.uv.sources]` URL for a transitive package the lock no longer holds (left behind by `uv remove <parent>` / `uv lock --upgrade-package <parent>`). `rollback` / `remove` removed both lines and the lock's `[manifest]` record of them (emptied tables are dropped). `list` only reports either and names the scoped `remove <purl>` that retires it alone. Never flips the exit. |
| `upstream_registry_fallback` | rollback/remove `warnings[]`; vendor advisory event (takeover) | upstream restore: a yarn berry, pnpm or vlt entry is restored from the version document of the registry the project resolves it against (yarn berry: `npmScopes` / `YARN_NPM_REGISTRY_SERVER` / `npmRegistryServer` from the `.yarnrc.yml` chain, as above, the pnpm lock's sibling `.npmrc` `registry` / `@scope:registry` or pnpm-workspace.yaml `registry` / `registries`, vlt's node registry); that registry could not be read (e.g. it needs credentials) or, for yarn berry, could not be determined (an rc value referencing an environment variable that is set, or an unset one with no default; a flow-style `npmScopes`), so the default registry's document was used and the restored tarball URL may not be the mirror's. |
| `upstream_gem_stale_cache` | rollback/remove `warnings[]`; vendor advisory event (takeover) | upstream restore (#1260): a restored gem's `<name>-<version>.gem` is still in Bundler's cache dir (`cache_path`, default `vendor/cache`, resolved as for the Gem stale-install guard) and its sha256 is not the upstream one (the restored `CHECKSUMS` entry, else the rubygems.org compact index), or it could not be checked (`--offline`, a registry error). Bundler installs from that dir first, so a `bundle cache` taken while the hosted pin was live makes every later install fail on the upstream checksum (exit 37) or, on bundler < 2.6 frozen installs, keep installing the patched bytes. The detail names the file. Remedy: delete it, then run `bundle cache` to cache the upstream gem in its place (or `bundle install` if the project does not commit its cache; with the cache dir committed, a frozen install reads only the cache). Read-only: the restore never deletes it. Not raised for an archive whose sha256 matches upstream. |
| `upstream_gem_stale_cache` | rollback/remove `warnings[]`; a vendored unwind's rollback `warnings[]`, remove / `vendor --revert` / `vendor` (manifest reconcile) advisory events and `scan --prune` `gc.warnings[]`; vendor advisory event (takeover) | upstream restore (#1260), or a wet vendored gem unwind (`remove`, `rollback`, `vendor --revert`, `scan --prune`, `vendor`'s reconcile) that puts the gem back on its upstream entry (#1594; a gem locked from a remote other than rubygems.org with no `CHECKSUMS` sha is not judged): a restored gem's `<name>-<version>.gem` is still in Bundler's cache dir (`cache_path`, default `vendor/cache`, resolved as for the Gem stale-install guard) and its sha256 is not the upstream one (the restored `CHECKSUMS` entry, else the rubygems.org compact index), or it could not be checked (`--offline`, a registry error). Bundler installs from that dir first, so a `bundle cache` taken while the hosted pin was live makes every later install fail on the upstream checksum (exit 37) or, on bundler < 2.6 frozen installs, keep installing the patched bytes. The detail names the file. Remedy: delete it, then run `bundle cache` to cache the upstream gem in its place (or `bundle install` if the project does not commit its cache; with the cache dir committed, a frozen install reads only the cache). Read-only: the restore never deletes it. Not raised for an archive whose sha256 matches upstream. The takeover's copy adds that the vendored copy is installed instead while the gem stays vendored, and that the vendored unwind names the archive again. |
| `upstream_pnpm_tarball_setting_guessed` | rollback/remove `warnings[]`; vendor advisory event (takeover) | upstream restore (#902): nothing showed which pnpm wrote a hosted `pnpm-lock.yaml` (no unpinned registry entry that shows the setting, no `node_modules/.modules.yaml` install record, no package.json `packageManager` pin; for a Rush lock, no rush.json `pnpmVersion`), so its entries were restored with or without `tarball:` by pnpm 10's reading of `lockfileIncludeTarballUrl` (pnpm-workspace.yaml, else `.npmrc` `lockfile-include-tarball-url`), and pnpm 9 (which reads only `.npmrc`) or pnpm >= 11 (which reads only pnpm-workspace.yaml) would have read it the other way. The detail names the lock, the setting followed, the pnpm that disagrees and the entries. Remedy: pin the pnpm (package.json `packageManager`, or reinstall so the install record names it; rush.json `pnpmVersion` for Rush), or give the two files the same value so every pnpm reads it alike; a rollback or remove can then be redone by restoring the lock from version control and re-running. Not raised when evidence decided, when both files read the same on every pnpm, or when the tarball is one pnpm records regardless. |
| `yarn_berry_node_gyp_unresolved` | rollback/remove `warnings[]`; vendor advisory event (takeover) | upstream restore (#737): yarn's npm resolver gives the restored registry entry an implicit `node-gyp: "npm:latest"` dependency (the version document runs `node-gyp` in a script without declaring it), which the hosted pin dropped because yarn never gives a tarball-locator entry one, and the lock no longer has a `node-gyp@npm:latest` entry (the pin dropped the subtree only node-gyp reached). The entry is restored without it; a plain `yarn install --immutable` accepts that, a hardened or `--refresh-lockfile` install reports the lock modified. Remedy: run `yarn install` once. Not raised while another entry keeps `node-gyp@npm:latest` in the lock: the dependency is then restored too. |
| `legacy_redirect_ledger_kept` | rollback `warnings[]` (+ remove stderr) | v5.0: a pre-v5 `.socket/vendor/redirect-state.json` could not be deleted once no hosted pin was left; the file is inert (never read for planning). Never flips the exit. |
Expand Down
34 changes: 31 additions & 3 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,23 @@ pub(crate) fn symlinked_wiring_warnings(cwd: &Path, purl: &str) -> Vec<VendorWar
.collect()
}

/// A hosted-restore advisory as a takeover reports it. The gem stale-cache
/// one describes a gem back on its upstream entry, but the takeover wires
/// the gem to its vendored copy right after, which bundler installs
/// instead: the archive only matters once that copy is unwired, and the
/// unwind names it again then (#1594).
fn takeover_detail(code: &str, detail: &str) -> String {
if code == socket_patch_core::patch::redirect::upstream::GEM_STALE_CACHE {
format!(
"{detail} (while the gem stays vendored, bundler installs the vendored copy \
instead; the command that unvendors it, such as `remove`, `rollback` or \
`vendor --revert`, names this archive again)"
)
} else {
detail.to_string()
}
}

pub(crate) fn record_warning(
env: &mut Envelope,
purl: &str,
Expand Down Expand Up @@ -3269,7 +3286,7 @@ pub(crate) async fn vendor_records_reusing(
let mut advisories: Vec<VendorWarning> = restore
.warnings
.iter()
.map(|(code, detail)| VendorWarning::new(code, detail.clone()))
.map(|(code, detail)| VendorWarning::new(code, takeover_detail(code, detail)))
.collect();
if common.dry_run {
// The refusal the backend would raise over the restored
Expand Down Expand Up @@ -4507,7 +4524,8 @@ pub(crate) struct VendorGcSummary {
}

/// The revert warnings `scan --prune` forwards into `gc.warnings[]`: only
/// the "the installed tree still holds the vendored copy" advisories. The
/// the "the installed tree still holds the vendored copy" advisories, and
/// the patched gem archive Bundler's cache would install again (#1594). The
/// revert's other warnings are routine for a prune and stay out:
/// `vendor_lock_entry_removed` is the normal leg-(b) case (the dependency
/// was uninstalled), and a drift keep is already reported through
Expand All @@ -4516,6 +4534,7 @@ const GC_FORWARDED_ADVISORIES: &[&str] = &[
socket_patch_core::vendor::bun_lock::REINSTALL_REQUIRED,
socket_patch_core::vendor::vlt_lock::REINSTALL_REQUIRED,
crate::commands::pypi_reinstall::VENDOR_CODE,
socket_patch_core::patch::redirect::upstream::GEM_STALE_CACHE,
];

impl VendorGcSummary {
Expand Down Expand Up @@ -4627,7 +4646,8 @@ pub(crate) async fn run_vendor_gc(
let mut outcome = dispatch_revert_one(&entry, &common.cwd, false).await;
// The patch left the manifest while the package stays locked:
// the restored lock pins the version the vendored build is
// installed as, which PDM, uv and Pipenv keep (#477).
// installed as, which PDM, uv and Pipenv keep (#477), and a
// patched gem archive in Bundler's cache installs again (#1594).
if outcome.success && !outcome.kept_artifact {
crate::commands::pypi_reinstall::push_vendor_advisory(
&common.cwd,
Expand All @@ -4636,6 +4656,14 @@ pub(crate) async fn run_vendor_gc(
&mut outcome.warnings,
)
.await;
crate::commands::vendored_backend::push_gem_stale_cache(
&common.cwd,
&entry.ecosystem,
&entry.base_purl,
common.offline,
&mut outcome.warnings,
)
.await;
}
out.take_advisories(&outcome);
if !outcome.success {
Expand Down
44 changes: 44 additions & 0 deletions crates/socket-patch-cli/src/commands/vendored_backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ impl<'a> VendoredBackend<'a> {
for key in keys {
// Captured before a clean revert drops the entry.
let flavor = state.entries.get(key).and_then(|e| e.flavor.clone());
let gem = state
.entries
.get(key)
.map(|e| (e.ecosystem.clone(), e.base_purl.clone()));
let mut result = revert_vendor_entry(&self.common.cwd, key, state, opts).await;
let hard_failure = matches!(
result.step,
Expand All @@ -154,6 +158,20 @@ impl<'a> VendoredBackend<'a> {
&mut result.warnings,
)
.await;
// A dry run reads the still-vendored lock, whose bare
// CHECKSUMS entry can't judge the archive: the wet run
// names it.
let dry = matches!(result.step, VendorRevertStep::WouldRevert);
if let Some((ecosystem, purl)) = gem.as_ref().filter(|_| !dry) {
push_gem_stale_cache(
&self.common.cwd,
ecosystem,
purl,
self.common.offline,
&mut result.warnings,
)
.await;
}
}
out.push(RevertedEntry {
key: key.clone(),
Expand All @@ -169,6 +187,32 @@ impl<'a> VendoredBackend<'a> {
}
}

/// Name a patched `<name>-<version>.gem` left in Bundler's cache dir once
/// the vendored ledger entry of `ecosystem` for `base_purl` is unwired
/// (#1594): a `bundle cache` taken while a hosted pin was live keeps it
/// there through the takeover, and bundler installs from it again once the
/// lock is back on upstream. The hosted unwind raises the same warning
/// (#1260).
pub(crate) async fn push_gem_stale_cache(
root: &Path,
ecosystem: &str,
base_purl: &str,
offline: bool,
warnings: &mut Vec<VendorWarning>,
) {
if socket_patch_core::vendor::jvm::layout::ledger_ecosystem(ecosystem) != "gem" {
return;
}
if let Some((code, detail)) =
socket_patch_core::patch::redirect::upstream::gem_stale_cache_after_unwind(
root, base_purl, offline,
)
.await
{
warnings.push(VendorWarning::new(code, detail));
}
}

/// One entry [`VendoredBackend::revert`] handled.
pub(crate) struct RevertedEntry {
/// The ledger key.
Expand Down
Loading
Loading