diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32a0b6b..2e992c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@
interval (`a2at.server.heartbeat-interval-seconds`, default 15 seconds, `0` disables). Keeps intermediaries
from idle-timing-out long-running SSE connections during agent execution; the heartbeat integrates with the
emitter lifecycle and stops on stream completion, error, timeout, or client disconnect.
+- Make collection fields on model types defensively copied and expose them through unmodifiable
+ views (`Workflow`, `WorkflowStep`, `WorkflowSearchResult`, `ExecutionResult`, `SendMessageResult`,
+ `TaskResult`, `BusinessFailure`, and the message records). Source- and binary-compatible; callers
+ that mutate the collections returned by these getters now receive `UnsupportedOperationException`
+ instead of silently corrupting engine-owned snapshots.
## [0.0.10] — 2026-09-16
diff --git a/config/spotbugs/spotbugs-exclude.xml b/config/spotbugs/spotbugs-exclude.xml
index e49f56a..b233304 100644
--- a/config/spotbugs/spotbugs-exclude.xml
+++ b/config/spotbugs/spotbugs-exclude.xml
@@ -5,16 +5,43 @@
for this codebase's patterns. Clean up over time; new findings still block verify.
-->
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,13 +96,19 @@
- NegotiationStrategy intentionally shadows the control interface name (sample mirror)
- Boolean-returning booleanParam helper may return null to signal "unset"
- handleLogin catches Exception broadly as the demo login boundary
- - doPrivileged warning: SecurityManager is not used by this project -->
+ - doPrivileged warning: SecurityManager is not used by this project
+ - OmcAgentLauncher.servers(): demo server registry, iterated by the demo only
+ - SpringWorkbenchExecutor/SpringWorkbenchExtensionLifecycle: storing the Spring
+ properties bean reference is the standard Spring configuration pattern -->
+
+
+
@@ -86,6 +119,9 @@
+
+
+
diff --git a/workflow-engine/src/main/java/dev/openan/workflow/engine/model/BusinessFailure.java b/workflow-engine/src/main/java/dev/openan/workflow/engine/model/BusinessFailure.java
index f7f94ae..4b829ff 100644
--- a/workflow-engine/src/main/java/dev/openan/workflow/engine/model/BusinessFailure.java
+++ b/workflow-engine/src/main/java/dev/openan/workflow/engine/model/BusinessFailure.java
@@ -39,8 +39,8 @@ public String code() {
return code;
}
- /** Safe business facts selected by the host. */
+ /** Safe business facts selected by the host; unmodifiable. */
public Map details() {
- return details;
+ return java.util.Collections.unmodifiableMap(details);
}
}
diff --git a/workflow-engine/src/main/java/dev/openan/workflow/engine/model/ExecutionResult.java b/workflow-engine/src/main/java/dev/openan/workflow/engine/model/ExecutionResult.java
index 645bb43..8111a51 100644
--- a/workflow-engine/src/main/java/dev/openan/workflow/engine/model/ExecutionResult.java
+++ b/workflow-engine/src/main/java/dev/openan/workflow/engine/model/ExecutionResult.java
@@ -4,7 +4,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*
- * Licensed under the Apache License, Version 2.0 (the License); you may
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
@@ -19,9 +19,11 @@
package dev.openan.workflow.engine.model;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -29,15 +31,17 @@
/**
* Outcome of one workflow run: overall success, per-step execution history, aggregated step
* outputs, and a terminal error description when the run failed.
+ *
+ *
Collection fields are copied on write and exposed unmodifiable, so callers cannot mutate the
+ * result after the engine has produced it.
*/
@Data
@NoArgsConstructor
-@AllArgsConstructor
@Builder
public class ExecutionResult {
private boolean success;
- /** Per-step records in execution order. */
+ /** Per-step records in execution order. Stored defensively; exposed unmodifiable. */
private List