diff --git a/CHANGELOG.md b/CHANGELOG.md index 98ea474..7995e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.1.0] - 2026-04-25 — Plugin Batch 1 / ADR-043 explainability fields on MCP responses + +Minor release. Surfaces fields the AxonFlow agent has emitted since v7.1.0 (Plugin Batch 1 / ADR-042 / ADR-043) but the SDK didn't declare. Pure Cat B field-additions on existing methods — additive only, no breaking changes. The pre-existing constructors are preserved as source-compat overloads. Documented in OpenAPI via platform v7.4.3 (axonflow-enterprise#1714); SDK catches up here. + +Coordinated cycle: TypeScript v6.1.0 / Python v6.8.0 / Go v5.8.0 ship same day with the same field set. + +### Added + +- **`MCPCheckInputResponse`** gains 5 optional Plugin Batch 1 fields: + - `decisionId: String` — audit correlator + - `riskLevel: String` — `low` | `medium` | `high` | `critical` + - `policyMatches: List` — per-policy explainability records + - `overrideAvailable: Boolean` — whether session override is permitted for the matched policies (boxed so callers can distinguish "unset" from `false` on older platforms) + - `overrideExistingId: String` — already-active override consumed by this decision (if any) +- **`MCPCheckOutputResponse`** gains 3 optional fields: + - `decisionId: String` + - `policyMatches: List` + - `redactedMessage: String` — text-redaction counterpart to `redactedData` (used when the connector returned a string message rather than tabular rows; e.g. execute-style responses) + +`ExplainPolicy` already shipped — same Jackson-annotated record now reused on the MCP response types. Pre-v7.1.0 platforms leave all new fields as `null`; callers should treat `null` as "context not available" rather than an error. + +### Source compatibility + +Both `MCPCheckInputResponse` and `MCPCheckOutputResponse` retain their v6.0.0 constructor signatures as overloads that delegate to the new `@JsonCreator` constructors with `null` for the new fields. Existing callers that build response instances locally compile unchanged. `equals()` / `hashCode()` / `toString()` updated to include the new fields. + +### Deferred + +`client.explainDecision(decisionId)` and the full `ExplainRule` / `DecisionExplanation` type surface are tracked separately as feature work — see axonflow-enterprise#1716. This release ships only field-surfacing on existing methods. + ## [6.0.0] - 2026-04-25 — Major: WebhookSubscription identity-based equality This is a major release. The bump is driven by a single observable-contract change: `WebhookSubscription.equals()` and `.hashCode()` now compare on `id` only, not every field. Coordinated with the TypeScript SDK v6.0.0 release (PolicyInfo rename) as a v6 alignment cycle for the SDKs that needed breaking changes; Python (v6.7.0) and Go (v5.7.0) ship as minor on the same day because their changes are purely additive. diff --git a/pom.xml b/pom.xml index c26fa07..620c40d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.getaxonflow axonflow-sdk - 6.0.0 + 6.1.0 jar AxonFlow Java SDK diff --git a/src/main/java/com/getaxonflow/sdk/types/MCPCheckInputResponse.java b/src/main/java/com/getaxonflow/sdk/types/MCPCheckInputResponse.java index ee50c5c..281e0c8 100644 --- a/src/main/java/com/getaxonflow/sdk/types/MCPCheckInputResponse.java +++ b/src/main/java/com/getaxonflow/sdk/types/MCPCheckInputResponse.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; import java.util.Objects; /** @@ -26,6 +27,11 @@ *

Indicates whether the input statement is allowed by configured policies. A 403 HTTP response * still returns a valid response body with {@code allowed=false} and details in {@code blockReason} * and {@code policyInfo}. + * + *

