Skip to content

Commit 7a86ef0

Browse files
docs(examples): add explain-decision example end-to-end
Closes the example-parity gap: explainDecision() shipped in April but no runnable example existed. Mirror the Rust SDK pattern (axonflow-sdk-rust#29) so callers can copy a complete demo without reading the SDK source. Reads AXONFLOW_DECISION_ID from env, calls explainDecision, and prints every ADR-043 field — policy matches, matched rules, override availability, historical hit count. Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com>
1 parent 25dd596 commit 7a86ef0

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

examples/explain-decision/pom.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.getaxonflow.examples</groupId>
6+
<artifactId>explain-decision</artifactId>
7+
<version>1.0-SNAPSHOT</version>
8+
<packaging>jar</packaging>
9+
10+
<name>AxonFlow Java SDK — Explain Decision Example</name>
11+
<description>
12+
Runnable example for the ADR-043 explainability flow. Given a
13+
decision_id from a recent blocked call, fetches the structured
14+
DecisionExplanation and prints every field. Run with mvn install
15+
on the SDK first to put the local artifact on the classpath.
16+
</description>
17+
18+
<properties>
19+
<maven.compiler.source>11</maven.compiler.source>
20+
<maven.compiler.target>11</maven.compiler.target>
21+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>com.getaxonflow</groupId>
27+
<artifactId>axonflow-sdk</artifactId>
28+
<version>7.1.0</version>
29+
</dependency>
30+
</dependencies>
31+
32+
<build>
33+
<plugins>
34+
<plugin>
35+
<groupId>org.codehaus.mojo</groupId>
36+
<artifactId>exec-maven-plugin</artifactId>
37+
<version>3.1.0</version>
38+
<configuration>
39+
<mainClass>com.getaxonflow.examples.ExplainDecision</mainClass>
40+
</configuration>
41+
</plugin>
42+
</plugins>
43+
</build>
44+
</project>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2026 AxonFlow
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
// AxonFlow Java SDK — explain a previously-made policy decision.
5+
//
6+
// Implements the ADR-043 explainability flow. Given a decision_id (typically
7+
// surfaced on the response of a blocked governed call, an audit_logs row, or
8+
// the `explain_decision` MCP tool), this example fetches the structured
9+
// explanation and renders the matched policies, risk level, and override
10+
// availability.
11+
//
12+
// Required env vars:
13+
// AXONFLOW_AGENT_URL (default: http://localhost:8080)
14+
// AXONFLOW_CLIENT_ID (default: community)
15+
// AXONFLOW_CLIENT_SECRET (default: empty)
16+
// AXONFLOW_DECISION_ID the decision to explain
17+
//
18+
// Run from this directory after `mvn install -DskipTests` at the SDK root:
19+
//
20+
// mvn -q compile exec:java
21+
package com.getaxonflow.examples;
22+
23+
import com.getaxonflow.sdk.AxonFlow;
24+
import com.getaxonflow.sdk.AxonFlowConfig;
25+
import com.getaxonflow.sdk.types.DecisionExplanation;
26+
import com.getaxonflow.sdk.types.ExplainPolicy;
27+
import com.getaxonflow.sdk.types.ExplainRule;
28+
29+
public class ExplainDecision {
30+
31+
public static void main(String[] args) {
32+
String endpoint = envOrDefault("AXONFLOW_AGENT_URL", "http://localhost:8080");
33+
String clientId = envOrDefault("AXONFLOW_CLIENT_ID", "community");
34+
String clientSecret = envOrDefault("AXONFLOW_CLIENT_SECRET", "");
35+
String decisionId = System.getenv("AXONFLOW_DECISION_ID");
36+
37+
if (decisionId == null || decisionId.isEmpty()) {
38+
System.err.println(
39+
"AXONFLOW_DECISION_ID must be set "
40+
+ "(a decision_id from a recent blocked call)");
41+
System.exit(2);
42+
}
43+
44+
System.out.println("Initializing AxonFlow client at " + endpoint + "...");
45+
try (AxonFlow client =
46+
AxonFlow.create(
47+
AxonFlowConfig.builder()
48+
.agentUrl(endpoint)
49+
.clientId(clientId)
50+
.clientSecret(clientSecret)
51+
.build())) {
52+
53+
System.out.println("Explaining decision " + decisionId + "...\n");
54+
DecisionExplanation exp = client.explainDecision(decisionId);
55+
56+
System.out.println("=== Decision Explanation ===");
57+
System.out.println(" decision_id: " + exp.getDecisionId());
58+
System.out.println(" timestamp: " + exp.getTimestamp());
59+
System.out.println(" decision: " + exp.getDecision());
60+
System.out.println(" reason: " + exp.getReason());
61+
if (exp.getRiskLevel() != null && !exp.getRiskLevel().isEmpty()) {
62+
System.out.println(" risk_level: " + exp.getRiskLevel());
63+
}
64+
if (exp.getToolSignature() != null && !exp.getToolSignature().isEmpty()) {
65+
System.out.println(" tool: " + exp.getToolSignature());
66+
}
67+
68+
System.out.printf("%n policy_matches (%d):%n", exp.getPolicyMatches().size());
69+
int i = 0;
70+
for (ExplainPolicy m : exp.getPolicyMatches()) {
71+
String name = m.getPolicyName() != null ? m.getPolicyName() : "(unnamed)";
72+
String action = m.getAction() != null ? m.getAction() : "-";
73+
String risk = m.getRiskLevel() != null ? m.getRiskLevel() : "-";
74+
System.out.printf(
75+
" [%d] %s (%s) — action=%s risk=%s allow_override=%b%n",
76+
i, m.getPolicyId(), name, action, risk, m.isAllowOverride());
77+
i++;
78+
}
79+
80+
if (exp.getMatchedRules() != null && !exp.getMatchedRules().isEmpty()) {
81+
System.out.printf("%n matched_rules (%d):%n", exp.getMatchedRules().size());
82+
for (ExplainRule r : exp.getMatchedRules()) {
83+
String ruleId = r.getRuleId() != null ? r.getRuleId() : "(no rule id)";
84+
String matchedOn = r.getMatchedOn() != null ? r.getMatchedOn() : "-";
85+
System.out.printf(
86+
" %s on %s: matched=%s%n", r.getPolicyId(), ruleId, matchedOn);
87+
}
88+
}
89+
90+
System.out.printf("%n override_available: %b%n",
91+
exp.isOverrideAvailable());
92+
if (exp.getOverrideExistingId() != null && !exp.getOverrideExistingId().isEmpty()) {
93+
System.out.println(" override_existing_id: "
94+
+ exp.getOverrideExistingId());
95+
}
96+
System.out.println(" historical_hit_count_session: "
97+
+ exp.getHistoricalHitCountSession());
98+
if (exp.getPolicySourceLink() != null && !exp.getPolicySourceLink().isEmpty()) {
99+
System.out.println(" policy_source_link: "
100+
+ exp.getPolicySourceLink());
101+
}
102+
}
103+
}
104+
105+
private static String envOrDefault(String key, String fallback) {
106+
String v = System.getenv(key);
107+
return (v == null || v.isEmpty()) ? fallback : v;
108+
}
109+
}

0 commit comments

Comments
 (0)