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

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


The following commit(s) were added to refs/heads/main by this push:
     new 182002df6deb CAMEL-26728: camel-chaos - what has been tried: the kinds 
of faults tried per route, how it went, and what is left (#29398)
182002df6deb is described below

commit 182002df6deb735079986e692ee84606599768d3
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Nov 8 09:16:58 2026 +0100

    CAMEL-26728: camel-chaos - what has been tried: the kinds of faults tried 
per route, how it went, and what is left (#29398)
    
    A rule keeps what it did per route: its faults there and how the exchanges 
it faulted there were done. The report has per route the kinds of fault tried 
(errors, slow, lost, corrupt, duplicate, consumer, lifecycle, dead letter 
channel), the rules, their faults and outcomes, their expectations, and the 
kinds the suggestions offer that no rule tried yet. camel get chaos --report 
shows a table, chaos-suggest and the w list mark a suggestion whose kind was 
tried in its route. The camel-ch [...]
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |  31 +++++
 components/camel-chaos/src/main/docs/chaos.adoc    |  31 +++++
 .../apache/camel/component/chaos/ChaosAdvisor.java |  97 +++++++++++++
 .../camel/component/chaos/ChaosExchangeState.java  |  22 ++-
 .../camel/component/chaos/ChaosLifecycle.java      |   7 +
 .../apache/camel/component/chaos/ChaosManager.java |  41 +++++-
 .../apache/camel/component/chaos/ChaosReport.java  |  71 ++++++++++
 .../apache/camel/component/chaos/ChaosRule.java    |  48 ++++++-
 .../camel/component/chaos/ChaosSendHook.java       |   2 +-
 .../chaos/ChaosCircuitBreakerEffectTest.java       |   4 +-
 .../camel/component/chaos/ChaosTriedTest.java      | 150 +++++++++++++++++++++
 design/chaos.adoc                                  |   2 +
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |   3 +-
 .../ROOT/pages/camel-jbang-tui-observe.adoc        |   3 +-
 .../dsl/jbang/core/commands/process/ListChaos.java |  52 +++++++
 .../jbang/core/commands/process/ListChaosTest.java |  27 ++++
 .../dsl/jbang/core/commands/tui/ChaosControl.java  |   7 +-
 .../dsl/jbang/core/commands/tui/ChaosInfo.java     |  25 ++++
 .../jbang/core/commands/tui/ChaosSuggestList.java  |  31 ++++-
 .../src/main/resources/tui/help/chaos.md           |   4 +-
 .../jbang/core/commands/tui/ChaosControlTest.java  |   6 +-
 21 files changed, 646 insertions(+), 18 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index bfc599944098..3f60d53e8671 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -223,6 +223,37 @@ The rules leave the query of the endpoint out: its options 
do not tell which end
 `ChaosManager.suggest()` gives the same as JSON (`suggestions`: `route`, 
`kind`, `recipe`, `title`, `rule`, `why`), and
 the chaos screen of the Camel TUI lists them (key `w`), where Enter types the 
rule in to change or add.
 
+=== What has been tried
+
+When is a route done? The report says per route which kinds of fault were 
tried, how it went, and which kinds the
+suggestions offer (see <<_what_to_try,What to try>>) that no rule tried yet:
+
+[source]
+----
+$ camel get chaos --report
+...
+  tried, per route:
+  ROUTE     KIND                     RULES  FIRED  OUTCOME
+  orders    errors                   r1        24  21 recovered, 1 handled, 0 
failed; expect orders:handled<=0 unmet
+  orders    slow, lost, duplicate                  not tried yet
+  shipping  dead letter channel                    not tried yet
+----
+
+* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a 
send is dropped), `corrupt`, `duplicate`,
+`consumer` (the polls, or the entry of the route), `lifecycle` (stall and 
lifecycle) and `dead letter channel`
+(`at=dlq`). A rule has one kind, from its fault and where it acts.
+* A kind is tried in a route when a rule of that kind fired in it: a rule 
counts its faults per route, so a rule that
+reaches several routes is tried in each. The rules removed or replaced count 
too, so the report says what the
+experiment tried, not only what runs now.
+* The outcome is how the exchanges the rules faulted came out (recovered by a 
redelivery, handled by the error handler,
+failed), counted per rule, and the outcomes of their expectations.
+* What is not tried yet comes from the suggestions for the route, read again 
at most every 10 seconds: when nothing is
+left, the route has been tried for what chaos can say about it.
+
+In JSON the report has `tried`: per `route` its `kinds` (`kind`, `rules`, 
`fired`, `recovered`, `handled`, `failed`,
+`expectations`) and `notTried`. `ChaosManager.suggest()` marks a suggestion 
whose kind was tried in its route already
+(`tried`), and the what to try list of the Camel TUI (key `w`) shows both.
+
 === From an AI agent (MCP)
 
 The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same operations
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index bfc599944098..3f60d53e8671 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -223,6 +223,37 @@ The rules leave the query of the endpoint out: its options 
do not tell which end
 `ChaosManager.suggest()` gives the same as JSON (`suggestions`: `route`, 
`kind`, `recipe`, `title`, `rule`, `why`), and
 the chaos screen of the Camel TUI lists them (key `w`), where Enter types the 
rule in to change or add.
 
+=== What has been tried
+
+When is a route done? The report says per route which kinds of fault were 
tried, how it went, and which kinds the
+suggestions offer (see <<_what_to_try,What to try>>) that no rule tried yet:
+
+[source]
+----
+$ camel get chaos --report
+...
+  tried, per route:
+  ROUTE     KIND                     RULES  FIRED  OUTCOME
+  orders    errors                   r1        24  21 recovered, 1 handled, 0 
failed; expect orders:handled<=0 unmet
+  orders    slow, lost, duplicate                  not tried yet
+  shipping  dead letter channel                    not tried yet
+----
+
+* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a 
send is dropped), `corrupt`, `duplicate`,
+`consumer` (the polls, or the entry of the route), `lifecycle` (stall and 
lifecycle) and `dead letter channel`
+(`at=dlq`). A rule has one kind, from its fault and where it acts.
+* A kind is tried in a route when a rule of that kind fired in it: a rule 
counts its faults per route, so a rule that
+reaches several routes is tried in each. The rules removed or replaced count 
too, so the report says what the
+experiment tried, not only what runs now.
+* The outcome is how the exchanges the rules faulted came out (recovered by a 
redelivery, handled by the error handler,
+failed), counted per rule, and the outcomes of their expectations.
+* What is not tried yet comes from the suggestions for the route, read again 
at most every 10 seconds: when nothing is
+left, the route has been tried for what chaos can say about it.
+
+In JSON the report has `tried`: per `route` its `kinds` (`kind`, `rules`, 
`fired`, `recovered`, `handled`, `failed`,
+`expectations`) and `notTried`. `ChaosManager.suggest()` marks a suggestion 
whose kind was tried in its route already
+(`tried`), and the what to try list of the Camel TUI (key `w`) shows both.
+
 === From an AI agent (MCP)
 
 The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same operations
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
index 0decbcc9fdca..66029be3e45a 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
@@ -89,6 +89,103 @@ final class ChaosAdvisor {
     record Suggestion(String route, Kind kind, String recipe, String title, 
String rule, String why, int rank) {
     }
 
+    /**
+     * A kind of fault tried in a route (CAMEL-26728): by which rules, how 
often they fired there, how the exchanges
+     * they faulted came out (counted per rule, so a rule that fired in more 
routes counts in each), and the outcomes of
+     * their expectations.
+     */
+    record KindTried(Kind kind, List<String> rules, long fired, long 
recovered, long handled, long failed,
+            List<String> expectations) {
+    }
+
+    /** What has been tried in a route, and the kinds that the suggestions 
offer for it and no rule tried yet. */
+    record RouteTried(String route, List<KindTried> tried, List<Kind> 
notTried) {
+    }
+
+    /** The kind of fault of a rule. */
+    static Kind kindOf(ChaosRule rule) {
+        if (rule.lifecycle != null || rule.fault == ChaosRule.Fault.LIFECYCLE) 
{
+            return Kind.LIFECYCLE;
+        }
+        if (rule.hooks.size() == 1 && rule.hooks.contains(ChaosRule.Hook.DLQ)) 
{
+            return Kind.DEAD_LETTER;
+        }
+        boolean consumer = rule.hooks.stream().allMatch(h -> h == 
ChaosRule.Hook.POLL || h == ChaosRule.Hook.ENTRY);
+        return switch (rule.fault) {
+            case DELAY -> Kind.SLOW;
+            case DROP -> consumer ? Kind.CONSUMER : Kind.LOST;
+            case CORRUPT -> Kind.CORRUPT;
+            case DUPLICATE -> Kind.DUPLICATE;
+            default -> consumer ? Kind.CONSUMER : Kind.ERRORS;
+        };
+    }
+
+    /**
+     * What has been tried per route: the kinds of fault of the rules (current 
and removed) that fired in it, and the
+     * kinds the suggestions offer for it that none tried. The routes in the 
order of the suggestions, then the others.
+     */
+    static List<RouteTried> tried(List<ChaosRule> rules, Map<String, 
Set<Kind>> suggested, long now) {
+        // route -> kind -> the rules of that kind that fired in the route
+        Map<String, Map<Kind, List<ChaosRule>>> byRoute = new 
LinkedHashMap<>();
+        suggested.keySet().forEach(route -> byRoute.put(route, new 
LinkedHashMap<>()));
+        for (ChaosRule rule : rules) {
+            for (Map.Entry<String, ChaosRule.InRoute> e : 
rule.inRoute.entrySet()) {
+                if (e.getValue().fired.sum() > 0) {
+                    byRoute.computeIfAbsent(e.getKey(), k -> new 
LinkedHashMap<>())
+                            .computeIfAbsent(kindOf(rule), k -> new 
ArrayList<>()).add(rule);
+                }
+            }
+        }
+        List<RouteTried> answer = new ArrayList<>();
+        for (Map.Entry<String, Map<Kind, List<ChaosRule>>> entry : 
byRoute.entrySet()) {
+            String route = entry.getKey();
+            List<KindTried> tried = new ArrayList<>();
+            for (Map.Entry<Kind, List<ChaosRule>> k : 
entry.getValue().entrySet()) {
+                List<String> ids = new ArrayList<>();
+                List<String> expectations = new ArrayList<>();
+                long fired = 0;
+                long recovered = 0;
+                long handled = 0;
+                long failed = 0;
+                for (ChaosRule rule : k.getValue()) {
+                    String id = rule.version > 1 ? rule.id + " v" + 
rule.version : rule.id;
+                    if (!ids.contains(id)) {
+                        ids.add(id);
+                    }
+                    // the outcomes of the exchanges the rule faulted in this 
route, not in all of them
+                    ChaosRule.InRoute in = rule.inRoute.get(route);
+                    fired += in.fired.sum();
+                    recovered += 
in.effect.count(ChaosRule.Effect.Outcome.RECOVERED);
+                    handled += 
in.effect.count(ChaosRule.Effect.Outcome.HANDLED);
+                    failed += in.effect.count(ChaosRule.Effect.Outcome.FAILED);
+                    if (rule.expectation != null) {
+                        expectations.add(rule.expectation.spec() + " " + 
rule.expectation.read(now).outcome().key);
+                    }
+                }
+                tried.add(new KindTried(k.getKey(), ids, fired, recovered, 
handled, failed, expectations));
+            }
+            List<Kind> notTried = new ArrayList<>();
+            for (Kind kind : Kind.values()) {
+                if (suggested.getOrDefault(route, Set.of()).contains(kind) && 
!entry.getValue().containsKey(kind)) {
+                    notTried.add(kind);
+                }
+            }
+            if (!tried.isEmpty() || !notTried.isEmpty()) {
+                answer.add(new RouteTried(route, tried, notTried));
+            }
+        }
+        return answer;
+    }
+
+    /** The kinds of fault the suggestions offer per route, in the order of 
the routes. */
+    static Map<String, Set<Kind>> suggestedKinds(List<Suggestion> suggestions) 
{
+        Map<String, Set<Kind>> answer = new LinkedHashMap<>();
+        for (Suggestion s : suggestions) {
+            answer.computeIfAbsent(s.route(), k -> new 
LinkedHashSet<>()).add(s.kind());
+        }
+        return answer;
+    }
+
     private ChaosAdvisor() {
     }
 
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExchangeState.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExchangeState.java
index ed6dc216308f..c1e1d4540d08 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExchangeState.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExchangeState.java
@@ -136,7 +136,7 @@ final class ChaosExchangeState implements SafeCopyProperty {
      * a producer template to an endpoint, not to a route) is not counted in 
the effect, as nothing tells when it is
      * done.
      */
-    static void faulted(ChaosRule rule, Exchange exchange) {
+    static void faulted(ChaosRule rule, Exchange exchange, String routeId) {
         exchange.setProperty(ChaosManager.CHAOS_RULE, rule.id);
         if (exchange.getUnitOfWork() == null) {
             return;
@@ -146,7 +146,7 @@ final class ChaosExchangeState implements SafeCopyProperty {
             state.faults = new Faults();
             exchange.getExchangeExtension().addOnCompletion(state.faults);
         }
-        state.faults.add(rule);
+        state.faults.add(rule, routeId);
     }
 
     /** The error handler redelivers the exchange: a rule that faulted it 
before is recovered when it then completes. */
@@ -161,13 +161,20 @@ final class ChaosExchangeState implements 
SafeCopyProperty {
 
         private final List<ChaosRule> rules = new ArrayList<>(2);
         private final List<Boolean> redelivered = new ArrayList<>(2);
+        // the route each rule first faulted the exchange in, where its 
outcome counts too (null when not followed)
+        private final List<ChaosRule.InRoute> inRoutes = new ArrayList<>(2);
 
-        void add(ChaosRule rule) {
+        void add(ChaosRule rule, String routeId) {
             int i = rules.indexOf(rule);
             if (i < 0) {
                 rules.add(rule);
                 redelivered.add(Boolean.FALSE);
                 rule.effect.faulted();
+                ChaosRule.InRoute in = rule.inRoute(routeId);
+                inRoutes.add(in);
+                if (in != null) {
+                    in.effect.faulted();
+                }
             } else {
                 // faulted again (such as at a redelivery): the redelivery 
that follows counts from now
                 redelivered.set(i, Boolean.FALSE);
@@ -189,9 +196,14 @@ final class ChaosExchangeState implements SafeCopyProperty 
{
                 ChaosRule rule = other.rules.get(i);
                 if (rules.contains(rule)) {
                     rule.effect.unfaulted();
+                    ChaosRule.InRoute in = other.inRoutes.get(i);
+                    if (in != null) {
+                        in.effect.unfaulted();
+                    }
                 } else {
                     rules.add(rule);
                     redelivered.add(other.redelivered.get(i));
+                    inRoutes.add(other.inRoutes.get(i));
                 }
             }
         }
@@ -248,6 +260,10 @@ final class ChaosExchangeState implements SafeCopyProperty 
{
                     outcome = ChaosRule.Effect.Outcome.COMPLETED;
                 }
                 rules.get(i).effect.done(outcome);
+                ChaosRule.InRoute in = inRoutes.get(i);
+                if (in != null) {
+                    in.effect.done(outcome);
+                }
             }
         }
 
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosLifecycle.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosLifecycle.java
index ccea18b451e0..214db049a610 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosLifecycle.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosLifecycle.java
@@ -475,6 +475,13 @@ final class ChaosLifecycle {
             return downUri;
         }
 
+        /** The id of the route the fault took down, where the outcomes of the 
exchanges sent to it count; else null. */
+        String downRouteId() {
+            // read after downEndpointUri, which is written after the route
+            Route down = route;
+            return down != null ? down.getRouteId() : null;
+        }
+
         /** Whether the fault brings its route back now (the rule was removed, 
or the comeback runs). */
         synchronized boolean isEnding() {
             return endRequested || endedEarly || state == 
FaultState.COMING_BACK;
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
index e258ff01ddd4..5d73cb512e5d 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Deque;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -471,8 +472,21 @@ public final class ChaosManager {
      */
     public Map<String, Object> suggest() {
         JsonArray list = new JsonArray();
+        // a suggestion whose kind of fault a rule tried in its route already 
is marked (CAMEL-26728)
+        List<ChaosRule> all;
+        synchronized (lock) {
+            all = new ArrayList<>(rules);
+            all.addAll(history);
+        }
+        Set<String> tried = new HashSet<>();
+        for (ChaosAdvisor.RouteTried route : ChaosAdvisor.tried(all, Map.of(), 
TimeSource.currentTimeMillis())) {
+            route.tried().forEach(k -> tried.add(route.route() + " " + 
k.kind().key));
+        }
         for (ChaosAdvisor.Suggestion s : ChaosAdvisor.suggest(camelContext)) {
             JsonObject jo = new JsonObject();
+            if (tried.contains(s.route() + " " + s.kind().key)) {
+                jo.put("tried", true);
+            }
             jo.put("route", s.route());
             jo.put("kind", s.kind().key);
             if (s.recipe() != null) {
@@ -574,9 +588,32 @@ public final class ChaosManager {
             lostAtDlq = evictedLostAtDlq;
         }
         Map<String, RouteCounts> counts = new TreeMap<>(routes);
+        long now = TimeSource.currentTimeMillis();
+        List<ChaosRule> all = new ArrayList<>(current);
+        all.addAll(removed);
+        List<ChaosAdvisor.RouteTried> tried = ChaosAdvisor.tried(all, 
suggestedKinds(now), now);
         return new ChaosReport(
-                seed, seedFrom, TimeSource.currentTimeMillis(), current, 
removed, counts, unmet, lost, pending,
-                lostAtDlq, recentFaults.latestFirst(), 
ChaosTargets.reach(camelContext));
+                seed, seedFrom, now, current, removed, counts, unmet, lost, 
pending,
+                lostAtDlq, recentFaults.latestFirst(), 
ChaosTargets.reach(camelContext), tried);
+    }
+
+    // the kinds of fault the suggestions offer per route, read again at most 
every 10 seconds (the report is read
+    // every second for the status of the application), or when the routes 
change
+    private static final long SUGGESTED_FOR_MILLIS = 10_000;
+    private volatile Suggested suggested;
+
+    /** The kinds the suggestions offer, with when they were read and for how 
many routes: one value, read as one. */
+    private record Suggested(Map<String, Set<ChaosAdvisor.Kind>> kinds, long 
at, int routes) {
+    }
+
+    private Map<String, Set<ChaosAdvisor.Kind>> suggestedKinds(long now) {
+        Suggested answer = suggested;
+        int routeCount = camelContext.getRoutes().size();
+        if (answer == null || now - answer.at() > SUGGESTED_FOR_MILLIS || 
routeCount != answer.routes()) {
+            answer = new 
Suggested(ChaosAdvisor.suggestedKinds(ChaosAdvisor.suggest(camelContext)), now, 
routeCount);
+            suggested = answer;
+        }
+        return answer.kinds();
     }
 
     // -- for the hooks, the event notifier and the plugin
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosReport.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosReport.java
index e56eee623847..a2dfd381f5ea 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosReport.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosReport.java
@@ -70,10 +70,21 @@ public final class ChaosReport {
     private final long now;
     // the last faults, the latest first
     private final List<ChaosRecentFaults.Entry> recentFaults;
+    // what has been tried per route, and what not yet (CAMEL-26728)
+    private final List<ChaosAdvisor.RouteTried> tried;
 
     ChaosReport(long seed, String seedFrom, long now, List<ChaosRule> current, 
List<ChaosRule> removed,
                 Map<String, RouteCounts> counts, int evictedUnmet, long 
evictedLost, int evictedPending,
                 long evictedLostAtDlq, List<ChaosRecentFaults.Entry> 
recentFaults, ChaosTargets.Reach reach) {
+        this(seed, seedFrom, now, current, removed, counts, evictedUnmet, 
evictedLost, evictedPending, evictedLostAtDlq,
+             recentFaults, reach, List.of());
+    }
+
+    ChaosReport(long seed, String seedFrom, long now, List<ChaosRule> current, 
List<ChaosRule> removed,
+                Map<String, RouteCounts> counts, int evictedUnmet, long 
evictedLost, int evictedPending,
+                long evictedLostAtDlq, List<ChaosRecentFaults.Entry> 
recentFaults, ChaosTargets.Reach reach,
+                List<ChaosAdvisor.RouteTried> tried) {
+        this.tried = List.copyOf(tried);
         this.seed = seed;
         this.seedFrom = seedFrom;
         this.now = now;
@@ -259,6 +270,38 @@ public final class ChaosReport {
                     .append(r.failed).append(" (kept by the consumer 
").append(r.failedKept).append("), handled ")
                     .append(r.handled).append(", redeliveries 
").append(r.redeliveries);
         }
+        if (!tried.isEmpty()) {
+            sb.append("\n  tried, per route:");
+            for (ChaosAdvisor.RouteTried r : tried) {
+                sb.append("\n    ").append(r.route()).append(':');
+                if (r.tried().isEmpty()) {
+                    sb.append(" nothing yet");
+                }
+                for (ChaosAdvisor.KindTried k : r.tried()) {
+                    sb.append("\n      ").append(k.kind().key).append(" 
(").append(String.join(", ", k.rules()))
+                            .append("): fired ").append(k.fired());
+                    StringJoiner effect = new StringJoiner(", ", ", ", "");
+                    effect.setEmptyValue("");
+                    if (k.recovered() > 0) {
+                        effect.add(k.recovered() + " recovered");
+                    }
+                    if (k.handled() > 0) {
+                        effect.add(k.handled() + " handled");
+                    }
+                    if (k.failed() > 0) {
+                        effect.add(k.failed() + " failed");
+                    }
+                    sb.append(effect);
+                    for (String e : k.expectations()) {
+                        sb.append(", expect ").append(e);
+                    }
+                }
+                if (!r.notTried().isEmpty()) {
+                    sb.append("\n      not tried yet: ")
+                            .append(String.join(", ", 
r.notTried().stream().map(k -> k.key).toList()));
+                }
+            }
+        }
         if (!recentFaults.isEmpty()) {
             sb.append("\n  recent faults:");
             for (ChaosRecentFaults.Entry f : recentFaults) {
@@ -364,6 +407,33 @@ public final class ChaosReport {
         return (tenths == Math.rint(tenths) ? Long.toString((long) tenths) : 
Double.toString(tenths)) + "%";
     }
 
+    /** What has been tried per route, and what not yet, as JSON. */
+    private JsonArray triedJson() {
+        JsonArray answer = new JsonArray();
+        for (ChaosAdvisor.RouteTried r : tried) {
+            JsonObject jo = new JsonObject();
+            jo.put("route", r.route());
+            JsonArray kinds = new JsonArray();
+            for (ChaosAdvisor.KindTried k : r.tried()) {
+                JsonObject ko = new JsonObject();
+                ko.put("kind", k.kind().key);
+                ko.put("rules", new JsonArray(k.rules()));
+                ko.put("fired", k.fired());
+                ko.put("recovered", k.recovered());
+                ko.put("handled", k.handled());
+                ko.put("failed", k.failed());
+                if (!k.expectations().isEmpty()) {
+                    ko.put("expectations", new JsonArray(k.expectations()));
+                }
+                kinds.add(ko);
+            }
+            jo.put("kinds", kinds);
+            jo.put("notTried", new JsonArray(r.notTried().stream().map(k -> 
k.key).toList()));
+            answer.add(jo);
+        }
+        return answer;
+    }
+
     /**
      * The report as JSON, for a tool.
      */
@@ -392,6 +462,7 @@ public final class ChaosReport {
         }
         root.put("routes", routeArray);
         root.put("recentFaults", recentFaultsJson());
+        root.put("tried", triedJson());
         return root;
     }
 
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosRule.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosRule.java
index 177dd39d50b3..576daa27acbd 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosRule.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosRule.java
@@ -242,6 +242,10 @@ final class ChaosRule {
     private final long[] faultsPerSecond = new long[MINUTE];
     private final long[] secondOf = new long[MINUTE];
     final Effect effect = new Effect();
+    // what the rule did per route, for what has been tried (CAMEL-26728): its 
faults there, and how the exchanges it
+    // faulted there were done; bounded, as the routes are
+    final ConcurrentMap<String, InRoute> inRoute = new ConcurrentHashMap<>();
+    static final int MAX_ROUTES = 200;
     ChaosRecentFaults recentFaults;
 
     ChaosRule(Builder b, String id, long seed, long addedAt, long maxDuration) 
{
@@ -391,19 +395,50 @@ final class ChaosRule {
         fired[hook.ordinal()].increment();
         long now = TimeSource.currentTimeMillis();
         countFault(now);
+        String route = routeId != null || exchange == null ? routeId : 
ChaosExchangeState.routeId(exchange);
         if (exchange != null) {
-            ChaosExchangeState.faulted(this, exchange);
+            ChaosExchangeState.faulted(this, exchange, route);
         }
+        countInRoute(route);
         ChaosRecentFaults recent = recentFaults;
         if (recent != null) {
-            recent.add(now, this, hook, routeId != null || exchange == null ? 
routeId : ChaosExchangeState.routeId(exchange),
-                    where, exchange != null ? exchange.getExchangeId() : null);
+            recent.add(now, this, hook, route, where, exchange != null ? 
exchange.getExchangeId() : null);
         }
     }
 
+    private void countInRoute(String route) {
+        InRoute in = inRoute(route);
+        if (in != null) {
+            in.fired.increment();
+        }
+    }
+
+    /**
+     * What the rule did in a route, or null when the route is null, or new 
while the rule follows {@link #MAX_ROUTES}
+     * routes already.
+     */
+    InRoute inRoute(String route) {
+        if (route == null) {
+            return null;
+        }
+        InRoute answer = inRoute.get(route);
+        if (answer == null) {
+            // a new route: the check of the size and the put as one, so the 
bound holds
+            synchronized (inRoute) {
+                answer = inRoute.get(route);
+                if (answer == null && inRoute.size() < MAX_ROUTES) {
+                    answer = new InRoute();
+                    inRoute.put(route, answer);
+                }
+            }
+        }
+        return answer;
+    }
+
     /** A lifecycle rule (or stall) took its route down: counted in its live 
state, as the faults at a hook are. */
     void firedLifecycle(String routeId, long now) {
         countFault(now);
+        countInRoute(routeId);
         ChaosRecentFaults recent = recentFaults;
         if (recent != null) {
             recent.add(now, this, null, routeId, null, null);
@@ -1135,6 +1170,13 @@ final class ChaosRule {
      * handled by the error handler (such as the dead letter channel), or 
failed. The others are still in flight. Only
      * the exchanges with a unit of work count, as it tells when the exchange 
is done.
      */
+    /** What a rule did in one route: its faults there, and how the exchanges 
it faulted there were done. */
+    static final class InRoute {
+
+        final LongAdder fired = new LongAdder();
+        final Effect effect = new Effect();
+    }
+
     static final class Effect {
 
         /** How an exchange was done, after the rule faulted it. */
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosSendHook.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosSendHook.java
index 628be5f64a2e..a885635c2ab1 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosSendHook.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosSendHook.java
@@ -96,7 +96,7 @@ final class ChaosSendHook implements SendHook, Ordered {
                     uri = endpoint.getEndpointBaseUri();
                 }
                 if (down.equals(uri)) {
-                    ChaosExchangeState.faulted(rule, exchange);
+                    ChaosExchangeState.faulted(rule, exchange, 
rule.lifecycle.downRouteId());
                 }
             }
         }
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCircuitBreakerEffectTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCircuitBreakerEffectTest.java
index 67b7ec992725..1d2ed176ecfb 100644
--- 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCircuitBreakerEffectTest.java
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCircuitBreakerEffectTest.java
@@ -142,12 +142,12 @@ class ChaosCircuitBreakerEffectTest extends 
ChaosTestSupport {
                 ChaosRuleParser.parse("endpoint=mock:pay error", 
context.getClassResolver(), TimeUnit.MINUTES.toMillis(10)),
                 "r9", 1, 0, TimeUnit.MINUTES.toMillis(10));
         ChaosExchangeState.Faults faults = new ChaosExchangeState.Faults();
-        faults.add(rule);
+        faults.add(rule, null);
         long now = TimeSource.currentTimeMillis();
         assertThat(chaos().waitForOriginal("ex-1", faults, false, 
true)).isTrue();
         // the circuit breaker ran again for the same exchange: one message
         ChaosExchangeState.Faults again = new ChaosExchangeState.Faults();
-        again.add(rule);
+        again.add(rule, null);
         chaos().waitForOriginal("ex-1", again, false, true);
 
         chaos().countWaitedTooLong(now + 30_000);
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
new file mode 100644
index 000000000000..54918e49cc1a
--- /dev/null
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.chaos;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-26728: what has been tried per route, how it went, and what the 
suggestions offer that no rule tried yet.
+ */
+class ChaosTriedTest extends ChaosTestSupport {
+
+    @BindToRegistry("http")
+    private final TestHttpComponent http = new TestHttpComponent();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders")
+                        
.errorHandler(defaultErrorHandler().maximumRedeliveries(2).redeliveryDelay(0))
+                        .to("http://stock/check";)
+                        .to("seda:shipping");
+                from("seda:shipping?pollTimeout=10").routeId("shipping")
+                        .errorHandler(deadLetterChannel("mock:dlq"))
+                        .to("mock:ship");
+                // one rule in two routes, done in another way in each
+                from("direct:handled").routeId("handled")
+                        .onException(Exception.class).handled(true).end()
+                        .to("mock:audit");
+                from("direct:unhandled").routeId("unhandled")
+                        .errorHandler(noErrorHandler())
+                        .to("mock:audit");
+            }
+        };
+    }
+
+    private JsonObject route(String routeId) {
+        for (Object o : (JsonArray) chaos().report().toJson().get("tried")) {
+            JsonObject jo = (JsonObject) o;
+            if (routeId.equals(jo.getString("route"))) {
+                return jo;
+            }
+        }
+        throw new AssertionError("No route " + routeId + " in what was tried");
+    }
+
+    @Test
+    void whatWasTriedPerRouteAndWhatIsLeft() {
+        // nothing tried yet: the suggestions say what is left
+        assertThat((JsonArray) route("orders").get("kinds")).isEmpty();
+        assertThat((JsonArray) 
route("orders").get("notTried")).contains("errors", "slow", "lost");
+        assertThat((JsonArray) 
route("shipping").get("notTried")).contains("dead letter channel");
+
+        String id = chaos().addRule("endpoint=http://stock/check error 
status=503 rate=every:2 until=10m");
+        for (int i = 0; i < 2; i++) {
+            template.sendBody("direct:orders", "order " + i);
+        }
+
+        JsonObject orders = route("orders");
+        JsonObject errors = (JsonObject) ((JsonArray) 
orders.get("kinds")).get(0);
+        assertThat(errors.getString("kind")).isEqualTo("errors");
+        assertThat((JsonArray) errors.get("rules")).containsExactly(id);
+        assertThat(errors.getLong("fired")).isPositive();
+        // the redelivery recovered them
+        assertThat(errors.getLong("recovered")).isPositive();
+        assertThat(errors.getLong("failed")).isZero();
+        assertThat((JsonArray) 
orders.get("notTried")).doesNotContain("errors").contains("slow", "lost");
+
+        assertThat(chaos().report().toText()).contains("tried, per 
route:").contains("orders:")
+                .contains("errors (" + id + "): fired").contains("not tried 
yet: slow");
+    }
+
+    @Test
+    void howItWentIsPerRoute() {
+        String id = chaos().addRule("endpoint=mock:audit error until=10m");
+        template.send("direct:handled", e -> e.getMessage().setBody("a"));
+        template.send("direct:unhandled", e -> e.getMessage().setBody("b"));
+
+        JsonObject handled = (JsonObject) ((JsonArray) 
route("handled").get("kinds")).get(0);
+        assertThat((JsonArray) handled.get("rules")).containsExactly(id);
+        assertThat(handled.getLong("fired")).isEqualTo(1);
+        assertThat(handled.getLong("handled")).isEqualTo(1);
+        assertThat(handled.getLong("failed")).isZero();
+        // the outcome of the other route does not count here
+        JsonObject unhandled = (JsonObject) ((JsonArray) 
route("unhandled").get("kinds")).get(0);
+        assertThat(unhandled.getLong("fired")).isEqualTo(1);
+        assertThat(unhandled.getLong("handled")).isZero();
+        assertThat(unhandled.getLong("failed")).isEqualTo(1);
+    }
+
+    @Test
+    void aSuggestionOfAKindTriedAlreadyIsMarked() {
+        chaos().addRule("endpoint=http://stock/check delay duration=1ms 
until=10m");
+        template.sendBody("direct:orders", "order");
+
+        boolean slowTried = false;
+        boolean errorsTried = false;
+        for (Object o : (JsonArray) chaos().suggest().get("suggestions")) {
+            JsonObject s = (JsonObject) o;
+            if ("orders".equals(s.getString("route")) && 
"slow".equals(s.getString("kind"))) {
+                slowTried = s.getBooleanOrDefault("tried", false);
+            }
+            if ("orders".equals(s.getString("route")) && 
"errors".equals(s.getString("kind"))) {
+                errorsTried = s.getBooleanOrDefault("tried", false);
+            }
+        }
+        assertThat(slowTried).isTrue();
+        assertThat(errorsTried).isFalse();
+    }
+
+    @Test
+    void theKindOfARule() {
+        assertThat(kind("endpoint=x 
error")).isEqualTo(ChaosAdvisor.Kind.ERRORS);
+        assertThat(kind("endpoint=x delay 
duration=1s")).isEqualTo(ChaosAdvisor.Kind.SLOW);
+        assertThat(kind("endpoint=x drop")).isEqualTo(ChaosAdvisor.Kind.LOST);
+        assertThat(kind("endpoint=x drop 
at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER);
+        assertThat(kind("endpoint=x error 
at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER);
+        assertThat(kind("route=orders 
error")).isEqualTo(ChaosAdvisor.Kind.CONSUMER);
+        assertThat(kind("route=orders corrupt 
mode=headers")).isEqualTo(ChaosAdvisor.Kind.CORRUPT);
+        assertThat(kind("endpoint=x 
duplicate")).isEqualTo(ChaosAdvisor.Kind.DUPLICATE);
+        assertThat(kind("endpoint=seda:dlq error 
at=dlq")).isEqualTo(ChaosAdvisor.Kind.DEAD_LETTER);
+        assertThat(kind("route=orders stall 
duration=1s")).isEqualTo(ChaosAdvisor.Kind.LIFECYCLE);
+    }
+
+    private ChaosAdvisor.Kind kind(String text) {
+        ChaosRule.Builder b = ChaosRuleParser.parse(text, 
context.getClassResolver(), 600_000);
+        return ChaosAdvisor.kindOf(new ChaosRule(b, "r1", 1, 0, 600_000));
+    }
+}
diff --git a/design/chaos.adoc b/design/chaos.adoc
index e8640c4ec29d..e18a62c8ae77 100644
--- a/design/chaos.adoc
+++ b/design/chaos.adoc
@@ -401,6 +401,8 @@ framework, so camel-test does not depend on camel-chaos; 
the rules come from the
   from the running routes (their sends, circuit breakers, dead letter channel, 
input and hand-offs), each a recipe of
   the chaos model with its target filled in and why. The About screen hints 
(CAMEL-26716) are meant to be one shared
   rule set; these facts come from the running context, and can become the 
chaos part of that set.
+- What has been tried (stage 2, CAMEL-26728): the report has per route the 
kinds of fault tried (a rule counts its
+  faults per route), how the exchanges came out, and the kinds the suggestions 
offer that no rule tried yet.
 - A `chaos` action of the CLI connector (`LocalCliConnector`) adds, changes 
and removes rules through the
   `ChaosManager`. The CLI has the flat verb-noun shape of every `camel cmd` 
today (`stop-route`, `stub`), with the
   reads under `camel get` (as `camel get circuit-breaker`): `camel cmd 
chaos-add`, `chaos-change`, `chaos-remove`,
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 4a22dbc61675..0188c1921aad 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -909,7 +909,8 @@ their route.
 breaker, a dead letter channel, a hand-off over seda, an input from outside), 
the most worth trying first, each with
 why and the `chaos-add` command that adds it.
 `camel get chaos` lists the current rules with their id, version, state, 
matches and the faults that fired, and
-`--report` shows the report: the verdict, the seed, every version of every 
rule and the counts per route. Chaos is for
+`--report` shows the report: the verdict, the seed, every version of every 
rule, the counts per route, and what has
+been tried per route (the kinds of fault, how it went, and what the 
suggestions offer that no rule tried yet). Chaos is for
 development only: it stays off with profile `prod`, and the commands cannot 
switch it on.
 
 === Chaos scenarios
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
index 76ddebd9f680..81f3e7c03a43 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
@@ -330,7 +330,8 @@ values start with a default, and the rule is shown as it is 
built. The last entr
 image::jbang/camel-tui-chaos-recipes.png[Adding a chaos rule from the known 
rules: the HTTP endpoint of the route is picked]
 * *w* shows what to try: the integration suggests rules per route from what 
its routes do, the most worth trying
 first, each with why (see the camel-chaos documentation, "What to try"). Enter 
types the selected rule in, to change
-or add.
+or add. A suggestion whose kind of fault was tried in its route is marked, and 
the detail says what was tried in the
+route and what is not tried yet.
 * *e* changes the selected rule, starting from the rule as it is.
 * *!* on the Endpoint tab, the Consumers tab or a node of the Diagram opens 
the known rules for what is selected, with
 it filled in: for a `to` or an endpoint the routes send to, the faults of that 
endpoint (an HTTP status only for an
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
index 26b482e16097..bf321715801a 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 import com.github.freva.asciitable.AsciiTable;
 import com.github.freva.asciitable.Column;
@@ -299,6 +300,57 @@ public class ListChaos extends ProcessWatchCommand {
                     new 
Column().header("EXCHANGE").dataAlign(HorizontalAlign.LEFT)
                             .with(r -> r.getStringOrDefault("exchangeId", 
"")))));
         }
+        printTried(report);
+    }
+
+    /**
+     * What has been tried per route (CAMEL-26728): a row per kind of fault, 
with the rules, the faults in the route and
+     * how the exchanges came out, and the kinds the suggestions offer that no 
rule tried yet.
+     */
+    private void printTried(JsonObject report) {
+        JsonArray tried = report.getCollection("tried");
+        if (tried == null || tried.isEmpty()) {
+            return;
+        }
+        List<String[]> rows = new ArrayList<>();
+        for (Object o : tried) {
+            JsonObject route = (JsonObject) o;
+            JsonArray kinds = route.getCollection("kinds");
+            if (kinds != null) {
+                for (Object k : kinds) {
+                    JsonObject kind = (JsonObject) k;
+                    List<Object> rules = kind.getCollection("rules");
+                    List<Object> expectations = 
kind.getCollection("expectations");
+                    rows.add(new String[] {
+                            route.getString("route"), kind.getString("kind"),
+                            rules != null ? 
rules.stream().map(String::valueOf).collect(Collectors.joining(", ")) : "",
+                            String.valueOf(kind.get("fired")),
+                            kind.get("recovered") + " recovered, " + 
kind.get("handled") + " handled, " + kind.get("failed")
+                                                               + " failed"
+                                                               + (expectations 
!= null && !expectations.isEmpty()
+                                                                       ? "; 
expect "
+                                                                         + 
expectations.stream().map(String::valueOf)
+                                                                               
  .collect(Collectors.joining(", "))
+                                                                       : "") 
});
+                }
+            }
+            List<Object> notTried = route.getCollection("notTried");
+            if (notTried != null && !notTried.isEmpty()) {
+                rows.add(new String[] {
+                        route.getString("route"),
+                        
notTried.stream().map(String::valueOf).collect(Collectors.joining(", ")), "", 
"", "not tried yet" });
+            }
+        }
+        printer().println("  tried, per route:");
+        printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
List.of(
+                new 
Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).with(r -> r[0]),
+                new 
Column().header("KIND").dataAlign(HorizontalAlign.LEFT).maxWidth(40, 
OverflowBehaviour.NEWLINE)
+                        .with(r -> r[1]),
+                new 
Column().header("RULES").dataAlign(HorizontalAlign.LEFT).with(r -> r[2]),
+                new 
Column().header("FIRED").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r[3]),
+                new 
Column().header("OUTCOME").dataAlign(HorizontalAlign.LEFT).maxWidth(60, 
OverflowBehaviour.NEWLINE)
+                        .with(r -> r[4]))));
     }
 
     /**
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
index f100d978b56f..56be4ec4d512 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaosTest.java
@@ -171,6 +171,33 @@ class ListChaosTest extends ProcessCommandTestSupport {
         assertThat(lineOf(out, "orders  
")).containsPattern("97\\s+6\\s+2\\s+1\\s+4");
     }
 
+    @Test
+    void whatHasBeenTriedInTheReport() throws Exception {
+        JsonObject chaos = chaosOn();
+        JsonObject kind = new JsonObject();
+        kind.put("kind", "errors");
+        kind.put("rules", new JsonArray(List.of("r1 v2")));
+        kind.put("fired", 24L);
+        kind.put("recovered", 21L);
+        kind.put("handled", 1L);
+        kind.put("failed", 0L);
+        kind.put("expectations", new JsonArray(List.of("orders:100 unmet")));
+        JsonObject route = new JsonObject();
+        route.put("route", "orders");
+        route.put("kinds", new JsonArray(List.of(kind)));
+        route.put("notTried", new JsonArray(List.of("slow", "duplicate")));
+        ((JsonObject) chaos.get("report")).put("tried", new 
JsonArray(List.of(route)));
+        writeStatus(chaos);
+
+        assertThat(run("--report")).isZero();
+
+        String out = printer.getStdout();
+        assertThat(out).contains("tried, per route:").contains("OUTCOME");
+        assertThat(lineOf(out, "r1 
v2")).contains("orders").contains("errors").contains("24")
+                .contains("21 recovered, 1 handled, 0 failed; expect 
orders:100 unmet");
+        assertThat(lineOf(out, "not tried yet")).contains("slow, duplicate");
+    }
+
     @Test
     void reportAsJson() throws Exception {
         writeStatus(chaosOn());
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
index e951863834b7..5c16edf51368 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
@@ -22,6 +22,7 @@ import java.nio.file.Path;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -265,6 +266,8 @@ final class ChaosControl {
 
     /** Asks the integration what to try, in the background; the list shows 
when it answers. */
     private void askWhatToTry() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        describing = info;
         suggestions = null;
         step = Step.SUGGEST;
         JsonObject request = new JsonObject();
@@ -288,7 +291,9 @@ final class ChaosControl {
                         : reply.getString("error"));
                 return;
             }
-            suggestions = new ChaosSuggestList(of, reply);
+            suggestions = new ChaosSuggestList(
+                    of, reply, describing != null && describing.chaos != null
+                            ? describing.chaos.tried : Map.of());
         };
         if (sender != null) {
             task.run();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosInfo.java
index bfd42f763839..d9a41f94d876 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosInfo.java
@@ -43,6 +43,8 @@ final class ChaosInfo {
     final List<Rule> rules = new ArrayList<>();
     final List<RouteCounts> routes = new ArrayList<>();
     final List<RecentFault> recentFaults = new ArrayList<>();
+    // what has been tried per route, by the route (CAMEL-26728)
+    final Map<String, TriedRoute> tried = new LinkedHashMap<>();
 
     /** A current rule, with its live state. */
     static final class Rule {
@@ -250,6 +252,10 @@ final class ChaosInfo {
     record RouteCounts(String route, long completed, long failed, long 
failedKept, long handled, long redeliveries) {
     }
 
+    /** The kinds of fault tried in a route, and those the suggestions offer 
that no rule tried yet. */
+    record TriedRoute(String route, List<String> tried, List<String> notTried) 
{
+    }
+
     /** A recent fault of a rule. */
     record RecentFault(long ago, String rule, String fault, String hook, 
String route, String where, String exchangeId) {
     }
@@ -299,6 +305,25 @@ final class ChaosInfo {
             info.seed = num(report.get("seed"), 0);
             info.seedGiven = report.getBooleanOrDefault("seedGiven", false);
             info.lost = num(report.get("lost"), 0);
+            if (report.get("tried") instanceof JsonArray tried) {
+                for (Object o : tried) {
+                    if (o instanceof JsonObject jo && jo.getString("route") != 
null) {
+                        List<String> kinds = new ArrayList<>();
+                        if (jo.get("kinds") instanceof JsonArray ks) {
+                            ks.forEach(k -> {
+                                if (k instanceof JsonObject ko && 
ko.getString("kind") != null) {
+                                    kinds.add(ko.getString("kind"));
+                                }
+                            });
+                        }
+                        List<String> notTried = new ArrayList<>();
+                        if (jo.get("notTried") instanceof JsonArray nt) {
+                            nt.forEach(k -> notTried.add(String.valueOf(k)));
+                        }
+                        info.tried.put(jo.getString("route"), new 
TriedRoute(jo.getString("route"), kinds, notTried));
+                    }
+                }
+            }
             if (report.get("reasons") instanceof JsonArray reasons) {
                 reasons.forEach(r -> info.reasons.add(String.valueOf(r)));
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosSuggestList.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosSuggestList.java
index 8915cff7450b..6fd8e25102f1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosSuggestList.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosSuggestList.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
+import java.util.Map;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
@@ -51,21 +52,28 @@ final class ChaosSuggestList {
     }
 
     /** A suggestion, as the chaos action of the integration gives it. */
-    record Item(String route, String kind, String title, String rule, String 
why) {
+    record Item(String route, String kind, String title, String rule, String 
why, boolean tried) {
     }
 
     private final String name;
     private final List<Item> items = new ArrayList<>();
+    // what has been tried per route, from the report (CAMEL-26728)
+    private final Map<String, ChaosInfo.TriedRoute> tried;
     private int selected;
 
     ChaosSuggestList(String name, JsonObject reply) {
+        this(name, reply, Map.of());
+    }
+
+    ChaosSuggestList(String name, JsonObject reply, Map<String, 
ChaosInfo.TriedRoute> tried) {
         this.name = name;
+        this.tried = tried;
         if (reply != null && reply.getCollection("suggestions") != null) {
             for (Object o : reply.getCollection("suggestions")) {
                 if (o instanceof JsonObject jo && jo.getString("rule") != 
null) {
                     items.add(new Item(
                             jo.getString("route"), jo.getString("kind"), 
jo.getString("title"),
-                            jo.getString("rule"), jo.getString("why")));
+                            jo.getString("rule"), jo.getString("why"), 
jo.getBooleanOrDefault("tried", false)));
                 }
             }
         }
@@ -133,6 +141,9 @@ final class ChaosSuggestList {
             List<Span> spans = new ArrayList<>();
             spans.add(Span.styled((sel ? " > " : "   ") + item.title(), 
style));
             spans.add(Span.styled("  " + item.route(), Theme.muted()));
+            if (item.tried()) {
+                spans.add(Span.styled("  tried", Theme.success()));
+            }
             lines.add(Line.from(spans));
         }
         // keep the selected one in sight
@@ -150,6 +161,22 @@ final class ChaosSuggestList {
         lines.add(Line.from(Span.raw(" " + item.why())));
         lines.add(Line.empty());
         lines.add(Line.from(Span.styled(" " + item.rule(), Theme.warning())));
+        if (item.tried()) {
+            lines.add(Line.empty());
+            lines.add(Line.from(Span.styled(" A rule of this kind was tried in 
route " + item.route() + " already",
+                    Theme.success())));
+        }
+        ChaosInfo.TriedRoute route = tried.get(item.route());
+        if (route != null) {
+            // what has been tried in the route, and what not yet: when it is 
all tried, the route is done
+            lines.add(Line.empty());
+            lines.add(Line.from(Span.styled(" Tried in route " + item.route() 
+ ": ", Theme.label()),
+                    Span.raw(route.tried().isEmpty() ? "nothing yet" : 
String.join(", ", route.tried()))));
+            if (!route.notTried().isEmpty()) {
+                lines.add(Line.from(Span.styled(" Not tried yet: ", 
Theme.label()),
+                        Span.styled(String.join(", ", route.notTried()), 
Theme.warning())));
+            }
+        }
         
frame.renderWidget(Paragraph.builder().overflow(Overflow.WRAP_WORD).text(Text.from(lines)).build(),
 area);
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
index 3ee695aad9a6..6f9331442905 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
@@ -77,7 +77,9 @@ error handler (HAND) and the redeliveries (REDEL).
   its routes do (an HTTP call without a circuit breaker, a dead letter
   channel, a hand-off over seda, an input from outside), the most worth
   trying first, each with why. Enter types the selected rule in, to change
-  it or add it after you confirm
+  it or add it after you confirm. A suggestion whose kind of fault was
+  tried in its route already is marked tried, and the detail says what
+  was tried in the route and what is not tried yet
 - **e** — Change the selected rule: the line starts with the rule as it is
 - **x** — Remove the selected rule
 - **c** — Clear: remove all rules (the report keeps what they did)
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
index 6f6daeacbc72..c58f9cf23b0a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
@@ -236,11 +236,15 @@ class ChaosControlTest {
                    "title": "The dead letter channel is unreachable", "rule": 
"endpoint=seda:dlq error at=dlq",
                    "why": "route shipping sends what fails to the dead letter 
channel seda:dlq"}]}
                 """);
+        // what has been tried in route orders, from the report (CAMEL-26728)
+        info.chaos.tried.put("orders",
+                new ChaosInfo.TriedRoute("orders", List.of("slow"), 
List.of("errors", "lost")));
         key('w');
         assertThat(requests).singleElement().satisfies(r -> 
assertThat(r.getString("command")).isEqualTo("suggest"));
         String screen = TuiTestHelper.renderToString(tab, 160, 40);
         assertThat(screen).contains("What to try in 
chaosdemo").contains("Returns an error (HTTP status)")
-                .contains("without a circuit breaker").contains("The dead 
letter channel is unreachable");
+                .contains("without a circuit breaker").contains("The dead 
letter channel is unreachable")
+                .contains("Tried in route orders: slow").contains("Not tried 
yet: errors, lost");
 
         tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.DOWN, KeyModifiers.NONE));
         enter();

Reply via email to