From d11a9322b2a273d58ffbfb0dc578a7e5b2515123 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Tue, 26 May 2026 15:29:25 +0200 Subject: [PATCH 1/3] feat(indonesia): add pii-indonesia category, cross-border audit fields Add PII_INDONESIA to PolicyCategory enum ("pii-indonesia") for Indonesian PII detection (NIK, KK, NPWP, BPJS). Add dataResidency and transferBasis nullable fields to AuditLogEntry for cross-border data transfer logging. Wire fields are data_residency and transfer_basis; both nullable for backward compatibility. Signed-off-by: Saurabh Jain Signed-off-by: Saurabh Jain --- CHANGELOG.md | 13 ++ .../getaxonflow/sdk/types/AuditLogEntry.java | 36 ++++- .../sdk/types/policies/PolicyTypes.java | 1 + .../sdk/types/IndonesiaPiiAuditTest.java | 138 ++++++++++++++++++ 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/getaxonflow/sdk/types/IndonesiaPiiAuditTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c1eb2df..e2c5077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to the AxonFlow Java SDK will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **`PII_INDONESIA` policy category constant** (`"pii-indonesia"`). + Enables filtering and creating policies for Indonesian PII detection + (NIK, KK, NPWP, BPJS) alongside the existing per-jurisdiction categories. +- **`dataResidency` and `transferBasis` fields on `AuditLogEntry`.** + Optional string fields supporting cross-border data transfer logging. + `dataResidency` is an ISO 3166-1 alpha-2 country code; + `transferBasis` is one of `adequacy`, `safeguards`, or `consent`. + Both are nullable for backward compatibility with older platform versions. + ## [8.2.0] - 2026-05-23 — `createHITLRequest` for explicit HITL row creation Enables agent-framework callers (Google ADK, n8n, OpenAI Agents SDK) to diff --git a/src/main/java/com/getaxonflow/sdk/types/AuditLogEntry.java b/src/main/java/com/getaxonflow/sdk/types/AuditLogEntry.java index 6e60e3f..9df545e 100644 --- a/src/main/java/com/getaxonflow/sdk/types/AuditLogEntry.java +++ b/src/main/java/com/getaxonflow/sdk/types/AuditLogEntry.java @@ -78,6 +78,12 @@ public final class AuditLogEntry { @JsonProperty("metadata") private final Map metadata; + @JsonProperty("data_residency") + private final String dataResidency; + + @JsonProperty("transfer_basis") + private final String transferBasis; + public AuditLogEntry( @JsonProperty("id") String id, @JsonProperty("request_id") String requestId, @@ -95,7 +101,9 @@ public AuditLogEntry( @JsonProperty("tokens_used") Integer tokensUsed, @JsonProperty("latency_ms") Integer latencyMs, @JsonProperty("policy_violations") List policyViolations, - @JsonProperty("metadata") Map metadata) { + @JsonProperty("metadata") Map metadata, + @JsonProperty("data_residency") String dataResidency, + @JsonProperty("transfer_basis") String transferBasis) { this.id = id != null ? id : ""; this.requestId = requestId != null ? requestId : ""; this.timestamp = timestamp != null ? timestamp : Instant.now(); @@ -113,6 +121,8 @@ public AuditLogEntry( this.latencyMs = latencyMs != null ? latencyMs : 0; this.policyViolations = policyViolations != null ? policyViolations : Collections.emptyList(); this.metadata = metadata != null ? metadata : Collections.emptyMap(); + this.dataResidency = dataResidency; + this.transferBasis = transferBasis; } /** Returns the unique audit log ID. */ @@ -200,6 +210,16 @@ public Map getMetadata() { return metadata; } + /** Returns the ISO 3166-1 alpha-2 data residency country code, or null if not set. */ + public String getDataResidency() { + return dataResidency; + } + + /** Returns the cross-border transfer basis (adequacy, safeguards, or consent), or null if not set. */ + public String getTransferBasis() { + return transferBasis; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -221,7 +241,9 @@ public boolean equals(Object o) { && Objects.equals(provider, that.provider) && Objects.equals(model, that.model) && Objects.equals(policyViolations, that.policyViolations) - && Objects.equals(metadata, that.metadata); + && Objects.equals(metadata, that.metadata) + && Objects.equals(dataResidency, that.dataResidency) + && Objects.equals(transferBasis, that.transferBasis); } @Override @@ -243,7 +265,9 @@ public int hashCode() { tokensUsed, latencyMs, policyViolations, - metadata); + metadata, + dataResidency, + transferBasis); } @Override @@ -269,6 +293,12 @@ public String toString() { + blocked + ", riskScore=" + riskScore + + ", dataResidency='" + + dataResidency + + '\'' + + ", transferBasis='" + + transferBasis + + '\'' + '}'; } } diff --git a/src/main/java/com/getaxonflow/sdk/types/policies/PolicyTypes.java b/src/main/java/com/getaxonflow/sdk/types/policies/PolicyTypes.java index f18f248..29aa39f 100644 --- a/src/main/java/com/getaxonflow/sdk/types/policies/PolicyTypes.java +++ b/src/main/java/com/getaxonflow/sdk/types/policies/PolicyTypes.java @@ -58,6 +58,7 @@ public enum PolicyCategory { PII_EU("pii-eu"), PII_INDIA("pii-india"), PII_SINGAPORE("pii-singapore"), + PII_INDONESIA("pii-indonesia"), // Static policy categories - Code Governance CODE_SECRETS("code-secrets"), diff --git a/src/test/java/com/getaxonflow/sdk/types/IndonesiaPiiAuditTest.java b/src/test/java/com/getaxonflow/sdk/types/IndonesiaPiiAuditTest.java new file mode 100644 index 0000000..2c48e0d --- /dev/null +++ b/src/test/java/com/getaxonflow/sdk/types/IndonesiaPiiAuditTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2025 AxonFlow + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + */ +package com.getaxonflow.sdk.types; + +import static org.assertj.core.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.getaxonflow.sdk.types.policies.PolicyTypes.PolicyCategory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("Indonesia PII + AuditLogEntry cross-border fields") +class IndonesiaPiiAuditTest { + + private static final ObjectMapper MAPPER = + new ObjectMapper().registerModule(new JavaTimeModule()); + + @Nested + @DisplayName("PolicyCategory.PII_INDONESIA") + class PiiIndonesiaCategory { + + @Test + @DisplayName("PII_INDONESIA value should be 'pii-indonesia'") + void piiIndonesiaValueShouldBePiiIndonesia() { + assertThat(PolicyCategory.PII_INDONESIA.getValue()).isEqualTo("pii-indonesia"); + } + } + + @Nested + @DisplayName("AuditLogEntry cross-border fields") + class AuditLogEntryCrossBorderFields { + + @Test + @DisplayName("should deserialize with data_residency and transfer_basis") + void shouldDeserializeWithCrossBorderFields() throws Exception { + String json = + "{" + + "\"id\": \"audit-indo-1\"," + + "\"request_id\": \"req-1\"," + + "\"timestamp\": \"2026-05-26T10:00:00Z\"," + + "\"user_email\": \"user@example.com\"," + + "\"client_id\": \"client-1\"," + + "\"tenant_id\": \"tenant-1\"," + + "\"request_type\": \"llm_chat\"," + + "\"query_summary\": \"Test query\"," + + "\"success\": true," + + "\"blocked\": false," + + "\"risk_score\": 0.1," + + "\"provider\": \"openai\"," + + "\"model\": \"gpt-4\"," + + "\"tokens_used\": 150," + + "\"latency_ms\": 250," + + "\"policy_violations\": []," + + "\"metadata\": {}," + + "\"data_residency\": \"ID\"," + + "\"transfer_basis\": \"consent\"" + + "}"; + + AuditLogEntry entry = MAPPER.readValue(json, AuditLogEntry.class); + + assertThat(entry.getId()).isEqualTo("audit-indo-1"); + assertThat(entry.getDataResidency()).isEqualTo("ID"); + assertThat(entry.getTransferBasis()).isEqualTo("consent"); + } + + @Test + @DisplayName("should deserialize without cross-border fields (backward compat)") + void shouldDeserializeWithoutCrossBorderFields() throws Exception { + String json = + "{" + + "\"id\": \"audit-old-1\"," + + "\"request_id\": \"req-2\"," + + "\"timestamp\": \"2026-05-26T10:00:00Z\"," + + "\"user_email\": \"user@example.com\"," + + "\"client_id\": \"client-1\"," + + "\"tenant_id\": \"tenant-1\"," + + "\"request_type\": \"llm_chat\"," + + "\"query_summary\": \"Old platform query\"," + + "\"success\": true," + + "\"blocked\": false," + + "\"risk_score\": 0.2," + + "\"provider\": \"openai\"," + + "\"model\": \"gpt-4\"," + + "\"tokens_used\": 100," + + "\"latency_ms\": 200," + + "\"policy_violations\": []," + + "\"metadata\": {}" + + "}"; + + AuditLogEntry entry = MAPPER.readValue(json, AuditLogEntry.class); + + assertThat(entry.getId()).isEqualTo("audit-old-1"); + assertThat(entry.getDataResidency()).isNull(); + assertThat(entry.getTransferBasis()).isNull(); + } + + @Test + @DisplayName("equals and hashCode should include cross-border fields") + void equalsAndHashCodeShouldIncludeCrossBorderFields() throws Exception { + String jsonWithFields = + "{" + + "\"id\": \"audit-1\"," + + "\"data_residency\": \"ID\"," + + "\"transfer_basis\": \"adequacy\"" + + "}"; + String jsonWithoutFields = "{\"id\": \"audit-1\"}"; + + AuditLogEntry with = MAPPER.readValue(jsonWithFields, AuditLogEntry.class); + AuditLogEntry without = MAPPER.readValue(jsonWithoutFields, AuditLogEntry.class); + + assertThat(with).isNotEqualTo(without); + assertThat(with.hashCode()).isNotEqualTo(without.hashCode()); + } + + @Test + @DisplayName("toString should include cross-border fields") + void toStringShouldIncludeCrossBorderFields() throws Exception { + String json = + "{" + + "\"id\": \"audit-1\"," + + "\"data_residency\": \"SG\"," + + "\"transfer_basis\": \"safeguards\"" + + "}"; + + AuditLogEntry entry = MAPPER.readValue(json, AuditLogEntry.class); + String str = entry.toString(); + + assertThat(str).contains("dataResidency='SG'"); + assertThat(str).contains("transferBasis='safeguards'"); + } + } +} From ed595b87845ada30150171417b6b15865b5ddbbb Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Tue, 26 May 2026 15:35:53 +0200 Subject: [PATCH 2/3] chore: update wire-shape baseline for cross-border audit fields Signed-off-by: Saurabh Jain Signed-off-by: Saurabh Jain --- tests/fixtures/wire-shape-baseline.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/wire-shape-baseline.json b/tests/fixtures/wire-shape-baseline.json index 7644281..3cfa678 100644 --- a/tests/fixtures/wire-shape-baseline.json +++ b/tests/fixtures/wire-shape-baseline.json @@ -186,9 +186,11 @@ "per_type_drift": { "AuditLogEntry": { "sdk_only": [ + "data_residency", "metadata", "model", - "policy_violations" + "policy_violations", + "transfer_basis" ], "spec_only": [] }, From d3ffb680fcd0fbbd63d7dcb6b1a24491db684979 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Tue, 26 May 2026 16:31:47 +0200 Subject: [PATCH 3/3] chore(release): bump to v8.3.0 Signed-off-by: Saurabh Jain Signed-off-by: Saurabh Jain --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c5077..11f434d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to the AxonFlow Java SDK will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [8.3.0] - 2026-05-26 — Indonesia PII category + cross-border audit fields ### Added diff --git a/pom.xml b/pom.xml index db692af..dc68b66 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.getaxonflow axonflow-sdk - 8.2.0 + 8.3.0 jar AxonFlow Java SDK