This is an automated email from the ASF dual-hosted git repository.

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kit.git

commit 17f91c1e9412f622c0e6036757657d395e0fa090
Author: Jinal Sarvaiya <[email protected]>
AuthorDate: Wed Nov 11 20:04:08 2026 +0000

    review feedback
---
 .../camel/kit/CamelKitCommandParityTest.java       | 27 +++++++-
 .../camel/kit/service/BaselineTestResults.java     |  6 +-
 .../org/apache/camel/kit/service/InitService.java  |  5 +-
 .../main/resources/skills/camel-execute/SKILL.md   | 14 ++--
 .../main/resources/skills/camel-validate/SKILL.md  | 20 +++---
 .../camel/kit/service/BaselineTestResultsTest.java |  6 +-
 .../apache/camel/kit/graph/ProjectDiscovery.java   | 73 +++++++++++++------
 .../apache/camel/kit/graph/parser/PomParser.java   | 18 +++--
 .../camel/kit/graph/ProjectDiscoveryTest.java      | 81 ++++++++++++++++++++--
 .../camel/kit/graph/parser/PomParserTest.java      |  7 +-
 10 files changed, 200 insertions(+), 57 deletions(-)

diff --git 
a/kit/camel-jbang-plugin-kit/src/test/java/org/apache/camel/kit/CamelKitCommandParityTest.java
 
