Repository navigation
fix(manifest): record every resolved Maven dependency edge - #1711
Conversation
Maven facts only had the edges that survive conflict resolution, so an edge onto a dependency Maven kept elsewhere (a direct dependency, or a mediated version) was missing, unlike Gradle and sbt facts. The Maven extension now records each node's children before conflict resolution and points every edge at the node Maven kept. Roots follow Gradle's runtime and test classpaths: the test root is the module's whole resolution, the compile root the nodes Maven kept with a prod scope. A dependency Maven widens to compile under a test parent is therefore prod, matching Maven's runtime classpath. A module whose conflict resolution fails has no resolved graph, so it is reported unscannable and contributes no dependencies.
PR SummaryMedium Risk Overview The Maven extension wraps resolution with an Prod/dev labeling no longer follows reachability from direct prod deps—it uses the effective scope Maven kept on each node (so compile-scoped artifacts under a test parent count as prod). Modules where conflict resolution fails entirely are marked unscannable with no partial nodes or jar fetches. Coverage adds compat smoke cases (dup-io/dup-util, mediated edges, container-lib → provided API, unsolvable conflicts) and expands Reviewed by Cursor Bugbot for commit 25c8a4f. Configure here. |
Oskar Haarklou Veileborg (BarrensZeppelin)
left a comment
There was a problem hiding this comment.
[agent] A simplification review (reuse, simplification, efficiency, altitude), not a bug hunt. The approach is at the right depth: recording edges fixes what the reachability-based prod/dev split was working around, the new root layout matches Gradle's runtime/test classpaths, and the assembler needs no Maven-specific code. The inline comments are mostly about how much machinery EdgeRecorder needs.
Looked at and not flagged:
- The
copiesmap: it only saves memory, but it keeps the pre-walk linear. - The per-module session copy: microseconds.
- Prod records appearing in both roots: this is the intended Gradle-shaped contract. Buffering every record until the run ends predates this PR, though the PR doubles what that buffer holds.
- Follow-up idea, older than this PR: store a sibling module's jar and test-jar under their own ids and let the assembler collapse them, the way it does for Gradle variants. That would remove the jar/test-jar caveats in
visit. - Dead code: none.
NODE_DATA_CONFLICT_WINNER,ArrayDequeandDequeare all still used.
The new unsolvable-conflict smoke test follows the existing one-script-per-failure-fixture pattern.
Martin Torp (mtorp)
left a comment
There was a problem hiding this comment.
Holding for a pending review
Build the resolved edges inside the transformer, so a broken assumption about Maven's graph becomes a collection error that marks only that module unscannable. A module whose graph never passed through the recorder is now reported instead of emitting nothing silently. Walk each artifact once again before touching its node, leave self-edges to the assembler, and exclude libdep from mid's lib dependency so the fixture catches a walk keyed by the shared jar/test-jar id.
Heads up, reviewer
We have been missing
mavenedges in dynamic SBOMs as the APIs for some reason only report dependencies as children of the first parent in the tree/graph rather than all the parents.LLM Description written by Claude Code:claude-opus-5-5
Maven Socket facts now include every dependency edge Maven resolves, as Gradle and sbt facts already do. Before, an edge onto a dependency Maven kept elsewhere was dropped:
jetty-io -> jetty-utilwhenjetty-utilis also a direct dependency, or an edge Maven mediated to another version. Dependency chains therefore missed paths to a package.What changes
providedone is kept, and the provided dependency stays dev.No facts format, assembler or Coana change.
Verification
v1.xextension.aether.conflictResolver.verbose.socket fix --dynamic-sbom-inferenceon a Maven reactor with a vulnerable package reached both directly and transitively gives the same fix as before.🤖 Generated with Claude Code