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

### Fixed

- **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
file contained the patch-server origin — e.g. an already hosted
`package-lock.json` — or leftover `patch.socket.dev/gopatch/…` go.sum
lines, even when the go.mod rewrite was refused. A Go module now counts
only when its go.mod `replace` and both go.sum lines are in place. A
module that go.mod does not require and go.sum does not list at the
patched version (another project's module found in the shared module
cache) is refused with `redirect_golang_not_in_module_graph` instead of
getting an inert `replace`.
- **Switching a vendored Go module to hosted mode cleans up the vendored
copy.** `scan`/`get --mode hosted` rewrote the vendored `replace` but
left `.socket/vendor/golang/<uuid>/` and its vendor-ledger entry
behind, so the next `vendor` run switched the module back. The hosted
run now reverts the vendored state first, as it already did for cargo
and npm.
- **Go `replace` directives the CLI did not write are left alone.** A
replace onto another checkout's vendored copy
(`../other/.socket/vendor/golang/…`) or onto a module under
`patch.socket.dev/gopatch/` other than `<patch uuid>` was treated as
socket-owned and could be rewritten or removed. Only
`./.socket/vendor/golang/…`, `./.socket/go-patches/…` and exactly
`patch.socket.dev/gopatch/<uuid>` are now owned.
- **Go replace edits keep go.mod valid and readable.** A go.mod carrying
two socket-owned `replace` lines for one module (for example after a
merge) is collapsed to one instead of leaving a duplicate go rejects;
refreshing a directive keeps its trailing `// comment`; and quoted
module paths (`"github.com/x/y"`) are recognized, so a quoted user
replace is no longer duplicated and a quoted `require` still gets the
version check.
- **`+incompatible` Go modules resolve in vendor and apply.** Purls that
spell the version `v2.0.0%2Bincompatible` are now percent-decoded, so
the module is found in the module cache and the `replace` and copy
directory carry the real `+incompatible` version.
- **`+incompatible` Go copies survive `apply` reconcile.** A manifest key
spelled `%2Bincompatible` no longer makes the freshly applied
`.socket/go-patches/…@v2.0.0+incompatible` copy look orphaned, so it and
its `replace` are kept.
- **Hosted Go rollback restores go.sum byte for byte.** The upstream
module's go.sum lines that the redirect pruned went back at the end of
the file; they now return to the position `go mod tidy` sorts them to
(semver order within a module). CRLF go.mod/go.sum files also unwind
cleanly, with no leftover socket lines or blank lines.
- **Vendoring a hosted Go module unwinds the hosted redirect first.**
`vendor` / `get --mode vendored` over a hosted-redirected Go module left
its redirect-ledger record and the socket module's go.sum lines behind
(with the upstream lines still pruned). Go now takes the same per-purl
takeover revert as cargo and npm (`vendor_takeover_reverted_redirect`),
and scoped `rollback <purl>` / `remove <purl>` of one hosted Go module
works without an unscoped rollback.
- **Vendored Go fetches honor `GOPROXY=off`, `direct` and `GOPRIVATE`.**
With the module missing from the module cache, the pristine fetch fell
back to `https://proxy.golang.org` even when go itself would ask no
proxy, sending private module paths off the machine. It is now refused
(`vendor_fetch_unverifiable`, then the usual `package_not_installed`
skip) unless `SOCKET_GOPROXY` names a proxy.
- **A vendoring-service outage no longer re-vendors packages.** An npm
re-run (every lock flavor, `bun.lockb` included) re-acquired its tarball
from whichever source answered — the service's prebuilt, or a local pack
Expand Down
12 changes: 6 additions & 6 deletions crates/socket-patch-cli/CLI_CONTRACT.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/socket-patch-cli/src/commands/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ async fn try_local_go_apply(
// `pkg_path` is the pristine, case-encoded module-cache dir; `module`/
// `version` are the decoded PURL components keying the copy + `replace`.
let (module, version) = parse_golang_purl(purl)?;
let (module, version) = (&*module, &*version);
Some(
apply_go_redirect(
purl,
Expand Down
28 changes: 17 additions & 11 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1078,10 +1078,9 @@ pub(crate) async fn run_redirect_selected(
/// records and the confirmation probe key on; everything the probe
/// needs AFTER the rewrite to decide whether the dep was actually
/// redirected (artifact URL, registry index URL, fail-closed maven's
/// suffixed version, golang's content-addressed module path) already
/// rides the override. The single vector is filtered in place by every
/// withhold/refusal step, and the rewriters' `overrides` slice is
/// materialized from it once, after the last filter.
/// suffixed version) already rides the override. The single vector is
/// filtered in place by every withhold/refusal step, and the rewriters'
/// `overrides` slice is materialized from it once, after the last filter.
struct Candidate {
purl: String,
dep: DepOverride,
Expand Down Expand Up @@ -1307,7 +1306,10 @@ pub(crate) async fn run_redirect_selected(
// VENDORED — for cargo a committed `[patch.crates-io]` path entry, a
// detached Cargo.lock entry, a committed copy, and a vendored ledger
// entry; for the npm family a `file:./.socket/vendor/…` lock resolution
// (plus a berry `resolutions` pin) and its committed tarball. The hosted
// (plus a berry `resolutions` pin) and its committed tarball; for golang
// the vendor-owned go.mod `replace`, its committed module copy, and its
// ledger entry (left behind, a later `vendor` run takes the module back
// and the modes flip-flop). The hosted
// rewriters know nothing about that wiring: cargo then refuses every
// `--locked` build over the now-unused `[patch]` entry while this run
// reports success, and the npm rewriters either hijack the vendored
Expand All @@ -1323,7 +1325,9 @@ pub(crate) async fn run_redirect_selected(
// whose vendored state cannot be cleanly reverted (revert failure, or
// vendored wiring with a missing/corrupt ledger) is REFUSED — skipped
// with an actionable error — never half-migrated.
let takeover_capable = |p: &str| p.starts_with("pkg:cargo/") || p.starts_with("pkg:npm/");
let takeover_capable = |p: &str| {
p.starts_with("pkg:cargo/") || p.starts_with("pkg:npm/") || p.starts_with("pkg:golang/")
};
let mut takeover_pre_warnings: Vec<serde_json::Value> = Vec::new();
// Dry-run takeover previews: `(purl, uuid)` pairs whose vendored state
// the wet run would revert and then redirect. Withheld from the
Expand Down Expand Up @@ -2326,17 +2330,20 @@ pub(crate) async fn run_redirect_selected(
if purl.starts_with("pkg:cargo/") {
return rewrite.confirmed_cargo_uuids.contains(uuid);
}
// Golang likewise: the goproxy `indexUrl` is the bare
// patch-server origin (present in any other hosted lock), and
// the socket module's go.sum lines outlive a removed replace.
if purl.starts_with("pkg:golang/") {
return rewrite.confirmed_golang_uuids.contains(uuid);
}
// The override's own targets: artifact URL; per-dependency
// registry index URL; fail-closed maven's globally-unique
// `-socket.<hex8>` suffixed version (never the `.pom` URL);
// golang's content-addressed `patch.socket.dev/gopatch/<uuid>`
// module path (go.mod + go.sum never carry a URL).
// `-socket.<hex8>` suffixed version (never the `.pom` URL).
let artifact_url = c.dep.artifact_url.as_str();
let registry = c.dep.registry_override.as_ref();
let index_url = registry.map(|o| o.index_url.as_str());
let suffixed_version =
registry.and_then(|o| o.identifiers.maven_suffixed_version.as_deref());
let go_module_path = registry.and_then(|o| o.identifiers.go_module_path.as_deref());
let encoded = socket_patch_core::utils::uri::encode_uri_component(artifact_url);
final_texts.iter().any(|text| {
// The rewriters' own predicate — raw, or the `\/`-escaped
Expand All @@ -2352,7 +2359,6 @@ pub(crate) async fn run_redirect_selected(
|| text.contains(encoded.as_str())
|| index_url.is_some_and(|iu| text.contains(iu))
|| suffixed_version.is_some_and(|sv| text.contains(sv))
|| go_module_path.is_some_and(|gm| text.contains(gm))
})
})
.map(|c| (c.purl.clone(), c.dep.patch_uuid.clone()))
Expand Down
3 changes: 3 additions & 0 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,9 @@ pub(crate) async fn vendor_records(
let reverted_what = if candidate.starts_with("pkg:cargo/") {
"the hosted edits (Cargo.toml registry pin, Cargo.lock \
source/checksum, registries block)"
} else if candidate.starts_with("pkg:golang/") {
"the hosted edits (go.mod replace, the socket module's go.sum \
lines, the pruned upstream go.sum lines)"
} else {
"the hosted lockfile edits back to their pre-redirect \
registry values"
Expand Down
Loading
Loading