davsclaus commented on code in PR #27649:
URL: https://github.com/apache/camel/pull/27649#discussion_r4236895518
##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -713,20 +786,50 @@ private Object evaluate(Exchange exchange, boolean
asPredicate) {
return Collections.unmodifiableMap(ordered);
}
SemanticEvaluation evaluation =
current.evaluations.get(names.get(0));
+ active.add(invocations.get(names.get(0)));
SemanticResult result =
current.groups.get(0).expert.provider.evaluate(evaluation, state);
+ failureReason = "invalid_result";
Object decision = validateResult(names.get(0),
current.groups.get(0).expert,
current.operations.get(names.get(0)), result);
+ invocations.get(names.get(0)).complete("success",
"evaluation_completed", result);
exchange.setProperty(RESULT, result);
return decision;
} catch (InterruptedException e) {
+ finishFailed(invocations, active, "interrupted");
exchange.removeProperty(RESULT);
exchange.removeProperty(RESULTS);
Thread.currentThread().interrupt();
throw RuntimeCamelException.wrapRuntimeCamelException(e);
- } catch (Exception e) {
+ } catch (Exception | AssertionError e) {
+ if (invocations.isEmpty()) {
+ for (String name : names) {
+ SemanticEvaluation declaration = selected.get(name);
+ SemanticAudit.Invocation rejected =
audit.begin(exchange, name, declaration, null,
+ auditExpert(declaration), null, null, null);
+ invocations.put(name, rejected);
+ rejected.complete("failed", failureReason, null);
+ }
+ }
+ finishFailed(invocations, active, failureReason);
exchange.removeProperty(RESULT);
exchange.removeProperty(RESULTS);
+ if (e instanceof AssertionError error) {
+ throw error;
+ }
throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ } finally {
+ if (!invocations.isEmpty()) {
+ exchange.setProperty(SemanticAudit.REFERENCES,
invocations.values().stream()
Review Comment:
With audit disabled and no observers, `begin()` returns the shared no-op
invocation, so `invocations` is never empty and every semantic
expression/predicate now sets `CamelSemanticAuditReferences` to an empty list.
That adds noise to the tracer, message history and debugger for users who never
enabled audit. Could the property only be set when at least one event ID is
non-null (same for the earlier set at line 734)?
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java:
##########
@@ -231,17 +231,18 @@ private String callSaveSummary(Map<String, Object> args) {
/** The TUI's own read-only tools; the shared tools add those flagged
read-only in the registry. */
private static final Set<String> READ_ONLY_TUI_TOOLS = Set.of(
- "tui_get_ai_log", "tui_get_diagram", "tui_get_events",
"tui_get_history", "tui_get_mcp_log",
+ "tui_get_audit", "tui_get_ai_log", "tui_get_diagram",
"tui_get_events", "tui_get_history", "tui_get_mcp_log",
"tui_get_ollama", "tui_get_options", "tui_get_processor_detail",
"tui_get_readme", "tui_get_screen",
"tui_get_spans", "tui_http_endpoints",
"tui_get_state", "tui_get_status", "tui_get_table",
"tui_get_themes", "tui_get_topology",
"tui_list_examples", "tui_locate", "tui_wait_for_idle");
/**
- * Tools that only return information and never change the TUI, the
integration or its data. The ACP permission
- * handler approves calls to these without asking; anything else,
including camel_control, tui_send_message and
- * tui_execute_sql, is put in front of the user. camel_eval_expression is
read-only because it evaluates an
- * expression against a scratch exchange and sends nothing through a route.
+ * Tools that inspect information without executing application actions or
editing data. An inspection may select
Review Comment:
This rewrites the definition of `READ_ONLY_TOOLS` (the set ACP auto-approves
without asking) so that a read-only tool may now change the visible
tab/view/filter/selection, to accommodate `tui_get_audit`. That widens a
safety-relevant definition for all tools. Could `tui_get_audit` instead return
the audit data without switching the user's screen, or be left out of the
auto-approve set?
Related: rather than raising `FULL_BUDGET_TOKENS` to 10,750 (measured
~10,563) for every session, `tui_get_audit` could be a tool group in
`TuiToolGroups`, like the SQL and HTTP tools, loaded only when the selected
integration has camel-semantic.
##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticAuditRecord.java:
##########
@@ -0,0 +1,137 @@
+/*
+ * 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.semantic;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Immutable, size-bounded snapshot. It never contains the exchange, input or
provider exception. Schema version 1 uses
+ * UTC timestamps and keeps route actions separate from expert results.
+ */
+public final class SemanticAuditRecord {
+ private final Map<String, Object> fields;
+
+ SemanticAuditRecord(Map<String, Object> fields) {
+ this.fields = Map.copyOf(fields);
Review Comment:
`Map.copyOf` does not preserve iteration order, so the carefully built field
order from `begin()`/`base()` is lost: `toMap()`, the `log` sink JSON and the
console output list fields in an unpredictable order.
```suggestion
this.fields = Collections.unmodifiableMap(new
LinkedHashMap<>(fields));
```
##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticAudit.java:
##########
@@ -0,0 +1,515 @@
+/*
+ * 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.semantic;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.builder.ThreadPoolBuilder;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.concurrent.ThreadPoolRejectedPolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Context-owned audit dispatcher and independent semantic observation hooks.
*/
+public final class SemanticAudit extends ServiceSupport {
+ public static final String REFERENCES = "CamelSemanticAuditReferences";
+ public static final String REQUEST = "CamelSemanticAuditRequest";
+ private static final Logger LOG =
LoggerFactory.getLogger(SemanticAudit.class);
+ private static final Object CREATION_LOCK = new Object();
+ private final CamelContext context;
+ private volatile SemanticAuditConfiguration configuration =
SemanticAuditConfiguration.DISABLED;
+ private String configurationSource;
+ private final AtomicLong dropped = new AtomicLong();
+ private final AtomicLong observerErrors = new AtomicLong();
+ private final Map<String, AtomicLong> sinkErrors = new LinkedHashMap<>();
+ private final Map<String, SemanticAuditSink> sinks = new LinkedHashMap<>();
+ private volatile List<SemanticObserver> observers = List.of();
+ private volatile ExecutorService executor;
+ private MemorySemanticAuditStore memory;
+ private volatile SemanticAuditReader reader;
+ private volatile boolean activated;
+ private boolean frozen;
+ private final Invocation noop = new Invocation(null, false);
+
+ private SemanticAudit(CamelContext context) {
+ this.context = context;
+ }
+
+ public static SemanticAudit get(CamelContext context) {
+ var extension = context.getCamelContextExtension();
+ SemanticAudit audit = extension.getContextPlugin(SemanticAudit.class);
+ if (audit == null) {
+ synchronized (CREATION_LOCK) {
+ audit = extension.getContextPlugin(SemanticAudit.class);
+ if (audit == null) {
+ audit = new SemanticAudit(context);
+ extension.addContextPlugin(SemanticAudit.class, audit);
+ }
+ }
+ }
+ return audit;
+ }
+
+ public synchronized void validateConfiguration(String source,
SemanticAuditConfiguration candidate) {
+ if (configurationSource != null && !configurationSource.equals(source)
&& candidate != null) {
+ throw new IllegalArgumentException("Declare semantic audit
configuration in only one resource");
+ }
+ if (candidate == null && !source.equals(configurationSource)) {
+ return;
+ }
+ SemanticAuditConfiguration next = candidate == null ?
SemanticAuditConfiguration.DISABLED : candidate;
+ if (frozen && !configuration.equals(next)) {
+ throw new IllegalArgumentException("Changing semantic audit
configuration requires a context restart");
+ }
+ }
+
+ public synchronized void configure(String source,
SemanticAuditConfiguration candidate) {
+ validateConfiguration(source, candidate);
+ if (candidate != null || source.equals(configurationSource)) {
+ configuration = candidate == null ?
SemanticAuditConfiguration.DISABLED : candidate;
+ configurationSource = candidate == null ? null : source;
+ }
+ }
+
+ synchronized void removeConfigurationSource(String source) {
+ if (source.equals(configurationSource)) {
+ configurationSource = null;
+ if (frozen) {
+ LOG.warn("Semantic audit resource {} was deleted; retaining
the active configuration until context restart",
+ source);
+ } else {
+ configuration = SemanticAuditConfiguration.DISABLED;
+ }
+ }
+ }
+
+ public void activate() {
+ if (!activated) {
+ synchronized (this) {
+ if (!activated) {
+ try {
+ context.addService(this, true, true);
+ activated = true;
+ } catch (Exception e) {
+ throw
RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ synchronized (this) {
+ frozen = true;
Review Comment:
`frozen` (and `memory` with its capacity) is never reset in `doStop()`.
After an in-place `stop()`/`start()` of the CamelContext, a changed audit block
is still rejected with "requires a context restart", even though the context
was just restarted. Either reset in `doStop()`, or adjust the message/docs to
say a JVM/application restart is required.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]