Skip to content

Commit 591627b

Browse files
feat: Workflow Policy Enforcement (Issues #1019, #1020, #1021) (#55)
* feat(workflow): add policy enforcement types for MAP and WCP Add new types to support workflow policy enforcement for Issues #1019, #1020, #1021: - PolicyEvaluationResult: Contains policy evaluation results including allowed status, applied policies, risk score, required actions, processing time, and database access flag - PolicyMatch: Represents a matched policy with policy ID, name, action, and reason fields - PlanExecutionResponse: Response from MAP plan execution with policyInfo field for policy evaluation results - StepGateResponse: Updated with policiesEvaluated and policiesMatched fields for WCP step gate checks * test: add tests for workflow policy types to increase coverage * fix: correct PlanExecutionResponse test methods to match actual API
1 parent 0fc41b1 commit 591627b

7 files changed

Lines changed: 1248 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Helper methods on `StepGateResponse`: `isAllowed()`, `isBlocked()`, `requiresApproval()`
2424
- Helper methods on `WorkflowStatus` and `WorkflowStatusResponse`: `isTerminal()`
2525

26+
- **Workflow Policy Enforcement** (Issues #1019, #1020, #1021): Policy transparency for workflow operations
27+
- `StepGateResponse` now includes `getPoliciesEvaluated()` and `getPoliciesMatched()` methods with `PolicyMatch` type
28+
- `PolicyMatch` class with `getPolicyId()`, `getPolicyName()`, `getAction()`, `getReason()` for policy transparency
29+
- `PolicyEvaluationResult` class for MAP execution with `isAllowed()`, `getAppliedPolicies()`, `getRiskScore()`
30+
- Workflow operations (`workflow_created`, `workflow_step_gate`, `workflow_completed`) logged to audit trail
31+
2632
---
2733

2834
## [2.4.0] - 2026-01-14
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
/*
2+
* Copyright 2026 AxonFlow
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.getaxonflow.sdk.types.workflow;
17+
18+
import com.fasterxml.jackson.annotation.JsonCreator;
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
22+
import java.time.Instant;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Objects;
27+
28+
/**
29+
* Response from executing a plan in Multi-Agent Planning (MAP).
30+
*
31+
* <p>Contains the execution result, policy evaluation information,
32+
* and metadata about the plan execution.
33+
*
34+
* @since 2.3.0
35+
*/
36+
@JsonIgnoreProperties(ignoreUnknown = true)
37+
public final class PlanExecutionResponse {
38+
39+
@JsonProperty("plan_id")
40+
private final String planId;
41+
42+
@JsonProperty("status")
43+
private final String status;
44+
45+
@JsonProperty("result")
46+
private final String result;
47+
48+
@JsonProperty("steps_completed")
49+
private final int stepsCompleted;
50+
51+
@JsonProperty("total_steps")
52+
private final int totalSteps;
53+
54+
@JsonProperty("started_at")
55+
private final Instant startedAt;
56+
57+
@JsonProperty("completed_at")
58+
private final Instant completedAt;
59+
60+
@JsonProperty("step_results")
61+
private final List<StepResult> stepResults;
62+
63+
@JsonProperty("policy_info")
64+
private final PolicyEvaluationResult policyInfo;
65+
66+
@JsonProperty("metadata")
67+
private final Map<String, Object> metadata;
68+
69+
@JsonCreator
70+
public PlanExecutionResponse(
71+
@JsonProperty("plan_id") String planId,
72+
@JsonProperty("status") String status,
73+
@JsonProperty("result") String result,
74+
@JsonProperty("steps_completed") int stepsCompleted,
75+
@JsonProperty("total_steps") int totalSteps,
76+
@JsonProperty("started_at") Instant startedAt,
77+
@JsonProperty("completed_at") Instant completedAt,
78+
@JsonProperty("step_results") List<StepResult> stepResults,
79+
@JsonProperty("policy_info") PolicyEvaluationResult policyInfo,
80+
@JsonProperty("metadata") Map<String, Object> metadata) {
81+
this.planId = planId;
82+
this.status = status;
83+
this.result = result;
84+
this.stepsCompleted = stepsCompleted;
85+
this.totalSteps = totalSteps;
86+
this.startedAt = startedAt;
87+
this.completedAt = completedAt;
88+
this.stepResults = stepResults != null ? Collections.unmodifiableList(stepResults) : Collections.emptyList();
89+
this.policyInfo = policyInfo;
90+
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
91+
}
92+
93+
/**
94+
* Returns the unique identifier of the executed plan.
95+
*
96+
* @return the plan ID
97+
*/
98+
public String getPlanId() {
99+
return planId;
100+
}
101+
102+
/**
103+
* Returns the current execution status.
104+
*
105+
* <p>Possible values: "pending", "in_progress", "completed", "failed", "blocked".
106+
*
107+
* @return the execution status
108+
*/
109+
public String getStatus() {
110+
return status;
111+
}
112+
113+
/**
114+
* Returns the execution result or error message.
115+
*
116+
* @return the result string, or null if not yet completed
117+
*/
118+
public String getResult() {
119+
return result;
120+
}
121+
122+
/**
123+
* Returns the number of steps that have been completed.
124+
*
125+
* @return the count of completed steps
126+
*/
127+
public int getStepsCompleted() {
128+
return stepsCompleted;
129+
}
130+
131+
/**
132+
* Returns the total number of steps in the plan.
133+
*
134+
* @return the total step count
135+
*/
136+
public int getTotalSteps() {
137+
return totalSteps;
138+
}
139+
140+
/**
141+
* Returns when the plan execution started.
142+
*
143+
* @return the start timestamp, or null if not yet started
144+
*/
145+
public Instant getStartedAt() {
146+
return startedAt;
147+
}
148+
149+
/**
150+
* Returns when the plan execution completed.
151+
*
152+
* @return the completion timestamp, or null if not yet completed
153+
*/
154+
public Instant getCompletedAt() {
155+
return completedAt;
156+
}
157+
158+
/**
159+
* Returns the results of individual steps.
160+
*
161+
* @return immutable list of step results
162+
*/
163+
public List<StepResult> getStepResults() {
164+
return stepResults;
165+
}
166+
167+
/**
168+
* Returns the policy evaluation information for this execution.
169+
*
170+
* <p>Contains details about which policies were applied, the risk score,
171+
* and any required actions.
172+
*
173+
* @return the policy evaluation result, or null if no policy evaluation was performed
174+
* @since 2.3.0
175+
*/
176+
public PolicyEvaluationResult getPolicyInfo() {
177+
return policyInfo;
178+
}
179+
180+
/**
181+
* Returns additional metadata about the execution.
182+
*
183+
* @return immutable map of metadata
184+
*/
185+
public Map<String, Object> getMetadata() {
186+
return metadata;
187+
}
188+
189+
/**
190+
* Checks if the plan execution completed successfully.
191+
*
192+
* @return true if status is "completed"
193+
*/
194+
public boolean isCompleted() {
195+
return "completed".equalsIgnoreCase(status);
196+
}
197+
198+
/**
199+
* Checks if the plan execution failed.
200+
*
201+
* @return true if status is "failed"
202+
*/
203+
public boolean isFailed() {
204+
return "failed".equalsIgnoreCase(status);
205+
}
206+
207+
/**
208+
* Checks if the plan execution was blocked by policy.
209+
*
210+
* @return true if status is "blocked"
211+
*/
212+
public boolean isBlocked() {
213+
return "blocked".equalsIgnoreCase(status);
214+
}
215+
216+
/**
217+
* Checks if the plan execution is still in progress.
218+
*
219+
* @return true if status is "in_progress" or "pending"
220+
*/
221+
public boolean isInProgress() {
222+
return "in_progress".equalsIgnoreCase(status) || "pending".equalsIgnoreCase(status);
223+
}
224+
225+
/**
226+
* Calculates the progress percentage.
227+
*
228+
* @return progress as a value between 0.0 and 1.0
229+
*/
230+
public double getProgress() {
231+
if (totalSteps == 0) {
232+
return 0.0;
233+
}
234+
return (double) stepsCompleted / totalSteps;
235+
}
236+
237+
@Override
238+
public boolean equals(Object o) {
239+
if (this == o) return true;
240+
if (o == null || getClass() != o.getClass()) return false;
241+
PlanExecutionResponse that = (PlanExecutionResponse) o;
242+
return stepsCompleted == that.stepsCompleted &&
243+
totalSteps == that.totalSteps &&
244+
Objects.equals(planId, that.planId) &&
245+
Objects.equals(status, that.status) &&
246+
Objects.equals(result, that.result) &&
247+
Objects.equals(startedAt, that.startedAt) &&
248+
Objects.equals(completedAt, that.completedAt) &&
249+
Objects.equals(stepResults, that.stepResults) &&
250+
Objects.equals(policyInfo, that.policyInfo) &&
251+
Objects.equals(metadata, that.metadata);
252+
}
253+
254+
@Override
255+
public int hashCode() {
256+
return Objects.hash(planId, status, result, stepsCompleted, totalSteps,
257+
startedAt, completedAt, stepResults, policyInfo, metadata);
258+
}
259+
260+
@Override
261+
public String toString() {
262+
return "PlanExecutionResponse{" +
263+
"planId='" + planId + '\'' +
264+
", status='" + status + '\'' +
265+
", stepsCompleted=" + stepsCompleted +
266+
", totalSteps=" + totalSteps +
267+
", policyInfo=" + policyInfo +
268+
'}';
269+
}
270+
271+
/**
272+
* Result of an individual step execution.
273+
*/
274+
@JsonIgnoreProperties(ignoreUnknown = true)
275+
public static final class StepResult {
276+
277+
@JsonProperty("step_index")
278+
private final int stepIndex;
279+
280+
@JsonProperty("step_name")
281+
private final String stepName;
282+
283+
@JsonProperty("status")
284+
private final String status;
285+
286+
@JsonProperty("output")
287+
private final String output;
288+
289+
@JsonProperty("error")
290+
private final String error;
291+
292+
@JsonProperty("duration_ms")
293+
private final long durationMs;
294+
295+
@JsonCreator
296+
public StepResult(
297+
@JsonProperty("step_index") int stepIndex,
298+
@JsonProperty("step_name") String stepName,
299+
@JsonProperty("status") String status,
300+
@JsonProperty("output") String output,
301+
@JsonProperty("error") String error,
302+
@JsonProperty("duration_ms") long durationMs) {
303+
this.stepIndex = stepIndex;
304+
this.stepName = stepName;
305+
this.status = status;
306+
this.output = output;
307+
this.error = error;
308+
this.durationMs = durationMs;
309+
}
310+
311+
public int getStepIndex() {
312+
return stepIndex;
313+
}
314+
315+
public String getStepName() {
316+
return stepName;
317+
}
318+
319+
public String getStatus() {
320+
return status;
321+
}
322+
323+
public String getOutput() {
324+
return output;
325+
}
326+
327+
public String getError() {
328+
return error;
329+
}
330+
331+
public long getDurationMs() {
332+
return durationMs;
333+
}
334+
335+
public boolean isSuccess() {
336+
return "completed".equalsIgnoreCase(status) || "success".equalsIgnoreCase(status);
337+
}
338+
339+
@Override
340+
public boolean equals(Object o) {
341+
if (this == o) return true;
342+
if (o == null || getClass() != o.getClass()) return false;
343+
StepResult that = (StepResult) o;
344+
return stepIndex == that.stepIndex &&
345+
durationMs == that.durationMs &&
346+
Objects.equals(stepName, that.stepName) &&
347+
Objects.equals(status, that.status) &&
348+
Objects.equals(output, that.output) &&
349+
Objects.equals(error, that.error);
350+
}
351+
352+
@Override
353+
public int hashCode() {
354+
return Objects.hash(stepIndex, stepName, status, output, error, durationMs);
355+
}
356+
357+
@Override
358+
public String toString() {
359+
return "StepResult{" +
360+
"stepIndex=" + stepIndex +
361+
", stepName='" + stepName + '\'' +
362+
", status='" + status + '\'' +
363+
'}';
364+
}
365+
}
366+
}

0 commit comments

Comments
 (0)