The JVM backend in crates/socket-patch-core/src/vendor/jvm/ is enabled by
build shape. No experimental environment variable is required. Maven roots
use suffixed coordinates; Gradle keeps the original coordinates. Both commit a
local repository so another checkout can build without socket-patch or the
Socket service.
Every Maven root goes through the reactor planner. A root pom.xml with no
<modules> is planned as a reactor of one: the same pin, .mvn/maven.config,
fallback repository and .socket/vendor/maven2 tree a multi-module reactor
gets. The pre-v5 single-POM backend (a same-GAV <repository> serving
.socket/vendor/maven/<uuid>/) is retired. Its ledger entries can still be
reverted byte for byte, but vendoring a root whose ledger holds one is
refused (see legacy_maven_root below).
Hosted Gradle wiring is a separate backend; see
ecosystem support.
sbt build roots and scala-cli directory builds use the same backend and ledger
ecosystem (jvm): sbt through a generated socket-patch-vendor.sbt over the
reactor-style suffixed .socket/vendor/maven2 tree, scala-cli through owned
files over a same-GAV .socket/vendor/coursier tree. Mill is not wired. See
sbt, Mill and scala-cli support.
socket-patch vendor
socket-patch vendor --check
socket-patch vendor --check --local-repo /path/to/maven/repository
socket-patch vendor --maven-config=none
socket-patch vendor --revertscan --mode vendored, get --mode vendored, vendor, repair, remove and
rollback share the v5 vendored backend. Discovery reads the Maven local
repository and the Gradle cache (<Gradle user home>/caches/modules-2/files-2.1,
plus the read-only cache); Gradle-only builds use the Maven local repository
only when the build can consume it. Upstream POMs, Gradle module metadata and
classifier jars are taken from any local cache (a Gradle copy must hash to its
hash directory, a Maven copy with a .sha1 sidecar must match it); an online
service-backed request downloads what no cache holds and checks every file
against the registry's checksums.
vendor --check is read-only and offline even without --offline. It checks
artifact hashes, recorded tree files, wiring, Gradle's index and script, and
unindexed files. --local-repo also detects a suffixed Maven jar or POM whose
bytes conflict with the committed copy. Failures produce per-package
vendor_check_failed events and exit 1. A patch without a ledger entry fails
with vendor_ledger_missing. Offline upstream metadata is identified by the
vendor_jvm_upstream_unverified warning; run vendor online to authenticate it
against registry checksums.
Supported reactors have an explicit root pom.xml and <modules> or
<subprojects> declarations, including declarations in profiles. A root
pom.xml without them is a reactor of one (#973). Run vendoring from the
reactor root. A discovered ancestor reactor produces not_build_root
instead of allowing a partial submodule edit.
The patched version is <base>-socket.<first-eight-uuid-hex>, matching the patch
service. A warm cache of the original version cannot shadow that coordinate.
The backend writes:
.socket/vendor/maven2/<group-path>/<artifact>/<suffixed-version>/: patched jar, suffixed POM, SHA-1 sidecars, and an ownership marker..socket/vendor/maven2/.gitattributes: disables line-ending conversion of committed repository bytes.- A shared file repository and dependency-management pins in each local root.
- Rewrites of conflicting base-version literals and resolved local properties, including profiles and local parent POMs outside the module list.
- Two lines in
.mvn/maven.config: offline file protocol access and the local repository tail at${session.rootDirectory}/.socket/vendor/maven2.
Parent chains must remain within the checkout. Local parent coordinates are checked before using their properties. Range selectors, unresolved properties, classifier declarations, conflicting explicit versions and publishing POMs produce specific warnings; the backend does not silently claim those unsupported declarations are patched. An enforcer repository ban omits the fallback repository and warns that the tail requires Maven 3.9.2 or newer.
A local root's pin beats every management from outside the checkout, so it is
weighed against that management first: the parents a local root resolves from
a repository (<relativePath/>, a corporate or Spring Boot parent) and every
<scope>import</scope> BOM of the reactor and of those parents, read from the
local caches or the registry like other upstream metadata. Properties follow
Maven's order (the reactor's own values override an external parent's). When
that management, or a literal an external parent declares, sets the artifact to
another version than the patch's base, the root is not pinned
(conflicting_managed_version); when a needed POM is in no cache and cannot be
fetched, the root is not pinned either (management_unresolved, resolve the
project once and vendor again). A re-run after a BOM bump drops the pin the
same way. vendor --check reads that metadata from the local repository only
and accepts either decision when some of it is missing.
Maven 3.9.2+ can read the repository tail without copying jars into ~/.m2 and
without routing through mirrors. Older Maven versions use the fallback file
repository, which copies the suffixed artifact into the local cache. A
mirrorOf=* configuration must exclude socket-patch-vendor on that fallback
path.
Maven 3.9.2–3.9.8 has a known interpolation limitation when -f is invoked from
outside the root. Use Maven from the root or select --maven-config=none on the
first vendoring run. That option uses the fallback file repository only, is
recorded in the ledger, and remains in effect on later runs and repair. Revert
existing auto-config wiring before changing to none. Combining none with a
repository ban is refused. Version detection reads wrapper properties only;
it never executes Maven. Without a wrapper (most single-module projects) the
version is unknown, so both maven_f_outside_root and maven_mirror_of_all
are reported as vendor_jvm_degraded warnings; a Maven Wrapper at 3.9.9 or
later clears both.
Gradle 6.8+ is supported, including Groovy and Kotlin settings, multi-project
builds, buildSrc, and literal includeBuild paths inside the checkout. A
wrapper proving a version below 6.8 is refused before writes; the generated
script also checks the running Gradle version.
Run vendoring from the Gradle root. A directory an ancestor settings file
includes (literally, through a relocated projectDir, or possibly, when its
includes cannot be read literally), and a project without a settings file of
its own below an ancestor settings file, produce not_build_root; nothing is
written, and repair refuses there too. A root holding both a pom.xml and a
Gradle build vendors both, in one ledger entry: the Maven half as a one-POM
reactor (suffixed tree, pin, maven.config), the Gradle half as below. A
refusal of either half writes nothing, and --check, vex, revert and repair
always handle both.
A root whose ledger still holds a pre-v5 single-POM (maven_pom_repository)
entry is refused whole, alone or beside a Gradle build:
vendor_jvm_shape_unsupported with reason legacy_maven_root, and nothing is
written. That entry's revert restores a whole-file pom.xml snapshot, so
planner edits on the same pom would make it unsafe, and nothing migrates it.
Run socket-patch vendor --revert, then vendor again.
The original GAV is retained under
.socket/vendor/gradle/<group-path>/<artifact>/<version>/, with the jar, the
upstream POM and module, and every classifier jar a build script or catalog
declares (plus the sources jar when a cache or the registry has it, so IDE
source attachment keeps working). A declared classifier that cannot be sourced
refuses with classifier_unavailable: exclusiveContent claims every file of
the GAV, so a missing one would stop resolving. A classifier jar that carries
an unpatched copy of a patched member is degraded (classifier_unpatched_copy).
The tree marker lists the patched members beside classifier jars, so vex
re-runs that check from the committed tree and withholds attestation.
Each vendored GA also gets .socket/vendor/gradle/<group-path>/<artifact>/maven-metadata.xml,
derived from the index: every vendored version in Gradle's version order, the
highest as latest and release, and no lastUpdated. Range, prefix and rich
selectors list versions from it, so vendoring never changes the version Gradle
selects (range_declared notes such a declaration). A declaration set in which
no selector admits the vendored version is refused with
gradle_range_excludes_vendored. The file is recomputed when a version is
reverted and removed with the GA's last one.
Lockfiles, version catalogs and project build scripts stay unchanged. Settings
files receive an apply line for .socket/gradle/socket-patch.settings.gradle.
Settings plugin and buildscript classpaths receive an in-block exclusive
repository entry because those classpaths resolve before the apply line runs.
The script, the index, the derived metadata and the .gitattributes files are
owned text and are compared line-ending blind, so a core.autocrlf checkout
passes --check and reverts clean. .socket/gradle/.gitattributes (* -text,
kept while a hosted script remains) and .socket/vendor/.gitattributes
(gradle-index.tsv -text, merged into an existing file) keep them out of EOL
conversion on new checkouts. A settings file vendor created is deleted on
revert once only whitespace is left of it.
The static script:
- Reads
.socket/vendor/gradle-index.tsv, with sorted GAV/path/SHA-256/UUID rows. - Hashes files as streams and requires jar and POM rows for every GAV.
- Rejects unsafe coordinates, selectors, mismatched paths and unindexed files.
- Adds an
exclusiveContentfile repository for the patched coordinates to the relevant repository handlers while respecting repository mode. - Fails configuration if a vendored artifact no longer matches its index.
When gradle/verification-metadata.xml already exists, vendoring updates the
patched jar's checksum and, when metadata verification is enabled, adds missing
POM and module entries for the artifact, its parents and imported BOMs.
An existing entry that holds only a <pgp> signature gets a sha256 beside
it: the vendored repository has no signatures, so Gradle falls back to
checksums. With metadata verification on, --check and the parent-chain
check require a checksum, so a tree vendored before this rule fails --check.
Metadata traversal is bounded and covers parent defaults and child property overrides. The backend
records supplementary entries as shared fragments so either patch can be
reverted first. Existing verification policy and unrelated checksums are kept.
A verification file is never created automatically.
The checks read the whole statically known script graph: settings, every
project's build script, buildSrc and included builds with their convention
plugins and plugin sources, apply from targets and version catalogs.
Android and Kotlin Multiplatform plugins anywhere, available-at module
redirects, a user exclusiveContent rule claiming the patched module
(gradle_exclusive_content_conflict, naming the file; group, subgroup, regex,
module and version rules) and paths leaving the checkout are refused. Build
logic the graph cannot follow (a computed apply from, a script that is not
UTF-8) is degraded as gradle_unscanned_build_logic, and nonliteral included
builds as unwired_build_logic. A settings file that is not UTF-8 is refused;
one with a byte-order mark keeps it. The backend never executes user build
code to discover settings or metadata.
vex attests a Gradle entry only while its wiring is live: the apply line and
index rows are present, the script is intact, and a re-plan over the committed
tree is refused nowhere and degraded nowhere.
Service jars pass transfer-integrity and patched-member checks. The server constructs jars and strips invalidated signatures; the CLI never rebuilds jar archives. Online vendoring authenticates upstream POM and Gradle module metadata against registry sidecars. Metadata fetches use a separate HTTP client and do not send Socket API credentials. SOCKET_MAVEN_REGISTRY supports a private mirror.
Maven v5 does not enable Resolver trusted-checksum processors at build time.
Those processors crash some release reactors and system-scope dependencies,
and do not reliably enforce pins on all supported Resolver versions.
vendor --check supplies the offline integrity audit. Gradle always performs
its index check at configuration time; this is separate from Gradle's own
optional dependency-verification policy.
New entries use ecosystem jvm with Maven PURLs. Old binaries refuse their
revert rather than interpreting them as legacy whole-POM edits. Existing
prototype entries identified by their JVM wiring kinds remain readable.
The planners return complete file writes and fragment records. The v5 group
commit captures build-file edits, the Gradle index, the owned settings script,
repository .gitattributes, and the vendor ledger. Artifact bytes are made
durable before those commit points. All vendor entry points use the same
transaction and recovery mechanism. Ordinary vendoring retains v5's per-patch
success/failure contract; hosted ejection retains its all-or-nothing contract.
Revert restores per-patch fragments, keeps shared wiring while other patches
still consume it, and preserves user edits that no longer match recorded
fragments. --preserve-state restores wiring while retaining artifacts and the
ledger. Patch updates remove superseded Maven trees after the new wiring is
committed. Gradle updates keep the same artifact paths. Unrecognized or forged
paths cannot direct writes outside the backend's allowed files.
repair redownloads the exact recorded jar and checks regenerated repository metadata against the ledger, preserving project wiring. A missing classifier jar, POM or derived maven-metadata.xml also triggers it: classifier jars are downloaded and checked against registry checksums, a mixed root's two trees come back from one download, and the derived metadata and the owned .gitattributes are rewritten when missing (for an otherwise healthy entry too, without a download). Tree directories reached through a link out of the checkout are refused (vendor_path_unsafe). Offline repair cannot restore missing or corrupt artifacts. The
ledger is required for exact reversal; restore a deleted ledger from version
control. In-place ledger reconstruction remains outside v5's repair contract.
The implementation is covered by planner, disk-safety, lifecycle and command
integration tests. Real-tool capstones exercise a Maven reactor from fresh
checkouts, root and module invocations, Gradle strict locking and repository
mode, existing verification metadata, offline builds, tamper detection and
byte-exact revert; e2e_vendor_gradle_build covers each Gradle rule above
against a fake Central, asserting on the jar Gradle actually consumes. The PR
tier pins Maven 3.6.3, 3.8.9, 3.9.2, 3.9.16 and 4.0.0-rc-6 (macOS and Windows on
3.9.16), and Gradle 6.9.4, 7.6.6, 8.14.3 and 9.8.0 on Linux, plus a Windows
8.14.3 row; gradle-compatibility.yml runs every Gradle version on Linux,
macOS and Windows nightly.
Pre-v5 single-POM entries are not migrated: revert them and vendor again. Maven 4 implicit subproject discovery, build-time Maven strict pins, creating Gradle verification policy, and online dependency-graph resolution checks remain separate work. They are not enabled by this release.