Skip to content

Commit 45e4dea

Browse files
committed
Merge main into feat/vlt-support
Brings in #268 (four vendored/hosted correctness fixes found while profiling). The only overlap with vlt is repair: #268 moved the no-local-source reporting into report_no_local_source, and that now passes the whole vendor entry to soft_restore_without_fingerprint, so the vlt-specific remedy text survives. The CHANGELOG, CLI_CONTRACT and lock-inventory test conflicts keep both sides. Assisted-by: Claude Code:claude-opus-5-5
2 parents d8b3151 + 0b4e645 commit 45e4dea

15 files changed

Lines changed: 1871 additions & 74 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,40 @@ into the new version's section — see docs/releasing.md.
946946
`flavor: "vlt"`) require the socket-patch release that adds vlt
947947
support.
948948

949+
- **A patch file the patch never changes no longer blocks vendoring.** The
950+
patch view serves `blobContent` only for the files a patch CHANGES, so a
951+
zero-delta file (`beforeHash == afterHash`) comes back with hashes and no
952+
content — and needs none: the pristine copy already carries the patched
953+
bytes. The vendor stager counted such a view as a failed fetch, which made
954+
any patch carrying a zero-delta file permanently unvendorable (live
955+
example: `pkg:npm/tar-fs@2.1.1`).
956+
- **One unstageable patch no longer kills a whole vendored run.** A package
957+
whose patch content cannot be obtained now gets its own `failed` event
958+
with `errorCode: "no_local_source"` and the rest of the run still vendors,
959+
in `vendor`, `scan`/`get --mode vendored` and `repair` alike. The event's
960+
`error` names the real reason (which file the view served without content,
961+
a malformed blob, or the fetch error) instead of the generic run-level
962+
"patch artifacts unavailable (offline or download failure)".
963+
**JSON consumers:** for a partial staging failure the vendor envelope is
964+
now `status: "partialFailure"` with `error: null` and per-package events,
965+
where it used to be `status: "error"` with a top-level
966+
`error.code: "no_local_source"` and an empty `events[]`. The run-level
967+
shape is unchanged when NOTHING in the manifest can be staged (including
968+
a one-patch manifest) — `no_local_source` can therefore arrive run-level
969+
or event-level, and both shapes are documented in CLI_CONTRACT.md.
970+
- **`get` emits its patch lists in a stable order.** The release-variant
971+
narrowing drained a `HashMap`, so `download.patches`, `apply.patches` and
972+
the per-patch stderr lines came out in bucket order: two identical runs of
973+
the same project emitted the same records in different orders. All of them
974+
are purl-ordered now, matching every sibling collection in the envelope.
975+
- **A requirements.txt this CLI already rewired stays in the lockfile
976+
inventory.** Both shapes we write — the hosted `name @ <patch-server url>`
977+
direct reference and the vendored bare `./.socket/vendor/pypi/…` wheel
978+
path tagged `# socket-patch vendor: <name>==<ver>` — are read back as the
979+
package they replace (discovery-only, exactly like the `==` pin they
980+
replaced). A second hosted run over a wet requirements.txt reported
981+
`packagesWithPatches: 1` instead of 12; a vendored one under-reported the
982+
same way.
949983
- **`vex`'s API-fallback note no longer depends on which refusal landed
950984
first.** When the patch API refuses several patch records, the
951985
`api_auth_fallback` note quoted whichever refusal happened to answer

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
12141214
| `already_patched` | `skipped` | apply: every file's hash already matches `afterHash`. |
12151215
| `package_not_installed` | `skipped` | apply: manifest entry has no matching installed package. |
12161216
| `apply_failed` | `failed` | apply: hash mismatch, write error, archive read error. |
1217-
| `no_local_source` | `skipped`/`failed` | `--offline` and the patch is missing from `.socket/`. |
1217+
| `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. |
12181218
| `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`). |
12191219
| `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). |
12201220
| `download_failed` | `failed` | repair/get: network or 404 on patch fetch. |
@@ -1278,7 +1278,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
12781278
| `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. |
12791279
| `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. Not emitted when no fetch happened: an in-sync re-run of a ledger-covered purl, or a cargo crate the patch service served (see Vendor auto-fetch § Deferred fetch). 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`). |
12801280
| `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. |
1281-
| `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. |
1281+
| `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. |
12821282
| `vendor_vlt_transitive_unsupported` | `failed` | vendor (vlt): the target has an inbound edge from another package in `vlt-lock.json` (the detail names it); vendored mode rewires only direct dependencies of the root or a workspace member, because vlt silently reverts transitive lock surgery. Remedy: `--mode hosted`. Refused before any download or write, dry runs included (`would_refuse`). |
12831283
| `vendor_vlt_lock_out_of_sync` | `failed` | vendor (vlt): an importer's `package.json` is missing, unparseable, or declares a spec for the dependency that differs from the lock's importer edge. Remedy: `vlt install` first. Refused before any write. |
12841284
| `vendor_vlt_build_scripts_unsupported` | `failed` | vendor (vlt): the package declares a `preinstall`, `install`, `postinstall` or `prepare` script, or ships a `binding.gyp`. vlt builds a registry copy in the untracked store, but a vendored `file:` dependency in place, so `vlt build` would rewrite the committed artifact (a platform binary over a JS shim, say) and every later vendor, repair and `vex` would treat it as tampered. Remedy: `--mode hosted`. Refused before any write. |
@@ -1297,6 +1297,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
12971297
| `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. |
12981298
| `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. |
12991299
| `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). |
1300+
| `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 eval-able 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 (no `vendor_fetched_missing` precedes it) when the lock both resolves AND verifies the gem and no ledger entry already vendors it; the gem backend raises 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`, an already-vendored gem re-runs green, and `--dry-run` still fetches and previews. See Vendor auto-fetch § Gem, local build only. |
13001301
| `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. |
13011302
| `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. |
13021303
| `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). |

0 commit comments

Comments
 (0)