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=,"