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
6 changes: 3 additions & 3 deletions crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,7 @@ command's integration tests check its output.
| `hosted_reverted` | `removed` | remove (v5.0): a hosted lockfile pin was restored to its upstream registry entry as part of removing the patch (`verified` on dry-run). Beside a manifest entry it bypasses `summary.removed` like `vendor_reverted`. v5.0: carries `details.mode: "hosted"`. |
| `hosted_revert_failed` | top-level error | remove (v5.0): a matched hosted pin could not be restored to its upstream registry entry (`--offline`, a registry that does not answer, `bun.lockb`, a lock shape the restore refuses — see "Hosted unwind coverage"), or writing the restored files failed; the message names the `git checkout -- <files>` remedy. The manifest was not modified, exit 1. Rollback's counterpart is a `failed` `hosted_restore_refused` event (exit 1). v4's `hosted_revert_unsupported` is no longer emitted (every ecosystem has a restore). |
| `cargo_cache_patch_kept` | `scan --prune`/`--sync` top-level `warnings[]` (human: `GC: …`) | a Cargo manifest entry the crawl no longer reports (the lock bumped or dropped the crate) was NOT pruned because its copy in the shared `$CARGO_HOME/registry/src` cache still carries the agent-mode patch; the record holds the only blobs that can restore it. The detail names the purl and `socket-patch rollback <purl>` (with `--global` when a `cargo vendor` dir hides the registry cache from the project crawl), which restores the copy and drops the entry. Never affects the exit. |
| `reinstall_required` | rollback `warnings[]` | rollback (v5.0): vendored/hosted wiring was unwound, but installed trees keep their patched bytes until the next package-manager install — the stale-install advisory. When the run also emits a Bun advisory (`vendor_bun_reinstall_required` / `redirect_bun_reinstall_required`) the detail and the human note add " (Bun: a plain `bun install` keeps them; run `bun install --force`)". When it emits a PyPI advisory (`vendor_pypi_reinstall_required` / `redirect_pypi_reinstall_required`) they add " (PDM, uv and Pipenv keep a same-version install through a plain sync: run the reinstall the pypi_reinstall_required warning names)". |
| `reinstall_required` | rollback `warnings[]` | rollback (v5.0): vendored/hosted wiring was unwound, but installed trees keep their patched bytes until the next package-manager install — the stale-install advisory. When the run also emits a Bun advisory (`vendor_bun_reinstall_required` / `redirect_bun_reinstall_required`) the detail and the human note add " (Bun: a plain `bun install` keeps them; run `bun install --force`)". When it emits a PyPI advisory (`vendor_pypi_reinstall_required` / `redirect_pypi_reinstall_required`) they add " (pip, PDM, uv and Pipenv keep a same-version install through a plain install or sync: run the reinstall the pypi_reinstall_required warning names)". |
| `hosted_state_not_preservable` | rollback / remove `warnings[]` | rollback `--preserve-state` (v5.0): hosted pins were restored to upstream anyway — the lockfile pins are hosted mode's only record, so there is no local state to preserve; re-run `scan --mode hosted` to re-wire. (`remove --preserve-state` reports the same code in its `warnings[]` — manifest-backed and hosted-only alike — and prints it as a `Note:` on stderr in human mode.) |
| `out_of_scope_copies_restored` | rollback `warnings[]` | path-scoped rollback (v5.0): a selected patch had installed copies outside the given patterns; ALL copies were restored (patches are per-package). Informational — never flips the exit. |
| `patched_in_other_scope` | rollback `warnings[]` | the manifest records no scope, so before a rollback drops a record it restored, found already original, or found not installed in its own scope, it checks the other scope (the project at `--cwd` for a `-g` / `--global-prefix` run, the default global install otherwise). A copy there that still holds the patch's afterHash bytes, or has a patched file that exists but cannot be hashed, keeps the record (with its variant group) and its blobs. This warning names the purl, the copy and the rollback of just that purl that restores it (`socket-patch rollback <purl>` or `socket-patch rollback <purl> -g`, plus `--cwd` when the run used one). Never flips the exit (#450). |
Expand Down Expand Up @@ -1737,8 +1737,8 @@ command's integration tests check its output.
| `redirect_vlt_artifact_unverifiable` | `warnings[]` (warning), `skipped` (hosted event) | scan/get `--mode hosted` (vlt): before any takeover or rewrite (dry runs included), each granted artifact with a default-registry instance in `vlt-lock.json` (or, for a purl a `flavor: "vlt"` vendored entry claims, its vendored node, probed before the takeover reverts it) is fetched once as vlt fetches it (`accept-encoding: gzip;q=1.0, identity;q=0.5`, no `Authorization`, up to 10 redirects) and must return 200 with no content encoding (or `identity`) and the granted sha512. On failure (`content-encoding <v>`, `sha512 mismatch`, `http <status>`, `fetch error <e>`, `offline`) the dep is withheld from every rewriter when vlt drives or it is vlt-vendored (which also keeps it vendored), and from the vlt rewrite only otherwise (detail "…; vlt-lock.json was not changed for {purl}"; only the sibling lock this run rewrote can confirm it). A lock already pinned by an earlier run is left pinned, and neither confirmed nor attested. Projects without `vlt-lock.json` make no such request. The detail quotes the artifact URL (and any fetch error that echoes it) with its grant-token path level, the one just before the patch uuid, spelled `<redacted>`; host, uuid and leaf stay. The in-memory hosted engine (`hosted-bundle`, the Node addon) has no network for this fetch, so it judges every in-scope artifact as `--offline` does (withheld, never pinned; the vendored takeover it refuses anyway). Exit 0. |
| `redirect_vlt_reinstall_required` | `warnings[]` (advisory); rollback/remove `warnings[]` (+ human stderr) | vlt: `vlt-lock.json` pins (or, after rollback/remove, no longer pins) Socket-patched packages, and vlt never refreshes an installed copy. The heal removes `node_modules/.vlt-lock.json` and each stale `node_modules/.vlt/<DepID>` of a Socket-owned node (never a link's target, never outside the project, never a copy it cannot judge) unless `--no-vlt-install-cleanup` or `--dry-run`. It never removes an optional node's copy (lock flags 1 or 3, or flags it cannot read): `vlt install` does not put a removed optional dependency back (its link dangles) unless the same install also reinstalls a non-optional node, so such a copy is left stale and the detail says to run `vlt ci` (or delete `node_modules` and run `vlt install`); vlt 0.0.0-30 … 1.0.4 install no optional dependency from the lock of a project that declares only optional dependencies, so there both commands remove the installed copy and the detail says to upgrade vlt to 1.0.5 or later first. The detail says whether copies were removed, left stale by a skipped cleanup, could not be checked, or none were stale, and adds how many optional copies were kept whenever there are any. The kept optional copies are named by what they are: `unpatched copies of optional dependencies` after `scan`/`get`, `patched copies of optional dependencies` after `rollback`/`remove`, and `installed copies of the vendored optional dependencies` after a hosted → vendored takeover (the copy the hosted pin left installed, which may still be the registry bytes). Stale or unchecked copies are not attested by the run's `--vex`, nor is a confirmed vlt pin the heal did not check (a URL on a host other than patch.socket.dev and the configured `--patch-server-url`/`--api-url`). A hidden lock that cannot be removed keeps every store entry. Invalidation failures only warn. |
| `redirect_bun_reinstall_required` | rollback/remove `warnings[]` (+ human stderr), `vendor --revert` `warnings[]` | a wet hosted unwind that restored `bun.lock` / `bun.lockb` pins to the registry record while their installed copy may be kept (the `vendor_bun_reinstall_required` rule: Bun's hoisted linker keeps it through a plain `bun install`). One run-level warning naming every such `name@version` and `bun install --force` (or deleting `node_modules`). `vendor --revert` leaves it out when the vendored revert already advised `vendor_bun_reinstall_required` for every package it re-hosted. |
| `vendor_pypi_reinstall_required` | `skipped` (advisory; human: `Warning: …`); rollback/remove `warnings[]`; `scan --prune` top-level `warnings[]` (human: `GC: …`) | a PyPI vendored revert (`vendor --revert`, rollback / remove of a vendored entry, the manifest reconcile, `--preserve-state` and `--dry-run` included) of a PDM, uv or Pipenv entry (#477). These tools reinstall a same-version package only when the LOCKED candidate is a URL or file that differs from the installed one, so once the lock is back on the registry release a plain `pdm sync` / `pdm install`, `uv sync` or `pipenv sync` reports nothing to do and keeps the vendored build. The detail names the purl and the reinstall that restores the upstream bytes: `pdm sync --reinstall` (or recreating the virtualenv / `__pypackages__`), `uv sync --reinstall-package <name>`, or Pipenv's `pipenv run pip uninstall -y <name> && pipenv sync` (the categories from `Pipfile.lock`; `pipenv --rm && pipenv sync` for a clean virtualenv). Poetry, requirements.txt, Hatch and PEP 751 entries do not get it. |
| `redirect_pypi_reinstall_required` | rollback/remove `warnings[]` (+ human stderr), `vendor --revert` `warnings[]` | a hosted unwind (wet or `--dry-run`) that restored PyPI pins in `pdm.lock`, `uv.lock` or `Pipfile.lock` to their registry entry: the `vendor_pypi_reinstall_required` rule for hosted pins (the patched build's `direct_url.json` still names the patch server). One run-level warning naming each purl and its reinstall. |
| `vendor_pypi_reinstall_required` | `skipped` (advisory; human: `Warning: …`); rollback/remove `warnings[]`; `scan --prune` top-level `warnings[]` (human: `GC: …`) | a PyPI vendored revert (`vendor --revert`, rollback / remove of a vendored entry, the manifest reconcile, `--preserve-state` and `--dry-run` included) of a requirements.txt (pip), PDM, uv or Pipenv entry (#477, #1953). These tools reinstall a same-version package only when the LOCKED candidate is a URL or file that differs from the installed one, so once the lock is back on the registry release a plain `pdm sync` / `pdm install`, `uv sync` or `pipenv sync` reports nothing to do and keeps the vendored build; once a requirements line is back to `name==version`, `pip install -r` (and `uv pip install -r`) reports it already satisfied and keeps it too. The detail names the purl and the reinstall that restores the upstream bytes: `pip uninstall -y <name>` then `pip install -r <file>` for each requirements file that wired the entry (or the `uv pip uninstall` / `uv pip install -r` pair), which keeps those files' version ranges, index and hash options, `pdm sync --reinstall` (or recreating the virtualenv / `__pypackages__`), `uv sync --reinstall-package <name>`, or Pipenv's `pipenv run pip uninstall -y <name> && pipenv sync` (the categories from `Pipfile.lock`; `pipenv --rm && pipenv sync` for a clean virtualenv). Poetry, Hatch and PEP 751 entries do not get it. |
| `redirect_pypi_reinstall_required` | rollback/remove `warnings[]` (+ human stderr), `vendor --revert` `warnings[]` | a hosted unwind (wet or `--dry-run`) that restored PyPI pins in `pdm.lock`, `uv.lock`, `Pipfile.lock` or a pip requirements file (`*.txt`) to their registry entry: the `vendor_pypi_reinstall_required` rule for hosted pins (the patched build's `direct_url.json` still names the patch server). One run-level warning naming each purl and its reinstall; a pin wired by both a lock and a requirements file (a `uv export` beside `uv.lock`) gets the lock tool's reinstall. |
| `redirect_withdrawn_check_failed` | `warnings[]` (warning; human: `Warning: …`) | scan `--mode hosted`: the reference lookup for the uuids the lockfiles pin failed, so the run could not tell whether any was withdrawn. Every pin is kept; status and exit unchanged. See "Withdrawn pins". |
| `redirect_withdrawn_restore_failed` | `warnings[]` (warning; human: `Warning: …` / `Error: …`) | scan `--mode hosted`: a pin whose patch is `withdrawn` / `not_found` could not be restored to its upstream registry entry (the restore refused it, writing the restored files failed, or the apply lock was held). The pin is kept; the detail names the remedy (`git checkout -- <lockfile>`, or re-run / `socket-patch rollback <purl>`). Status and exit unchanged. |
| `patch_withdrawn` | `rolledBack` / `verified` (hosted event) | scan `--mode hosted`: the lockfile pinned a patch the server reports `withdrawn` or `not_found`; the pin was restored (dry run: would be) to its upstream registry entry. `reason` names the uuid and status. See "Withdrawn pins". |
Expand Down
13 changes: 7 additions & 6 deletions crates/socket-patch-cli/src/commands/hosted_unwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,17 @@ pub(crate) async fn run_hosted_leg(common: &GlobalArgs, pins: &[HostedPin]) -> H
}
}
}
// PDM, uv and Pipenv keep a same-version install through a plain sync
// (#477): name the reinstall that restores the upstream bytes.
// pip, PDM, uv and Pipenv keep a same-version install through a plain
// install or sync (#477, #1953): name the reinstall that restores the
// upstream bytes.
if outcome.flush_error.is_none() {
use crate::commands::pypi_reinstall::{advisory, Tool, HOSTED_CODE};
let unwound: Vec<(String, Tool)> = outcome
use crate::commands::pypi_reinstall::{advisory, Tool, Unwound, HOSTED_CODE};
let unwound: Vec<Unwound> = outcome
.restored()
.filter(|pin| pin.purl.starts_with("pkg:pypi/"))
.filter_map(|pin| {
let tool = pin.files.iter().find_map(|f| Tool::of_file(f))?;
Some((pin.purl.clone(), tool))
let tool = Tool::of_files(&pin.files)?;
Some(Unwound::new(&pin.purl, tool, &pin.files))
})
.collect();
if let Some(detail) = advisory(&common.cwd, &unwound).await {
Expand Down
Loading
Loading