The five Plugin Batch 1 / ADR-042 / ADR-043 fields ({@code decisionId}, {@code riskLevel}, + * {@code policyMatches}, {@code overrideAvailable}, {@code overrideExistingId}) are populated + * when the AxonFlow platform is v7.1.0+. Pre-v7.1.0 platforms leave these as {@code null}. + * Source of truth: {@code platform/agent/mcp_server_handler.go:880-940}. */ @JsonIgnoreProperties(ignoreUnknown = true) public final class MCPCheckInputResponse { @@ -42,16 +48,52 @@ public final class MCPCheckInputResponse { @JsonProperty("policy_info") private final ConnectorPolicyInfo policyInfo; + @JsonProperty("decision_id") + private final String decisionId; + + @JsonProperty("risk_level") + private final String riskLevel; + + @JsonProperty("policy_matches") + private final List policyMatches; + + @JsonProperty("override_available") + private final Boolean overrideAvailable; + + @JsonProperty("override_existing_id") + private final String overrideExistingId; + @JsonCreator public MCPCheckInputResponse( @JsonProperty("allowed") boolean allowed, @JsonProperty("block_reason") String blockReason, @JsonProperty("policies_evaluated") int policiesEvaluated, - @JsonProperty("policy_info") ConnectorPolicyInfo policyInfo) { + @JsonProperty("policy_info") ConnectorPolicyInfo policyInfo, + @JsonProperty("decision_id") String decisionId, + @JsonProperty("risk_level") String riskLevel, + @JsonProperty("policy_matches") List policyMatches, + @JsonProperty("override_available") Boolean overrideAvailable, + @JsonProperty("override_existing_id") String overrideExistingId) { this.allowed = allowed; this.blockReason = blockReason; this.policiesEvaluated = policiesEvaluated; this.policyInfo = policyInfo; + this.decisionId = decisionId; + this.riskLevel = riskLevel; + this.policyMatches = policyMatches; + this.overrideAvailable = overrideAvailable; + this.overrideExistingId = overrideExistingId; + } + + /** + * Source-compat overload. Callers that build {@code MCPCheckInputResponse} instances locally + * with the v6.0.0 4-argument shape continue to compile — the five Plugin Batch 1 fields default + * to {@code null}. Server-side responses always go through the {@code @JsonCreator} 9-arg + * constructor regardless. + */ + public MCPCheckInputResponse( + boolean allowed, String blockReason, int policiesEvaluated, ConnectorPolicyInfo policyInfo) { + this(allowed, blockReason, policiesEvaluated, policyInfo, null, null, null, null, null); } /** Returns whether the input is allowed by policies. */ @@ -74,6 +116,46 @@ public ConnectorPolicyInfo getPolicyInfo() { return policyInfo; } + /** + * Returns the audit correlator for this policy decision (Plugin Batch 1, v7.1.0+). Null on + * older platforms. + */ + public String getDecisionId() { + return decisionId; + } + + /** + * Returns the highest risk level across matched policies ({@code low} | {@code medium} | + * {@code high} | {@code critical}; Plugin Batch 1, v7.1.0+). Null on older platforms. + */ + public String getRiskLevel() { + return riskLevel; + } + + /** + * Returns the per-policy explainability records (ADR-043, v7.1.0+). Null on older platforms. + */ + public List getPolicyMatches() { + return policyMatches; + } + + /** + * Returns whether at least one matched policy permits a session override (Plugin Batch 1, + * v7.1.0+). Null on older platforms; callers should treat null as "context not available" + * rather than {@code false}. + */ + public Boolean getOverrideAvailable() { + return overrideAvailable; + } + + /** + * Returns the ID of an active override consumed by this decision, if any (Plugin Batch 1, + * v7.1.0+). Null on older platforms or when no override was consumed. + */ + public String getOverrideExistingId() { + return overrideExistingId; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -82,12 +164,26 @@ public boolean equals(Object o) { return allowed == that.allowed && policiesEvaluated == that.policiesEvaluated && Objects.equals(blockReason, that.blockReason) - && Objects.equals(policyInfo, that.policyInfo); + && Objects.equals(policyInfo, that.policyInfo) + && Objects.equals(decisionId, that.decisionId) + && Objects.equals(riskLevel, that.riskLevel) + && Objects.equals(policyMatches, that.policyMatches) + && Objects.equals(overrideAvailable, that.overrideAvailable) + && Objects.equals(overrideExistingId, that.overrideExistingId); } @Override public int hashCode() { - return Objects.hash(allowed, blockReason, policiesEvaluated, policyInfo); + return Objects.hash( + allowed, + blockReason, + policiesEvaluated, + policyInfo, + decisionId, + riskLevel, + policyMatches, + overrideAvailable, + overrideExistingId); } @Override @@ -102,6 +198,19 @@ public String toString() { + policiesEvaluated + ", policyInfo=" + policyInfo + + ", decisionId='" + + decisionId + + '\'' + + ", riskLevel='" + + riskLevel + + '\'' + + ", policyMatches=" + + policyMatches + + ", overrideAvailable=" + + overrideAvailable + + ", overrideExistingId='" + + overrideExistingId + + '\'' + '}'; } } diff --git a/src/main/java/com/getaxonflow/sdk/types/MCPCheckOutputResponse.java b/src/main/java/com/getaxonflow/sdk/types/MCPCheckOutputResponse.java index 1fa8a4b..933f0b1 100644 --- a/src/main/java/com/getaxonflow/sdk/types/MCPCheckOutputResponse.java +++ b/src/main/java/com/getaxonflow/sdk/types/MCPCheckOutputResponse.java @@ -18,14 +18,20 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; import java.util.Objects; /** * Response from the MCP output policy check endpoint. * - *

Indicates whether the output data passes configured policies. May include redacted data if PII - * redaction policies are active, and exfiltration check information if data volume limits are - * configured. + *

Indicates whether the output data passes configured policies. May include redacted data + * (tabular) or a redacted message (text) if PII redaction policies are active, and exfiltration + * check information if data volume limits are configured. + * + *

The three Plugin Batch 1 / ADR-043 fields ({@code decisionId}, {@code policyMatches}, + * {@code redactedMessage}) are populated when the AxonFlow platform is v7.1.0+. Pre-v7.1.0 + * platforms leave these as {@code null}. Source of truth: {@code + * platform/agent/mcp_server_handler.go:988, 1005, 1051}. */ @JsonIgnoreProperties(ignoreUnknown = true) public final class MCPCheckOutputResponse { @@ -39,6 +45,9 @@ public final class MCPCheckOutputResponse { @JsonProperty("redacted_data") private final Object redactedData; + @JsonProperty("redacted_message") + private final String redactedMessage; + @JsonProperty("policies_evaluated") private final int policiesEvaluated; @@ -48,20 +57,57 @@ public final class MCPCheckOutputResponse { @JsonProperty("policy_info") private final ConnectorPolicyInfo policyInfo; + @JsonProperty("decision_id") + private final String decisionId; + + @JsonProperty("policy_matches") + private final List policyMatches; + @JsonCreator public MCPCheckOutputResponse( @JsonProperty("allowed") boolean allowed, @JsonProperty("block_reason") String blockReason, @JsonProperty("redacted_data") Object redactedData, + @JsonProperty("redacted_message") String redactedMessage, @JsonProperty("policies_evaluated") int policiesEvaluated, @JsonProperty("exfiltration_info") ExfiltrationCheckInfo exfiltrationInfo, - @JsonProperty("policy_info") ConnectorPolicyInfo policyInfo) { + @JsonProperty("policy_info") ConnectorPolicyInfo policyInfo, + @JsonProperty("decision_id") String decisionId, + @JsonProperty("policy_matches") List policyMatches) { this.allowed = allowed; this.blockReason = blockReason; this.redactedData = redactedData; + this.redactedMessage = redactedMessage; this.policiesEvaluated = policiesEvaluated; this.exfiltrationInfo = exfiltrationInfo; this.policyInfo = policyInfo; + this.decisionId = decisionId; + this.policyMatches = policyMatches; + } + + /** + * Source-compat overload. Callers that build {@code MCPCheckOutputResponse} instances locally + * with the v6.0.0 6-argument shape continue to compile — {@code redactedMessage}, {@code + * decisionId}, and {@code policyMatches} default to {@code null}. Server-side responses always + * go through the {@code @JsonCreator} 9-arg constructor regardless. + */ + public MCPCheckOutputResponse( + boolean allowed, + String blockReason, + Object redactedData, + int policiesEvaluated, + ExfiltrationCheckInfo exfiltrationInfo, + ConnectorPolicyInfo policyInfo) { + this( + allowed, + blockReason, + redactedData, + null, + policiesEvaluated, + exfiltrationInfo, + policyInfo, + null, + null); } /** Returns whether the output data is allowed by policies. */ @@ -74,11 +120,24 @@ public String getBlockReason() { return blockReason; } - /** Returns the redacted version of the data, or null if no redaction was applied. */ + /** + * Returns the redacted tabular data with PII fields masked (used when the connector returned + * rows; e.g. SQL/CSV results). Null if no redaction was applied or if the response was a text + * message. + */ public Object getRedactedData() { return redactedData; } + /** + * Returns the redacted text message with PII fields masked (used when the connector returned a + * string message rather than tabular rows; e.g. execute-style responses). Null if no redaction + * was applied or if the response was tabular. + */ + public String getRedactedMessage() { + return redactedMessage; + } + /** Returns the number of policies evaluated. */ public int getPoliciesEvaluated() { return policiesEvaluated; @@ -94,6 +153,21 @@ public ConnectorPolicyInfo getPolicyInfo() { return policyInfo; } + /** + * Returns the audit correlator for this policy decision (Plugin Batch 1, v7.1.0+). Null on + * older platforms. + */ + public String getDecisionId() { + return decisionId; + } + + /** + * Returns the per-policy explainability records (ADR-043, v7.1.0+). Null on older platforms. + */ + public List getPolicyMatches() { + return policyMatches; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -103,14 +177,25 @@ public boolean equals(Object o) { && policiesEvaluated == that.policiesEvaluated && Objects.equals(blockReason, that.blockReason) && Objects.equals(redactedData, that.redactedData) + && Objects.equals(redactedMessage, that.redactedMessage) && Objects.equals(exfiltrationInfo, that.exfiltrationInfo) - && Objects.equals(policyInfo, that.policyInfo); + && Objects.equals(policyInfo, that.policyInfo) + && Objects.equals(decisionId, that.decisionId) + && Objects.equals(policyMatches, that.policyMatches); } @Override public int hashCode() { return Objects.hash( - allowed, blockReason, redactedData, policiesEvaluated, exfiltrationInfo, policyInfo); + allowed, + blockReason, + redactedData, + redactedMessage, + policiesEvaluated, + exfiltrationInfo, + policyInfo, + decisionId, + policyMatches); } @Override @@ -127,6 +212,11 @@ public String toString() { + exfiltrationInfo + ", policyInfo=" + policyInfo + + ", decisionId='" + + decisionId + + '\'' + + ", policyMatches=" + + policyMatches + '}'; } } diff --git a/tests/fixtures/wire-shape-baseline.json b/tests/fixtures/wire-shape-baseline.json index 08d9656..515f3c9 100644 --- a/tests/fixtures/wire-shape-baseline.json +++ b/tests/fixtures/wire-shape-baseline.json @@ -182,7 +182,7 @@ "PolicyMatch": 2 } }, - "openapi_specs_sha": "10d44eb4585a7b5321d7286b354ff8b43b8c6a57", + "openapi_specs_sha": "8c8d6c9c506aa25fcd8a887f6fc71f71b5d6bc30", "per_type_drift": { "AuditLogEntry": { "sdk_only": [ @@ -264,6 +264,20 @@ "started_at" ] }, + "DecisionExplanation": { + "sdk_only": [ + "decisionId", + "historicalHitCountSession", + "matchedRules", + "overrideAvailable", + "overrideExistingId", + "policyMatches", + "policySourceLink", + "riskLevel", + "toolSignature" + ], + "spec_only": [] + }, "DynamicPolicy": { "sdk_only": [ "category", @@ -320,6 +334,25 @@ "limit_type" ] }, + "ExplainPolicy": { + "sdk_only": [ + "allowOverride", + "policyDescription", + "policyId", + "policyName", + "riskLevel" + ], + "spec_only": [] + }, + "ExplainRule": { + "sdk_only": [ + "matchedOn", + "policyId", + "ruleId", + "ruleText" + ], + "spec_only": [] + }, "ListWorkflowsResponse": { "sdk_only": [], "spec_only": [ @@ -558,6 +591,7 @@ "CreateWebhookRequest", "CreateWorkflowRequest", "CreateWorkflowResponse", + "DecisionExplanation", "DynamicPolicy", "DynamicPolicyInfo", "DynamicPolicyMatch", @@ -565,6 +599,8 @@ "ExecutionSnapshot", "ExecutionSummary", "ExfiltrationCheckInfo", + "ExplainPolicy", + "ExplainRule", "ListWorkflowsResponse", "MCPCheckInputRequest", "MCPCheckInputResponse",