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 9ee5438231bb CAMEL-26788: camel-jbang - an experiment waits at least 
the within= of its expectations, so they can decide (found in a recorded run)
9ee5438231bb is described below

commit 9ee5438231bb27da0fac87c73c00f772bb7d8609
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 18:35:12 2026 +0100

    CAMEL-26788: camel-jbang - an experiment waits at least the within= of its 
expectations, so they can decide (found in a recorded run)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel-jbang-cmd-chaos-experiment.adoc          |  2 +-
 .../META-INF/camel-jbang-commands-metadata.json    |  2 +-
 .../action/CamelChaosExperimentAction.java         |  3 ++-
 .../dsl/jbang/core/commands/ai/ToolRegistry.java   |  2 +-
 .../dsl/jbang/core/common/ChaosExperimentRun.java  | 23 ++++++++++++++++++++++
 .../jbang/core/common/ChaosExperimentRunTest.java  | 18 ++++++++++++++++-
 .../src/test/resources/golden/cli-help.txt         |  3 ++-
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |  2 +-
 8 files changed, 48 insertions(+), 7 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
index f058c4f411d6..a8793619685d 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
@@ -29,7 +29,7 @@ camel cmd chaos-experiment [options]
 | `--repeat` | How many messages to send in all (at most 10000) |  | int
 | `--rule` | A rule in its one-line text form, as an entry of 
camel.chaos.rules; can be given more than once, and one of them needs an 
expectation (expect=) |  | List
 | `--timeout` | How long to wait for the integration to answer, in millis or 
