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

davsclaus pushed a commit to branch CAMEL-26788-chaos-explore-save
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/CAMEL-26788-chaos-explore-save 
by this push:
     new 7803a0e7f0da CAMEL-26788: camel-jbang - an experiment says when a rule 
matches nothing, and the playbook asks for 0 rather than a guess in an 
expectation (found in a recorded run)
7803a0e7f0da is described below

commit 7803a0e7f0da329d48470f21a2a990cf56f71c46
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 18:05:09 2026 +0100

    CAMEL-26788: camel-jbang - an experiment says when a rule matches nothing, 
and the playbook asks for 0 rather than a guess in an expectation (found in a 
recorded run)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/cli/connector/ChaosAction.java  |  2 ++
 .../connector/LocalCliConnectorChaosActionTest.java  |  8 ++++++++
 .../commands/action/CamelChaosExperimentAction.java  |  4 ++++
 .../jbang/core/commands/ai/ChaosExplorePlaybook.java | 11 +++++++----
 .../dsl/jbang/core/commands/ai/ToolRegistry.java     |  3 ++-
 .../dsl/jbang/core/common/ChaosExperimentRun.java    |  5 +++++
 .../core/commands/action/CamelChaosActionTest.java   |  7 +++++++
 .../jbang/core/common/ChaosExperimentRunTest.java    | 20 +++++++++++++++++++-
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java    |  5 +++--
 9 files changed, 57 insertions(+), 8 deletions(-)

diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
index e313d421dca3..1d3cbfceac55 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
@@ -161,6 +161,8 @@ final class ChaosAction {
                 Map<String, Object> begun = (Map<String, Object>) call(type, 
chaos, "beginExperiment",
                         root.getString("hypothesis"), rule);
                 answer.putAll(begun);
+                // a rule that matches nothing makes the experiment say 
nothing: as with add, the reason goes along
+                warnings(type, chaos, (List<?>) begun.get("rules"), answer);
             } else if ("experiment-end".equals(command)) {
                 // what came of it, and its rules are removed
                 @SuppressWarnings("unchecked")
diff --git 
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
 
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
index eae9ee24a76d..1f41b807f294 100644
--- 
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
+++ 
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
@@ -278,6 +278,14 @@ class LocalCliConnectorChaosActionTest {
         assertThat(ended.result().getString("verdict")).isEqualTo("FAIL");
         assertThat(ended.result().getString("note")).isEqualTo("no error 
handler");
         assertThat(chaos("experiment-end", null, null).error()).isEqualTo("No 
experiment is open");
+
+        // a rule that matches nothing (a typo) comes with its warning, as 
with add
+        action.put("rule", "endpoint=file:/nowhere error 
expect=start:failed<=0 within=1m");
+        CliActionOutcome typo = CliActionOutcome.dispatch(connector, 
action).get(10, TimeUnit.SECONDS);
+        assertThat(typo.error()).isNull();
+        assertThat(typo.result().<List<Object>> 
getCollection("warnings")).singleElement().asString()
+                .startsWith("r2: endpoint=file:/nowhere matches no");
+        assertThat(chaos("experiment-end", null, null).error()).isNull();
         assertThat(chaos("experiment-begin", null, 
null).error()).isEqualTo("The rules of the experiment are required");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
index 449ec6763a88..5986e63a1526 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
@@ -191,6 +191,10 @@ public class CamelChaosExperimentAction extends 
CamelChaosAction {
         if (!lines.isEmpty()) {
             lines.set(0, "Experiment " + lines.get(0));
         }
+        List<Object> warnings = experiment.getCollection("warnings");
+        if (warnings != null) {
+            warnings.forEach(w -> lines.add("  Warning: " + w));
+        }
         JsonObject traffic = (JsonObject) experiment.get("traffic");
         if (traffic != null) {
             lines.add("  traffic: " + traffic(traffic));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
index 23f0adce1d1b..6e7ed9b3eb8c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
@@ -80,7 +80,8 @@ public final class ChaosExplorePlaybook {
                 route still completing its messages in time.
                    - hypothesis: what should hold, in a few words, such as "a 
failed refund is retried, not lost"
                    - rule: the suggested rule with a short until= (such as 
until=30s), and one expectation that \
-                decides the hypothesis, with within= a little longer than 
until=: expect=<route>:failed<=0 \
+                decides the hypothesis, with within= a little longer than 
until=. Expect 0 where nothing may go \
+                wrong, not a guess such as failed<=5, which hides the messages 
that were lost: expect=<route>:failed<=0 \
                 (nothing fails), expect=<route>-><next route>:lost<=0 (nothing 
lost when a route hands messages to \
                 the next route, as the suggestions of kind lost name them), \
                 expect=<route>:20 (20 completed), expect=<route>:maxTime<=5s 
(none slower than 5s)
@@ -96,7 +97,8 @@ public final class ChaosExplorePlaybook {
                    - wait: longer than the within= of the expectation
                    - note: what you make of the result, in one sentence
                 4. Read the result: the verdict (FAIL, PASS or INCONCLUSIVE), 
the expectations, what the routes \
-                did, and the traffic. On FAIL, call %s for the failed 
exchanges and their exceptions, and say why it \
+                did, the traffic, and the warnings: a rule that matched 
nothing made the experiment say nothing, so \
+                fix the rule (its pattern, such as endpoint=file://parked) and 
run it again. On FAIL, call %s for the failed exchanges and their exceptions, 
and say why it \
                 broke. On INCONCLUSIVE, run it again with a longer wait. On 
PASS, move on.
                 5. Go on while timeLeft of the result (millis) is more than 
the next experiment needs: its wait \
                 plus a minute. When it is less, do not begin another 
experiment: finish.
@@ -108,7 +110,8 @@ public final class ChaosExplorePlaybook {
                 exist; say when it looks like a bug in Camel rather than in 
the route
                    - What held: one line per PASS
                    - Not tried: the kinds of fault or routes left out, and why 
(no feed, out of time)
-                """.formatted(where, time, about, start, tools.report(), 
tools.suggest(), tools.experiment(),
-                tools.errors(), tools.finish());
+                """
+                .formatted(where, time, about, start, tools.report(), 
tools.suggest(), tools.experiment(),
+                        tools.errors(), tools.finish());
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 848bf5a7c9b6..d0e2caaa5ff3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -963,7 +963,8 @@ public final class ToolRegistry {
                                           + "INCONCLUSIVE: the wait was up 
before one was decided, so wait longer than "
                                           + "its within=), the expectations 
with their actual values, the faults per "
                                           + "rule, what the routes did 
meanwhile, the traffic sent, and the time left "
-                                          + "of the exploration (timeLeft, 
millis). chaos_suggest has rules to try and "
+                                          + "of the exploration (timeLeft, 
millis), and warnings for a rule that matched "
+                                          + "nothing (fix it and run again). 
chaos_suggest has rules to try and "
                                           + "the feed of each route.")
                 .param("rule", "string",
                         "The rules in their one-line text form, separated by 
;, one of them with expect=, such as"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
index ddd2bf47bd77..dbaeab9254b7 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
@@ -147,6 +147,8 @@ public final class ChaosExperimentRun {
             root.put("rule", rules);
         });
         int number = begun.getIntegerOrDefault("number", 0);
+        // a rule that matches nothing in the integration now (often a typo)
+        List<Object> warnings = begun.getCollection("warnings");
         JsonObject traffic = null;
         long waited = 0;
         try {
@@ -178,6 +180,9 @@ public final class ChaosExperimentRun {
             answer.put("traffic", traffic);
         }
         answer.put("waited", waited);
+        if (warnings != null && !warnings.isEmpty()) {
+            answer.put("warnings", warnings);
+        }
         JsonObject exploration = exploration(integration.status());
         if (exploration != null && exploration.containsKey("timeLeft")) {
             answer.put("timeLeft", exploration.getLong("timeLeft"));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
index 6a84a4bd54ce..4a5378fc8061 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
@@ -573,6 +573,13 @@ class CamelChaosActionTest extends 
ActionCommandTestSupport {
                 "  waited 2m0s for the verdict; 8m15s left of the exploration",
                 "  An expectation was not decided when it ended: wait longer 
than its within= (--wait)");
 
+        JsonObject typo = new JsonObject(
+                Map.of("number", 4, "verdict", "PASS", "elapsed", 1_000L,
+                        "warnings",
+                        new JsonArray(List.of("r5: endpoint=file:/parked 
matches no endpoint of the context now"))));
+        assertThat(CamelChaosExperimentAction.lines(typo))
+                .contains("  Warning: r5: endpoint=file:/parked matches no 
endpoint of the context now");
+
         JsonObject notSent = new JsonObject(
                 Map.of("number", 3, "verdict", "PASS", "elapsed", 1_000L,
                         "traffic", new JsonObject(Map.of("endpoint", 
"refunds", "status", "failed", "error", "No endpoint"))));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
index 6cd5633f692c..d9c994f8fd55 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
@@ -44,6 +44,7 @@ class ChaosExperimentRunTest {
         RuntimeException sendFails;
         RuntimeException endFails;
         RuntimeException beginFails;
+        List<String> beginWarnings;
         JsonObject ended = experiment("FAIL", false);
 
         @Override
@@ -56,7 +57,11 @@ class ChaosExperimentRunTest {
                 if (beginFails != null) {
                     throw beginFails;
                 }
-                return experiment("PENDING", true);
+                JsonObject begun = experiment("PENDING", true);
+                if (beginWarnings != null) {
+                    begun.put("warnings", new JsonArray(beginWarnings));
+                }
+                return begun;
             }
             if (endFails != null) {
                 throw endFails;
@@ -221,6 +226,19 @@ class ChaosExperimentRunTest {
         assertThat(integration.actions).isEmpty();
     }
 
+    @Test
+    void aRuleThatMatchesNothingComesWithItsWarning() throws Exception {
+        integration.shows("PENDING", true);
+        integration.beginWarnings = List.of("r1: endpoint=file:/parked matches 
no endpoint of the context now");
+        integration.ended = experiment("PASS", false);
+
+        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
null, 1_000, null, clock);
+
+        // the PASS says nothing: the AI reads why
+        assertThat(answer.<List<Object>> getCollection("warnings"))
+                .containsExactly("r1: endpoint=file:/parked matches no 
endpoint of the context now");
+    }
+
     @Test
     void theWait() {
         
assertThat(ChaosExperimentRun.waitMillis(null)).isEqualTo(ChaosExperimentRun.DEFAULT_WAIT);
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index 95e1d96f308f..fb76a9d01cc6 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -1295,8 +1295,9 @@ public class RuntimeTools {
                   the experiment, which removes its rules. Returns the verdict 
(FAIL: an expectation is unmet; PASS: \
                   all are met; INCONCLUSIVE: the wait was up before one was 
decided, so wait longer than its \
                   within=), the expectations with their actual values, the 
faults per rule, what the routes did \
-                  meanwhile, the traffic sent, and the time left of the 
exploration (timeLeft, millis). \
-                  camel_runtime_chaos_suggest has rules to try and the feed of 
each route.""")
+                  meanwhile, the traffic sent, the time left of the 
exploration (timeLeft, millis), and warnings \
+                  for a rule that matched nothing (fix it and run again). 
camel_runtime_chaos_suggest has rules to \
+                  try and the feed of each route.""")
     public JsonObject camel_runtime_chaos_experiment(
             @ToolArg(description = CHAOS_NAME_OR_PID_DESC, required = false) 
String nameOrPid,
             @ToolArg(description = "The rules in their one-line text form, 
separated by ;, one of them with expect=,"

Reply via email to