Skip to content

Fix hosted Maven pom edits outside live markup (#259, #262, #342) - #1327

Merged
Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/v5-maven-pom-scope
Oct 10, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/v5-maven-pom-scope

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #259
Fixes #262
Fixes #342

Summary

scan / get --mode hosted for a Maven pom could edit markup Maven never reads for the project, suffix artifacts the patch server does not serve, or write a second top-level section that Maven refuses. Every one of these exited 0 with redirected: 1 and no warning. After this PR the hosted rewriter reads the pom the way Maven builds the project, and the same way vex reads it.

Root cause

rewrite_maven_pom (patch/redirect/mod.rs) found its edit points with raw text anchors:

  • a bare <dependency>…</dependency> regex for matches,
  • pom.contains("<repositories>") for the repository,
  • a <dependencyManagement>\s*<dependencies> regex for the transitive pin.

None of these skipped comments, <profiles> or <build> plugin dependencies, none recognized self-closed sections, and the matcher never read <classifier>.

Fix

  • formats::maven::PomScope (new, shared). It blanks comments and CDATA (offsets kept) and excludes <build>, <reporting>, <pluginRepositories>, <distributionManagement> and <profiles>. parse_pom (which VEX discovery uses) now runs on it, so the rewriter and VEX read the same scope. ScopedDep carries the <classifier> and the version byte range.
  • Matches (Hosted Maven rewriter edits commented-out, plugin and profile markup, so builds either stay unpatched or break #259 a/c, Hosted Maven rewriter ignores <classifier> and suffixes sources/tests/native classifier dependencies, which breaks the build #262): only live, classifier-less declarations decide between rewriting the literal and adding the depMgmt pin.
    • A classifier variant (sources, tests, a native build) keeps its version. Warning: redirect_maven_classifier_unsupported.
    • A base literal inside <profiles> is not edited. Warning: redirect_maven_profile_dependency_unpatched. If a profile holds the GA's only declaration, the dep is skipped. This keeps the existing maven_profile_scoped_dependency_is_not_redirected contract.
    • A GA that is declared only in a comment or a plugin counts as transitive and gets the top-level pin.
  • Anchors (Hosted Maven rewriter edits commented-out, plugin and profile markup, so builds either stay unpatched or break #259 b/d/e, Maven pom rewrites add a second <repositories> or <dependencyManagement> section when the existing one is self-closed or has a comment before <dependencies>, so Maven refuses the pom #342): insert_maven_repository and insert_maven_dependency_management target only the project's own top-level sections.
    • A self-closed <repositories/>, <dependencyManagement/> or <dependencies/> is expanded in place.
    • A comment between <dependencyManagement> and <dependencies> no longer hides the section.
    • The "repository already present" and URL-refresh checks count only live repositories.
    • A pom that is not readable as one is left untouched. Warning: redirect_maven_pom_unreadable.
  • Performance: the rewriter reads the pom once into a PomIndex (patch/redirect/maven_index.rs) and keeps its offsets in step with its own edits, instead of rescanning the pom for every patched dependency.
    • This fixes the scan performance regression the first push showed: maven/hosted +35%, maven/rescan +25%.
    • Local probe, 1000 patched dependencies in one pom, release build:
      • main: 1.70 s first scan, 0.96 s rescan.
      • First push: 4.68 s / 3.33 s.
      • Now: 25 ms / 9.5 ms.
    • edits_keep_the_index_in_step checks the kept index against a fresh build after every kind of edit.
  • Upstream restore (rollback): it recognizes a pin the rewriter added past a comment. It also counts "the GA's only literal" in the same scope, so a classifier or profile sibling doesn't make an authored pin look like the user's.
  • VEX discovery: a classifier variant neither pins nor shadows the main jar, so what the rewriter reports as redirected, vex attests.
  • Docs: docs/ecosystems.md (hosted Maven caveats) and CLI_CONTRACT.md (new warning codes, scope).

#342, vendored row: this is already fixed on main. v5 vendors every pom root through the reactor planner (vendor/jvm/maven_reactor.rs), which expands self-closed sections. See the existing test single_line_poms_and_self_closing_sections. The retired maven_repo.rs single-pom backend only reverts now.

Not in this PR: the depscan TS twin (registry-rewrite/maven-pom.ts) lives in another repo. The new shared goldens under tests/fixtures/redirect/maven/pom/ are what it needs to match.

Per-issue checklist

Red → green

On origin/main (a8e9397), with the new fixtures copied in:

  • redirect_golden fails on the first new case (classifier-only-transitive-main: pom.xml byte-mismatch).
  • A per-case dump showed all 11 new cases differ from the expected output. Only the depMgmt cases warned, and only with redirect_maven_dep_management_added.

On this branch all of them pass.

Commands run

  • cargo test -p socket-patch-core (full, --no-fail-fast): green after the restore-golden exclusion for the two self-closed cases.
  • cargo test -p socket-patch-cli (default features): green except two e2e_vendor_cargo_build old-toolchain cases. Those depend on locally installed old cargo toolchains and are unrelated to this change.
  • cargo test -p socket-patch-cli --test e2e_redirect_maven_build -- --ignored with real Maven 3.9.16: green.
  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo fmt --all -- --check: clean for the changed files. Local rustfmt flags only the untouched upstream/mod.rs:917.

🤖 Generated with Claude Code

WIP: draft PR placeholder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hosted Maven rewrites found their edit points with raw text anchors,
so a scan could rewrite a commented-out or plugin <dependency>, put
the Socket repository or pin inside an inactive profile or a
comment, suffix sources/tests/native classifier jars that the patch
server does not serve, and add a second <repositories> or
<dependencyManagement> next to a self-closed or commented one. Each
case exited 0 with redirected=1 while the build stayed unpatched or
broke.

The rewriter now reads the pom through formats::maven::PomScope, the
same scope VEX attests from: comments, CDATA, <build>, <reporting>,
<pluginRepositories>, <distributionManagement> and <profiles> are
never matched or edited, self-closed sections are expanded in place,
and only classifier-less declarations decide literal-vs-pin.
Classifier variants and profile base literals are left alone with
new warnings (redirect_maven_classifier_unsupported,
redirect_maven_profile_dependency_unpatched); an unreadable pom is
skipped with redirect_maven_pom_unreadable. Upstream restore and VEX
discovery follow the same scope so these rewrites round-trip and
attest.

Fixes #259, #262, #342.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A GA whose only declaration sits in an inactive profile got a
top-level dependencyManagement pin that is inert exactly when the
profile pulls it in. Skip such a dep with
redirect_maven_profile_dependency_unpatched instead of reporting it
redirected, keeping the existing in-process contract. Self-closed
sections restore to an equivalent empty element, so those goldens
are excluded from the byte-exact restore round trip and covered by
a unit test instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 9, 2026 18:00
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1bbdd63. Configure here.

Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs Outdated
Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs
Comment thread crates/socket-patch-core/src/patch/redirect/upstream/maven.rs
Comment thread crates/socket-patch-core/src/vex/discover/maven.rs
Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs Outdated
Re-reading the pom through PomScope for every patched dependency made
hosted Maven scans slower (scan-performance flagged maven/hosted
+35%, maven/rescan +25%). The rewriter now reads the pom once into a
PomIndex and keeps its offsets in step with its own edits (version
rewrites, repository and dependencyManagement insertions, superseded
repository removal, URL refresh), so a pom with many patched
dependencies is scanned once instead of once per dependency. A
1000-dependency pom rewrites in ~25 ms, down from ~1.7 s before this
PR series. A unit test checks the kept index against a fresh build
after every kind of edit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	crates/socket-patch-cli/CLI_CONTRACT.md
#	docs/ecosystems.md
- maven_index: on a pure insertion, an element end (and a self-closed
  element's inner offsets) exactly at the insertion point no longer
  shifts, so a <dependencyManagement> inserted before </project> is not
  overwritten when a self-closed <repositories/> ending there is expanded.
- Profiles: any versioned declaration inside <profiles> (a ${property}
  or another literal, not just the base literal) is reported unpatched,
  and the dep is skipped when profiles hold its only versioned
  declarations.
- Classifiers: a variant other than sources/javadoc at the patched
  release skips the dep instead of pinning the main jar beside it.
- Restore: entry_is_users reads the same live scope as the versioned
  count, so a commented, plugin, profile or classifier versionless
  sibling no longer makes the authored pin look user-written.
- Rollback: a commented-out socket-patch repository twin no longer
  counts as a second repository, nor as a leftover name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve the CLI_CONTRACT hosted-redirect paragraph: keep this branch's
Maven classifier/profile/unreadable-pom notes and main's NuGet
member-lock text (#353/#514).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A hosted Maven pin is a classifier-less coordinate: it does not retarget a
<classifier> dependency of the same GA, and the grant serves only the main
jar, so a tests or native classifier copy resolves from the next repository
(the public release). VEX discovery grouped deps with classifiers dropped, so
such a pom was still attested as patched.

The pin now stays a ref (list/rollback/remove find it) but is marked
unattested (vex_maven_classifier_unpatched) when a live, non-profile
classifier copy other than sources/javadoc resolves any version but the pin:
its own literal, a classifier-keyed dependencyManagement entry, or none in
the root pom. sources/javadoc copies keep attesting; they never execute.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 10, 2026
Merged via the queue into main with commit 009dca3 Oct 10, 2026
53 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/v5-maven-pom-scope branch October 10, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

2 participants