Skip to content

Vendored Maven 4 reactor with implicit subprojects (model 4.1.0, no <subprojects>) is wired as a single POM, so a warm ~/.m2 builds the unpatched jar while vex attests not_affected #622

Description

[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).

Summary

Maven 4 (model 4.1.0) discovers subprojects on its own. A root pom.xml with <packaging>pom</packaging> and no <subprojects>/<modules> element still builds every child directory that holds a pom.xml, and children can use <parent/>. socket-patch vendor routes a project into the reactor planner only when the root pom declares <modules>/<subprojects> (vendor/jvm/mod.rs:223, maven_reactor::declares_modules). An implicit-subproject reactor therefore falls through to the legacy single-POM backend (vendor/maven_repo.rs). Its multi-module refusal (vendor_maven_multimodule_unsupported, vendor/maven_repo.rs:205) checks the same declared-modules test, so it doesn't fire either.

The result is the weak same-GAV wiring: one <repository> in the root pom pointing at .socket/vendor/maven/<uuid> with the unsuffixed 1.10.0 coordinate, and no module edits, pins or .mvn/maven.config. The run reports applied: 1, vendor --check passes, and vex attests not_affected.

Impact

  • The developer's own tree builds the unpatched jar. It resolved commons-text before vendoring, so ~/.m2 is warm, and the local repository wins over the root <repository> (the documented single-POM shadow). Both modules get Central's commons-text-1.10.0.jar, yet vex exits 0 with not_affected (only a stderr warning that "the installed tree does not match its vendored artifact").
  • A fresh checkout can't build offline. mvn -o fails: "Cannot access socket-patch-vendor-… in offline mode".
  • The same tree with an explicit <subprojects> list gets the suffixed reactor wiring (1.10.0-socket.1d3c1fd2 + .mvn/maven.config tail). It resolves the patched jar warm, cold and offline. So the difference comes down to whether one element that Maven 4 itself treats as optional is present.

Repro (Maven 4.0.0-rc-7, Linux, JDK 21)

mkdir -p imp/a imp/b && cd imp
cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0" root="true">
  <modelVersion>4.1.0</modelVersion>
  <groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version>
  <packaging>pom</packaging>
</project>
EOF
cat > a/pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0">
  <modelVersion>4.1.0</modelVersion>
  <parent/>
  <artifactId>a</artifactId>
  <dependencies><dependency>
    <groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>1.10.0</version>
  </dependency></dependencies>
</project>
EOF
cat > b/pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0">
  <modelVersion>4.1.0</modelVersion>
  <parent/>
  <artifactId>b</artifactId>
  <dependencies><dependency>
    <groupId>com.example</groupId><artifactId>a</artifactId><version>${project.version}</version>
  </dependency></dependencies>
</project>
EOF
mvn package dependency:3.5.0:build-classpath -Dmdep.outputFile=target/cp.txt   # Maven builds root, a, b
# stage an agent-mode commons-text 1.10.0 patch (.socket/manifest.json + blob), then:
socket-patch vendor --json --offline        # applied: 1, exit 0
git diff                                    # only pom.xml: <repository> socket-patch-vendor-<uuid>; a/b untouched, no .mvn/
socket-patch vendor --check --json          # vendor_check_ok, exit 0
socket-patch vex                            # not_affected, exit 0
mvn package dependency:3.5.0:build-classpath -Dmdep.outputFile=target/cp.txt
grep -o '[^:]*commons-text[^:]*' a/target/cp.txt   # …/commons-text/1.10.0/commons-text-1.10.0.jar  (Central bytes)

The capstone-style oracle is a local, uncommitted variant of e2e_vendor_jvm_build::maven_reactor (real vendor --offline through the prebuilt fixture server, then a real mvn package + maven-dependency-plugin:3.5.0:build-classpath, with the jar's META-INF/NOTICE.txt checked for the patch marker).

Variant (4.0.0-rc-7) vendor --check vex warm in-tree build a / b fresh checkout, cache purged, -o fresh, online
implicit subprojects (run 1) applied 1, single-POM wiring ok not_affected 1.10.0 unpatched / unpatched fails (offline file repo) 1.10.0 patched
implicit subprojects (run 2) same ok not_affected unpatched / unpatched fails patched
control: same tree + <subprojects>a,b</subprojects> applied 1, reactor wiring ok not_affected 1.10.0-socket.1d3c1fd2 patched / patched patched / patched —

Expected vs actual

  • Expected, per docs/design/maven-vendoring.md ("Supported reactors have an explicit root pom.xml and <modules> or <subprojects> declarations") and the CLI_CONTRACT.md maven row ("Multi-module aggregator poms refused (vendor_maven_multimodule_unsupported)"): a reactor is either planned as a reactor (suffixed coordinates, immune to the warm cache) or refused. It's never wired as a single POM. Alternatively, treat a 4.1.0 pom-packaged root with no declared subprojects, and with child dirs holding pom.xml, as a reactor (or refuse it with a hint to list <subprojects>).
  • Actual: wired as a single POM with the unsuffixed coordinate. It reports success, --check passes and vex attests, while the developer's warm build and every mvn -o build don't use the patch.

OS × version

OS Maven Result
Linux 4.0.0-rc-7 (newest 4.x) fails (2×)
Linux 3.6.3 – 3.9.16 n/a: Maven 3 has no implicit subproject discovery
macOS / Windows 4.0.0-rc-7 untested (the routing is OS-independent: a string check on the root pom)

Tested on main 045d7ec (latest release v4.0.0). Not bisected: the v5 reactor planner introduced the declares_modules routing.

Suspect code

  • crates/socket-patch-core/src/vendor/jvm/mod.rs:223: detect routes to Shape::MavenReactor only when declares_modules sees <modules>/<subprojects>.
  • crates/socket-patch-core/src/vendor/maven_repo.rs:205: the single-POM backend's multi-module refusal uses the same declared-modules test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions