Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,61 @@ 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).

## [8.0.0] - 2026-05-08 — Decision history API + telemetry simplification

**Major release.** The headline feature is the new decision-history client
API: `listDecisions` for paging through recorded decisions, plus a
runnable example showing the full record → list → explain audit flow.
Bundled into a major because the v8 line also tightens the telemetry
contract — see `Removed` at the bottom of this entry for that.

### Added

- **`listDecisions(ListDecisionsOptions opts)` client method.** Pages
over recorded decision history from the orchestrator, mirroring `GET
/api/v1/decisions`. Companion to the v7.4.0 `getDecisionExplain`
method — callers can now both list and drill in. See
`examples/list-decisions/`.
- **`examples/explain-decision/`** end-to-end runnable example covering
the full decision audit flow: record → list → explain.

### Migration guide (v7 → v8)

- **`AxonFlowConfig.Builder.telemetry(Boolean)` removed.** Code that
called `.telemetry(true)` or `.telemetry(false)` on the builder will
fail to compile. Migration: remove the call from your builder chain.
If you were using it to disable telemetry, set
`AXONFLOW_TELEMETRY=off` in the environment instead — that's the
sole opt-out lever as of v8. If you were using it to force-enable,
the default is now ON for every mode so the override is no longer
needed.
- **`AxonFlowConfig.getTelemetry()` removed.** Code reading the
override field will fail to compile. Same migration: drop the call
site; `AXONFLOW_TELEMETRY=off` is the only telemetry knob.
- **`TelemetryReporter.isEnabled` and `TelemetryReporter.sendPing`
signatures simplified.** Both methods previously took the
`(mode, configOverride, hasCredentials, ...)` parameter shape from
the v7 mode-and-override gate. v8 collapses to a single env-var
signal: `isEnabled(String axonflowTelemetry)` and
`sendPing(String mode, String sdkEndpoint, boolean debug, ...)`.
Application code does not call these directly; only test harnesses
that exercise the testability surface need to update.

### Removed

- **`AxonFlowConfig.Builder.telemetry(Boolean)` builder method** (was
`Builder telemetry(Boolean)`) and **`AxonFlowConfig.getTelemetry()`**
accessor. `AXONFLOW_TELEMETRY=off` is now the sole opt-out path. Tests
that need to defend against contaminated dev environments should
pass `null` to the testability `axonflowTelemetry` parameter or set
`AXONFLOW_TELEMETRY=` (empty) at the test level.
- **Sandbox-mode silent telemetry suppression.** Sandbox-mode clients
(constructed via `Mode.SANDBOX`) now fire telemetry on the same
heartbeat schedule as production-mode clients. Pings are tagged
`stream="sandbox"` so analytics can distinguish dev pings from
production heartbeat — see the checkpoint-service
`IsValidIncomingStream` allowlist for the wire-side gate.

## [7.1.0] - 2026-05-06 — X-Axonflow-Client header + scope-aware license validation

**Companion release to platform v7.7.0.** The Java SDK now sends an
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ Three short videos covering different angles of the platform:
<dependency>
<groupId>com.getaxonflow</groupId>
<artifactId>axonflow-sdk</artifactId>
<version>4.0.0</version>
<version>8.0.0</version>
</dependency>
```

### Gradle

```groovy
implementation 'com.getaxonflow:axonflow-sdk:4.0.0'
implementation 'com.getaxonflow:axonflow-sdk:8.0.0'
```

## Evaluation Tier (Free License)
Expand Down Expand Up @@ -604,6 +604,12 @@ For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflo
This SDK sends anonymous usage telemetry (SDK version, OS, enabled features) to help improve AxonFlow.
No prompts, payloads, or PII are ever collected. Opt out: `AXONFLOW_TELEMETRY=off`.

`AXONFLOW_TELEMETRY=off` is the **sole opt-out lever** as of v8.0. The
v7.x `telemetry(Boolean)` config-builder method has been removed; the
previous silent suppression of sandbox-mode pings has also been removed
(sandbox-mode pings now fire and are tagged `stream="sandbox"` so
they're distinguishable from production heartbeat).

### Scope of `AXONFLOW_TELEMETRY=off`

`AXONFLOW_TELEMETRY=off` disables the anonymous SDK heartbeat (version, OS, architecture). On **self-hosted** and **in-VPC** deployments, that heartbeat is the only data the SDK sends to AxonFlow, so setting `=off` means we receive nothing. On **Community SaaS** (`try.getaxonflow.com`) the hosted service also processes operational data — registrations, audit logs, policy enforcement records, workflow state, plan data, and request-header metadata aggregated for usage analytics — as part of running the platform; that operational data flow is governed by the [Privacy Policy](https://getaxonflow.com/privacy/), not by `AXONFLOW_TELEMETRY`.
Expand Down
2 changes: 1 addition & 1 deletion examples/explain-decision/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<dependency>
<groupId>com.getaxonflow</groupId>
<artifactId>axonflow-sdk</artifactId>
<version>7.1.0</version>
<version>8.0.0</version>
</dependency>
</dependencies>

Expand Down
2 changes: 1 addition & 1 deletion examples/list-decisions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<dependency>
<groupId>com.getaxonflow</groupId>
<artifactId>axonflow-sdk</artifactId>
<version>7.1.0</version>
<version>8.0.0</version>
</dependency>
</dependencies>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.getaxonflow</groupId>
<artifactId>axonflow-sdk</artifactId>
<version>7.1.0</version>
<version>8.0.0</version>
<packaging>jar</packaging>

<name>AxonFlow Java SDK</name>
Expand Down
45 changes: 45 additions & 0 deletions runtime-e2e/sandbox_telemetry_stream_tag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Runtime proof — Sandbox-mode telemetry fires with stream=sandbox (Java SDK v8)

Verifies the v8 contract: a Java SDK client constructed with
`Mode.SANDBOX` produces an anonymous heartbeat ping that lands in
checkpoint DynamoDB with the row tagged `stream="sandbox"`.

## When to run

**Post-deploy verification.** Two infrastructure prerequisites:

1. **`axonflow-enterprise` PR #2005 deployed** — without the server-side
wire-allowlist, the Lambda hardcodes `stream=heartbeat` regardless of
payload, and this test will fail at the assertion step. Confirm with:
```sh
curl -sS -X POST -H 'Content-Type: application/json' \
-d '{"sdk":"java","sdk_version":"8.0.0","stream":"community_saas_operational","instance_id":"x"}' \
https://checkpoint.getaxonflow.com/v1/ping
# Expect HTTP 400 "invalid stream value"
```
2. **AWS credentials** with read on `/aws/lambda/prod-axonflow-checkpoint`.

## Usage

```sh
AWS_REGION=us-east-1 ./test.sh
```

## What it asserts

1. Builds the local SDK to the Maven local repo via `mvn install -DskipTests`.
2. A small Java consumer that depends on `com.getaxonflow:axonflow-sdk:8.0.0` is
compiled and run.
3. The consumer constructs `AxonFlow.create(AxonFlowConfig.builder()
.endpoint("http://localhost:65530").mode(Mode.SANDBOX)...)` — pointing
at an unreachable port so we exercise the heartbeat ping but not any
platform call.
4. The Lambda's CloudWatch audit log records an `event_stored` row with
`sdk=java/8` AND `stream=sandbox`.

## Pre-v8 behavior (regression-guard context)

In v7.x, sandbox-mode clients were silently suppressed by the SDK gate
(`mode != "sandbox"` default-off rule). This test guards against that
hole being re-introduced. If a future refactor restores any mode-based
suppression, this test fires loudly.
161 changes: 161 additions & 0 deletions runtime-e2e/sandbox_telemetry_stream_tag/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env bash
# Runtime proof — Java SDK v8 sandbox-mode telemetry fires with stream=sandbox.
#
# Builds the local SDK with Maven (`mvn install -DskipTests`), then a tiny
# consumer that depends on `com.getaxonflow:axonflow-sdk:8.0.0`, constructs
# a Mode.SANDBOX client against an unreachable agent endpoint, and waits for
# the anonymous telemetry ping to fire. We then query the deployed
# checkpoint Lambda's CloudWatch logs for the audit line that should record
# stream=sandbox in DynamoDB.
#
# Pre-v8 this test would have produced ZERO pings (sandbox-mode silent
# suppression). Post-v8 we expect exactly one ping with stream=sandbox.
#
# Stack-state assumptions:
# - axonflow-enterprise PR #2005 is deployed (server-side stream allowlist
# accepts and persists "sandbox" — without that, this row is stored
# as stream=heartbeat, defeating the test's purpose).
# - AWS credentials with read access on /aws/lambda/prod-axonflow-checkpoint.
#
# Usage:
# AWS_REGION=us-east-1 ./test.sh

set -uo pipefail

REGION=${AWS_REGION:-us-east-1}
LOG_GROUP=${LOG_GROUP:-/aws/lambda/prod-axonflow-checkpoint}
RUN_TAG=$(date -u +%s)
SDK_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

red() { printf '\033[31m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }

# 1. Install the local SDK to the Maven local repo so the consumer can
# resolve `com.getaxonflow:axonflow-sdk:8.0.0` from `~/.m2/repository`.
echo "Installing local SDK to Maven local repo..."
(
cd "$SDK_ROOT"
./mvnw -q -DskipTests install
) || {
red "FAIL: mvn install of local SDK failed"
exit 1
}

# 2. Build a transient consumer pom + main class. The unreachable :65530
# endpoint is intentional — we only want the anonymous heartbeat to fire,
# not any platform call.
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT

cat > "$WORK/pom.xml" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.getaxonflow.runtime-e2e</groupId>
<artifactId>sandbox-telemetry-stream-tag</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.getaxonflow</groupId>
<artifactId>axonflow-sdk</artifactId>
<version>8.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>SandboxRuntimeProof</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
EOF

mkdir -p "$WORK/src/main/java"
cat > "$WORK/src/main/java/SandboxRuntimeProof.java" <<'EOF'
import com.getaxonflow.sdk.AxonFlow;
import com.getaxonflow.sdk.AxonFlowConfig;
import com.getaxonflow.sdk.types.Mode;

public class SandboxRuntimeProof {
public static void main(String[] args) throws Exception {
System.out.println("[runtime-e2e] Constructing Sandbox client (unreachable agent)...");
AxonFlowConfig config = AxonFlowConfig.builder()
.endpoint("http://localhost:65530")
.clientId("rt-test")
.clientSecret("rt-test")
.mode(Mode.SANDBOX)
.build();
// Construction triggers the synchronous heartbeat ping to checkpoint.
@SuppressWarnings("unused")
AxonFlow client = AxonFlow.create(config);
System.out.println("[runtime-e2e] AxonFlow.create returned. Sleeping 2s for inflight HTTP...");
Thread.sleep(2000);
System.out.println("[runtime-e2e] Done.");
}
}
EOF

T0_MS=$(($(date -u +%s)*1000))
echo "Run tag: $RUN_TAG"
echo "T0 (ms): $T0_MS"
echo

# Note: the SDK reads AXONFLOW_TELEMETRY directly via System.getenv, so we
# explicitly clear it for this run. Pre-v8 dev envs commonly had it set to
# `off` to suppress noise.
unset AXONFLOW_TELEMETRY

(
cd "$WORK"
mvn -q -DskipTests package 2>&1 | tail -3
mvn -q exec:java 2>&1
)

echo
echo "Waiting 10s for CloudWatch log delivery..."
sleep 10

# Look for the audit row our run produced — match by sdk=java and a fresh
# correlation_id stamped within the last ~1 minute window.
echo "Querying CloudWatch logs since T0 for sdk=java event_stored entries..."
HITS=$(aws --region "$REGION" logs filter-log-events \
--log-group-name "$LOG_GROUP" \
--start-time "$T0_MS" \
--filter-pattern '"event_stored" "sdk=java/8"' \
--query 'events[*].message' \
--output text 2>&1)

if [ -z "$HITS" ]; then
red "FAIL: no event_stored sdk=java/8 row landed in checkpoint logs since T0"
red " Expected: one audit row tagged stream=sandbox"
red " CloudWatch query window: $T0_MS → now"
exit 1
fi

echo "Audit rows found:"
echo "$HITS"
echo

if echo "$HITS" | grep -q 'stream=sandbox'; then
green "PASS: Java SDK sandbox-mode ping landed with stream=sandbox"
else
red "FAIL: audit row did not include stream=sandbox"
red " This usually means PR #2005 (server-side allowlist) is not yet deployed —"
red " the server still hardcodes stream=heartbeat regardless of payload."
exit 1
fi
14 changes: 4 additions & 10 deletions src/main/java/com/getaxonflow/sdk/AxonFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,18 +227,12 @@ private AxonFlow(AxonFlowConfig config) {
* delays a user API call.
*/
private void invokeHeartbeat() {
boolean hasCredentials =
config.getClientId() != null
&& !config.getClientId().isEmpty()
&& config.getClientSecret() != null
&& !config.getClientSecret().isEmpty();
String modeStr = config.getMode() != null ? config.getMode().getValue() : "production";
String envOptOut = System.getenv("AXONFLOW_TELEMETRY");
boolean isEnabled =
TelemetryReporter.isEnabled(modeStr, config.getTelemetry(), hasCredentials, envOptOut);
// Two-arg public overload reads AXONFLOW_TELEMETRY=off itself via
// System.getenv. Tests that bypass this method use the package-private
// 3-arg overload to inject the env value directly.
// v8: AXONFLOW_TELEMETRY=off is the SOLE opt-out path. The v7.x mode-based suppression
// and the AxonFlowConfig.telemetry(Boolean) override were both removed. Sandbox-mode
// pings now fire and are tagged stream="sandbox" in the payload.
boolean isEnabled = TelemetryReporter.isEnabled(envOptOut);
HeartbeatState.shared()
.maybeSendHeartbeat(
isEnabled,
Expand Down
Loading
Loading