oscerd commented on code in PR #27480:
URL: https://github.com/apache/camel/pull/27480#discussion_r4204959725
##########
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:
Fixed in 572f019c6d7d. You're right — `ManagementStrategy.notify()` loops
the notifiers without isolating a failure, so catching only `Exception` let an
`Error` (e.g. `AssertionError`) thrown by a notifier escape
`AiToolExecutor.execute()` and break the "never affects the refusal" guarantee.
Now catching `Throwable` (consistent with `EventHelper.doNotifyEvent()`) and
logging at `WARN`.
Added
`AiToolAuthorizationDeniedEventTest.aNotifierThrowingAnErrorDoesNotBreakTheRefusal`:
a denying policy plus a notifier that throws an `AssertionError` for
`AiToolAuthorizationDeniedEvent` — the result is still `AuthorizationDenied`.
Verified it fails on the parent revision and passes now.
(The sibling `camel-openai` `OpenAIAgenticObservability.notify()` uses the
same catch-`Exception` pattern, but those are lifecycle events with no refusal
guarantee; I've kept this fix scoped to ai-tool where the guarantee is explicit
— happy to file a follow-up for openai if you think it's worth hardening there
too.)
_Claude Code on behalf of oscerd_
--
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]