as a duration such as 30s | 10000 | long
-| `--wait` | The longest wait for the verdict after the messages were sent (at 
most 10m) | 2m | String
+| `--wait` | The longest wait for the verdict after the messages were sent, 
and at least the within= of the expectations (at most 10m) | 2m | String
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index a98548c7bc7f..f05a290e35fb 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
     { "name": "ask", "fullName": "ask", "description": "Ask a question about a 
running Camel application using AI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names": 
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY, 
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai', 'anthropic', or 'watsonx'", "javaType":  [...]
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
\"[[apigroup\/]version:]kind:[namespace\/]name\", plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": " [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How [...]
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 5986e63a1526..f3ea315b53c7 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
@@ -96,7 +96,8 @@ public class CamelChaosExperimentAction extends 
CamelChaosAction {
     String interval;
 
     @CommandLine.Option(names = { "--wait" }, defaultValue = "2m",
-                        description = "The longest wait for the verdict after 
the messages were sent (at most 10m)")
+                        description = "The longest wait for the verdict after 
the messages were sent, and at least the"
+                                      + " within= of the expectations (at most 
10m)")
     String wait;
 
     @CommandLine.Option(names = { "--note" },
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 d0e2caaa5ff3..60ce44ddcabb 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
@@ -983,7 +983,7 @@ public final class ToolRegistry {
                         "The pause between two messages in millis, or a range 
such as 100..500 (at most 60000)", false)
                 .param("wait", "string",
                         "The longest wait for the verdict after the messages 
were sent, such as 90s (default 2m, at most"
-                                         + " 10m)",
+                                         + " 10m); it is at least the within= 
of the expectations",
                         false)
                 .param("note", "string", "What to note with the experiment in 
the journal", false)
                 .param("name", "string", CHAOS_NAME, false)
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 dbaeab9254b7..2951bfd76a25 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
@@ -19,6 +19,8 @@ package org.apache.camel.dsl.jbang.core.common;
 import java.util.List;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.camel.util.TimeUtils;
 import org.apache.camel.util.json.JsonObject;
@@ -140,6 +142,11 @@ public final class ChaosExperimentRun {
                 throw new IllegalArgumentException(why);
             }
         }
+        // an expectation such as failed<=0 is decided when its within= is up: 
a shorter wait leaves it undecided
+        long within = longestWithin(rules);
+        if (within + POLL * 2 > wait) {
+            wait = Math.min(within + POLL * 2, MAX_WAIT);
+        }
         JsonObject begun = integration.chaos("experiment-begin", root -> {
             if (hypothesis != null && !hypothesis.isBlank()) {
                 root.put("hypothesis", hypothesis);
@@ -190,6 +197,22 @@ public final class ChaosExperimentRun {
         return answer;
     }
 
+    /** The longest within= of the rules, in millis, or 0 (an expectation 
without it has the time of its rule). */
+    static long longestWithin(String rules) {
+        long answer = 0;
+        Matcher m = WITHIN.matcher(rules);
+        while (m.find()) {
+            try {
+                answer = Math.max(answer, 
TimeUtils.toMilliSeconds(m.group(1)));
+            } catch (RuntimeException e) {
+                // not a time: camel-chaos refuses the rule with the reason
+            }
+        }
+        return answer;
+    }
+
+    private static final Pattern WITHIN = 
Pattern.compile("(?:^|\\s)within=(\\S+)");
+
     /** Sends the traffic: the summary of the send, or why it could not be 
sent. */
     private static JsonObject traffic(Integration integration, Feed feed) {
         JsonObject answer;
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 d9c994f8fd55..a6bf3a60dafd 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
@@ -154,7 +154,9 @@ class ChaosExperimentRunTest {
         integration.shows("PENDING", true);
         integration.ended = experiment("INCONCLUSIVE", false);
 
-        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
null, 5_000, null, clock);
+        // an expectation without within= has the time of its rule, which the 
wait does not know
+        JsonObject answer = ChaosExperimentRun.run(integration, null, 
"route=refunds error expect=refunds:failed<=0", null,
+                5_000, null, clock);
 
         assertThat(answer.getString("verdict")).isEqualTo("INCONCLUSIVE");
         assertThat(answer.getLong("waited")).isEqualTo(5_000);
@@ -239,6 +241,20 @@ class ChaosExperimentRunTest {
                 .containsExactly("r1: endpoint=file:/parked matches no 
endpoint of the context now");
     }
 
+    @Test
+    void theWaitIsAtLeastTheWithinOfTheExpectations() throws Exception {
+        integration.shows("PENDING", true);
+        integration.ended = experiment("PASS", false);
+
+        // RULE has within=30s: a wait of 5s would end it before failed<=0 is 
decided
+        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
null, 5_000, null, clock);
+
+        assertThat(answer.getLong("waited")).isEqualTo(32_000);
+        assertThat(ChaosExperimentRun.longestWithin("route=a error expect=a:1 
within=1m; route=b drop expect=b:1 within=90s"))
+                .isEqualTo(90_000);
+        assertThat(ChaosExperimentRun.longestWithin("route=a error")).isZero();
+    }
+
     @Test
     void theWait() {
         
assertThat(ChaosExperimentRun.waitMillis(null)).isEqualTo(ChaosExperimentRun.DEFAULT_WAIT);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 94d33a3c2fd4..76f9cefccd26 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -786,7 +786,8 @@ rules, send its traffic, wait for the verdict, and end it
                               range such as 100..500 to draw each pause at
                               random (at most 60s)
       --wait=<wait>         The longest wait for the verdict after the messages
-                              were sent (at most 10m)
+                              were sent, and at least the within= of the
+                              expectations (at most 10m)
                               Default: 2m
       --note=<note>         What to note with the experiment when it ends
       --end                 End the open experiment (with --note) instead of
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 fb76a9d01cc6..813e34815deb 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
@@ -1314,7 +1314,7 @@ public class RuntimeTools {
             @ToolArg(description = "The pause between two messages in millis, 
or a range such as 100..500",
                      required = false) String interval,
             @ToolArg(description = "The longest wait for the verdict after the 
messages were sent, such as 90s"
-                                   + " (default 2m, at most 10m)",
+                                   + " (default 2m, at most 10m); it is at 
least the within= of the expectations",
                      required = false) String wait,
             @ToolArg(description = "What to note with the experiment in the 
journal", required = false) String note) {
         requireArg("rule", rule);

Reply via email to