Skip to content
Draft
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
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,53 @@ into the new version's section — see docs/releasing.md.

### Fixed

- **A patch file the patch never changes no longer blocks vendoring.** The
patch view serves `blobContent` only for the files a patch CHANGES, so a
zero-delta file (`beforeHash == afterHash`) comes back with hashes and no
content — and needs none: the pristine copy already carries the patched
bytes. The vendor stager counted such a view as a failed fetch, which made
any patch carrying a zero-delta file permanently unvendorable (live
example: `pkg:npm/tar-fs@2.1.1`).
- **One unstageable patch no longer kills a whole vendored run.** A package
whose patch content cannot be obtained now gets its own `failed` event
with `errorCode: "no_local_source"` and the rest of the run still vendors,
in `vendor`, `scan`/`get --mode vendored` and `repair` alike. The event's
`error` names the real reason (which file the view served without content,
a malformed blob, or the fetch error) instead of the generic run-level
"patch artifacts unavailable (offline or download failure)".
**JSON consumers:** for a partial staging failure the vendor envelope is
now `status: "partialFailure"` with `error: null` and per-package events,
where it used to be `status: "error"` with a top-level
`error.code: "no_local_source"` and an empty `events[]`. The run-level
shape is unchanged when NOTHING in the manifest can be staged (including
a one-patch manifest) — `no_local_source` can therefore arrive run-level
or event-level, and both shapes are documented in CLI_CONTRACT.md.
- **`get` emits its patch lists in a stable order.** The release-variant
narrowing drained a `HashMap`, so `download.patches`, `apply.patches` and
the per-patch stderr lines came out in bucket order: two identical runs of
the same project emitted the same records in different orders. All of them
are purl-ordered now, matching every sibling collection in the envelope.
- **A requirements.txt this CLI already rewired stays in the lockfile
inventory.** Both shapes we write — the hosted `name @ <patch-server url>`
direct reference and the vendored bare `./.socket/vendor/pypi/…` wheel
path tagged `# socket-patch vendor: <name>==<ver>` — are read back as the
package they replace (discovery-only, exactly like the `==` pin they
replaced). A second hosted run over a wet requirements.txt reported
`packagesWithPatches: 1` instead of 12; a vendored one under-reported the
same way.
- **`vendor --vendor-source build` no longer downloads a gem it cannot use.**
A gem the lockfile resolves and verifies, with no installed copy, is
refused with `failed`/`gem_spec_missing` BEFORE the registry round trip:
the bundler path source needs the stub gemspec rubygems writes at install
time, which a downloaded `.gem` does not carry, so the local build refused
it after paying for the download on every run. The refusal names the real
remedy (`bundle install`, or `--vendor-source=auto`).
**JSON consumers:** that run no longer carries the
`vendor_fetched_missing` warning event it used to emit before failing.
Unaffected: a gem the lock cannot verify keeps its documented
`vendor_fetch_unverifiable` + `package_not_installed` pair, an
already-vendored gem still re-runs green, and `auto`/`service` still
fetch.
- **Hosted Go redirects no longer claim patches that did not land.**
`scan`/`get --mode hosted` counted a Go module as redirected (recorded
it in the redirect ledger, so `vex` attested it) whenever any project
Expand Down
5 changes: 3 additions & 2 deletions crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
| `already_patched` | `skipped` | apply: every file's hash already matches `afterHash`. |
| `package_not_installed` | `skipped` | apply: manifest entry has no matching installed package. |
| `apply_failed` | `failed` | apply: hash mismatch, write error, archive read error. |
| `no_local_source` | `skipped`/`failed` | `--offline` and the patch is missing from `.socket/`. |
| `no_local_source` | `skipped`/`failed` | `--offline` and the patch is missing from `.socket/`. **Vendored staging (v5.0) reports it at TWO levels:** per package (a `failed` event whose `error` names the reason — which file the patch view served with no `blobContent`, a malformed blob, or the fetch error — envelope `partialFailure`, `error: null`) when at least one other patch staged; and run-level (top-level `error.code`, `status: "error"`, empty `events[]`) when NOTHING in the manifest can be staged, which includes a one-patch manifest. A consumer routing on this code must handle both. A file the patch does not change (`beforeHash == afterHash`) is never a reason: the view serves it without content because it needs none. |
| `offline_missing_sources` / `sources_download_failed` | apply run-level `warnings[]` | apply (additive): the patch sources were unavailable — `--offline` with no local source, or the download left a patch with no source — so nothing was attempted. The envelope keeps its pinned shape (`partialFailure`, empty `events[]`, zero summary, no top-level `error`); the warning is its machine-readable reason (the human path prints the staging `Error:` line on stderr instead, even under `--silent`). |
| `paid_required` | `failed` / status=`paidRequired` | get/scan: patch needs a paid plan and the caller's token isn't entitled. `get <uuid>` on the public proxy reports it (exit 0) both for a `tier: "paid"` view and for the proxy's 403 refusal, whose record then carries only `uuid` + `tier` (the proxy never named the purl). |
| `download_failed` | `failed` | repair/get: network or 404 on patch fetch. |
Expand Down Expand Up @@ -1226,7 +1226,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
| `vendor_content_mismatch_overwritten` | `skipped` (warning) | vendor: a staged file matched NEITHER beforeHash nor afterHash (patch built against different bytes, or local edits); the stage was overwritten with the verified patched content and the vendor succeeded. |
| `vendor_fetched_missing` | `skipped` (warning) | vendor: the package was not installed; its pristine artifact was fetched per the lockfile resolution (or staged from the committed vendor artifact), integrity-verified, and vendored — the project tree was not touched. For `poetry.lock` (which records hashes but no URLs) the pure-Python wheel's sha256 selects the file through PyPI's JSON API (`SOCKET_PYPI_JSON_API` overrides the endpoint); Poetry 0.12's bare `[metadata.hashes]` names no wheel, so those locks still need an installed copy (`vendor_fetch_unverifiable`). |
| `vendor_fetch_failed` | `failed` | vendor: the lockfile-resolved fetch was attempted and failed (HTTP error, size cap, integrity mismatch, or a PRESENT-but-corrupt committed artifact — pointed at `socket-patch repair`). A MISSING committed artifact no longer lands here: it falls through to the ledger-recovered registry fetch. Suppresses the duplicate `package_not_installed` skip. |
| `vendor_fetch_unverifiable` | `skipped` (warning) | vendor: the lockfile records no usable integrity for the missing package; nothing was fetched (fail-closed) and the `package_not_installed` skip follows. |
| `vendor_fetch_unverifiable` | `skipped` (warning) | vendor: the lockfile records no usable integrity for the missing package; nothing was fetched (fail-closed) and the `package_not_installed` skip follows. Unchanged for gems by the build-mode `gem_spec_missing` refusal below, which fires only where a fetch WOULD have run. |
| `vendor_artifact_missing` | `skipped` (warning) / `failed` | vendor: the committed artifact is gone — the registry resolution is recovered from the ledger and the artifact rebuilt (warning); repair `--offline` with no local source surfaces it as the per-entry failure instead. |
| `vendor_artifact_corrupt` | `failed` | repair `--offline`: the committed artifact fails verification (member afterHashes or the ledger's whole-file sha256) and no local source can rebuild it. Online repairs rebuild instead. |
| `vendor_artifact_reused` | `skipped` (verbose note) | vendor / scan `--vendor` (pypi): the wiring was dropped by a relock but the committed wheel the ledger vouches for verified, so it was re-wired as-is — no service download, no rebuild; the lock pins the first run's sha again. |
Expand All @@ -1236,6 +1236,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
| `vendor_uuid_mismatch` | `skipped` | repair: the manifest's patch uuid moved past the vendored artifact — a re-vendor (`vendor` / `scan --vendor`) is pending; repair does not cross patch generations. |
| `content_mismatch_overwritten` | `skipped` (warning) | apply (default policy): a file matched NEITHER beforeHash nor afterHash and was overwritten with the full verified patched content. `--strict` turns this case into a `failed` event instead. |
| `vendor_lock_checksums_unsupported` / `vendor_stale_lock_checksum` | `failed` | vendor (gem): an ambiguous/platform CHECKSUMS entry, or a v1-wired lock whose stale token blocks the hot path (run `vendor --revert` + re-vendor). |
| `gem_spec_missing` | `failed` | vendor (gem): the gem is not installed and the run cannot use the patch service (`--vendor-source build`, or no service config), so the local build has no stub gemspec to give bundler's path source — a downloaded `.gem` carries its gemspec only as YAML in `metadata.gz`. Raised BEFORE the registry round trip when the lockfile both resolves AND verifies the gem and no ledger entry already vendors it (that run never downloaded anything usable); the gem backend keeps the same refusal as the backstop for every other route into it. A run that would not have fetched at all is unaffected: an unverifiable lock entry keeps `vendor_fetch_unverifiable` + `package_not_installed`, and an already-vendored gem re-runs green. The detail names the remedy (`bundle install`, or `--vendor-source=auto`). Because the pre-fetch refusal skips the download, that run no longer emits the `vendor_fetched_missing` warning it used to emit before failing. |
| `redirect_pypi_stale_install` | `redirect.warnings[]` (warning) | Hosted Python redirect: readable installed files differ from patched hashes. Read-only, repeated on re-scan, and excludes the package from same-run VEX. See the "Python stale-install guard" section. |
| `redirect_gem_stale_install` | `redirect.warnings[]` (warning) | scan `--mode hosted` (gem): a stale UNPATCHED materialization (installed gem, or committed `vendor/cache` archive) that `bundle install` will reuse instead of fetching the redirected patch; the detail carries the verified remedy. Full rules and flavors: the "Gem stale-install guard" section. |
| `redirect_pipenv_refused` | `redirect.warnings[]` (warning) | scan `--mode hosted` (pipenv): the Pipfile.lock pins another version or a non-registry / foreign source for the package — refused atomically across categories, and the patch is vetoed from the sibling Python rewriters (see the "Pipenv hosted redirect" section). |
Expand Down
Loading
Loading