b/kit/camel-jbang-plugin-kit/src/test/java/org/apache/camel/kit/CamelKitCommandParityTest.java
index db707b90..0ee04d92 100644
--- 
a/kit/camel-jbang-plugin-kit/src/test/java/org/apache/camel/kit/CamelKitCommandParityTest.java
+++ 
b/kit/camel-jbang-plugin-kit/src/test/java/org/apache/camel/kit/CamelKitCommandParityTest.java
@@ -121,7 +121,18 @@ class CamelKitCommandParityTest {
 
         // --record-baseline: the project's own wrapper is run and the result 
is recorded
         for (String name : new String[]{"standalone-baseline", 
"plugin-baseline"}) {
-            Path dir = existingProject(name, "#!/bin/sh\ntouch 
mvnw-was-run\nexit 0\n");
+            // The wrapper must produce fresh Surefire reports for the 
baseline to be recorded as "passed"
+            Path dir = existingProject(name, """
+                    #!/bin/sh
+                    touch mvnw-was-run
+                    mkdir -p target/surefire-reports
+                    cat > 
target/surefire-reports/TEST-com.example.RouteTest.xml <<'XML'
+                    <testsuite name="com.example.RouteTest" tests="1">
+                      <testcase classname="com.example.RouteTest" 
name="testRoute"/>
+                    </testsuite>
+                    XML
+                    exit 0
+                    """);
             int exit = name.startsWith("plugin")
                     ? plugin.execute("kit", "init", dir.toString(), "--ai", 
"bob2", "--silent", "--no-fetch", "--force",
                             "--record-baseline")
@@ -132,6 +143,20 @@ class CamelKitCommandParityTest {
             assertEquals("passed", 
loadConfig(dir).getProperty("project.baselineTestStatus"), name);
         }
 
+        // --record-baseline with exit 0 but no reports: recorded as not_run
+        for (String name : new String[]{"standalone-no-reports", 
"plugin-no-reports"}) {
+            Path dir = existingProject(name, "#!/bin/sh\ntouch 
mvnw-was-run\nexit 0\n");
+            int exit = name.startsWith("plugin")
+                    ? plugin.execute("kit", "init", dir.toString(), "--ai", 
"bob2", "--silent", "--no-fetch", "--force",
+                            "--record-baseline")
+                    : standalone.execute("init", dir.toString(), "--ai", 
"bob2", "--silent", "--no-fetch", "--force",
+                            "--record-baseline");
+            assertEquals(0, exit, name);
+            assertTrue(Files.exists(dir.resolve("mvnw-was-run")), name + ": 
--record-baseline must run the wrapper");
+            assertEquals("not_run", 
loadConfig(dir).getProperty("project.baselineTestStatus"),
+                    name + ": exit 0 without fresh reports must record 
not_run");
+        }
+
         // without --replace-wrapper the project's wrapper is kept; with it, 
it is replaced
         for (String name : new String[]{"standalone-keep", "plugin-keep"}) {
             Path dir = existingProject(name, "#!/bin/sh\n# ORIGINAL\n");
diff --git 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/BaselineTestResults.java
 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/BaselineTestResults.java
index 79f7e13b..ee47171d 100644
--- 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/BaselineTestResults.java
+++ 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/BaselineTestResults.java
@@ -158,9 +158,12 @@ final class BaselineTestResults {
      * @param reason   why, for a human or the validation report
      * @param revision the source revision the results were recorded against 
(the Git HEAD, or {@code unknown})
      * @param dirty    whether the working tree had uncommitted changes when 
the tests ran
+     * @param complete whether all test reports were successfully parsed; 
false if any were malformed or unreadable
      * @param tests    per-test outcomes; empty when none were recorded
      */
-    static String toJson(String status, String reason, String revision, 
boolean dirty, Map<String, Outcome> tests)
+    static String toJson(
+            String status, String reason, String revision, boolean dirty, 
boolean complete,
+            Map<String, Outcome> tests)
             throws IOException {
         ObjectNode root = MAPPER.createObjectNode();
         root.put("status", status);
@@ -168,6 +171,7 @@ final class BaselineTestResults {
         root.put("timestamp", Instant.now().toString());
         root.put("revision", revision);
         root.put("dirty", dirty);
+        root.put("complete", complete);
         root.put("testCount", tests.size());
         ObjectNode testNode = root.putObject("tests");
         tests.forEach((id, outcome) -> {
diff --git 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/InitService.java
 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/InitService.java
index c9992d60..65134512 100644
--- 
a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/InitService.java
+++ 
b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/service/InitService.java
@@ -930,7 +930,7 @@ public class InitService {
             }
             writeBaselineFile(projectRoot, baselineResultsFile,
                     BaselineTestResults.toJson(status, reason, 
sourceRevision(projectRoot),
-                            sourceTreeDirty(projectRoot), tests));
+                            sourceTreeDirty(projectRoot), 
collectionResult.complete(), tests));
             config.setProperty("project.baselineTestStatus", status);
         } catch (Exception e) {
             writeBaselineStatus(baselineResultsFile, "not_run", "Could not 
execute tests: " + e.getMessage(),
@@ -998,9 +998,10 @@ public class InitService {
      */
     private void writeBaselineStatus(Path file, String status, String reason, 
Path projectRoot) {
         try {
+            // Error statuses (not_run, timeout) have no collection, so 
complete=true (no parse failures)
             writeBaselineFile(projectRoot, file,
                     BaselineTestResults.toJson(status, reason, 
sourceRevision(projectRoot),
-                            sourceTreeDirty(projectRoot), java.util.Map.of()));
+                            sourceTreeDirty(projectRoot), true, 
java.util.Map.of()));
         } catch (Exception e) {
             // Best-effort - ignore write failures
         }
diff --git 
a/kit/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md 
b/kit/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md
index b4f16154..a81cfece 100644
--- a/kit/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md
+++ b/kit/camel-kit-core/src/main/resources/skills/camel-execute/SKILL.md
@@ -261,16 +261,18 @@ Read `.camel-kit/config.properties`:
 
 6. **Capture the test baseline BEFORE the first edit (existing projects 
only):**
    - Read `.camel-kit/baseline-test-results.json`. If `status` is `passed` or 
`failed`, `tests` is not empty, `dirty`
-     is `false`, and `revision` equals the current `git rev-parse HEAD`, the 
baseline already exists. Do not touch it.
+     is `false`, `complete` is `true`, and `revision` equals the current `git 
rev-parse HEAD`, the baseline already
+     exists. Do not touch it.
    - Otherwise nothing has been edited yet, so this is the last moment a 
baseline can exist. Note the current timestamp
      (to reject stale reports), run the project's tests with its own wrapper 
(`./mvnw test -B`), then write
      `.camel-kit/baseline-test-results.json` in the schema that 
`camel-validate` Step 1 documents: `status`, `reason`,
      `timestamp`, `revision` (`git rev-parse HEAD`), `dirty` (`true` if `git 
status --porcelain --untracked-files=no`
-     is not empty), and every test under `tests` keyed `class#method` with 
`passed`, `failed`, `error` or `skipped`,
-     read from `target/surefire-reports/TEST-*.xml` and 
`target/failsafe-reports/TEST-*.xml` **modified after the
-     noted timestamp**. Reports older than the test invocation are stale and 
must be ignored. If the tests exit 0 but
-     produce no fresh reports, set `status` to `not_run` with reason "Tests 
did not run (no reports produced)" rather
-     than claiming they passed.
+     is not empty), `complete` (`true` if all reports parsed successfully, 
`false` if any were malformed), and every
+     test under `tests` keyed `[module:]class#method` (module prefix only for 
non-root modules) with `passed`, `failed`,
+     `error` or `skipped`, read from `target/surefire-reports/TEST-*.xml` and 
`target/failsafe-reports/TEST-*.xml`
+     **modified after the noted timestamp**. Reports older than the test 
invocation are stale and must be ignored. If
+     the tests exit 0 but produce no fresh reports, set `status` to `not_run` 
with reason "Tests did not run (no reports
+     produced)" rather than claiming they passed. If any report cannot be 
parsed, set `complete` to `false`.
    - If the tests cannot be run (no wrapper, build broken, timeout), write the 
file with `status` `not_run` or
      `timeout` and the reason, and tell the user that baseline preservation 
will be reported as UNVERIFIED. Do not
      proceed as if a baseline existed.
diff --git 
a/kit/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md 
b/kit/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md
index 03f16e76..b6b99013 100644
--- a/kit/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md
+++ b/kit/camel-kit-core/src/main/resources/skills/camel-validate/SKILL.md
@@ -101,22 +101,24 @@ The baseline is `.camel-kit/baseline-test-results.json`. 
It is written before an
   "timestamp": "2026-01-01T00:00:00Z",
   "revision": "<git HEAD the tests ran against, or unknown>",
   "dirty": false,
+  "complete": true,
   "testCount": 2,
   "tests": {
     "com.example.ExistingTest#testA": {"status": "passed"},
-    "com.example.ExistingTest#testB": {"status": "failed", "reason": "..."}
+    "module-a:com.example.SharedTest#sameTest": {"status": "failed", "reason": 
"..."}
   },
-  "nonPassing": ["com.example.ExistingTest#testB"]
+  "nonPassing": ["module-a:com.example.SharedTest#sameTest"]
 }
 ```
 
-`tests` maps `class#method` to `passed`, `failed`, `error` or `skipped`. Read 
the file with the file-reading tool; do not
-derive anything from counts.
+`tests` maps `[module:]class#method` to `passed`, `failed`, `error` or 
`skipped`. Module prefix is only present for
+non-root modules. Read the file with the file-reading tool; do not derive 
anything from counts.
 
 The baseline is usable only when ALL of these hold:
 
 - `status` is `passed` or `failed` and `tests` is not empty. (`not_run`, 
`timeout`, a missing file, or an empty `tests`
   means there is no per-test evidence.)
+- `complete` is `true`. If `complete` is `false`, some test reports could not 
be parsed, so the evidence is incomplete.
 - `revision` is a Git revision (not `unknown`) and `dirty` is `false` (no 
tracked file had uncommitted changes), so the
   results describe the project as it was before this pipeline. If `git 
merge-base --is-ancestor <revision> HEAD` fails, the baseline belongs to 
different code.
 
@@ -226,9 +228,10 @@ Run the project's tests again with the project's own 
wrapper, so the current res
 
 Then read the current per-test outcomes from the Surefire/Failsafe XML reports 
(`target/surefire-reports/TEST-*.xml`,
 `target/failsafe-reports/TEST-*.xml`, including module directories). Delete 
stale reports first if they predate this run.
-Do not run a subset of the baseline tests and do not rely on console totals.
+Do not run a subset of the baseline tests and do not rely on console totals. 
Build test IDs using the same module-qualified
+format as the baseline: `[module:]class#method` (module prefix only for 
non-root modules).
 
-If the baseline is usable (Step 1), compare **by test identity** 
(`class#method`), never by totals:
+If the baseline is usable (Step 1), compare **by test identity** 
(`[module:]class#method`), never by totals:
 
 | Baseline | Current | Verdict |
 |----------|---------|---------|
@@ -250,6 +253,7 @@ comparison above reports that as a regression.
 | Test | Baseline | Current | Verdict |
 |------|----------|---------|---------|
 | com.example.FooTest#a | passed | failed | REGRESSION |
+| module-b:com.example.SharedTest#sameTest | passed | passed | OK |
 ```
 
 **Verdict:**
@@ -275,8 +279,8 @@ The validation report for existing projects MUST have this 
structure:
 **New Routes:** N routes
 
 **Baseline Test Results (per-test comparison):**
-- Baseline revision: <revision> (dirty: false)
-- Baseline tests compared: N; regressions: N (list each `class#method`); 
pre-existing failures: N; fixed: N
+- Baseline revision: <revision> (dirty: false, complete: true)
+- Baseline tests compared: N; regressions: N (list each 
`[module:]class#method`); pre-existing failures: N; fixed: N
 - Regressions: YES/NO/UNVERIFIED
 
 ---
diff --git 
a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/service/BaselineTestResultsTest.java
 
b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/service/BaselineTestResultsTest.java
index 64fcb09b..8001f5a1 100644
--- 
a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/service/BaselineTestResultsTest.java
+++ 
b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/service/BaselineTestResultsTest.java
@@ -209,11 +209,12 @@ class BaselineTestResultsTest {
         tests.put("com.example.A#ok", new 
BaselineTestResults.Outcome("passed", ""));
         tests.put("com.example.A#bad", new 
BaselineTestResults.Outcome("failed", "nope"));
 
-        JsonNode json = MAPPER.readTree(BaselineTestResults.toJson("failed", 
"reason", "abc123", true, tests));
+        JsonNode json = MAPPER.readTree(BaselineTestResults.toJson("failed", 
"reason", "abc123", true, true, tests));
 
         assertEquals("failed", json.get("status").asText());
         assertEquals("abc123", json.get("revision").asText());
         assertTrue(json.get("dirty").asBoolean());
+        assertTrue(json.get("complete").asBoolean(), "all reports parsed 
successfully");
         assertEquals(2, json.get("testCount").asInt());
         assertEquals("passed", 
json.at("/tests/com.example.A#ok/status").asText());
         assertEquals("nope", 
json.at("/tests/com.example.A#bad/reason").asText());
@@ -224,9 +225,10 @@ class BaselineTestResultsTest {
     @Test
     void statusOnlyBaselinesHaveTheSameShapeWithAnEmptyTestList() throws 
Exception {
         JsonNode json = MAPPER.readTree(
-                BaselineTestResults.toJson("not_run", "not run", "unknown", 
true, Map.of()));
+                BaselineTestResults.toJson("not_run", "not run", "unknown", 
true, true, Map.of()));
 
         assertEquals("not_run", json.get("status").asText());
+        assertTrue(json.get("complete").asBoolean(), "no collection means no 
parse failures");
         assertEquals(0, json.get("testCount").asInt());
         assertTrue(json.get("tests").isObject());
         assertEquals(0, json.get("tests").size());
diff --git 
a/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/ProjectDiscovery.java
 
b/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/ProjectDiscovery.java
index e4e7655d..c408b679 100644
--- 
a/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/ProjectDiscovery.java
+++ 
b/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/ProjectDiscovery.java
@@ -131,10 +131,12 @@ public final class ProjectDiscovery {
     }
 
     private static boolean isCamelBom(String groupId, String artifactId) {
-        // Only Camel core BOMs (org.apache.camel group) represent Camel core 
versions.
-        // Camel Quarkus extension BOMs (org.apache.camel.quarkus group) have 
independent versioning.
-        return artifactId != null && artifactId.startsWith("camel-") && 
artifactId.endsWith("-bom")
-                && "org.apache.camel".equals(groupId);
+        // Camel core BOMs (org.apache.camel and org.apache.camel.springboot 
groups) represent Camel core versions.
+        // Camel Quarkus extension BOMs (org.apache.camel.quarkus group) have 
independent versioning and are excluded.
+        if (artifactId == null || !artifactId.startsWith("camel-") || 
!artifactId.endsWith("-bom")) {
+            return false;
+        }
+        return "org.apache.camel".equals(groupId) || 
"org.apache.camel.springboot".equals(groupId);
     }
 
     private static void addVersion(Map<String, Set<String>> versions, String 
version, String source) {
@@ -184,37 +186,68 @@ public final class ProjectDiscovery {
     /**
      * The Spring Boot framework version: an imported {@code 
spring-boot-dependencies}, otherwise the
      * {@code spring-boot-starter-parent} parent. This is a different artifact 
from the Camel BOM and is kept apart so
-     * the two are never mixed up.
+     * the two are never mixed up. Returns null if modules disagree on the 
version.
      */
     private static String detectSpringBootVersion(ProjectGraph graph) {
-        String fromBom = firstManaged(graph, "spring-boot-dependencies");
-        if (fromBom != null) {
-            return fromBom;
+        Map<String, Set<String>> bomVersions = collectManagedVersions(graph, 
"spring-boot-dependencies");
+        if (!bomVersions.isEmpty()) {
+            // If modules agree on the BOM version, use it; if they conflict, 
return null
+            return bomVersions.size() == 1 ? 
bomVersions.keySet().iterator().next() : null;
         }
-        for (GraphNode parent : graph.findByType(NodeType.CONFIG_PROPERTY)) {
-            if ("parent".equals(parent.properties().get("kind"))
-                    && 
"spring-boot-starter-parent".equals(parent.properties().get("artifactId"))) {
-                String version = resolve(graph, 
parent.properties().get("version"));
-                if (version != null && !version.contains("${") && 
!"null".equals(version)) {
-                    return version;
-                }
-            }
+        Map<String, Set<String>> parentVersions = collectParentVersions(graph, 
"spring-boot-starter-parent");
+        if (!parentVersions.isEmpty()) {
+            // If modules agree on the parent version, use it; if they 
conflict, return null
+            return parentVersions.size() == 1 ? 
parentVersions.keySet().iterator().next() : null;
         }
         return null;
     }
 
-    private static String firstManaged(ProjectGraph graph, String... 
artifactIds) {
+    /**
+     * Collects all distinct versions of the given managed artifacts across 
all modules, with their module provenance.
+     * Returns a map of version → set of sources (e.g., "4.8.0" → 
{"module-a/pom.xml"}).
+     */
+    private static Map<String, Set<String>> 
collectManagedVersions(ProjectGraph graph, String... artifactIds) {
         List<String> wanted = List.of(artifactIds);
+        Map<String, Set<String>> versions = new TreeMap<>();
         for (GraphNode managed : graph.findByType(NodeType.CONFIG_PROPERTY)) {
             if ("managed".equals(managed.properties().get("kind"))
                     && 
wanted.contains(managed.properties().get("artifactId"))) {
                 String version = resolve(graph, 
managed.properties().get("version"));
-                if (version != null && !"managed".equals(version) && 
!version.contains("${")) {
-                    return version;
+                String file = managed.properties().get("file");
+                if (version != null && !"managed".equals(version) && 
!version.contains("${") && file != null) {
+                    versions.computeIfAbsent(version, k -> new 
TreeSet<>()).add(file);
                 }
             }
         }
-        return null;
+        return versions;
+    }
+
+    /**
+     * Collects all distinct versions of the given parent artifacts across all 
modules, with their module provenance.
+     * Returns a map of version → set of sources.
+     */
+    private static Map<String, Set<String>> collectParentVersions(ProjectGraph 
graph, String artifactId) {
+        Map<String, Set<String>> versions = new TreeMap<>();
+        for (GraphNode parent : graph.findByType(NodeType.CONFIG_PROPERTY)) {
+            if ("parent".equals(parent.properties().get("kind"))
+                    && 
artifactId.equals(parent.properties().get("artifactId"))) {
+                String version = resolve(graph, 
parent.properties().get("version"));
+                String file = parent.properties().get("file");
+                if (version != null && !version.contains("${") && 
!"null".equals(version) && file != null) {
+                    versions.computeIfAbsent(version, k -> new 
TreeSet<>()).add(file);
+                }
+            }
+        }
+        return versions;
+    }
+
+    private static String firstManaged(ProjectGraph graph, String... 
artifactIds) {
+        Map<String, Set<String>> versions = collectManagedVersions(graph, 
artifactIds);
+        if (versions.isEmpty()) {
+            return null;
+        }
+        // If modules agree, return the single version; if they conflict, 
return null
+        return versions.size() == 1 ? versions.keySet().iterator().next() : 
null;
     }
 
     private static Set<DslType> detectDslTypes(ProjectGraph graph) {
diff --git 
a/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/parser/PomParser.java
 
b/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/parser/PomParser.java
index 031f22a1..a1c8c505 100644
--- 
a/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/parser/PomParser.java
+++ 
b/kit/camel-kit-graph/src/main/java/org/apache/camel/kit/graph/parser/PomParser.java
@@ -74,13 +74,15 @@ public class PomParser implements GraphParser {
 
             // Version evidence from the parent and from dependencyManagement 
(imported BOMs). These carry the platform
             // version (Spring Boot, Quarkus platform, Camel BOM) that 
dependencies without a version rely on. They are
-            // recorded as separate nodes so they never masquerade as a 
dependency of the project. Versions are resolved
+            // recorded with module provenance in their IDs to prevent 
cross-module overwriting. Versions are resolved
             // against the declaring POM's own properties to prevent 
cross-module property collision.
             String pomRelative = projectRoot.relativize(pomFile).toString();
             if (model.getParent() != null) {
                 String parentVersion = 
resolveProperty(model.getParent().getVersion(), model);
+                // Include module provenance in node ID to preserve per-module 
parent versions
                 graph.addNode(new GraphNode(
-                        "maven-parent:" + model.getParent().getGroupId() + ":" 
+ model.getParent().getArtifactId(),
+                        "maven-parent:" + pomRelative + ":" + 
model.getParent().getGroupId() + ":"
+                                            + 
model.getParent().getArtifactId(),
                         NodeType.CONFIG_PROPERTY,
                         Map.of("kind", "parent",
                                 "groupId", model.getParent().getGroupId(),
@@ -93,8 +95,9 @@ public class PomParser implements GraphParser {
                 for (Dependency managed : management.getDependencies()) {
                     String rawVersion = managed.getVersion();
                     String resolvedVersion = rawVersion != null ? 
resolveProperty(rawVersion, model) : "managed";
+                    // Include module provenance in node ID to preserve 
per-module managed versions
                     graph.addNode(new GraphNode(
-                            "maven-managed:" + managed.getGroupId() + ":" + 
managed.getArtifactId(),
+                            "maven-managed:" + pomRelative + ":" + 
managed.getGroupId() + ":" + managed.getArtifactId(),
                             NodeType.CONFIG_PROPERTY,
                             Map.of("kind", "managed",
                                     "groupId", managed.getGroupId(),
@@ -148,11 +151,12 @@ public class PomParser implements GraphParser {
         DependencyManagement management = model.getDependencyManagement();
         if (management != null) {
             for (Dependency managed : management.getDependencies()) {
-                // Only Camel core BOMs (org.apache.camel group) represent 
Camel core versions.
+                // Camel core BOMs (org.apache.camel and 
org.apache.camel.springboot groups) represent Camel core versions.
                 // Camel Quarkus extension BOMs (org.apache.camel.quarkus 
group) have independent versioning.
-                if ("pom".equals(managed.getType()) && 
"org.apache.camel".equals(managed.getGroupId())
-                        && managed.getArtifactId() != null
-                        && managed.getArtifactId().startsWith("camel-") && 
managed.getArtifactId().endsWith("-bom")) {
+                if ("pom".equals(managed.getType()) && managed.getArtifactId() 
!= null
+                        && managed.getArtifactId().startsWith("camel-") && 
managed.getArtifactId().endsWith("-bom")
+                        && ("org.apache.camel".equals(managed.getGroupId())
+                                || 
"org.apache.camel.springboot".equals(managed.getGroupId()))) {
                     addCamelVersionEvidence(graph, pomRelative, "imported BOM 
" + managed.getArtifactId(),
                             managed.getVersion(), model);
                 }
diff --git 
a/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/ProjectDiscoveryTest.java
 
b/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/ProjectDiscoveryTest.java
index 309cd8fa..74c674ea 100644
--- 
a/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/ProjectDiscoveryTest.java
+++ 
b/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/ProjectDiscoveryTest.java
@@ -507,12 +507,17 @@ class ProjectDiscoveryTest {
     }
 
     private static GraphNode managed(String artifactId, String version) {
+        return managed(artifactId, version, "pom.xml");
+    }
+
+    private static GraphNode managed(String artifactId, String version, String 
file) {
+        String groupId = artifactId.startsWith("camel-")
+                ? "org.apache.camel.springboot"
+                : "org.springframework.boot";
         return new GraphNode(
-                "maven-managed:org.example:" + artifactId, 
NodeType.CONFIG_PROPERTY,
-                Map.of("kind", "managed", "groupId", 
artifactId.startsWith("camel-")
-                        ? "org.apache.camel.springboot"
-                        : "org.springframework.boot", "artifactId", 
artifactId, "version", version, "type", "pom",
-                        "scope", "import"));
+                "maven-managed:" + file + ":" + groupId + ":" + artifactId, 
NodeType.CONFIG_PROPERTY,
+                Map.of("kind", "managed", "groupId", groupId, "artifactId", 
artifactId, "version", version, "type",
+                        "pom", "scope", "import", "file", file));
     }
 
     private static GraphNode springBootStarter(String file) {
@@ -549,9 +554,9 @@ class ProjectDiscoveryTest {
         ProjectGraph graph = new ProjectGraph();
         graph.addNode(springBootStarter("pom.xml"));
         graph.addNode(new GraphNode(
-                
"maven-parent:org.springframework.boot:spring-boot-starter-parent",
+                
"maven-parent:pom.xml:org.springframework.boot:spring-boot-starter-parent",
                 NodeType.CONFIG_PROPERTY, Map.of("kind", "parent", "groupId", 
"org.springframework.boot",
-                        "artifactId", "spring-boot-starter-parent", "version", 
"3.2.1")));
+                        "artifactId", "spring-boot-starter-parent", "version", 
"3.2.1", "file", "pom.xml")));
 
         assertEquals("3.2.1", 
ProjectDiscovery.discover(graph).springBootVersion());
     }
@@ -683,6 +688,7 @@ class ProjectDiscoveryTest {
                         "groupId", "org.apache.camel.quarkus",
                         "artifactId", "camel-quarkus-bom",
                         "version", "3.15.0",
+                        "type", "pom",
                         "file", "pom.xml")));
 
         ProjectDiscovery.DiscoveryResult result = 
ProjectDiscovery.discover(graph);
@@ -706,6 +712,7 @@ class ProjectDiscoveryTest {
                         "groupId", "org.apache.camel.quarkus",
                         "artifactId", "camel-quarkus-bom",
                         "version", "3.15.0",
+                        "type", "pom",
                         "file", "pom.xml")));
         graph.addNode(new GraphNode(
                 "maven:org.apache.camel.quarkus:camel-quarkus-core",
@@ -722,4 +729,64 @@ class ProjectDiscoveryTest {
         assertTrue(result.allCamelVersions().isEmpty(),
                 "no Camel core version evidence should be present");
     }
+
+    @Test
+    void camelSpringBootBomProvidesCamelCoreVersion() {
+        // org.apache.camel.springboot:camel-spring-boot-bom version 
represents Camel core version
+        ProjectGraph graph = new ProjectGraph();
+        graph.addNode(new GraphNode(
+                
"maven-managed:org.apache.camel.springboot:camel-spring-boot-bom",
+                NodeType.CONFIG_PROPERTY,
+                Map.of(
+                        "kind", "managed",
+                        "groupId", "org.apache.camel.springboot",
+                        "artifactId", "camel-spring-boot-bom",
+                        "version", "4.8.0",
+                        "type", "pom",
+                        "file", "pom.xml")));
+
+        ProjectDiscovery.DiscoveryResult result = 
ProjectDiscovery.discover(graph);
+
+        assertEquals("4.8.0", result.camelVersion(),
+                "Camel Spring Boot BOM version should be recognized as Camel 
core version");
+        assertTrue(result.allCamelVersions().contains("4.8.0"));
+    }
+
+    @Test
+    void detectsSpringBootVersionConflictAcrossModules() {
+        // Two modules with different Spring Boot versions should cause 
conflict detection
+        ProjectGraph graph = new ProjectGraph();
+        graph.addNode(springBootStarter("module-a/pom.xml"));
+        graph.addNode(springBootStarter("module-b/pom.xml"));
+
+        // Module-a uses Spring Boot 3.3.4
+        graph.addNode(new GraphNode(
+                
"maven-managed:module-a/pom.xml:org.springframework.boot:spring-boot-dependencies",
+                NodeType.CONFIG_PROPERTY,
+                Map.of("kind", "managed",
+                        "groupId", "org.springframework.boot",
+                        "artifactId", "spring-boot-dependencies",
+                        "version", "3.3.4",
+                        "type", "pom",
+                        "scope", "import",
+                        "file", "module-a/pom.xml")));
+
+        // Module-b uses Spring Boot 3.4.0
+        graph.addNode(new GraphNode(
+                
"maven-managed:module-b/pom.xml:org.springframework.boot:spring-boot-dependencies",
+                NodeType.CONFIG_PROPERTY,
+                Map.of("kind", "managed",
+                        "groupId", "org.springframework.boot",
+                        "artifactId", "spring-boot-dependencies",
+                        "version", "3.4.0",
+                        "type", "pom",
+                        "scope", "import",
+                        "file", "module-b/pom.xml")));
+
+        ProjectDiscovery.DiscoveryResult result = 
ProjectDiscovery.discover(graph);
+
+        assertNull(result.springBootVersion(),
+                "Conflicting Spring Boot versions across modules should not be 
resolved");
+        assertEquals("spring-boot", result.runtime());
+    }
 }
diff --git 
a/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/parser/PomParserTest.java
 
b/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/parser/PomParserTest.java
index 9bb04725..eec94226 100644
--- 
a/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/parser/PomParserTest.java
+++ 
b/kit/camel-kit-graph/src/test/java/org/apache/camel/kit/graph/parser/PomParserTest.java
@@ -85,11 +85,11 @@ class PomParserTest {
 
         new PomParser().parse(root, local);
 
-        var parent = 
local.getNode("maven-parent:org.springframework.boot:spring-boot-starter-parent");
+        var parent = 
local.getNode("maven-parent:pom.xml:org.springframework.boot:spring-boot-starter-parent");
         assertNotNull(parent);
         assertEquals("parent", parent.properties().get("kind"));
         assertEquals("3.3.5", parent.properties().get("version"));
-        var bom = 
local.getNode("maven-managed:org.apache.camel.springboot:camel-spring-boot-bom");
+        var bom = 
local.getNode("maven-managed:pom.xml:org.apache.camel.springboot:camel-spring-boot-bom");
         assertNotNull(bom);
         assertEquals("managed", bom.properties().get("kind"));
         assertEquals("4.8.0", bom.properties().get("version"),
@@ -143,7 +143,8 @@ class PomParserTest {
         ProjectGraph reactor = new ProjectGraph();
         new PomParser().parse(root, reactor);
 
-        var bom = 
reactor.getNode("maven-managed:org.apache.camel.springboot:camel-spring-boot-bom");
+        var bom = reactor
+                
.getNode("maven-managed:module-a/pom.xml:org.apache.camel.springboot:camel-spring-boot-bom");
         assertNotNull(bom, "Camel Spring Boot BOM should be recorded");
         assertEquals("4.8.0", bom.properties().get("version"),
                 "BOM version should be resolved from module-a's bom.version 
property, not module-b's");

Reply via email to