luigidemasi commented on code in PR #27480:
URL: https://github.com/apache/camel/pull/27480#discussion_r4204879642


##########
components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java:
##########
@@ -176,17 +177,42 @@ public static AiToolResult execute(AiToolSpec spec, 
Map<String, Object> argument
      * present in its cause chain (the route's {@link 
org.apache.camel.spi.AuthorizationPolicy} rejected the call).
      * Returns a caller-safe {@link AiToolResult.AuthorizationDenied} refusal 
that does not leak the policy's internal
      * message, or {@code null} when the error is not an authorization denial.
+     * <p>
+     * On a denial it also logs at {@code WARN} and emits an {@link 
AiToolAuthorizationDeniedEvent} so operators can
+     * observe and alert on denials; neither affects the refusal returned to 
the model.
      */
-    private static AiToolResult authorizationDenied(String toolName, Throwable 
error) {
+    private static AiToolResult authorizationDenied(String toolName, Throwable 
error, Exchange exchange) {
         CamelAuthorizationException denial = findAuthorizationException(error);
         if (denial == null) {
             return null;
         }
         LOG.warn("Tool '{}' call denied by authorization policy: {}", 
toolName, denial.getMessage());
+        fireAuthorizationDeniedEvent(exchange, toolName, denial);
         return new AiToolResult.AuthorizationDenied(
                 String.format("Access denied: not authorized to call tool 
'%s'", toolName), denial);
     }
 
+    /**
+     * Emits an {@link AiToolAuthorizationDeniedEvent} for the denied tool 
call. The policy guards the route's outer
+     * processor, so a denial never runs the route's unit of work and fires no 
exchange-lifecycle event or route span;
+     * this event is the observable signal. Best-effort: when no {@code 
EventNotifier} is registered nothing is emitted,
+     * and a failure to notify is swallowed so it never affects the refusal 
returned to the model.
+     */
+    private static void fireAuthorizationDeniedEvent(Exchange exchange, String 
toolName, CamelAuthorizationException denial) {
+        ManagementStrategy management = 
exchange.getContext().getManagementStrategy();
+        if (management.getEventNotifiers().isEmpty()) {
+            return;
+        }
+        try {
+            management.notify(new AiToolAuthorizationDeniedEvent(exchange, 
toolName, denial));
+        } catch (Exception e) {

Review Comment:
   **P2 — Preserve the authorization refusal when a notifier throws an Error.**
   
   This catches only `Exception`, and `DefaultManagementStrategy.notify()` does 
not isolate failures from individual notifiers. If a registered notifier throws 
an `AssertionError` while handling this event, that error escapes 
`AiToolExecutor.execute()` instead of returning `AuthorizationDenied`. I 
reproduced this with a denying policy and a notifier that throws only for 
`AiToolAuthorizationDeniedEvent`: the parent revision returns 
`AuthorizationDenied`, while this revision propagates the assertion error.
   
   That contradicts the new guarantee that notification failures never affect 
the refusal returned to the model. Please catch `Throwable` here, consistent 
with `EventHelper.doNotifyEvent()`, and add a regression test proving the 
refusal survives a notifier failure. The guarded route remains blocked; the 
regression is in refusal handling.
   
   _AI-generated review by Codex on behalf of Luigi De Masi (@luigidemasi)._



-- 
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]

Reply via email to